Merge branch 'funny-spirit-quest' into 'master'

Nature Spirit quest

See merge request 2009scape/2009scape!228
This commit is contained in:
Ceikry 2021-08-16 02:22:10 +00:00
commit 050fced0ea
40 changed files with 1349 additions and 301 deletions

View file

@ -346,13 +346,13 @@ public final class Class39 {
} else if (Unsorted.incomingOpcode == 17) {
var1 = BufferedDataStream.incomingBuffer.readUnsignedByte();
var2 = Class65.currentChunkX + (var1 >> 4 & 7);
var3 = currentChunkY - -(var1 & 7);
var3 = currentChunkY + (var1 & 7);
var4 = BufferedDataStream.incomingBuffer.readUnsignedShort();
var5 = BufferedDataStream.incomingBuffer.readUnsignedByte();
var6 = BufferedDataStream.incomingBuffer.readUnsignedShort();
if (var2 >= 0 && var3 >= 0 && var2 < 104 && var3 < 104) {
var2 = var2 * 128 - -64;
var3 = var3 * 128 - -64;
var2 = var2 * 128 + 64;
var3 = var3 * 128 + 64;
Class140_Sub2 var32 = new Class140_Sub2(var4, WorldListCountry.localPlane, var2, var3, -var5 + Class121.method1736(WorldListCountry.localPlane, 1, var2, var3), var6, Class44.anInt719);
TextureOperation17.aLinkedList_3177.method1215(new Class3_Sub28_Sub2(var32));
}

View file

@ -2,6 +2,7 @@ package org.runite.client;
import org.rs09.Discord;
import org.rs09.SlayerTracker;
import org.rs09.SystemLogger;
import org.rs09.XPGainDraw;
import java.nio.charset.StandardCharsets;

View file

@ -530,5 +530,13 @@
{
"item_id": "4619",
"loc_data": "{1,2713,4913,0,2}"
},
{
"item_id": "2964",
"loc_data": "{1,3437,3337,0,100}"
},
{
"item_id": "2957",
"loc_data": "{1,3443,9742,0,100}-{1,3443,9742,1,100}"
}
]

View file

@ -10595,28 +10595,31 @@
"lifepoints": "10",
"strength_level": "1",
"id": "1052",
"aggressive": "true",
"aggressive": "false",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "Arrghhh... A Ghast.",
"range_animation": "0",
"attack_speed": "5",
"melee_animation": "1087",
"attack_speed": "4",
"magic_level": "1",
"respawn_delay": "60",
"defence_animation": "0",
"defence_animation": "1088",
"weakness": "9",
"magic_animation": "0",
"death_animation": "9467",
"death_animation": "1089",
"death_gfx": "265",
"name": "Ghast",
"defence_level": "25",
"defence_level": "18",
"safespot": null,
"lifepoints": "71",
"strength_level": "25",
"lifepoints": "45",
"strength_level": "22",
"id": "1053",
"aggressive": "true",
"range_level": "1",
"attack_level": "25"
"attack_level": "22"
},
{
"examine": "Ticket trader for the Brimhaven Agility Arena.",
@ -69050,22 +69053,6 @@
"range_level": "1",
"attack_level": "55"
},
{
"examine": "The oldest man in Nardah.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghaslor the Elder",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "3029",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "A water salesman from Pollnivneach.",
"melee_animation": "0",
@ -84891,43 +84878,6 @@
"projectile": "100",
"attack_level": "25"
},
{
"examine": "Arrghhh... A Ghast.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"weakness": "9",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghast",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "1052",
"aggressive": "true",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "Arrghhh... A Ghast.",
"range_animation": "0",
"attack_speed": "5",
"respawn_delay": "60",
"defence_animation": "0",
"weakness": "9",
"magic_animation": "0",
"death_animation": "9467",
"name": "Ghast",
"defence_level": "25",
"safespot": null,
"lifepoints": "71",
"strength_level": "25",
"id": "1053",
"aggressive": "true",
"range_level": "1",
"attack_level": "25"
},
{
"examine": "Ticket trader for the Brimhaven Agility Arena.",
"melee_animation": "0",
@ -102186,22 +102136,6 @@
"range_level": "1",
"attack_level": "55"
},
{
"examine": "The oldest man in Nardah.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghaslor the Elder",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "3029",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "A water salesman from Pollnivneach.",
"melee_animation": "0",
@ -143465,22 +143399,6 @@
"range_level": "1",
"attack_level": "55"
},
{
"examine": "The oldest man in Nardah.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghaslor the Elder",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "3029",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "A water salesman from Pollnivneach.",
"melee_animation": "0",
@ -159314,43 +159232,6 @@
"projectile": "100",
"attack_level": "25"
},
{
"examine": "Arrghhh... A Ghast.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"weakness": "9",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghast",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "1052",
"aggressive": "true",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "Arrghhh... A Ghast.",
"range_animation": "0",
"attack_speed": "5",
"respawn_delay": "60",
"defence_animation": "0",
"weakness": "9",
"magic_animation": "0",
"death_animation": "9467",
"name": "Ghast",
"defence_level": "25",
"safespot": null,
"lifepoints": "71",
"strength_level": "25",
"id": "1053",
"aggressive": "true",
"range_level": "1",
"attack_level": "25"
},
{
"examine": "Ticket trader for the Brimhaven Agility Arena.",
"melee_animation": "0",
@ -176609,22 +176490,6 @@
"range_level": "1",
"attack_level": "55"
},
{
"examine": "The oldest man in Nardah.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghaslor the Elder",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "3029",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "A water salesman from Pollnivneach.",
"melee_animation": "0",
@ -217640,22 +217505,6 @@
"range_level": "1",
"attack_level": "55"
},
{
"examine": "The oldest man in Nardah.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghaslor the Elder",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "3029",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "A water salesman from Pollnivneach.",
"melee_animation": "0",
@ -233481,43 +233330,6 @@
"projectile": "100",
"attack_level": "25"
},
{
"examine": "Arrghhh... A Ghast.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"weakness": "9",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghast",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "1052",
"aggressive": "true",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "Arrghhh... A Ghast.",
"range_animation": "0",
"attack_speed": "5",
"respawn_delay": "60",
"defence_animation": "0",
"weakness": "9",
"magic_animation": "0",
"death_animation": "9467",
"name": "Ghast",
"defence_level": "25",
"safespot": null,
"lifepoints": "71",
"strength_level": "25",
"id": "1053",
"aggressive": "true",
"range_level": "1",
"attack_level": "25"
},
{
"examine": "Ticket trader for the Brimhaven Agility Arena.",
"melee_animation": "0",
@ -250776,22 +250588,6 @@
"range_level": "1",
"attack_level": "55"
},
{
"examine": "The oldest man in Nardah.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghaslor the Elder",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "3029",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "A water salesman from Pollnivneach.",
"melee_animation": "0",
@ -292055,22 +291851,6 @@
"range_level": "1",
"attack_level": "55"
},
{
"examine": "The oldest man in Nardah.",
"melee_animation": "0",
"range_animation": "0",
"defence_animation": "0",
"magic_animation": "0",
"death_animation": "0",
"name": "Ghaslor the Elder",
"defence_level": "1",
"safespot": null,
"lifepoints": "10",
"strength_level": "1",
"id": "3029",
"range_level": "1",
"attack_level": "1"
},
{
"examine": "A water salesman from Pollnivneach.",
"melee_animation": "0",

View file

@ -2484,7 +2484,7 @@
"loc_data": "{3409,3489,0,1,4}-{3413,3485,0,1,4}-{3414,3491,1,1,6}"
},
{
"npc_id": "1047",
"npc_id": "7690",
"loc_data": "{3415,3489,2,0,0}"
},
{
@ -2492,7 +2492,7 @@
"loc_data": "{3437,3486,0,0,0}"
},
{
"npc_id": "1049",
"npc_id": "7707",
"loc_data": "{3440,9895,0,0,0}"
},
{
@ -10734,5 +10734,13 @@
{
"npc_id": "1164",
"loc_data": "{2780,3058,0,1,0}"
},
{
"npc_id": "1051",
"loc_data": "{3440,9738,1,1,0}"
},
{
"npc_id": "1052",
"loc_data": "{3420,3442,0,1,0}-{3429,3436,0,1,0}-{3439,3437,0,1,0}-{3448,3444,0,1,0}-{3458,3433,0,1,0}-{3459,3419,0,1,0}-{3449,3420,0,1,0}-{3441,3421,0,1,0}-{3427,3421,0,1,0}-{3414,3422,0,1,0}-{3411,3423,0,1,0}-{3415,3414,0,1,0}-{3410,3409,0,1,0}-{3422,3405,0,1,0}-{3431,3402,0,1,0}-{3444,3405,0,1,0}-{3453,3397,0,1,0}-{3467,3402,0,1,0}-{3473,3395,0,1,0}-{3470,3385,0,1,0}-{3463,3379,0,1,0}-{3451,3380,0,1,0}-{3445,3374,0,1,0}-{3438,3365,0,1,0}-{3429,3367,0,1,0}-{3417,3367,0,1,0}-{3413,3362,0,1,0}-{3417,3358,0,1,0}-{3426,3352,0,1,0}-{3435,3349,0,1,0}-{3442,3353,0,1,0}-{3448,3358,0,1,0}-{3457,3355,0,1,0}-{3461,3348,0,1,0}-{3457,3343,0,1,0}-{3471,3344,0,1,0}-{3426,3338,0,1,0}-{3423,3335,0,1,0}"
}
]

View file

@ -2131,5 +2131,14 @@
"id": "240",
"title": "Tiadeche's Karambwan Stall",
"stock": "{3142,10}-{3157,10}"
},
{
"npcs": "",
"high_alch": "0",
"currency": "995",
"general_store": "false",
"id": "241",
"title": "Wishing Well",
"stock": "{12204,10}-{12207,10}-{12210,0}-{12213,0}-{12216,0}-{12219,0}-{12222,0}-{12183,65000}-{12155,5000}"
}
]

View file

@ -109,6 +109,10 @@ public abstract class Consumable implements Plugin<Object> {
return effect.getHealthEffectValue(player);
}
public ConsumableEffect getEffect() {
return effect;
}
public int[] getIds() {
return ids;
}

View file

@ -381,6 +381,6 @@ public final class DrezelDialogue extends DialoguePlugin {
@Override
public int[] getIds() {
return new int[] { 1047 };
return new int[] { 7690 };
}
}

View file

@ -1,10 +1,16 @@
package core.game.content.dialogue;
import api.ContentAPI;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.quest.Quest;
import core.plugin.Initializable;
import core.game.node.item.Item;
import org.rs09.consts.NPCs;
import rs09.game.content.quest.members.naturespirit.NSDrezelDialogue;
import rs09.game.system.SystemLogger;
import static rs09.tools.DialogueConstKt.END_DIALOGUE;
/**
* Represents the dialogue plugin used for the drezel monument.
@ -44,7 +50,7 @@ public final class DrezelMonumentDialogue extends DialoguePlugin {
@Override
public boolean open(Object... args) {
npc = (NPC) args[0];
final Quest quest = player.getQuestRepository().getQuest("Priest in Peril");
Quest quest = player.getQuestRepository().getQuest("Priest in Peril");
if (quest.getStage(player) == 17) {
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Ah, " + player.getUsername() + ". I see you finally made it down here.", "Things are worse than I feared. I'm not sure if I will", "be able to repair the damage.");
stage = 900;
@ -64,9 +70,23 @@ public final class DrezelMonumentDialogue extends DialoguePlugin {
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "So can I pass through that barrier now?");
stage = 400;
return true;
} else {
}
/*else {
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Greetings again adventurer, How go your travels in", "Morytania? Is it as evil as I have heard?");
stage = 420;
}*/
quest = player.getQuestRepository().getQuest("Nature Spirit");
if(quest.getStage(player) <= 5){
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Greetings again adventurer, How go your travels in", "Morytania? Is it as evil as I have heard?");
stage = 420;
} else if (quest.getStage(player) < 100){
ContentAPI.openDialogue(player, new NSDrezelDialogue(), npc);
} else {
npcl(FacialExpression.NEUTRAL, "I heard you finished your quest with Filliman! Great work!");
stage = END_DIALOGUE;
}
return true;
}
@ -108,16 +128,37 @@ public final class DrezelMonumentDialogue extends DialoguePlugin {
end();
break;
case 420:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Well, I'm going to look around a bit more.");
options("Well, I'm going to look around a bit more.", "Is there anything else interesting to do around here?");
stage = 421;
break;
case 421:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Well, that sounds like a good idea. Don't get into any", "trouble though!");
stage = 422;
switch(buttonId){
case 1:
playerl(FacialExpression.FRIENDLY, "Well, I'm going to look around a bit more.");
stage++;
break;
case 2:
playerl(FacialExpression.HALF_THINKING, "Is there anything else interesting to do around here?");
stage = 425;
break;
}
break;
case 422:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "Well, that sounds like a good idea. Don't get into any", "trouble though!");
stage = 423;
break;
case 423:
end();
break;
case 425:
npcl(FacialExpression.HALF_THINKING, "Well, not a great deal... but there is something you can do for me if you're interested. Though it is quite dangerous.");
stage++;
break;
case 426:
end();
player.getDialogueInterpreter().open(new NSDrezelDialogue(), npc);
player.getDialogueInterpreter().handle(0,0);
break;
case 120:
interpreter.sendDialogues(npc, FacialExpression.HALF_GUILTY, "I need " + player.getGameAttributes().getAttribute("priest-in-peril:rune", 50) + " more.");
stage = 121;
@ -239,6 +280,6 @@ public final class DrezelMonumentDialogue extends DialoguePlugin {
@Override
public int[] getIds() {
return new int[] { 1049 };
return new int[] { NPCs.DREZEL_7707 };
}
}

View file

@ -20,7 +20,7 @@ import core.plugin.Plugin;
import rs09.plugin.PluginManager;
import org.rs09.consts.Items;
import static rs09.tools.stringtools.StringToolsKt.colorize;
import static rs09.tools.stringtools.GlobalsKt.colorize;
/**

View file

@ -3,6 +3,7 @@ package core.game.interaction.item;
import java.util.ArrayList;
import java.util.List;
import api.ContentAPI;
import core.cache.def.impl.ItemDefinition;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
@ -13,6 +14,9 @@ import core.plugin.Initializable;
import core.plugin.Plugin;
import core.tools.RandomFunction;
import core.tools.StringUtils;
import org.rs09.consts.Items;
import rs09.game.content.global.WeightBasedTable;
import rs09.game.content.global.WeightedItem;
/**
* Represents the casket handling plugin.
@ -25,7 +29,16 @@ public final class CasketPlugin extends OptionHandler {
/**
* Represents the casket rewards.
*/
private static final ChanceItem[] CASKET_REWARD = new ChanceItem[] { new ChanceItem(995, 8, 3000, 30), new ChanceItem(1623, 1, 30), new ChanceItem(1621, 1, 70), new ChanceItem(1619, 1, 70), new ChanceItem(1617, 1, 97), new ChanceItem(987, 1, 97), new ChanceItem(985, 1, 97), new ChanceItem(1454, 1, 30), new ChanceItem(1452, 1, 70), new ChanceItem(1462, 1, 97) };
private WeightBasedTable table = WeightBasedTable.create(
new WeightedItem(Items.COINS_995, 20, 640, 55, false),
new WeightedItem(Items.UNCUT_SAPPHIRE_1623, 1, 1, 32, false),
new WeightedItem(Items.UNCUT_EMERALD_1621, 1, 1, 16, false),
new WeightedItem(Items.UNCUT_RUBY_1619, 1, 1, 9, false),
new WeightedItem(Items.UNCUT_DIAMOND_1617, 1, 1, 2, false),
new WeightedItem(Items.COSMIC_TALISMAN_1454, 1, 1, 8, false),
new WeightedItem(Items.LOOP_HALF_OF_A_KEY_987, 1, 1, 1, false),
new WeightedItem(Items.TOOTH_HALF_OF_A_KEY_985, 1, 1, 1, false)
);
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
@ -35,10 +48,10 @@ public final class CasketPlugin extends OptionHandler {
@Override
public boolean handle(Player player, Node node, String option) {
final ChanceItem reward = getChanceItem(CASKET_REWARD);
final Item reward = table.roll().get(0);
player.getInventory().remove((Item) node);
player.getDialogueInterpreter().sendItemMessage(reward, "You open the casket. Inside you find " + (reward.getAmount() > 1 ? "some" : (StringUtils.isPlusN(reward.getName()) ? "an" : "a")) + " " + reward.getName().toLowerCase() + ".");
player.getInventory().add(reward.getAmount() == 1 ? reward : new Item(reward.getId(), RandomFunction.random(reward.getMinimumAmount(), reward.getMaximumAmount())));
ContentAPI.addItemOrDrop(player, reward.getId(), reward.getAmount());
return true;
}

View file

@ -12,6 +12,7 @@ import core.game.world.map.RegionManager;
import core.plugin.Initializable;
import core.plugin.Plugin;
import core.tools.RandomFunction;
import rs09.game.content.quest.members.naturespirit.NSUtils;
/**
* Handles the Silver Sickle (b) to collect Mort Myre Fungus.
@ -29,45 +30,14 @@ public final class SilverSicklePlugin extends OptionHandler {
@Override
public boolean handle(Player player, Node node, String option) {
Region region = RegionManager.forId(player.getLocation().getRegionId());
switch (option) {
case "operate":
case "cast bloom":
if (player.getSkills().getPrayerPoints() < 1) {
player.getPacketDispatch().sendMessage("You don't have enough prayer points to do this.");
}
for (Scenery[] o : region.getPlanes()[0].getObjects()) {
for (Scenery obj : o) {
if (obj != null) {
if (obj.getName().equalsIgnoreCase("Rotting log") && player.getSkills().getPrayerPoints() >= 1) {
if (player.getLocation().withinDistance(obj.getLocation(), 2)) {
handleVisuals(player, node);
SceneryBuilder.add(new Scenery(3509, obj.getLocation(), obj.getRotation()));
}
}
}
}
}
RegionManager.getLock().unlock();
player.getPacketDispatch().sendAnimation(9021);
NSUtils.castBloom(player);
return true;
}
return false;
}
/**
* Handles the draining of prayer points and physical graphics and
* animation.
*/
public void handleVisuals(Player player, Node node) {
player.getSkills().decrementPrayerPoints(RandomFunction.random(1, 3));
player.getPacketDispatch().sendAnimation(9021);
final Location[] AROUND_YOU = new Location[] { Location.create(player.getLocation().getX() - 1, player.getLocation().getY(), 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY(), 0), Location.create(player.getLocation().getX(), player.getLocation().getY() - 1, 0), Location.create(player.getLocation().getX(), player.getLocation().getY() + 1, 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY() + 1, 0), Location.create(player.getLocation().getX() - 1, player.getLocation().getY() + 1, 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY() - 1, 0), Location.create(player.getLocation().getX() - 1, player.getLocation().getY() - 1, 0), Location.create(player.getLocation().getX() + 1, player.getLocation().getY() + 1, 0), };
for (Location location : AROUND_YOU) {
// The graphic is meant to play on a 3x3 radius around you, but not
// including the tile you are on.
player.getPacketDispatch().sendGlobalPositionGraphic(263, location);
}
}
}

View file

@ -14,6 +14,7 @@ import core.game.node.object.Scenery;
import core.game.world.map.Location;
import core.plugin.Initializable;
import core.plugin.Plugin;
import org.rs09.consts.NPCs;
/**
* Represents the quest node plugin handler.
@ -200,7 +201,7 @@ public class PriestInPerilOptionPlugin extends OptionHandler {
}
break;
case "talk-through":
player.getDialogueInterpreter().open(1047, NPC.create(1047, player.getLocation()));
player.getDialogueInterpreter().open(NPCs.DREZEL_7690, NPC.create(NPCs.DREZEL_7690, player.getLocation()));
break;
}
break;

View file

@ -638,7 +638,6 @@ public abstract class Entity extends Node {
public Properties getProperties() {
return properties;
}
/**
* Gets the updateMasks.
* @return The updateMasks.

View file

@ -55,8 +55,9 @@ public final class DeathTask extends NodeTask {
}
}
}
e.getAnimator().forceAnimation(e.getProperties().getDeathAnimation());
e.graphics(Animator.RESET_G);
e.visualize(e.getProperties().getDeathAnimation(), e.getProperties().deathGfx);
e.getAnimator().forceAnimation(e.getProperties().getDeathAnimation());
e.commenceDeath(killer);
e.getImpactHandler().setDisabledTicks(50);
}

View file

@ -12,6 +12,7 @@ import core.game.node.entity.player.Player;
import core.game.node.item.Item;
import core.game.world.map.Location;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import rs09.game.node.entity.combat.CombatPulse;
import rs09.game.system.config.ItemConfigParser;
import rs09.game.system.config.NPCConfigParser;
@ -92,6 +93,8 @@ public final class Properties {
*/
private Animation deathAnimation = new Animation(9055, Animator.Priority.HIGH);
public Graphics deathGfx = new Graphics(-1);
/**
* The range animation.
*/

View file

@ -25,6 +25,7 @@ import core.game.world.map.RegionManager;
import core.game.world.map.build.DynamicRegion;
import core.game.world.map.path.Pathfinder;
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.game.world.update.flag.npc.NPCFaceEntity;
import core.game.world.update.flag.npc.NPCFaceLocation;
import core.game.world.update.flag.npc.NPCForceChat;
@ -305,6 +306,9 @@ public class NPC extends Entity {
if (definition.getConfiguration("movement_radius") != null) {
this.setWalkRadius(definition.getConfiguration("movement_radius"));
}
if(definition.getConfiguration("death_gfx") != null) {
getProperties().deathGfx = new Graphics(definition.getConfiguration("death_gfx"));
}
}
/**

View file

@ -80,7 +80,6 @@ import rs09.game.ge.PlayerGrandExchange;
import rs09.game.node.entity.combat.CombatSwingHandler;
import rs09.game.node.entity.combat.equipment.EquipmentDegrader;
import rs09.game.node.entity.skill.runecrafting.PouchManager;
import rs09.game.node.entity.skill.skillcapeperks.SkillcapePerks;
import rs09.game.node.entity.state.newsys.State;
import rs09.game.node.entity.state.newsys.StateRepository;
import rs09.game.world.GameWorld;
@ -95,7 +94,7 @@ import java.util.*;
import static rs09.game.node.entity.player.info.stats.StatAttributeKeysKt.STATS_BASE;
import static rs09.game.node.entity.player.info.stats.StatAttributeKeysKt.STATS_DEATHS;
import static rs09.tools.stringtools.StringToolsKt.colorize;
import static rs09.tools.stringtools.GlobalsKt.colorize;
/**
* Represents a player entity.

View file

@ -322,7 +322,7 @@ public final class PacketDispatch {
* @param location the location.
*/
public void sendPositionedGraphic(int id, int height, int delay, Location location) {
PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, new Graphics(id, height, delay), location));
PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, new Graphics(id, height, delay), location, 0, 0));
}
/**
@ -342,8 +342,7 @@ public final class PacketDispatch {
* @param location the location.
*/
public void sendPositionedGraphics(Graphics graphics, Location location) {
PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, graphics, location));
PacketRepository.send(PositionedGraphic.class, new PositionedGraphicContext(player, graphics, location, 0, 0));
}
/**

View file

@ -11,7 +11,7 @@ import core.net.packet.PacketRepository;
import core.net.packet.context.ContainerContext;
import core.net.packet.out.ContainerPacket;
import static rs09.tools.stringtools.StringToolsKt.colorize;
import static rs09.tools.stringtools.GlobalsKt.colorize;
/**
* Represents the container during a trade session.

View file

@ -87,9 +87,12 @@ public class BoneOfferPlugin extends UseWithHandler {
return;
}
final Location start = player.getLocation();
Location gfxLoc = player.getLocation().transform(player.getDirection(), 1);
ContentAPI.submitIndividualPulse(player, new Pulse(1) {
int counter = 0;
@Override
public boolean pulse() {
counter++;
@ -97,7 +100,7 @@ public class BoneOfferPlugin extends UseWithHandler {
if (player.getInventory().remove(new Item(b.getItemId()))) {
player.animate(ANIM);
player.getAudioManager().send(new Audio(958));
player.getPacketDispatch().sendPositionedGraphics(GFX, altar.getLocation());
player.getPacketDispatch().sendPositionedGraphics(GFX, gfxLoc);
player.sendMessage(getMessage(isLit(left), isLit(right)));
player.getSkills().addExperience(Skills.PRAYER, b.getExperience() * getMod(altar, isLit(left), isLit(right)));
}

View file

@ -1,6 +1,7 @@
package core.game.node.entity.skill.herblore;
import core.game.node.item.Item;
import org.rs09.consts.Items;
/**
* Represents a finished potion.
@ -21,7 +22,7 @@ public enum FinishedPotion {
SUPER_ANTIPOISON(UnfinishedPotion.IRIT, new Item(235), 48, 106.3, new Item(181)),
FISHING_POTION(UnfinishedPotion.AVANTOE, new Item(231), 50, 112.5, new Item(151)),
SUPER_ENERGY(UnfinishedPotion.AVANTOE, new Item(2970), 52, 117.5, new Item(3018)),
HUNTING_POTION(UnfinishedPotion.AVANTOE, new Item(10109), 53, 120, new Item(10000)),
HUNTING_POTION(UnfinishedPotion.AVANTOE, new Item(Items.KEBBIT_TEETH_DUST_10111), 53, 120, new Item(10000)),
SUPER_STRENGTH(UnfinishedPotion.KWUARM, new Item(225), 55, 125, new Item(157)),
WEAPON_POISON(UnfinishedPotion.KWUARM, new Item(241), 60, 137.5, new Item(187)),
SUPER_RESTORE(UnfinishedPotion.SNAPDRAGON, new Item(223), 63, 142.5, new Item(3026)),

View file

@ -6,6 +6,8 @@ import core.game.world.map.path.Path;
import core.game.world.map.path.Pathfinder;
import core.tools.RandomFunction;
import java.util.ArrayList;
/**
* Represents a location on the world map.
* @author Emperor
@ -250,6 +252,24 @@ public final class Location extends Node {
return Math.sqrt(xdiff * xdiff + ydiff * ydiff);
}
/**
* Gets the 8 tiles surrounding this location as an ArrayList<Location>
*/
public ArrayList<Location> getSurroundingTiles() {
ArrayList<Location> locs = new ArrayList<>();
locs.add(transform(0,1,0)); //N
locs.add(transform(1,1,0)); //NE
locs.add(transform(1,0,0)); //E
locs.add(transform(1,-1,0)); //SE
locs.add(transform(0,-1,0)); //S
locs.add(transform(-1,-1,0));//SW
locs.add(transform(-1,0,0));//W
locs.add(transform(-1,1,0));//NW
return locs;
}
/**
* Gets the x position on the region chunk.
* @return The x position on the region chunk.
@ -339,7 +359,7 @@ public final class Location extends Node {
* @return The local y-coordinate.
*/
public int getSceneY(Location loc) {
return y - ((loc.getRegionY() - 6) * 8);
return y - ((loc.getRegionY() - 6) << 3);
}
/**

View file

@ -26,16 +26,23 @@ public final class PositionedGraphicContext implements Context {
*/
private final Location location;
public int sceneX, sceneY;
public int offsetX, offsetY;
/**
* Constructs a new {@code PositionedGraphicContext} {@code Object}.
* @param player The player.
* @param graphic The graphic to display on the given location.
* @param location The location to display the graphic on.
*/
public PositionedGraphicContext(Player player, Graphics graphic, Location location) {
public PositionedGraphicContext(Player player, Graphics graphic, Location location, int offsetX, int offsetY) {
this.player = player;
this.graphic = graphic;
this.location = location;
this.sceneX = location.getSceneX(player.getPlayerFlags().getLastSceneGraph());
this.sceneY = location.getSceneY(player.getPlayerFlags().getLastSceneGraph());
this.offsetX = offsetX;
this.offsetY = offsetY;
}
@Override

View file

@ -338,7 +338,7 @@ public final class InteractionPacket implements IncomingPacket {
player.debug("Handled by quest interaction manager.");
return;
}
if(InteractionListeners.run(item.getId(), InteractionListener.Companion.getITEM(),option.getName(),player,item)){
if(InteractionListeners.run(item.getId(), InteractionListener.Companion.getGROUNDITEM(), option.getName(),player,item)){
return;
}
item.getInteraction().handle(player, option);

View file

@ -5,6 +5,7 @@ import core.game.world.update.flag.context.Graphics;
import core.net.packet.IoBuffer;
import core.net.packet.OutgoingPacket;
import core.net.packet.context.PositionedGraphicContext;
import rs09.game.system.SystemLogger;
/**
* The positioned graphic outgoing packet.
@ -16,8 +17,16 @@ public final class PositionedGraphic implements OutgoingPacket<PositionedGraphic
public void send(PositionedGraphicContext context) {
Location l = context.getLocation();
Graphics g = context.getGraphic();
IoBuffer buffer = UpdateAreaPosition.getBuffer(context.getPlayer(), l).put(17).put((l.getChunkOffsetX() << 4) | (l.getChunkOffsetY() & 0x7)).putShort(g.getId()).put(g.getHeight()).putShort(g.getDelay());
buffer.cypherOpcode(context.getPlayer().getSession().getIsaacPair().getOutput());context.getPlayer().getSession().write(buffer);
int offsetHash = (context.offsetX << 4) | context.offsetY;
IoBuffer buffer = new IoBuffer()
.put(26) //update current scene x and scene y client-side
.putC(context.sceneX) //this has to be done for each graphic being sent
.put(context.sceneY) //opcode 26 is the lastSceneX/lastSceneY update packet
.put(17).put(offsetHash) //send the graphics
.putShort(g.getId())
.put(g.getHeight())
.putShort(g.getDelay());
context.getPlayer().getSession().write(buffer);
}
}
}

View file

@ -1090,6 +1090,29 @@ object ContentAPI {
player.packetDispatch.sendItemOnInterface(item,amount,iface,child)
}
/**
* Sends a dialogue box with a single item and some text
* @param player the player to send it to
* @param item the ID of the item to show
* @param message the text to display
*/
@JvmStatic
fun sendItemDialogue(player: Player, item: Int, message: String){
player.dialogueInterpreter.sendItemMessage(item, *DialUtils.splitLines(message))
}
/**
* Sends a dialogue box with two items and some text
* @param player the player to send it to
* @param item1 the ID of the first item to show
* @param item2 the ID of the second item to show
* @param message the text to display
*/
@JvmStatic
fun sendDoubleItemDialogue(player: Player, item1: Int, item2: Int, message: String){
player.dialogueInterpreter.sendDoubleItemMessage(item1, item2, message)
}
/**
* Send an input dialogue to retrieve a specified value from the player
* @param player the player to send the input dialogue to

View file

@ -0,0 +1,32 @@
package rs09.game.content.quest.members.naturespirit
import api.ContentAPI
import core.game.node.entity.npc.AbstractNPC
import core.game.world.map.Location
import core.plugin.Initializable
import org.rs09.consts.NPCs
@Initializable
class FillimanTarlockNPC : AbstractNPC {
var spawnedTicks = 0
constructor() : super(NPCs.FILLIMAN_TARLOCK_1050, null, true) {}
private constructor(id: Int, location: Location) : super(id, location) {}
override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC {
return FillimanTarlockNPC(id, location)
}
init {
isNeverWalks = true
isWalks = false
}
override fun handleTickActions() {
super.handleTickActions()
if(spawnedTicks++ > 100) ContentAPI.poofClear(this)
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.FILLIMAN_TARLOCK_1050)
}
}

View file

@ -0,0 +1,120 @@
package rs09.game.content.quest.members.naturespirit
import api.ContentAPI
import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.audio.Audio
import core.game.system.task.Pulse
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Items
import rs09.game.content.dialogue.DialogueFile
import rs09.tools.END_DIALOGUE
import rs09.tools.stringtools.colorize
class NSDrezelDialogue : DialogueFile() {
var questStage = 0
override fun handle(componentID: Int, buttonID: Int) {
questStage = player!!.questRepository.getStage("Nature Spirit")
if(questStage <= 5){
when(stage){
0 -> options("Sorry, not interested...", "Well, what is it, I may be able to help?").also { stage++ }
1 -> when(buttonID){
1 -> playerl(FacialExpression.NEUTRAL, "Sorry, not interested.").also { stage = END_DIALOGUE }
2 -> playerl(FacialExpression.FRIENDLY, "Well, what is it, I may be able to help?").also { stage++ }
}
2 -> npcl(FacialExpression.HALF_THINKING, "There's a man called Filliman who lives in Mort Myre, I wonder if you could look for him? The swamps of Mort Myre are dangerous though, they're infested with Ghasts!").also { stage++ }
3 -> options("Who is this Filliman?", "Where's Mort Myre?", "What's a Ghast?", "Yes, I'll go and look for him.", "Sorry, I don't think I can help.").also { stage++ }
4 -> when(buttonID){
1 -> npcl(FacialExpression.NEUTRAL, "Filliman Tarlock is his full name and he's a Druid. He lives in Mort Myre much like a hermit, but there's many a traveller who he's helped.").also { stage-- }
2 -> npcl(FacialExpression.NEUTRAL, "Mort Myre is a decayed and dangerous swamp to the south. It was once a beautiful forest but has since become filled with vile emanations from within Morytania.").also { stage = 6 }
3 -> npcl(FacialExpression.NEUTRAL, "A Ghast is a poor soul who died in Mort Myre. They're undead of a special class, they're untouchable as far as I'm aware!").also { stage = 5 }
4 -> playerl(FacialExpression.FRIENDLY, "Yes, I'll go and look for him.").also { stage = 10 }
5 -> playerl(FacialExpression.NEUTRAL, "Sorry, I don't think I can help.").also { stage = END_DIALOGUE }
}
5 -> npcl(FacialExpression.NEUTRAL, "Filliman knew how to tackle them, but I've not heard from him in a long time. Ghasts, when they attack, will devour any food you have. If you have no food, they'll draw their nourishment from you!").also { stage = 3 }
6 -> npcl(FacialExpression.NEUTRAL, " We put a fence around it to stop unwary travellers going in. Anyone who dies in the swamp is forever cursed to haunt it as a Ghast. Ghasts attack travellers, turning food to rotten filth.").also { stage = 3 }
10 -> npcl(FacialExpression.NEUTRAL, "That's great, but it is very dangerous. Are you sure you want to do this?").also { stage++ }
11 -> options("Yes, I'm sure.", "Sorry, I don't think I can help.").also { stage++ }
12 -> when(buttonID){
1 -> playerl(FacialExpression.FRIENDLY, "Yes, I'm sure.").also { stage = 20 }
2 -> playerl(FacialExpression.NEUTRAL, "Sorry, I don't think I can help.").also { stage = END_DIALOGUE }
}
20 -> npcl(FacialExpression.NEUTRAL, "That's great! Many Thanks! Now then, please be aware of the Ghasts, you cannot attack them, only Filliman knew how to take them on.").also { stage++ }
21 -> npcl(FacialExpression.NEUTRAL, "Just run from them if you can. If you start to get lost, try to make your way back to the temple.").also { stage++ }
22 -> {
ContentAPI.sendDoubleItemDialogue(player!!, Items.MEAT_PIE_2327, Items.APPLE_PIE_2323, "The cleric hands you some food.")
if(questStage == 0){
repeat(3) { ContentAPI.addItemOrDrop(player!!, Items.MEAT_PIE_2327, 1) }
repeat(3) { ContentAPI.addItemOrDrop(player!!, Items.APPLE_PIE_2323, 1) }
player!!.questRepository.getQuest("Nature Spirit").setStage(player!!, 5)
}
stage++
}
23 -> npcl(FacialExpression.NEUTRAL, "Please take this food to Filliman, he'll probably appreciate a bit of cooked food. Now, he's never revealed where he lives in the swamps but I guess he'd be to the south, search for him won't you?").also { stage++ }
24 -> playerl(FacialExpression.FRIENDLY, "I'll do my very best, don't worry, if he's in there and he's still alive I'll definitely find him.").also { stage = END_DIALOGUE; player!!.questRepository.getQuest("Nature Spirit").start(player!!) }
}
}
else if(questStage == 15) {
when(stage){
0 -> playerl(FacialExpression.HALF_GUILTY, "I've found Filliman and you should prepare for some sad news.").also { stage++ }
1 -> npcl(FacialExpression.HALF_GUILTY, "You mean... he's dead?").also { stage++ }
2 -> playerl(FacialExpression.NEUTRAL, "Well, er sort of. I got to his camp and I encountered a spirit of some kind. I don't think it was a Ghast, it tried to communicate with me, but made no sense, it was all 'ooooh' this and 'oooh' that.").also { stage++ }
3 -> npcl(FacialExpression.NEUTRAL, "Hmmm, that's very interesting, I seem to remember Father Aereck in Lumbridge and his predecessor Father Urhney having a similar issue. Though this is probably not related to your problem.").also { stage++ }
4 -> npcl(FacialExpression.NEUTRAL, " I will pray that it wasn't the spirit of my friend Filliman, but some lost soul who needs some help. Please do let me know how you get on with it.").also { stage = END_DIALOGUE }
}
}
else if(questStage == 35){
when(stage){
0 -> playerl(FacialExpression.FRIENDLY, "Hello again! I'm helping Filliman, he plans to become a nature spirit. I have a spell to cast but first I need to be blessed. Can you bless me?").also { stage++ }
1 -> npcl(FacialExpression.NEUTRAL, "But you haven't sneezed!").also { stage++ }
2 -> playerl(FacialExpression.FRIENDLY, "You're so funny! But can you bless me?").also { stage++ }
3 -> npcl(FacialExpression.NEUTRAL, "Very well my friend, prepare yourself for the blessings of Saradomin. Here we go!").also { stage++ }
4 -> {
end()
player!!.lock();
ContentAPI.submitIndividualPulse(player!!, BlessingPulse(npc!!, player!!))
}
}
}
else if(questStage == 40){
npcl(FacialExpression.NEUTRAL, "There you go my friend, you're now blessed. It's funny, now I look at you, there seems to be something of the faith about you. Anyway, good luck with your quest!").also { stage = END_DIALOGUE; player!!.questRepository.getQuest("Nature Spirit").setStage(player!!, 45) }
}
else {
when(stage){
0 -> npcl(FacialExpression.NEUTRAL, "Hello, friend, how goes your quest with Filliman?").also { stage++ }
1 -> playerl(FacialExpression.NEUTRAL, "Still working at it.").also { stage++ }
2 -> npcl(FacialExpression.NEUTRAL, "Well enough! Do let me know when something develops!").also { stage = END_DIALOGUE }
}
}
}
}
private class BlessingPulse(val drezel: NPC, val player: Player) : Pulse(){
var ticks = 0
override fun pulse(): Boolean {
when(ticks){
0 -> ContentAPI.animate(drezel, 1162).also { ContentAPI.spawnProjectile(drezel, player, 268); ContentAPI.playAudio(player, Audio(2674)) }
2 -> ContentAPI.visualize(player, Animation(645), Graphics(267, 100))
4 -> ContentAPI.unlock(player).also { player.questRepository.getQuest("Nature Spirit").setStage(player, 40); return true }
}
ticks++
return false
}
override fun stop() {
super.stop()
ContentAPI.openDialogue(player, NSDrezelDialogue(), drezel)
}
}

View file

@ -0,0 +1,234 @@
package rs09.game.content.quest.members.naturespirit
import api.Container
import api.ContentAPI
import api.DialUtils
import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.item.GroundItem
import core.game.node.item.GroundItemManager
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.map.Location
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import rs09.game.content.dialogue.DialogueFile
import rs09.game.content.global.action.PickupHandler
import rs09.game.interaction.InteractionListener
import rs09.game.node.entity.npc.other.MortMyreGhastNPC
import rs09.game.system.SystemLogger
import rs09.game.system.config.ShopParser
import rs09.tools.END_DIALOGUE
class NSListeners : InteractionListener() {
val GROTTO_TREE = 3517
val GROTTO_ENTRANCE = 3516
val GROTTO_ALTAR = 3520
val NATURE_ALTAR = 3521
val JOURNAL = Items.JOURNAL_2967
val NATURE_STONE = 3527
val FAITH_STONE = 3528
val FREELY_GIVEN_STONE = 3529
val WASHING_BOWL = Items.WASHING_BOWL_2964
val MIRROR = Items.MIRROR_2966
val SPELLCARD = Items.DRUIDIC_SPELL_2968
val USED_SPELLCARD = Items.A_USED_SPELL_2969
val FUNGUS = Items.MORT_MYRE_FUNGUS_2970
val MIRROR_TAKEN = "/save:ns:mirror_taken"
val GROTTO_SEARCHED = "/save:ns:grotto_searched"
val WISHING_WELL = 28715
val DRUID_POUCH = Items.DRUID_POUCH_2958
val DRUID_POUCH_EMPTY = Items.DRUID_POUCH_2957
val stones = intArrayOf(NATURE_STONE, FAITH_STONE, FREELY_GIVEN_STONE)
val items = intArrayOf(USED_SPELLCARD, FUNGUS)
override fun defineListeners() {
on(GROTTO_TREE, SCENERY, "look-at"){player, _ ->
ContentAPI.sendMessage(player, "It looks like a tree on a large rock with roots trailing down to the ground.")
return@on true
}
on(GROTTO_TREE, SCENERY, "search"){player, _ ->
if(!ContentAPI.getAttribute(player, GROTTO_SEARCHED, false) || !(ContentAPI.inInventory(player, JOURNAL) || ContentAPI.inBank(player, JOURNAL))){
ContentAPI.sendItemDialogue(player, JOURNAL, "You search the strange rock. You find a knot and inside of it you discover a small tome. The words on the front are a bit vague, but you can make out the words 'Tarlock' and 'journal'.")
ContentAPI.addItemOrDrop(player, JOURNAL, 1)
ContentAPI.setAttribute(player, GROTTO_SEARCHED, true)
return@on true
}
return@on false
}
on(GROTTO_ENTRANCE, SCENERY, "enter"){player, node ->
val questStage = player.questRepository.getQuest("Nature Spirit").getStage(player)
if(questStage < 55) {
val npc = core.game.node.entity.npc.NPC.create(NPCs.FILLIMAN_TARLOCK_1050, Location.create(3440, 3336, 0))
npc.init()
} else if(questStage < 60) {
player.teleport(Location.create(3442, 9734, 0))
} else if (questStage >= 60){
player.teleport(Location.create(3442, 9734, 1))
}
return@on true
}
on(GROTTO_ALTAR, SCENERY,"search"){player, node ->
val stage = player.questRepository.getStage("Nature Spirit")
if(stage == 55){
ContentAPI.openDialogue(player, FillimanCompletionDialogue(), NPC(NPCs.FILLIMAN_TARLOCK_1050))
return@on true
}
return@on false
}
on(NATURE_STONE, SCENERY, "search"){player, _ ->
ContentAPI.sendDialogue(player, "You search the stone and find that it has some sort of nature symbol scratched into it.")
return@on true
}
on(FAITH_STONE, SCENERY, "search"){player, _ ->
ContentAPI.sendDialogue(player, "You search the stone and find that it has some sort of faith symbol scratched into it.")
return@on true
}
on(FREELY_GIVEN_STONE, SCENERY, "search"){player, _ ->
ContentAPI.sendDialogue(player, "You search the stone and find it has some sort of spirit symbol scratched into it.")
return@on true
}
on(WISHING_WELL, SCENERY, "make-wish"){player, node ->
if(player.questRepository.isComplete("Nature Spirit") && player.questRepository.isComplete("Wolf Whistle"))
ShopParser.openUid(player, 241)
else
ContentAPI.sendDialogue(player, "You can't do that yet.")
return@on true
}
on(JOURNAL, ITEM, "read"){player, _ ->
player.dialogueInterpreter.open(NSJournalDialogue())
return@on true
}
on(WASHING_BOWL, GROUNDITEM, "take"){player, node ->
SystemLogger.logInfo("Running listener")
GroundItemManager.create(Item(MIRROR), node.location, player)
PickupHandler.take(player, node as GroundItem)
return@on true
}
on(MIRROR, GROUNDITEM, "take"){player, node ->
if(ContentAPI.getAttribute(player, MIRROR_TAKEN, false) && (ContentAPI.inInventory(player, MIRROR) || ContentAPI.inBank(player, MIRROR))){
ContentAPI.sendDialogue(player, "I don't need another one of these.")
return@on true
}
ContentAPI.setAttribute(player, MIRROR_TAKEN, true)
PickupHandler.take(player, node as GroundItem)
return@on true
}
on(SPELLCARD, ITEM, "cast"){player, node ->
if(NSUtils.castBloom(player)){
ContentAPI.removeItem(player, node.asItem(), Container.INVENTORY)
ContentAPI.addItem(player, Items.A_USED_SPELL_2969)
}
return@on true
}
on(intArrayOf(DRUID_POUCH, DRUID_POUCH_EMPTY), ITEM, "fill"){player, node ->
if(player.questRepository.getStage("Nature Spirit") >= 75) {
if (ContentAPI.amountInInventory(player, Items.MORT_MYRE_FUNGUS_2970) >= 3) {
if (node.id != Items.DRUID_POUCH_2958) {
ContentAPI.removeItem(player, node, Container.INVENTORY)
}
ContentAPI.removeItem(player, Item(Items.MORT_MYRE_FUNGUS_2970, 3), Container.INVENTORY)
ContentAPI.addItem(player, Items.DRUID_POUCH_2958, 3)
} else {
ContentAPI.sendDialogue(player, "You need 3 fungus before you can do that.")
}
} else {
ContentAPI.sendDialogue(player, "I don't know how to use that yet.")
}
return@on true
}
onUseWith(SCENERY, Items.SILVER_SICKLE_2961, NATURE_ALTAR){player, used, with ->
ContentAPI.sendItemDialogue(player, Items.SILVER_SICKLEB_2963, "You dump the sickle into the waters.")
if(ContentAPI.removeItem(player, Items.SILVER_SICKLE_2961, Container.INVENTORY)){
ContentAPI.addItem(player, Items.SILVER_SICKLEB_2963, 1)
}
return@onUseWith true
}
onUseWith(NPC, DRUID_POUCH, NPCs.GHAST_1052){player, used, with ->
NSUtils.activatePouch(player, with as MortMyreGhastNPC)
}
onUseWith(SCENERY, items, *stones) { player, used, with ->
when (used.id) {
USED_SPELLCARD -> {
if (with.id == FREELY_GIVEN_STONE) {
if(ContentAPI.removeItem(player, used, Container.INVENTORY)){
ContentAPI.sendNPCDialogue(player, NPCs.FILLIMAN_TARLOCK_1050, "Aha, yes, that seems right well done!")
ContentAPI.sendMessage(player, "The stone seems to absorb the used spell scroll.")
NSUtils.flagCardPlaced(player)
}
} else ContentAPI.sendMessage(player, "You try to put the item on the stone, but it just moves off.")
}
FUNGUS -> {
if (with.id == NATURE_STONE) {
if(ContentAPI.removeItem(player, used, Container.INVENTORY)){
ContentAPI.sendNPCDialogue(player, NPCs.FILLIMAN_TARLOCK_1050, "Aha, yes, that seems right well done!")
ContentAPI.sendMessage(player, "The stone seems to absorb the used fungus.")
NSUtils.flagFungusPlaced(player)
}
} else ContentAPI.sendMessage(player, "You try to put the item on the stone, but it just moves off.")
}
}
return@onUseWith true
}
}
}
class NSJournalDialogue : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when(stage){
0 -> dialogue(*DialUtils.splitLines("Most of the writing is pretty uninteresting, but something inside refers to a nature spirit. The requirements for which are,")).also { stage++ }
1 -> dialogue(*DialUtils.splitLines("'Something from nature', 'something with faith' and 'something of the spirit-to-become freely given'. It's all pretty vague.")).also { stage = END_DIALOGUE }
}
}
}
class FillimanCompletionDialogue : DialogueFile() {
override fun handle(componentID: Int, buttonID: Int) {
when(stage){
0 -> npcl(FacialExpression.NEUTRAL, "Well, hello there again. I was just enjoying the grotto. Many thanks for your help, I couldn't have become a Spirit of nature without you.").also { stage++ }
1 -> npcl(FacialExpression.NEUTRAL, "I must complete the transformation now. Just stand there and watch the show, apparently it's quite good!").also { stage++ }
2 -> {
end()
ContentAPI.lock(player!!, 10)
ContentAPI.submitWorldPulse(CompleteSpellPulse(player!!))
}
}
}
}
class CompleteSpellPulse(val player: Player) : Pulse(2){
var counter = 0
val locations = arrayOf(Location.create(3444, 9740, 0), Location.create(3439, 9740, 0), Location.create(3439, 9737, 0), Location.create(3444, 9737, 0), Location.create(3444, 9735, 0), Location.create(3438, 9735, 0))
val dest = Location.create(3441, 9738, 0)
override fun pulse(): Boolean {
when(counter++){
0 -> repeat(6) { ContentAPI.spawnProjectile(locations[it], dest, 268, 0, 1000, 0, 40, 20) }
1 -> player.questRepository.getQuest("Nature Spirit").setStage(player, 60)
2 -> player.teleport(player.location.transform(0,0,1))
3 -> ContentAPI.openDialogue(player, NPCs.NATURE_SPIRIT_1051, ContentAPI.findLocalNPC(player, NPCs.NATURE_SPIRIT_1051) as NPC).also { ContentAPI.unlock(player); return true }
}
return false
}
}

View file

@ -0,0 +1,241 @@
package rs09.game.content.quest.members.naturespirit
import api.Container
import api.ContentAPI
import api.DialUtils
import core.game.content.dialogue.DialoguePlugin
import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.quest.Quest
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.update.flag.context.Graphics
import core.plugin.Initializable
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import rs09.tools.END_DIALOGUE
@Initializable
class NSTarlockDialogue(player: Player? = null) : DialoguePlugin(player) {
var questStage = 0
override fun newInstance(player: Player?): DialoguePlugin {
return NSTarlockDialogue(player)
}
override fun open(vararg args: Any?): Boolean {
npc = args[0] as NPC
val quest = player.questRepository.getQuest("Nature Spirit")
questStage = quest.getStage(player)
if(questStage > 10 && !ContentAPI.inEquipment(player, Items.GHOSTSPEAK_AMULET_552)){
npcl(FacialExpression.HALF_GUILTY, "OooOOOOOOoooOoOOoOOOo")
setQuest(15)
return false
}
when(questStage){
10, 15 -> sendDialogue("A shifting apparition appears in front of you.").also { stage = 1000 }
20 -> npcl(FacialExpression.HALF_GUILTY, "Oh, hello there, do you still think I'm dead? It's hard to see how I could be dead when I'm still in the world. I can see everything quite clearly. And nothing of what you say reflects the truth.")
25 -> npcl(FacialExpression.HALF_GUILTY, "Oh, hello... Sorry, you've caught me at a bad time, it's just that I've had a sign you see and I need to find my journal.").also { stage = 7 }
30 -> npcl(FacialExpression.HALF_GUILTY, "Thanks for the journal, I've been reading it. It looks like I came to a violent and bitter end but that's not really important. I just have to figure out what I am going to do now?").also { stage = 14 }
35 -> npcl(FacialExpression.NEUTRAL, "Hello there, have you been blessed yet?").also { stage = 60 }
45 -> {
if(ContentAPI.inInventory(player, Items.MORT_MYRE_FUNGUS_2970)){
npcl(FacialExpression.NEUTRAL, "Did you manage to get something from nature?").also { stage = 80 }
} else {
playerl(
FacialExpression.NEUTRAL,
"Hello, I've been blessed but I don't know what to do now."
).also { stage = 70 }
}
}
50 -> npcl(FacialExpression.NEUTRAL, " Hello again! I don't suppose you've found out what the other components of the Nature spell are have you?").also { stage = 90 }
else -> npcl(FacialExpression.NEUTRAL, ".......").also { stage = END_DIALOGUE }
}
return true
}
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
when(stage){
0 -> if(ContentAPI.inInventory(player, Items.MIRROR_2966)){
sendDialogue("You use the mirror on the spirit","of the dead Filliman Tarlock.").also { stage++ }
} else {
playerl(FacialExpression.NEUTRAL, "Yes, I do think you're dead and I'll prove it somehow.").also { stage = 1002 }
}
1 -> playerl(FacialExpression.NEUTRAL, "Here take a look at this, perhaps you can see that you're utterly transparent now!").also { stage++ }
2 -> sendDialogue("The spirit of Filliman reaches forwards and takes the mirror.").also { stage++ }
3 -> npcl(FacialExpression.HALF_GUILTY, "Well, that is the most peculiar thing I've ever experienced. Strange how well it reflects the stagnant swamp behind me, but there is nothing of my own visage apparent.").also { stage++ }
4 -> playerl(FacialExpression.NEUTRAL, "That's because you're dead! Dead as a door nail... Deader in fact... You bear a remarkable resemblance to wormbait! Err... No offence...").also { stage++ }
5 -> npcl(FacialExpression.HALF_GUILTY, "I think you might be right my friend, though I still feel very much alive. It is strange how I still come to be here and yet I've not turned into a Ghast.").also { stage++ }
6 -> npcl(FacialExpression.HALF_GUILTY, " It must be a sign... Yes a sign... I must try to find out what it means. Now, where did I put my journal?").also { stage++ }
7 -> if(!ContentAPI.inInventory(player, Items.JOURNAL_2967)){
playerl(FacialExpression.NEUTRAL, "Where did you put it?").also { stage++; setQuest(25) }
} else sendDialogue("You give the journal to Filliman Tarlock").also { ContentAPI.removeItem(player, Items.JOURNAL_2967, Container.INVENTORY); stage = 10; setQuest(30) }
//no journal
8 -> npcl(FacialExpression.HALF_GUILTY, "Well, if I knew that, I wouldn't still be looking for it. However, I do remember something about a knot? Perhaps I was meant to tie a knot or something?").also { stage = END_DIALOGUE }
//has journal
10 -> playerl(FacialExpression.NEUTRAL, "Here, I found this, maybe you can use it?").also { stage++ }
11 -> npcl(FacialExpression.FRIENDLY, "My journal! That should help to collect my thoughts.").also { stage++ }
12 -> sendDialogue("~ The spirit starts leafing through the journal. ~", "~ He seems quite distant as he regards the pages. ~", "~ After some time the druid faces you again. ~").also {stage++}
13 -> npcl(FacialExpression.HALF_GUILTY, "It's all coming back to me now. It looks like I came to a violent and bitter end but that's not important now. I just have to figure out what I am going to do now?").also { stage++ }
14 -> options("Being dead, what options do you think you have?", "So, what's your plan?", "Well, good luck with that.", "How can I help?", "Ok thanks.").also { stage++ }
15 -> when(buttonId){
1 -> playerl(FacialExpression.NEUTRAL, "Being dead, what options do you think you have? I'm not trying to be rude or anything, but it's not like you have many options is it? I mean, it's either up or down for you isn't it?").also { stage = 20 }
2 -> playerl(FacialExpression.NEUTRAL, "So, what's your plan?").also { stage = 30 }
3 -> playerl(FacialExpression.NEUTRAL, "Well, good luck with that.").also { stage = 40 }
4 -> playerl(FacialExpression.NEUTRAL, "How can I help?").also { stage = 50 }
5 -> playerl(FacialExpression.NEUTRAL, "Ok thanks.").also { stage = END_DIALOGUE }
}
//Being dead, what options
20 -> npcl(FacialExpression.HALF_GUILTY, "Hmm, well you're a poetic one aren't you. Your material world logic stands you in good stead... If you're standing in the material world...").also { stage = 14 }
//what's your plan?
30 -> npcl(FacialExpression.HALF_GUILTY, "In my former incarnation I was Filliman Tarlock, a great druid of some power. I spent many years in this place, which was once a forest and I would wish to protect it as a nature spirit.").also { stage = 14 }
//good luck with that
40 -> npcl(FacialExpression.HALF_GUILTY, "Won't you help me to become a nature spirit? I could really use your help!").also { stage = 14 }
//How can I help?
50 -> npcl(FacialExpression.HALF_GUILTY, "Will you help me to become a nature spirit? The directions for becoming one are a bit vague, I need three things but I know how to get one of them. Perhaps you can help collect the rest?").also { stage++ }
51 -> playerl(FacialExpression.NEUTRAL, "I might be interested, what's involved?").also { stage++ }
52 -> npcl(FacialExpression.HALF_GUILTY, "Well, the book says, that I need, and I quote:- 'Something with faith', 'something from nature' and the 'spirit-to-become' freely given'. Hmm, I know how to get something from nature.").also { stage++ }
53 -> playerl(FacialExpression.NEUTRAL, "Well, that does seem a bit vague.").also { stage++ }
54 -> npcl(FacialExpression.HALF_GUILTY, "Hmm, it does and I could understand if you didn't want to help. However, if you could perhaps at least get the item from nature, that would be a start. Perhaps we can figure out the rest as we go along.").also { stage++ }
55 -> sendDialogue(*DialUtils.splitLines("The druid produces a small sheet of papyrus with some writing on it.")).also { ContentAPI.addItemOrDrop(player, Items.DRUIDIC_SPELL_2968); setQuest(35); stage++ }
56 -> npcl(FacialExpression.NEUTRAL, "This spell needs to be cast in the swamp after you have been blessed. I'm afraid you'll need to go to the temple to the North and ask a member of the clergy to bless you.").also { stage++ }
57 -> playerl(FacialExpression.NEUTRAL, "Blessed, what does that do?").also { stage++ }
58 -> npcl(FacialExpression.NEUTRAL, "It is required if you're to cast this druid spell. Once you've cast the spell, you should find something from nature. Bring it back to me and then we'll try to figure out the other things we need.").also { stage = END_DIALOGUE }
//have you been blessed yet
60 -> playerl(FacialExpression.NEUTRAL, "No, not yet.").also { stage++ }
61 -> npcl(FacialExpression.NEUTRAL, "Well, hurry up!").also { stage++ }
62 -> if(ContentAPI.inInventory(player, Items.DRUIDIC_SPELL_2968) || ContentAPI.inBank(player, Items.DRUIDIC_SPELL_2968)) end()
else playerl(FacialExpression.NEUTRAL, "Could I have another bloom scroll please?").also { stage++ }
63 -> npcl(FacialExpression.NEUTRAL, "Sure, but please look after this one.").also { stage++ }
64 -> sendDialogue("The spirit of Filliman Tarlock gives you another bloom spell.").also { ContentAPI.addItemOrDrop(player, Items.DRUIDIC_SPELL_2968); stage = END_DIALOGUE }
//I've been blessed
70 -> npcl(FacialExpression.NEUTRAL, "Well, you need to bring 'something from nature', 'something with faith' and 'something of the spirit-to- become freely given.'").also { stage++ }
71 -> playerl(FacialExpression.NEUTRAL, "Yeah, but what does that mean?").also { stage++ }
72 -> npcl(FacialExpression.NEUTRAL, "Hmm, it is a conundrum, however, if you use that spell I gave you, you should be able to get from nature. Once you have that, we may be puzzle the rest out.").also { stage++ }
73 -> if(!ContentAPI.inInventory(player, Items.DRUIDIC_SPELL_2968) && !ContentAPI.inBank(player, Items.DRUIDIC_SPELL_2968)){
playerl(FacialExpression.NEUTRAL, "Could I have another bloom scroll please?").also { stage++ }
} else end()
74 -> npcl(FacialExpression.NEUTRAL, "Sure, but please look after this one.").also { stage++ }
75 -> sendDialogue("The spirit of Filliman Tarlock gives you","another bloom spell.").also { ContentAPI.addItem(player, Items.DRUIDIC_SPELL_2968); stage = END_DIALOGUE }
//has fungus
80 -> sendDialogue("You show the fungus to Filliman.").also { stage++ }
81 -> playerl(FacialExpression.NEUTRAL, "Yes, I have a fungus here that I picked.").also { stage++ }
82 -> npcl(FacialExpression.NEUTRAL, "Wonderful, the mushroom represents 'something from nature'. Now we need to work out what the other components of the spell are!").also { stage = 90; setQuest(50) }
//pre-spell options
90 -> options("What are the things that are needed?", "What should I do when I have those things?", "I think I've solved the puzzle!", "Could I have another bloom scroll please?", "Ok, thanks.").also { stage++ }
91 -> when(buttonId){
1 -> playerl(FacialExpression.NEUTRAL, "What are the things that are needed?").also { stage = 100 }
2 -> playerl(FacialExpression.NEUTRAL, "What should I do when I have those things?").also { stage = 110 }
3 -> playerl(FacialExpression.FRIENDLY, "I think I've solved the puzzle!").also { stage = 120 }
4 -> playerl(FacialExpression.FRIENDLY, "Can I have another bloom scroll please?").also { stage = 130 }
5 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE }
}
//What things are needed?
100 -> npcl(FacialExpression.NEUTRAL, "The three things are: 'Something with faith', 'something from nature' and 'something of the spirit-to-become freely given'.").also { stage++ }
101 -> playerl(FacialExpression.FRIENDLY, " Ok, and 'something from nature' is the mushroom from the bloom spell you gave me?").also { stage++ }
102 -> npcl(FacialExpression.FRIENDLY, "Yes, that's correct, that seems right to me. The other things we need are 'something with faith' and 'something of the spirit-to-become freely given.").also { stage++ }
103 -> playerl(FacialExpression.NEUTRAL, "Do you have any ideas what those things are?").also { stage++ }
104 -> npcl(FacialExpression.HALF_GUILTY, "I'm sorry my friend, but I do not.").also { stage = 90 }
//What should I do when I have them?
110 -> npcl(FacialExpression.NEUTRAL, "It says,.. 'to arrange upon three rocks around the spirit-to-become...'. Then I must cast a spell. As you can see, I've already placed the rocks.").also { stage++ }
111 -> playerl(FacialExpression.NEUTRAL, "Can we just place the components on any rock?").also { stage++ }
112 -> npcl(FacialExpression.NEUTRAL, "Well, the only thing the journal says is that 'something with faith stands south of the spirit-to-become', but I'm so confused now I don't really know what that means.").also { stage = 90 }
//I think I've solved the puzzle!
120 -> npcl(FacialExpression.NEUTRAL, "Oh really.. Have you placed all the items on the stones? Ok, well, let's try!").also { stage++ }
121 -> sendDialogue("~ The druid attempts to cast a spell. ~").also { stage++ }
122 -> {
ContentAPI.animate(npc, 812)
if(NSUtils.hasPlacedCard(player) && NSUtils.hasPlacedFungus(player) && NSUtils.onStone(player)){
end()
player.lock()
val locations = arrayOf(Location.create(3439, 3336, 0), Location.create(3441, 3336, 0), Location.create(3440, 3335, 0))
repeat(3) {i -> ContentAPI.spawnProjectile(locations[i], Location.create(3440, 3336, 0), 268, 0, 35, 0, 100, 20) }
ContentAPI.submitIndividualPulse(player, object : Pulse(4){
override fun pulse(): Boolean {
ContentAPI.sendNPCDialogue(player, npc.originalId, "Aha, everything seems to be in place! You can come through now into the grotto for the final section of my transformation.")
setQuest(55)
ContentAPI.unlock(player)
return true
}
override fun stop() {
ContentAPI.visualize(npc, -1, Graphics(266, 80))
super.stop()
}
})
} else {
npcl(FacialExpression.NEUTRAL, "Hmm, something still doesn't seem right. I think we need something more before we can continue.")
}
stage = END_DIALOGUE
}
130 -> if(ContentAPI.inInventory(player, Items.DRUIDIC_SPELL_2968) || ContentAPI.inBank(player, Items.DRUIDIC_SPELL_2968)){
npcl(FacialExpression.NEUTRAL, "No, you've already got one!").also { stage = END_DIALOGUE }
} else {
npcl(FacialExpression.NEUTRAL, "Sure, but look after this one.")
ContentAPI.addItem(player, Items.DRUIDIC_SPELL_2968)
stage = END_DIALOGUE
}
//Initial dialogue
1000 -> playerl(FacialExpression.HALF_ASKING, "Hello?").also { stage++ }
1001 -> if(ContentAPI.inEquipment(player, Items.GHOSTSPEAK_AMULET_552)){
npcl(FacialExpression.EXTREMELY_SHOCKED, "Oh, I understand you! At last, someone who doesn't just mumble. I understand what you're saying!").also { stage++ }
} else npcl(FacialExpression.HALF_GUILTY, "OooOOoOOoOOOOo.")
1002 -> options("I'm wearing an amulet of ghost speak!","How long have you been a ghost?", "What's it like being a ghost?", "Ok, thanks.").also { stage++ }
1003 -> when(buttonId){
1 -> playerl(FacialExpression.NEUTRAL, "I'm wearing an amulet of ghost speak!").also { stage = 1010; setQuest(20) }
2 -> playerl(FacialExpression.NEUTRAL, "How long have you been a ghost?").also { stage = 1020; setQuest(20) }
3 -> playerl(FacialExpression.NEUTRAL, "What's it like being a ghost?").also { stage = 1030; setQuest(20) }
4 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE }
}
1010 -> npcl(FacialExpression.HALF_GUILTY, "Why you poor fellow, have you passed away and you want to send a message back to a loved one?").also { stage++ }
1011 -> playerl(FacialExpression.HALF_THINKING, "Err.. Not exactly...").also { stage++ }
1012 -> npcl(FacialExpression.HALF_GUILTY, "You have come to haunt my dreams until I pass on your message to a dearly loved one. I understand. Pray, tell me who would you like me to pass a message on to?").also { stage++ }
1013 -> playerl(FacialExpression.NEUTRAL, "Ermm, you don't understand... It's just that...").also { stage++ }
1014 -> npcl(FacialExpression.HALF_GUILTY, "Yes!").also { stage++ }
1015 -> playerl(FacialExpression.NEUTRAL, "Well please don't be upset or anything... But you're the ghost!").also { stage++ }
1016 -> npcl(FacialExpression.HALF_GUILTY, "Don't be silly now! That in no way reflects the truth!").also { stage = 1002 }
1020 -> npcl(FacialExpression.HALF_GUILTY, "What?! Don't be preposterous! I'm not a ghost! How could you say something like that?").also { stage++ }
1021 -> playerl(FacialExpression.NEUTRAL, "But it's true, you're a ghost... well, at least that is to say, you're sort of not alive anymore.").also { stage++ }
1022 -> npcl(FacialExpression.HALF_GUILTY, "Don't be silly, I can see you. I can see that tree. If I were dead, I wouldn't be able to see anything. What you say just doesn't reflect the truth. You'll have to try harder to put one over on me!").also { stage = 1002 }
1030 -> npcl(FacialExpression.HALF_GUILTY, "Oh, it's quite.... Oh... Trying to catch me out were you! Anyone can clearly see that I am not a ghost!").also { stage++ }
1031 -> playerl(FacialExpression.NEUTRAL, "But you are a ghost, look at yourself! I can see straight through you! You're as dead as this swamp! Err... No offence or anything...").also { stage++ }
1032 -> npcl(FacialExpression.HALF_GUILTY, "No I won't take offence because I'm not dead and I'm afraid you'll have to come up with some pretty conclusive proof before I believe it. What a strange dream this is.").also { stage = 1002 }
}
return true
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.FILLIMAN_TARLOCK_1050)
}
fun setQuest(stage: Int){
player.questRepository.getQuest("Nature Spirit").setStage(player, stage)
}
}

View file

@ -0,0 +1,125 @@
package rs09.game.content.quest.members.naturespirit
import api.Container
import api.ContentAPI
import core.game.node.Node
import core.game.node.`object`.Scenery
import core.game.node.`object`.SceneryBuilder
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.map.RegionManager.forId
import core.game.world.map.RegionManager.lock
import core.game.world.update.flag.context.Graphics
import core.tools.RandomFunction
import org.rs09.consts.Items
import rs09.game.node.entity.npc.other.MortMyreGhastNPC
object NSUtils {
fun flagFungusPlaced(player: Player) {
ContentAPI.setAttribute(player, "/save:ns:fungus_placed", true)
}
fun flagCardPlaced(player: Player){
ContentAPI.setAttribute(player, "/save:ns:card_placed", true)
}
fun hasPlacedFungus(player: Player): Boolean {
return ContentAPI.getAttribute(player, "ns:fungus_placed", false)
}
fun hasPlacedCard(player: Player): Boolean {
return ContentAPI.getAttribute(player, "ns:card_placed", false)
}
fun onStone(player: Player): Boolean {
return player.location.equals(3440, 3335, 0)
}
fun getGhastKC(player: Player): Int {
return ContentAPI.getAttribute(player,"ns:ghasts_killed", 0) as Int
}
fun incrementGhastKC(player: Player){
ContentAPI.setAttribute(player, "/save:ns:ghasts_killed", getGhastKC(player) + 1)
val msg = when(getGhastKC(player)) {
1 -> "That's one down, two more to go."
2 -> "Two down, only one more to go."
3 -> "That's it! I've killed all 3 Ghasts!"
else -> ""
}
if(!msg.isEmpty()){
ContentAPI.sendMessage(player, msg)
}
}
fun activatePouch(player: Player, attacker: MortMyreGhastNPC): Boolean {
var shouldAddEmptyPouch = false
val pouchAmt = ContentAPI.amountInInventory(player, Items.DRUID_POUCH_2958)
if(pouchAmt == 1) shouldAddEmptyPouch = true
if(pouchAmt > 0 && ContentAPI.removeItem(player, Items.DRUID_POUCH_2958, Container.INVENTORY)){
if(shouldAddEmptyPouch){
ContentAPI.addItem(player, Items.DRUID_POUCH_2957)
}
ContentAPI.spawnProjectile(player, attacker, 268)
ContentAPI.submitWorldPulse(object : Pulse(){
var ticks = 0
override fun pulse(): Boolean {
when(ticks++){
2 -> ContentAPI.visualize(attacker, -1, Graphics(269, 125))
3 -> attacker.transform(attacker.id + 1).also { attacker.attack(player); attacker.setAttribute("woke", ContentAPI.getWorldTicks()); return true }
}
return false
}
})
return true
}
return false
}
fun cleanupAttributes(player: Player){
player.removeAttribute("ns:fungus_placed")
player.removeAttribute("ns:card_placed")
}
@JvmStatic
fun castBloom(player: Player): Boolean{
var success = false
val region = forId(player.location.regionId)
if (player.skills.prayerPoints < 1) {
player.packetDispatch.sendMessage("You don't have enough prayer points to do this.")
return false
}
handleVisuals(player)
val locs = player.location.surroundingTiles
for (o in locs) {
val obj = RegionManager.getObject(o)
if (obj != null) {
if (obj.name.equals("Rotting log", ignoreCase = true) && player.skills.prayerPoints >= 1) {
if (player.location.withinDistance(obj.location, 2)) {
SceneryBuilder.replace(obj, obj.transform(3509))
success = true
}
}
}
}
return success
}
/**
* Handles the draining of prayer points and physical graphics and
* animation.
*/
private fun handleVisuals(player: Player) {
player.skills.decrementPrayerPoints(RandomFunction.random(1, 3).toDouble())
val AROUND_YOU = player.location.surroundingTiles
for (location in AROUND_YOU) {
// The graphic is meant to play on a 3x3 radius around you, but not
// including the tile you are on.
player.packetDispatch.sendGlobalPositionGraphic(263, location)
}
}
}

View file

@ -0,0 +1,164 @@
package rs09.game.content.quest.members.naturespirit
import api.Container
import api.ContentAPI
import core.game.content.dialogue.DialoguePlugin
import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.plugin.Initializable
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import rs09.game.content.dialogue.DialogueFile
import rs09.tools.END_DIALOGUE
@Initializable
class NatureSpiritDialogue(player: Player? = null) : DialoguePlugin(player){
val questStage = player?.questRepository?.getStage("Nature Spirit") ?: 0
override fun newInstance(player: Player?): DialoguePlugin {
return NatureSpiritDialogue(player)
}
override fun open(vararg args: Any?): Boolean {
npc = args[0] as NPC
when(questStage){
60 -> npcl(FacialExpression.NEUTRAL, "Hmm, good, the transformation is complete. Now, my friend, in return for your assistance, I will help you to kill the Ghasts. First bring to me a silver sickle so that I can bless it for you.").also { return true }
65 -> npcl(FacialExpression.NEUTRAL, "Have you brought me a silver sickle?").also { stage = 100; return true }
70 -> npcl(FacialExpression.NEUTRAL, "Now you can go forth and make the swamp bloom. Collect nature's bounty to fill a druids pouch. So armed will the Ghasts be bound to you until, you flee or they are defeated.").also { stage = 200 }
75 -> npcl(FacialExpression.NEUTRAL, "Hello again, my friend. Have you defeated three ghasts as I asked you?").also { stage = 300 }
else -> npcl(FacialExpression.FRIENDLY, "Welcome to my grotto, friend. Enjoy your visit.").also { stage = END_DIALOGUE }
}
return true
}
override fun handle(componentID: Int, buttonID: Int): Boolean {
when(stage){
0 -> playerl(FacialExpression.NEUTRAL,"A silver sickle? What's that?").also { stage++ }
1 -> npcl(FacialExpression.NEUTRAL, "The sickle is the symbol and weapon of the Druid, you need to construct one of silver so that I can bless it, with its powers you will be able to defeat the Ghasts of Mort Myre.").also { stage++; setQuest(65) }
2 -> options("Where would I get a silver sickle?", "What will you do to the silver sickle?", "How can a blessed sickle help me to defeat the Ghasts?", "Ok, thanks.").also { stage++ }
3 -> when(buttonID){
1 -> playerl(FacialExpression.NEUTRAL, "Where would I get a silver sickle?").also { stage = 10 }
2 -> playerl(FacialExpression.NEUTRAL, "What will you do to the silver sickle?").also { stage = 20 }
3 -> playerl(FacialExpression.NEUTRAL, "How can a blessed sickle help me to defeat the Ghasts?").also { stage = 30 }
4 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE }
}
//where sickle
10 -> npcl(FacialExpression.NEUTRAL, "You could make one yourself if you're artisan enough. I've heard of a distant sandy place where you can buy the mould that you require, it's similar in many respects to the creating of a holy symbol.").also { stage = 2 }
//What you gonna do to my sickle bro
20 -> npcl(FacialExpression.NEUTRAL, "Why, I will give it my blessings so that the very swamp in which you stand will blossom and bloom!").also { stage = 2 } //pompous git
//bruh how does a silver sickle help me tho
30 -> npcl(FacialExpression.NEUTRAL, "My blessings will entice nature to bloom in Mort Myre! And then with nature's harvest you can fill a druids pouch and release the Ghasts from their torment.").also { stage = 2 } //this dude kinda weird
//have you brought me a sickle yet bro
100 -> if(ContentAPI.inInventory(player, Items.SILVER_SICKLE_2961)){
playerl(FacialExpression.FRIENDLY, "Yes, here it is. What are you going to do with it?").also { stage = 110 }
} else {
playerl(FacialExpression.NEUTRAL, "No sorry, not yet!").also { stage++ }
}
101 -> npcl(FacialExpression.NEUTRAL, "Well, come to me when you have it.").also { stage = 2 }
/**
* This dialogue drags on so much man this quest has been like 95% dialogue.
* Nature Spirit dude also talks like an uppity self-righteous deity looking dude
*/
//yeah bro I got it
110 -> npcl(FacialExpression.NEUTRAL, "My friend, I will bless it for you and you will then be able to accomplish great things. Now then, I must cast the enchantment. You can bless a new sickle by dipping it in the holy water of the grotto.").also { stage++ }
111 -> sendDialogue("- The Nature Spirit casts a spell on the player. -").also { stage++ }
/**
* Here we go uoooh
*/
112 -> end().also { ContentAPI.lock(player, 10); ContentAPI.submitWorldPulse(SickleBlessPulse(player, npc)) }
//go kill some ghasts bro
200 -> npcl(FacialExpression.NEUTRAL, "Go forth into Mort Myre and slay three Ghasts. You'll be releasing their souls from Mort Myre.").also { stage++ }
201 -> ContentAPI.sendItemDialogue(player, Items.DRUID_POUCH_2957, "The nature spirit gives you an empty pouch.").also { stage++; setQuest(75) }
202 -> npcl(FacialExpression.NEUTRAL, "You'll need this in order to collect together nature's bounty. It will bind the Ghast to you until you flee or it is defeated.").also { stage = END_DIALOGUE }
//Have you killed the ghasts yet bro
300 -> if(NSUtils.getGhastKC(player) >= 3){
playerl(FacialExpression.NEUTRAL, "Yes, I've killed all three and their spirits have been released!").also { stage = 350 }
} else {
playerl(FacialExpression.NEUTRAL, "Not yet.").also { stage++ }
}
//nah bro
301 -> npcl(FacialExpression.NEUTRAL, "Well, when you do, please come to me and I'll reward you!").also { stage++ }
302 -> options("How do I get to attack the Ghasts?", "What's this pouch for?", "What can I do with this sickle?", "I've lost my sickle.", "Ok, thanks.").also { stage++ }
303 -> when(buttonID){
1 -> playerl(FacialExpression.NEUTRAL, "How do I get to attack the Ghasts?").also { stage = 310 }
2 -> playerl(FacialExpression.NEUTRAL, "What's this pouch for?").also { stage = 320 }
3 -> playerl(FacialExpression.NEUTRAL, "What can I do with this sickle?").also { stage = 330 }
4 -> playerl(FacialExpression.NEUTRAL, "I've lost my sickle.").also { stage = 340 }
5 -> playerl(FacialExpression.NEUTRAL, "Ok, thanks.").also { stage = END_DIALOGUE }
}
//How do I attack duh ghosty bois
310 -> npcl(FacialExpression.NEUTRAL, "Go forth and with the sickle make the swamp bloom. Collect natures bounty to fill a druids pouch. So armed will the Ghasts be bound to you until, you flee or they are defeated.").also { stage = 302 }
//What's dis funny pouch for?
320 -> npcl(FacialExpression.NEUTRAL, "It is for collecting natures bounty, once it contains the blossomed items of the swamp, it will make the Ghasts appear and you can then attack them.").also { stage = 302 }
//What can I do wif da sickle m8
330 -> npcl(FacialExpression.NEUTRAL, "You may use it wisely within the area of Mort Myre to affect natures balance and bring forth a bounty of natures harvest. Once collected into the druid pouch will the Ghast be apparent.").also { stage = 302 }
//oi I lost it bruv
340 -> npcl(FacialExpression.NEUTRAL, "If you should lose the blessed sickle, simply bring another to my altar of nature and refresh it in the grotto waters.").also { stage = 302 }
//killed all dem buggers bruv
350 -> npcl(FacialExpression.NEUTRAL, "Many thanks my friend, you have completed your quest!").also { stage++ }
351 -> end().also { player.questRepository.getQuest("Nature Spirit").finish(player) }
}
return true
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.NATURE_SPIRIT_1051)
}
/**
* Needs to spawn 4ish of those green projectiles and the player needs to lift up the sickle
* then needs to open the dialogue back up with the quest stage at 70
*/
internal class SickleBlessPulse(val player: Player, val spirit: NPC) : Pulse() {
var ticks = 0
val locs: MutableList<Location> = player.location.surroundingTiles
override fun pulse(): Boolean {
when(ticks++){
0 -> ContentAPI.animate(spirit, 812)
1 -> repeat(4) {
val loc = locs.random()
locs.remove(loc)
ContentAPI.spawnProjectile(loc, player.location, 268, 0, 400, 0, 125, 180)
ContentAPI.animate(player, 9021)
}
4 -> {
if(ContentAPI.removeItem(player, Items.SILVER_SICKLE_2961, Container.INVENTORY)){
ContentAPI.addItem(player, Items.SILVER_SICKLEB_2963)
ContentAPI.unlock(player)
player.questRepository.getQuest("Nature Spirit").setStage(player, 70)
ContentAPI.openDialogue(player, NPCs.NATURE_SPIRIT_1051, ContentAPI.findLocalNPC(player, NPCs.NATURE_SPIRIT_1051) as NPC)
ContentAPI.sendMessage(player, "Your sickle has been blessed! You can bless a new sickle by dipping it into the grotto waters.")
}
}
6 -> return true
}
return false
}
}
fun setQuest(stage: Int){
player!!.questRepository.getQuest("Nature Spirit").setStage(player!!, stage)
}
}

View file

@ -0,0 +1,122 @@
package rs09.game.content.quest.members.naturespirit
import api.ContentAPI
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.quest.Quest
import core.game.node.entity.skill.Skills
import core.plugin.Initializable
import org.rs09.consts.Items
@Initializable
class NatureSpiritQuest : Quest("Nature Spirit", 95, 94, 2, 307, 0, 1, 110 ) {
override fun newInstance(`object`: Any?): Quest {
return this
}
override fun drawJournal(player: Player?, stage: Int) {
super.drawJournal(player, stage)
player ?: return
var line = 11
if(stage == 0){
line(player, "I can start this quest by speaking to !!Drezel?? in the !!temple of Saradomin??.", line++)
} else {
if(stage >= 10){
line(player, "After talking to Drezel in the temple of Saradomin I've",line++, true)
line(player,"agreed to look for a Druid called Filliman Tarlock.", line++, true)
}
if(stage == 10){
line(player, "I need to look for !!Filliman Tarlock?? in the !!Swamps?? of Mort",line++)
line(player, "Myre. I should be wary of !!Ghasts??.", line++)
}
if(stage == 15){
line(player, "I located a !!spirit?? in the swamp. I believe he's", line++, false)
line(player, "!!Filliman Tarlock?? but I can't understand him.",line++, false)
}
if(stage == 20){
line(player, "I located !!Filliman Tarlock?? in the swamp. I believe he's",line++)
line(player, "dead but he doesn't believe me. I need to convince him.", line++)
}
if(stage >= 25){
line(player, "I located Filliman Tarlock in the swamp and managed to",line++,true)
line(player, "convince him that he is in fact a ghost. ", line++, true)
}
if(stage == 25){
line(player, "Filliman needs his !!journal?? to figure out what to do",line++)
line(player, "next. He mentioned something about a !!knot??.", line++)
}
if(stage >= 30){
line(player, "I recovered Filliman's journal for him.", line++, true)
}
if(stage == 30) {
line(player, "I should speak to !!Filliman Tarlock?? to see what I can",line++)
line(player, "do to help.", line++)
}
if(stage >= 40){
line(player, "I've gone and gotten blessed by Drezel.", line++, true)
}
if(stage >= 35) {
line(player, "I've agreed to help Filliman become a Nature Spirit.",line++, true)
}
if(stage == 35){
line(player, "The first thing Filliman needs me to do is go and get",line++)
line(player, "blessed by !!Drezel?? in the temple of Saradomin.",line++)
}
if (stage == 40){
line(player, "I should return to !!Filliman?? to see what I need to do.", line++, false)
}
if(stage in 45 until 55){
line(player, "In order to help Filliman I need to find 3 things:", line++, false)
line(player, "Something of !!Faith??.",line++, false)
line(player, "Something of !!Nature??.", line++, stage >= 50)
line(player, "Something of the !!spirit-to-be freely given??.", line++, false)
}
if(stage == 50){
line(player, "I know for a fact the fungus is !!something of Nature??.", line++, false)
}
if(stage >= 55){
line(player, "I've helped Filliman complete the spell.", line++, true)
}
if(stage == 55){
line(player, "Filliman has asked me to meet him back inside the !!grotto??.", line++, false)
}
if(stage == 75){
line(player, "I need to go and kill !!3 Ghasts?? for Filliman.", line++, false)
}
if(stage >= 100){
line(player,"!!QUEST COMPLETE!??",line++)
}
}
}
override fun finish(player: Player?) {
super.finish(player)
player ?: return
var ln = 10
player.packetDispatch.sendItemZoomOnInterface(Items.SILVER_SICKLEB_2963,230,277,5)
drawReward(player, "2 Quest Points", ln++)
drawReward(player, "3,000 Crafting XP",ln++)
drawReward(player, "2,000 Hitpoints XP", ln++)
drawReward(player, "2,000 Defence XP", ln++)
ContentAPI.rewardXP(player, Skills.CRAFTING, 3000.0)
ContentAPI.rewardXP(player, Skills.HITPOINTS, 2000.0)
ContentAPI.rewardXP(player, Skills.DEFENCE, 2000.0)
NSUtils.cleanupAttributes(player)
}
}

View file

@ -9,6 +9,7 @@ abstract class InteractionListener : Listener{
val ITEM = 0
val SCENERY = 1
val NPC = 2
val GROUNDITEM = 3
}
fun on(id: Int, type: Int, vararg option: String,handler: (player: Player, node: Node) -> Boolean){
InteractionListeners.add(id,type,option,handler)

View file

@ -170,6 +170,7 @@ object InteractionListeners {
@JvmStatic
fun run(id: Int, type: Int, option: String, player: Player, node: Node): Boolean{
val flag = when(type){
3 -> DestinationFlag.ITEM
2 -> DestinationFlag.ENTITY
1 -> DestinationFlag.OBJECT
else -> DestinationFlag.OBJECT

View file

@ -15,7 +15,7 @@ import rs09.game.interaction.InteractionListener
class MorytaniaListeners : InteractionListener() {
val GROTTO_ENTRANCE = 3516
val GROTTO_EXIT = 3526
val GROTTO_EXIT = intArrayOf(3525, 3526)
val GROTTO_BRIDGE = 3522
val outside = Location.create(3439, 3337, 0)
val inside = Location.create(3442, 9734, 1)
@ -23,10 +23,10 @@ class MorytaniaListeners : InteractionListener() {
private val JUMP_ANIM = Animation(1603)
override fun defineListeners() {
on(GROTTO_ENTRANCE,SCENERY,"enter"){ player, node ->
/* on(GROTTO_ENTRANCE,SCENERY,"enter"){ player, node ->
player.teleport(inside)
return@on true
}
}*/
on(GROTTO_EXIT,SCENERY,"exit"){ player, node ->
player.teleport(outside)

View file

@ -0,0 +1,104 @@
package rs09.game.node.entity.npc.other
import api.Container
import api.ContentAPI
import core.game.content.consumable.Consumables
import core.game.content.consumable.Food
import core.game.interaction.MovementPulse
import core.game.node.entity.Entity
import core.game.node.entity.combat.ImpactHandler
import core.game.node.entity.npc.AbstractNPC
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.update.flag.context.Animation
import core.plugin.Initializable
import core.tools.RandomFunction
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.rs09.consts.Items
import org.rs09.consts.NPCs
import rs09.game.content.quest.members.naturespirit.NSUtils
@Initializable
class MortMyreGhastNPC : AbstractNPC {
//Constructor spaghetti because Arios I guess
constructor() : super(NPCs.GHAST_1052, null, true) {}
private constructor(id: Int, location: Location) : super(id, location) {}
override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC {
isAggressive = id != ids[0]
return MortMyreGhastNPC(id, location)
}
override fun handleTickActions() {
super.handleTickActions()
if(id == ids[0] && RandomFunction.roll(35)){
val players = RegionManager.getLocalPlayers(this, 5).filter { !it.inCombat() }
if(players.isNotEmpty()){
val player = players.random()
ContentAPI.submitIndividualPulse(this, object : MovementPulse(this, player){
override fun pulse(): Boolean {
animate(Animation(1093))
attemptLifeSiphon(player)
return true
}
})
}
} else {
val ticksTransformed = ContentAPI.getWorldTicks() - ContentAPI.getAttribute(this, "woke", 0)
if(!inCombat() && ticksTransformed > 10){
reTransform()
}
}
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.GHAST_1052, NPCs.GHAST_1053)
}
fun attemptLifeSiphon(player: Player){
var hasFood = false
if(NSUtils.activatePouch(player, this)) return
GlobalScope.launch {
for(i in player.inventory.toArray()){
if(i == null) continue
val consumable = Consumables.getConsumableById(i.id)
if(consumable != null && consumable is Food) {
hasFood = true
ContentAPI.removeItem(player, i, Container.INVENTORY)
ContentAPI.addItem(player, Items.ROTTEN_FOOD_2959)
ContentAPI.sendMessage(player, "You feel something attacking your backpack, and smell a terrible stench.")
break
}
}
if(!hasFood && RandomFunction.roll(3)) {
ContentAPI.sendMessage(player, "An attacking Ghast just misses you.")
} else if(!hasFood){
ContentAPI.impact(player, RandomFunction.random(3,6), ImpactHandler.HitsplatType.NORMAL)
ContentAPI.sendMessage(player, "A supernatural force draws energy from you.")
}
}
}
override fun commenceDeath(killer: Entity?) {
super.commenceDeath(killer)
}
override fun finalizeDeath(killer: Entity?) {
super.finalizeDeath(killer)
if(id == ids[1]) {
reTransform()
if(killer is Player){
NSUtils.incrementGhastKC(killer)
ContentAPI.rewardXP(killer, Skills.PRAYER, 30.0)
removeAttribute("woke")
}
}
}
}

View file

@ -245,6 +245,7 @@ NPCConfigParser {
"prj_height",
"end_height",
"spell_id",
"death_gfx",
"magic_level" -> configs.put(it.key.toString(), if (it.value.toString().isEmpty()) Unit else it.value.toString().toInt())
//doubles