Merge branch 2009scape:master into master

This commit is contained in:
Tooze 2026-02-20 19:01:50 +00:00
commit 32e08837dc
23 changed files with 258 additions and 780 deletions

View file

@ -477,7 +477,7 @@
},
{
"item_id": "1939",
"loc_data": "{1,3193,3181,0,150}-{1,3194,3168,0,150}-{1,3191,3162,0,150}-{1,3189,3163,0,150}-{1,3185,3161,0,150}-{1,3182,3165,0,150}-{1,3172,3166,0,150}-{1,3170,3167,0,150}-{1,3164,3169,0,150}-{1,3171,3177,0,150}-{1,3173,3178,0,150}-{1,3164,3180,0,150}-{1,3165,3187,0,150}-{1,3171,3191,0,150}-{1,3178,3190,0,150}-{1,3181,3193,0,150}-{1,3182,3181,0,150}-{1,3493,3408,0,30}-{1,3494,3402,0,30}-{1,3494,3396,0,30}-{1,3499,3401,0,30}-"
"loc_data": "{1,3193,3181,0,150}-{1,3194,3168,0,150}-{1,3191,3162,0,150}-{1,3189,3163,0,150}-{1,3185,3161,0,150}-{1,3182,3165,0,150}-{1,3172,3166,0,150}-{1,3170,3167,0,150}-{1,3164,3169,0,150}-{1,3171,3177,0,150}-{1,3173,3178,0,150}-{1,3164,3180,0,150}-{1,3165,3187,0,150}-{1,3171,3191,0,150}-{1,3178,3190,0,150}-{1,3181,3193,0,150}-{1,3182,3181,0,150}-{1,3428,3322,0,45}-{1,3436,3311,0,45}-{1,3437,3298,0,45}-{1,3428,3293,0,45}-{1,3422,3286,0,45}-{1,3451,3320,0,45}-{1,3434,3277,0,45}-{1,3446,3281,0,45}-{1,3447,3269,0,45}-{1,3450,3359,0,45}-{1,3451,3359,0,45}-{1,3452,3379,0,45}-{1,3452,3380,0,45}-{1,3438,3379,0,45}-{1,3440,3377,0,45}-{1,3414,3367,0,45}-{1,3409,3363,0,45}-{1,3452,3390,0,45}-{1,3452,3391,0,45}-{1,3466,3377,0,45}-{1,3460,3374,0,45}-{1,3459,3370,0,45}-{1,3469,3363,0,45}-{1,3476,3365,0,45}-{1,3460,3353,0,45}-{1,3462,3349,0,45}-{1,3472,3344,0,45}-{1,3493,3408,0,30}-{1,3494,3402,0,30}-{1,3494,3396,0,30}-{1,3499,3401,0,30}-{1,3464,3432,0,45}-{1,3469,3434,0,45}-{1,3473,3435,0,45}-{1,3471,3430,0,45}-{1,3478,3440,0,45}-{1,3477,3444,0,45}-{1,3472,3447,0,45}-{1,3458,3418,0,45}-{1,3460,3414,0,45}-{1,3467,3417,0,45}-{1,3472,3419,0,45}-{1,3474,3415,0,45}-{1,3480,3419,0,45}-{1,3481,3427,0,45}-{1,3460,3406,0,45}-{1,3471,3403,0,45}-{1,3428,3437,0,45}-{1,3434,3433,0,45}-{1,3435,3425,0,45}-{1,3441,3438,0,45}-{1,3445,3435,0,45}-{1,3449,3434,0,45}-{1,3448,3430,0,45}-{1,3454,3441,0,45}-{1,3441,3415,0,45}-{1,3442,3414,0,45}-{1,3436,3403,0,45}-{1,3432,3402,0,45}-{1,3450,3416,0,45}-{1,3445,3405,0,45}-{1,3453,3402,0,45}-"
},
{
"item_id": "1944",

View file

@ -3389,7 +3389,7 @@
},
{
"npc_id": "1227",
"loc_data": "{3424,3318,0,1,0}-{3423,3312,0,1,4}-{3494,3388,0,1,0}-{3507,3435,0,1,4}-{3511,3455,0,1,0}-{3508,3420,0,1,3}-{3508,3420,0,1,4}-{3506,3402,0,1,7}-{3502,3392,0,1,3}-{3484,3456,0,1,0}-{3486,3458,0,1,4}-"
"loc_data": "{3424,3318,0,1,0}-{3423,3312,0,1,4}-{3426,3381,0,1,0}-{3418,3375,0,1,0}-{3414,3363,0,1,0}-{3411,3358,0,1,0}-{3441,3356,0,1,0}-{3424,3426,0,1,0}-{3414,3413,0,1,0}-{3421,3408,0,1,0}-{3422,3408,0,1,0}-{3440,3406,0,1,0}-{3412,3392,0,1,0}-{3494,3388,0,1,0}-{3467,3346,0,1,0}-{3465,3347,0,1,0}-{3507,3435,0,1,4}-{3511,3455,0,1,0}-{3508,3420,0,1,3}-{3508,3420,0,1,4}-{3506,3402,0,1,7}-{3502,3392,0,1,3}-{3481,3446,0,1,0}-{3480,3445,0,1,0}-{3479,3443,0,1,0}-{3471,3433,0,1,0}-{3459,3432,0,1,0}-{3459,3426,0,1,0}-{3459,3418,0,1,0}-{3456,3415,0,1,0}-{3484,3456,0,1,0}-{3486,3458,0,1,4}-{3563,3441,0,1,0}-{3544,3430,0,1,0}-{3540,3413,0,1,0}-{3529,3441,0,1,0}-"
},
{
"npc_id": "1228",
@ -3397,11 +3397,11 @@
},
{
"npc_id": "1229",
"loc_data": "{3421,3302,0,1,4}-{3417,3291,0,1,4}-{3423,3288,0,1,4}-{3418,3284,0,1,6}-{3427,3298,0,1,3}-{3431,3303,0,1,1}-{3432,3311,0,1,1}-{3502,3455,0,1,2}-{3516,3415,0,1,4}-{3511,3401,0,1,1}-{3492,3457,0,1,1}-"
"loc_data": "{3421,3302,0,1,4}-{3417,3291,0,1,4}-{3423,3288,0,1,4}-{3418,3284,0,1,6}-{3427,3298,0,1,3}-{3431,3303,0,1,1}-{3432,3311,0,1,1}-{3427,3376,0,1,0}-{3420,3407,0,1,0}-{3425,3403,0,1,0}-{3438,3399,0,1,0}-{3446,3406,0,1,0}-{3420,3426,0,1,0}-{3477,3368,0,1,0}-{3479,3367,0,1,0}-{3502,3455,0,1,2}-{3516,3415,0,1,4}-{3511,3401,0,1,1}-{3484,3443,0,1,0}-{3483,3442,0,1,0}-{3457,3416,0,1,0}-{3469,3402,0,1,0}-{3471,3401,0,1,0}-{3492,3457,0,1,1}-"
},
{
"npc_id": "1230",
"loc_data": "{3419,3280,0,1,4}-{3421,3277,0,1,4}-{3431,3275,0,1,3}-{3428,3314,0,1,1}-{3436,3318,0,1,5}-{3434,3322,0,1,6}-{3496,3447,0,1,7}-{3520,3437,0,1,4}-"
"loc_data": "{3419,3280,0,1,4}-{3421,3277,0,1,4}-{3431,3275,0,1,3}-{3428,3314,0,1,1}-{3436,3318,0,1,5}-{3434,3322,0,1,6}-{3476,3350,0,1,0}-{3496,3447,0,1,7}-{3481,3442,0,1,0}-{3520,3437,0,1,4}-"
},
{
"npc_id": "1231",
@ -3409,7 +3409,7 @@
},
{
"npc_id": "1232",
"loc_data": "{3441,3265,0,1,5}-{3438,3277,0,1,7}-{3438,3283,0,1,4}-{3439,3315,0,1,3}-{3439,3310,0,1,1}-{3435,3302,0,1,3}-{3508,3420,0,1,1}-{3514,3415,0,1,7}-{3525,3417,0,1,3}-"
"loc_data": "{3441,3265,0,1,5}-{3438,3277,0,1,7}-{3438,3283,0,1,4}-{3439,3315,0,1,3}-{3439,3310,0,1,1}-{3435,3302,0,1,3}-{3411,3362,0,1,0}-{3415,3365,0,1,0}-{3415,3371,0,1,0}-{3413,3373,0,1,0}-{3431,3383,0,1,0}-{3508,3420,0,1,1}-{3514,3415,0,1,7}-{3470,3400,0,1,0}-{3464,3405,0,1,0}-{3474,3417,0,1,0}-{3474,3419,0,1,0}-{3477,3419,0,1,0}-{3480,3441,0,1,0}-{3525,3417,0,1,3}-"
},
{
"npc_id": "1233",
@ -3417,7 +3417,7 @@
},
{
"npc_id": "1234",
"loc_data": "{3446,3282,0,1,1}-{3446,3298,0,1,3}-{3449,3304,0,1,1}-{3513,3383,0,1,0}-{3491,3388,0,1,3}-{3493,3454,0,1,2}-{3493,3454,0,1,0}-{3518,3454,0,1,2}-{3519,3441,0,1,7}-{3506,3417,0,1,5}-{3519,3401,0,1,2}-{3499,3420,0,1,3}-{3516,3410,0,1,7}-{3520,3412,0,1,0}-"
"loc_data": "{3446,3282,0,1,1}-{3446,3298,0,1,3}-{3449,3304,0,1,1}-{3409,3387,0,1,0}-{3424,3377,0,1,0}-{3438,3351,0,1,0}-{3513,3383,0,1,0}-{3491,3388,0,1,3}-{3461,3355,0,1,0}-{3473,3348,0,1,0}-{3477,3367,0,1,0}-{3493,3454,0,1,2}-{3493,3454,0,1,0}-{3518,3454,0,1,2}-{3519,3441,0,1,7}-{3506,3417,0,1,5}-{3519,3401,0,1,2}-{3499,3420,0,1,3}-{3516,3410,0,1,7}-{3483,3444,0,1,0}-{3479,3423,0,1,0}-{3466,3408,0,1,0}-{3466,3407,0,1,0}-{3520,3412,0,1,0}-"
},
{
"npc_id": "1235",

View file

@ -11,6 +11,7 @@ import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Sounds
fun kidnapPlayer(npc: NPC, player: Player, dest: Location, playerLine: String? = null, callback: (player: Player, npc: NPC) -> Unit) {
clearScripts(player)
val lockDuration = if (playerLine != null) 4 else 6
lock(player, lockDuration)
queueScript(player, 1, QueueStrength.SOFT) { stage: Int ->

View file

@ -43,7 +43,7 @@ class CiderOnForesterDialogue : DialogueFile() {
end()
}
else if(player!!.getAttribute("seersCiderPub",-1) !in 0..3 && !player!!.achievementDiaryManager.hasCompletedTask(DiaryType.SEERS_VILLAGE,0,6)){
player!!.setAttribute("seersCiderPub",0)
player!!.setAttribute("seersCiderPub", 1)
end()
}else if(player!!.getAttribute("seersCiderPub",-1) in 0..3){
player!!.incrementAttribute("seersCiderPub",1)

View file

@ -20,7 +20,7 @@ public class SnelmCraftPlugin extends UseWithHandler {
* The snelm data.
*/
private static final int[][] DATA = new int[][] { { 3345, 3327 }, { 3355, 3337 },// blamish
{ 3349, 3341 }, { 3341, 3359 },// ochre
{ 3349, 3331 }, { 3359, 3341 },// ochre
{ 3347, 3329 }, { 3357, 3339 },// blood
{ 3351, 3333 }, { 3361, 3343 },// blue
{ 3353, 3335 },// bark

View file

@ -12,26 +12,29 @@ class LeprechaunNoter : InteractionListener {
val LEPRECHAUNS = intArrayOf(NPCs.TOOL_LEPRECHAUN_3021,NPCs.GOTH_LEPRECHAUN_8000,NPCs.TOOL_LEPRECHAUN_4965,NPCs.TECLYN_2861)
override fun defineListeners() {
onUseWith(IntType.NPC,CROPS,*LEPRECHAUNS){ player, used, with ->
onUseAnyWith(IntType.NPC,*LEPRECHAUNS) { player, used, with ->
val usedItem = used.asItem()
val npc = with.asNpc()
val expr = when(npc.id){
3021 -> core.game.dialogue.FacialExpression.OLD_NORMAL
NPCs.TOOL_LEPRECHAUN_3021 -> core.game.dialogue.FacialExpression.OLD_NORMAL
else -> core.game.dialogue.FacialExpression.FRIENDLY
}
if(usedItem.noteChange != usedItem.id){
val amt = player.inventory.getAmount(usedItem.id)
if(player.inventory.remove(Item(usedItem.id,amt))){
player.inventory.add(Item(usedItem.noteChange,amt))
when {
(!usedItem.definition.isUnnoted) -> { // If the item is a banknote
player.dialogueInterpreter.sendDialogues(npc.id,expr,"That IS a banknote!")
}
(usedItem.id !in CROPS || usedItem.definition.noteId < 0) -> { // If the item is not a crop or cannot be noted
player.dialogueInterpreter.sendDialogues(npc.id,expr,"Nay, I can't turn that into a banknote.")
}
else -> { // Note the item
val amt = amountInInventory(player, usedItem.id)
if (removeItem(player, Item(usedItem.id, amt))) {
addItem(player, usedItem.noteChange, amt)
}
sendItemDialogue(player, usedItem.id, "The leprechaun exchanges your items for banknotes.")
}
sendItemDialogue(player,usedItem.id,"The leprechaun exchanges your items for banknotes.")
} else {
// Unsure why the line below no longer functions, despite only changing the line above to be more correct. Using your note(NOT CROP) on the leprechaun no longer functions because of this. - Crash
player.dialogueInterpreter.sendDialogues(npc.id,expr,"That IS a banknote!")
}
return@onUseWith true
return@onUseAnyWith true
}
}
}

View file

@ -209,7 +209,15 @@ class Patch(val player: Player, val patch: FarmingPatch, var plantable: Plantabl
PatchType.HERB_PATCH -> {
if(isDead) setVisualState(getHerbDeathValue())
else if(isDiseased && !isDead) setVisualState(getHerbDiseaseValue())
else setVisualState((plantable?.value ?: 0) + currentGrowthStage)
else {
var state = (plantable?.value ?: 0) + currentGrowthStage
// Spirit Weed contains states with bits 0x40 and 0x80 set, which the disease-free patch scenery interprets as watered/dead
// Visually transmogrifies Spirit Weed (204-208) into Guam (4-8) while it's planted in the disease-free patch
if (patch == FarmingPatch.TROLL_STRONGHOLD_HERB && plantable == Plantable.SPIRIT_WEED_SEED) {
state = 4 + currentGrowthStage
}
setVisualState(state)
}
}
PatchType.MUSHROOM_PATCH -> {
if(isDead) setVisualState(getMushroomDeathValue())

View file

@ -50,7 +50,7 @@ class WoodcuttingListener : InteractionListener {
defineInteraction(
IntType.SCENERY,
ids = WoodcuttingNode.values().map { it.id }.toIntArray(),
"chop-down", "chop", "chop down", "cut down",
"chop-down", "chop", "chop down", "cut down", "cut-branch",
persistent = true,
allowedDistance = 1,
handler = ::handleWoodcutting
@ -74,10 +74,10 @@ class WoodcuttingListener : InteractionListener {
if (clockReady(player, Clocks.SKILLING)) {
animateWoodcutting(player)
if (resource == WoodcuttingNode.DRAMEN_TREE) {
if (resource in arrayOf(WoodcuttingNode.DRAMEN_TREE, WoodcuttingNode.SWAYING_TREE)) {
// Reward after one chop and then abort chopping (this is authentic)
queueScript(player, 1, QueueStrength.STRONG) {
sendMessage(player, "You cut a branch from the Dramen tree.")
sendMessage(player, "You cut a branch from the ${if (resource == WoodcuttingNode.DRAMEN_TREE) "Dramen" else "strangely musical"} tree.")
addItem(player, resource.getReward())
return@queueScript clearScripts(player)
}

View file

@ -1,81 +0,0 @@
package content.global.skill.summoning.familiar;
import core.game.dialogue.DialoguePlugin;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.TeleportManager.TeleportType;
import core.game.world.map.Location;
import core.plugin.Initializable;
import core.game.world.map.zone.impl.WildernessZone;
/**
* Represents the spirit graahk's dialogue
* @author Splinter
* @version 1.0
*/
@Initializable
public final class SpiritGraahkDialogue extends DialoguePlugin {
/**
* Constructs a new {@code GertrudesCatDialogue} {@code Object}.
*/
public SpiritGraahkDialogue() {
/**
* empty.
*/
}
/**
* Constructs a new {@code GertrudesCatDialogue} {@code Object}.
* @param player the player.
*/
public SpiritGraahkDialogue(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new SpiritGraahkDialogue(player);
}
@Override
public boolean open(Object... args) {
npc = (NPC) args[0];
if (!(npc instanceof Familiar)) {
return false;
}
final Familiar fam = (Familiar) npc;
if (fam.getOwner() != player) {
player.getPacketDispatch().sendMessage("This is not your familiar.");
return true;
} else {
interpreter.sendOptions("Select an Option", "Chat", "Teleport");
}
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
switch (buttonId) {
case 1:
player.sendMessage("The Graahk does not feel like talking now.");
end();
break;
case 2:
if (!WildernessZone.checkTeleport(player, 20)) {
player.sendMessage("You cannot teleport with the Graahk above level 20 wilderness.");
end();
} else {
player.getTeleporter().send(new Location(2786, 3002), TeleportType.NORMAL);
end();
}
break;
}
return true;
}
@Override
public int[] getIds() {
return new int[] { 7353, 7364 };
}
}

View file

@ -0,0 +1,115 @@
package content.global.skill.summoning.familiar
import core.api.openDialogue
import core.game.dialogue.ChatAnim
import core.game.dialogue.DialogueLabeller
import core.game.dialogue.DialoguePlugin
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.TeleportManager.TeleportType
import core.game.node.entity.skill.Skills
import core.game.world.map.Location
import core.game.world.map.zone.impl.WildernessZone
import core.plugin.Initializable
import core.tools.RandomFunction
import org.rs09.consts.NPCs
/**
* Represents the spirit graahk's dialogue
* @author Splinter & Bishop
* @version 2.0
*/
@Initializable
class SpiritGraahkDialogue : DialoguePlugin {
constructor()
constructor(player: Player?) : super(player)
override fun newInstance(player: Player?): DialoguePlugin {
return SpiritGraahkDialogue(player)
}
override fun open(vararg args: Any?): Boolean {
npc = args[0] as NPC?
if (npc !is Familiar) {
return false
}
val fam = npc as Familiar
if (fam.getOwner() !== player) {
player.packetDispatch.sendMessage("This is not your familiar.")
return true
} else {
interpreter.sendOptions("Select an Option", "Chat", "Teleport")
}
return true
}
override fun handle(interfaceId: Int, buttonId: Int): Boolean {
when (buttonId) {
1 -> if (player.skills.getStaticLevel(Skills.SUMMONING) >= 67) {
openDialogue(player, SpiritGraahkDialogueFile(), npc)
} else {
player.sendMessage("The Graahk does not feel like talking now.") //This message is likely inauthentic, but I cannot source the correct one so I'm keeping the default here -Bishop
end()
}
2 -> if (!WildernessZone.checkTeleport(player, 20)) {
player.sendMessage("You cannot teleport with the Graahk above level 20 wilderness.")
end()
} else {
player.teleporter.send(Location(2786, 3002), TeleportType.NORMAL)
end()
}
}
return true
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.SPIRIT_GRAAHK_7363, NPCs.SPIRIT_GRAAHK_7364)
}
}
class SpiritGraahkDialogueFile : DialogueLabeller() {
override fun addConversation() {
exec { player, npc ->
val spiritGraahkRandomConversation = RandomFunction.random(4)
when (spiritGraahkRandomConversation) {
0 -> loadLabel(player, "spiky spikes")
1 -> loadLabel(player, "pet the spikes")
2 -> loadLabel(player, "therapeutic spikes")
3 -> loadLabel(player, "inspect the spikes")
}
}
label("spiky spikes")
player(ChatAnim.NEUTRAL, "Your spikes are looking particularly spiky today.")
npc(ChatAnim.GHRAAK_SHAKE_VIGOROUS, "Graaaaahk raaaawr?", "(Really, you think so?)")
player(ChatAnim.HAPPY, "Yes. Most pointy, indeed.")
npc(ChatAnim.GHRAAK_SHAKE_VIGOROUS, "Raaaawr...", "(That's really kind of you to say. I was going to spike", "you but I won't now...)")
player(ChatAnim.HAPPY, "Thanks?")
npc(ChatAnim.GHRAAK_SHAKE_VIGOROUS, "...Grrrrr ark.", "(...I'll do it later instead.)")
player(ChatAnim.HAPPY, "*Sigh!*")
label("pet the spikes")
npc(ChatAnim.GHRAAK_SHAKE_MILD, "Graahk grrrrowl?", "(My spikes hurt, could you pet them for me?)")
player(ChatAnim.HAPPY, "Aww, of course I can I'll just... Oww! I think you drew", "blood that time.")
label("therapeutic spikes")
npc(ChatAnim.GHRAAK_SHAKE_MILD, "Graahk!", "(Hi!)") //?
player(ChatAnim.SUSPICIOUS, "Hello. Are you going to spike me again?")
npc(ChatAnim.GHRAAK_SHAKE_VIGOROUS, "Grar rawr howl!", "(No, I got a present to apologise for last time.)")
player(ChatAnim.HAPPY, "That's really sweet, thank you.")
npc(ChatAnim.GRAAHK_NOD, "Howl graaahk rawr.", "(Here you go, it's a special cushion to make you", "comfortable.)")
player(ChatAnim.ANGRY, "It's made of spikes!")
npc(ChatAnim.GHRAAK_SHAKE_VIGOROUS, "Howl graaahk rawr.", "(Yes, but they're therapeutic spikes.)")
player(ChatAnim.ANGRY, "...")
label("inspect the spikes")
player(ChatAnim.HAPPY, "How's your day going?")
npc(ChatAnim.GHRAAK_SHAKE_VIGOROUS, "Graaahk. Grak grak!", "(It's great! Actually I've got something to show you!)")
player(ChatAnim.HALF_ASKING, "Oh? What's that?")
npc(ChatAnim.GRAAHK_NOD, "Grrrrrk hiss graaaaa!", "(You'll need to get closer!)")
player(ChatAnim.HALF_ASKING, "I can't see anything...")
npc(ChatAnim.GHRAAK_SHAKE_VIGOROUS, "Grah grr aaaaahk grahk.", "(It's really small - even closer.)")
player(ChatAnim.ANGRY, "Oww! I'm going to have your spikes trimmed!")
}
}

View file

@ -1,52 +0,0 @@
package content.global.skill.summoning.familiar;
import core.game.node.entity.skill.SkillBonus;
import core.game.node.entity.skill.Skills;
import core.game.node.entity.combat.equipment.WeaponInterface;
import core.plugin.Initializable;
import core.game.node.entity.player.Player;
/**
* Represents the Spirit Graahk familiar.
* @author Aero
*/
@Initializable
public class SpiritGraahkNPC extends Familiar {
/**
* Constructs a new {@code SpiritGraahkNPC} {@code Object}.
*/
public SpiritGraahkNPC() {
this(null, 7363);
}
/**
* Constructs a new {@code SpiritGraahkNPC} {@code Object}.
* @param owner The owner.
* @param id The id.
*/
public SpiritGraahkNPC(Player owner, int id) {
super(owner, id, 4900, 12810, 3, WeaponInterface.STYLE_AGGRESSIVE);
boosts.add(new SkillBonus(Skills.HUNTER, 5));
}
@Override
public Familiar construct(Player owner, int id) {
return new SpiritGraahkNPC(owner, id);
}
@Override
protected boolean specialMove(FamiliarSpecial special) {
if (!super.isOwnerAttackable()) {
return false;
}
call();
return true;
}
@Override
public int[] getIds() {
return new int[] { 7363, 7364 };
}
}

View file

@ -0,0 +1,51 @@
package content.global.skill.summoning.familiar
import core.game.node.entity.Entity
import core.game.node.entity.combat.equipment.WeaponInterface
import core.game.node.entity.player.Player
import core.game.node.entity.skill.SkillBonus
import core.game.node.entity.skill.Skills
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import core.plugin.Initializable
import core.tools.RandomFunction
import org.rs09.consts.Items
import org.rs09.consts.NPCs
/**
* Represents the Spirit Graahk familiar.
* @author Aero & Bishop
*/
@Initializable
class SpiritGraahkNPC @JvmOverloads constructor(owner: Player? = null, id: Int = NPCs.SPIRIT_GRAAHK_7363) :
Familiar(owner, id, 4900, Items.SPIRIT_GRAAHK_POUCH_12810, 3, WeaponInterface.STYLE_AGGRESSIVE) {
init {
boosts.add(SkillBonus(Skills.HUNTER, 5.0))
}
override fun construct(owner: Player?, id: Int): Familiar {
return SpiritGraahkNPC(owner, id)
}
override fun specialMove(special: FamiliarSpecial): Boolean {
val target: Entity = special.target ?: return false
if (!canCombatSpecial(target)) {
return false
}
properties.combatPulse.attack(target)
return true
}
override fun visualizeSpecialMove() {
owner.visualize(Animation.create(7660), Graphics.create(1316))
}
override fun getIds(): IntArray {
return intArrayOf(NPCs.SPIRIT_GRAAHK_7363, NPCs.SPIRIT_GRAAHK_7364)
}
override fun getText(): String {
return if (RandomFunction.random(2) == 0) "Howl!" else "Rowr!"
}
}

View file

@ -1,29 +0,0 @@
package content.global.skill.summoning.familiar;
import core.cache.def.impl.NPCDefinition;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
import core.game.node.entity.player.Player;
import core.plugin.Initializable;
import core.plugin.Plugin;
/**
* Represents the plugin used to handle the spirit graahk familiar
* @author Splinter
*/
@Initializable
public final class SpiritGraahkOptionPlugin extends OptionHandler {
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
NPCDefinition.forId(7363).getHandlers().put("option:interact", this);
return this;
}
@Override
public boolean handle(final Player player, Node node, String option) {
player.getDialogueInterpreter().open(7353, node.asNpc());
return true;
}
}

View file

@ -0,0 +1,27 @@
package content.global.skill.summoning.familiar
import core.cache.def.impl.NPCDefinition
import core.game.interaction.OptionHandler
import core.game.node.Node
import core.game.node.entity.player.Player
import core.plugin.Initializable
import core.plugin.Plugin
import org.rs09.consts.NPCs;
/**
* Represents the plugin used to handle the spirit graahk familiar
* @author Splinter
*/
@Initializable
class SpiritGraahkOptionPlugin : OptionHandler() {
@Throws(Throwable::class)
override fun newInstance(arg: Any?): Plugin<Any?> {
NPCDefinition.forId(NPCs.SPIRIT_GRAAHK_7363).getHandlers()["option:interact"] = this
return this
}
override fun handle(player: Player, node: Node, option: String?): Boolean {
player.dialogueInterpreter.open(NPCs.SPIRIT_GRAAHK_7364, node.asNpc())
return true
}
}

View file

@ -84,7 +84,7 @@ class DiamondOfIceListeners : InteractionListener {
on(Scenery.ICE_LEDGE_6455, SCENERY, "use") { player, node ->
if ((getQuestStage(player, DesertTreasure.questName) == 9 &&
DesertTreasure.getSubStage(player, DesertTreasure.attributeIceStage) >= 3) ||
DesertTreasure.getSubStage(player, DesertTreasure.attributeIceStage) >= 3) ||
getQuestStage(player, DesertTreasure.questName) >= 10) {
if (inEquipment(player, Items.SPIKED_BOOTS_3107)) {
teleport(player, Location(2838, 3803, 1))

View file

@ -67,8 +67,8 @@ class KamilCombatHandler: MultiSwingHandler(
// I can't be bothered to fix fucking frozen. The player can hit through frozen. What the fuck is frozen for then, to glue his fucking legs???
if (RandomFunction.roll(3) && !hasTimerActive(victim, "frozen") && !hasTimerActive(victim, "frozen:immunity")) {
sendChat(entity as NPC, "Sallamakar Ro!") // Salad maker roll.
impact(victim, 5)
impact(victim, 5)
impact(victim, if (victim.hasProtectionPrayer(CombatStyle.MAGIC)) 0 else 5)
impact(victim, if (victim.hasProtectionPrayer(CombatStyle.RANGE)) 0 else 5)
registerTimer(victim, spawnTimer("frozen", 7, true))
sendMessage(victim, "You've been frozen!")
// FIXME: before the below vfx hits, there should be another one that looks kinda like a wind wave exploding at the player's feet. Hope somebody finds the id.

View file

@ -260,11 +260,6 @@ class TFTInteractionListeners : InteractionListener {
return@on true
}
on(SWAYING_TREE, IntType.SCENERY, "cut-branch"){ player, node ->
player.pulseManager.run(WoodcuttingSkillPulse(player, node as Scenery))
return@on true
}
on(SHOPNPCS, IntType.NPC, "Trade") { player, npc ->
if(isQuestComplete(player, Quests.THE_FREMENNIK_TRIALS)){
npc.asNpc().openShop(player)

View file

@ -1,575 +0,0 @@
package content.region.wilderness.handlers;
import core.game.dialogue.DialogueAction;
import core.game.dialogue.DialoguePlugin;
import core.game.dialogue.FacialExpression;
import core.game.global.action.DoorActionHandler;
import core.game.node.entity.skill.SkillPulse;
import core.game.node.entity.skill.Skills;
import content.global.skill.gather.SkillingResource;
import content.data.skill.SkillingTool;
import core.game.interaction.NodeUsageEvent;
import core.game.interaction.Option;
import core.game.interaction.UseWithHandler;
import core.game.node.Node;
import core.game.node.entity.Entity;
import core.game.node.entity.combat.BattleState;
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.item.Item;
import core.game.world.GameWorld;
import core.game.world.map.Location;
import core.game.world.map.zone.MapZone;
import core.game.world.map.zone.ZoneBorders;
import core.game.world.map.zone.ZoneBuilder;
import core.plugin.Initializable;
import core.plugin.Plugin;
import core.plugin.ClassScanner;
import java.util.ArrayList;
import java.util.List;
/**
* Handles the Wilderness Area Zone.
* @author Empathy
* @author Vexia
*
*/
@Initializable
public class WildernessAreaZone extends MapZone implements Plugin<Object> {
/**
* The list of players in the arena.
*/
private final List<Player> players = new ArrayList<>(20);
/**
* Constructs a new {@code WildernessAreaZone} {@code Object}
*/
public WildernessAreaZone() {
super("Wilderness Area Zone", true);
}
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
ZoneBuilder.configure(this);
ClassScanner.definePlugins(new MandrithDialoguePlugin(), new PilesDialoguePlugin(), new PilesItemHandler(), new RuniteGolemNPC());
return this;
}
@Override
public boolean interact(Entity e, final Node node, Option option) {
if (e instanceof Player) {
Player player = e.asPlayer();
switch (node.getId()) {
case 42044:
switch (option.getName()) {
case "Open":
if (player.getLocation().equals(Location.create(3184, 3945, 0))) {
if (player.getInventory().contains(995, 7500)) {
player.getDialogueInterpreter().sendOptions("Pay 7500 coins?", "Yes", "No");
player.getDialogueInterpreter().addAction(new DialogueAction() {
@Override
public void handle(Player player, int buttonId) {
switch (buttonId) {
case 2:
if (player.getInventory().remove(new Item(995, 7500))) {
DoorActionHandler.handleDoor(player, node.asScenery());
player.sendMessage("You pay 7500 coins and enter the resource arena.");
}
break;
}
}
});
return true;
}
player.sendMessage("You do not have enough coins to enter the Arena.");
return true;
}
DoorActionHandler.handleDoor(player, node.asScenery());
return true;
case "Players-inside":
if (player.getLocation().getY() < 3495) {
player.sendMessage("All you see is the barren wasteland of the Wilderness.");
return true;
}
String message = "You peek inside the gate and see no adventurers inside the arena.";
if (players.size() > 0) {
message = players.size() == 1 ? "You peek inside the gate and see 1 adventurer inside the arena." : "You peek inside the gate and see " + players.size() + " inside the arena.";
}
player.getDialogueInterpreter().sendDialogue(message);
break;
}
break;
case 8666:
switch (option.getName()) {
case "Mine":
player.getPulseManager().run(new RuniteGolemMinePulse(player, node.asNpc()));
return true;
case "Prospect":
player.sendMessages("You examine the rock for ores...", "This rock contains runite ore.");
return true;
}
break;
}
}
return super.interact(e, node, option);
}
@Override
public Object fireEvent(String identifier, Object... args) {
return null;
}
@Override
public void configure() {
register(new ZoneBorders(3174, 3924, 3196, 3945));
}
@Override
public boolean enter(Entity e) {
if (e instanceof Player) {
Player p = e.asPlayer();
players.add(p);
}
return super.enter(e);
}
@SuppressWarnings("deprecation")
@Override
public boolean leave(Entity e, boolean logout) {
if (e instanceof Player) {
Player p = e.asPlayer();
players.remove(p);
if (logout && p.getLocation().getY() <= 3944) {
p.setLocation(Location.create(3184, 3945, 0));
return true;
}
}
return super.leave(e, logout);
}
/**
* The mining of a runite golem.
* @author Vexia
*
*/
public class RuniteGolemMinePulse extends SkillPulse<NPC> {
/**
* The skilling tool used.
*/
private SkillingTool tool;
/**
* The ticks.
*/
private int ticks;
/**
* Constructs a new @{Code RuniteGolemMinePulse} object.
* @param player The player.
* @param node The node.
*/
public RuniteGolemMinePulse(Player player, NPC node) {
super(player, node);
}
@Override
public void start() {
resource = SkillingResource.RUNITE_ORE_0;
if (node.getId() != 8666) {
player.getPacketDispatch().sendMessage("This rock contains no ore.");
return;
}
if (resource == null) {
return;
}
super.start();
}
@Override
public boolean checkRequirements() {
if (node.getId() != 8666) {
return false;
}
if (player.getSkills().getLevel(resource.getSkillId()) < resource.getLevel()) {
player.getPacketDispatch().sendMessage("You need a " + Skills.SKILL_NAME[resource.getSkillId()] + " level of " + resource.getLevel() + " to mine this rock.");
return false;
}
if ((tool = SkillingTool.getPickaxe(player)) == null) {
player.getPacketDispatch().sendMessage("You do not have a pickaxe to use.");
return false;
}
if (player.getInventory().freeSlots() < 1) {
player.getDialogueInterpreter().sendDialogue("Your inventory is too full to hold any more runite ore.");
return false;
}
return true;
}
@Override
public void animate() {
if (tool != null) {
player.animate(tool.getAnimation());
}
}
@Override
public boolean reward() {
if (++ticks % 4 != 0) {
return false;
}
if (!checkReward()) {
return false;
}
node.transform(8667);
node.setAttribute("reward-tick", GameWorld.getTicks() + resource.getRespawnDuration());
final Item item = new Item(resource.getReward());
player.getInventory().add(item);
player.getSkills().addExperience(resource.getSkillId(), resource.getExperience(), true);
return true;
}
/**
* Checks if the player gets rewarded.
* @return {@code True} if so.
*/
private boolean checkReward() {
int skill = Skills.MINING;
int level = 1 + player.getSkills().getLevel(skill) + player.getFamiliarManager().getBoost(skill);
double hostRatio = Math.random() * (100.0 * resource.getRate());
double clientRatio = Math.random() * ((level - resource.getLevel()) * (1.0 + tool.getRatio()));
return hostRatio < clientRatio;
}
}
/**
* Handles the runite golem npc.
* @author Vexia
*
*/
public class RuniteGolemNPC extends AbstractNPC {
/**
* The respawn time.
*/
private int respawn = -1;
/**
* Constructs a new @{Code RuniteGolemNPC} object.
* @param id The npc id.
* @param location The location.
*/
public RuniteGolemNPC(int id, Location location) {
super(id, location);
}
/**
* Constructs a new @{Code RuniteGolemNPC} object.
*/
public RuniteGolemNPC() {
super(-1, null);
}
@Override
public AbstractNPC construct(int id, Location location, Object... objects) {
return new RuniteGolemNPC(id, location);
}
@Override
public void init() {
super.init();
setAggressive(false);
}
@Override
public void sendImpact(BattleState state) {
super.sendImpact(state);
if (state.getEstimatedHit() > 16) {
state.setEstimatedHit(16);
}
}
@Override
public void handleTickActions() {
super.handleTickActions();
if (isAggressive()) {
setAggressive(false);
}
if (respawn != -1 && respawn < GameWorld.getTicks() && getId() > 8664) {
transform(8664);
unlock();
}
if (getId() == 8667) {
int rewardTick = getAttribute("reward-tick", -1);
if (rewardTick != -1 && rewardTick < GameWorld.getTicks()) {
transform(8666);
}
}
}
@Override
public void finalizeDeath(Entity killer) {
respawn = GameWorld.getTicks() + 500;
fullRestore();
getProperties().setTeleportLocation(getLocation());
setRespawnTick(0);
setInvisible(false);
transform(8666);
lock();
faceLocation(null);
face(null);
}
@Override
public int[] getIds() {
return new int[] {8664};
}
}
/**
* Handles the item on piles interaction.
* @author Vexia
*
*/
public static class PilesItemHandler extends UseWithHandler {
/**
* The allowed items for piles.
*/
private static final int[] ALLOWED = new int[] {14937, 14939, 449, 447, 440, 444, 453, 451, 1515, 1513, 2349, 9467,2351,2355,2353,2357,2359,2361,2363};
/**
* Constructs a new @{Code PilesItemHandler} object.
*/
public PilesItemHandler() {
super();
}
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
addHandler(8663, NPC_TYPE, this);
return this;
}
@Override
public boolean handle(final NodeUsageEvent event) {
final Player player = event.getPlayer();
for (int itemId : ALLOWED) {
if (event.getUsedItem().getId() == itemId) {
final int amount = player.getInventory().getAmount(itemId);
final int price = 50 * amount;
if (!player.getInventory().contains(995, price)) {
player.getDialogueInterpreter().sendDialogues(8663, FacialExpression.OLD_NORMAL, "I'll need 50 coins per item.");
return true;
}
player.getDialogueInterpreter().sendOptions("Banknote " + amount + " x " + event.getUsedItem().getName() + "?", "Yes - " + price + " gp", "Cancel");
player.getDialogueInterpreter().addAction((player1, buttonId) -> {
if (player1.getInventory().remove(new Item(995, price)) && player1.getInventory().remove(new Item(event.getUsedItem().getId(), amount))) {
player1.getInventory().add(new Item(event.getUsedItem().getNoteChange(), amount));
player1.getDialogueInterpreter().sendItemMessage(event.getUsedItem().getId(), "Piles converts your items to banknotes.");
}
});
return true;
}
}
player.getDialogueInterpreter().sendDialogues(8663, FacialExpression.OLD_NORMAL, "Sorry, I wasn't expecting anyone to want to convert", "that sort of item, so I haven't any banknotes for it.");
return true;
}
@Override
public boolean isDynamic() {
return true;
}
}
/**
* Handles the Piles dialogue.
* @author Vexia
*
*/
public class PilesDialoguePlugin extends DialoguePlugin {
/**
* Constructs a new @{Code PilesDialoguePlugin} object.
*/
public PilesDialoguePlugin() {
/*
* empty.
*/
}
/**
* Constructs a new @{Code PilesDialoguePlugin} object.
* @param player The player.
*/
public PilesDialoguePlugin(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new PilesDialoguePlugin(player);
}
@Override
public boolean open(Object... args) {
npc("Hello. I can convert items to banknotes, for 50 coins", "per item. Just hand me the items you'd like me to", "convert.");
stage = 0;
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
switch (stage) {
case 0:
options("Who are you?", "Thanks.");
stage++;
break;
case 1:
switch (buttonId) {
case 1:
player("Who are you?");
stage = 10;
break;
case 2:
player("Thanks.");
stage = 2;
break;
}
break;
case 2:
end();
break;
case 10:
npc("I'm Piles. I lived in Draynor Village when I was", "young, where I saw three men working in the market,", "converting items to banknotes.");
stage++;
break;
case 11:
npc("Their names were Niles, Miles and Giles. I'm trying to", "be like them, so I've changed my name and started this", "business here.");
stage++;
break;
case 12:
player("Thanks.");
stage = 2;
break;
}
return true;
}
@Override
public int[] getIds() {
return new int[] {8663};
}
}
/**
* Handles the Mandrith dialogue.
* @author Empathy
*
*/
public class MandrithDialoguePlugin extends DialoguePlugin {
/**
*
* Constructs a new @{Code MandrithDialoguePlugin} object.
*/
public MandrithDialoguePlugin() {
/*
* empty
*/
}
/**
*
* Constructs a new @{Code MandrithDialoguePlugin} object.
* @param player
*/
public MandrithDialoguePlugin(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new MandrithDialoguePlugin(player);
}
@Override
public boolean open(Object... args) {
player("Who are you and what is this place?" );
stage = 1;
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
switch (stage) {
case 1:
npc("My name is Mandrith." );
stage = 2;
break;
case 2:
npc("I collect valuable resources and pawn off access to them", "to foolish adventurers, like yourself" );
stage = 3;
break;
case 3:
npc("You should take a look inside my arena. There's an", "abundance of valuable resources inside." );
stage = 4;
break;
case 4:
player("And I can take whatever I want?" );
stage = 5;
break;
case 5:
npc("It's all yours. All I ask is that you pay the upfront fee." );
stage = 6;
break;
case 6:
player("Will others be able to kill me inside?" );
stage = 7;
break;
case 7:
npc("Yes. These walls will only hold them back for so long." );
stage = 8;
break;
case 8:
player("You'll stop them though, right?" );
stage = 9;
break;
case 9:
npc("Haha! For the right price, I won't deny anyone access", "to my arena. Even if their intention is to kill you." );
stage = 10;
break;
case 10:
player("Right..." );
stage = 11;
break;
case 11:
npc("My arena holds many treasure that I've acquired at", "great expense, adventurer. Their bounty can come at a", "price." );
stage = 12;
break;
case 12:
npc("One day, adventurer, I will boast ownership of a much", "larger, much more dangerous arena than this. Take", "advantage of this offer while it lasts." );
stage = 13;
break;
case 13:
end();
break;
}
return false;
}
@Override
public int[] getIds() {
return new int[] { 8662 };
}
}
}

View file

@ -212,7 +212,7 @@ public final class EquipmentContainer extends Container {
for(Item currentItem : currentItems) {
Plugin<Object> plugin = currentItem.getDefinition().getConfiguration("equipment", null);
if (plugin != null) {
Object object = plugin.fireEvent("unequip", player, currentItem, newItem);
Object object = plugin.fireEvent("unequip", player, currentItem);
if (object != null && !((Boolean) object)) {
canContinue = false;
break;

View file

@ -104,13 +104,19 @@ public enum FacialExpression {
CHILD_SUSPICIOUS(7179),
CHILD_SURPRISED(7180),
//Familiar Chatheads
GRAAHK_NOD(6551),
GHRAAK_SHAKE_MILD(6553),
GHRAAK_SHAKE_VIGOROUS(6555),
; //TODO: More?
/*
* From some sources: here's a potential list of chatheads.
* // 667
* Chat animation group: 1540; linked animations: [7, 8, 9, 6824]
* Chat animation group: 1489; linked animations: [225, 6550, 6551, 6552, 6553, 6555, 8372, 8373, 8374, 8375, 8581, 8582, 9178, 9179, 9180, 9181, 9183, 9187, 9189, 9190, 9192, 9202]
* Chat animation group: 1489; potentially all familiars; linked animations: [225, 6550, 6551, 6552, 6553, 6555, 8372, 8373, 8374, 8375, 8581, 8582, 9178, 9179, 9180, 9181, 9183, 9187, 9189, 9190, 9192, 9202]
* Chat animation group: 82; linked animations: [554, 555, 556, 557, 562, 563, 564, 565, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 610, 611, 612, 613, 614, 615, 616, 617]
* Chat animation group: 84; linked animations: [558, 559, 560, 561]
* Chat animation group: 80; linked animations: [584, 585, 586, 587]

View file

@ -3,6 +3,7 @@ package core.game.node.entity.player;
import content.global.handlers.item.equipment.BarrowsEquipment;
import content.global.handlers.item.equipment.special.SalamanderSwingHandler;
import content.global.skill.runecrafting.PouchManager;
import content.global.skill.slayer.SlayerEquipmentFlags;
import core.api.ContentAPIKt;
import core.game.component.Component;
import core.game.container.Container;
@ -638,7 +639,7 @@ public class Player extends Entity {
if (i == null) continue;
InteractionListeners.run(i.getId(), this, i, false);
Plugin equipPlugin = i.getDefinition().getConfiguration("equipment", null);
if (equipPlugin != null) equipPlugin.fireEvent("unequip");
if (equipPlugin != null) equipPlugin.fireEvent("unequip", this, i);
}
boolean canCreateGrave = GraveController.allowGenerate(this);
@ -669,9 +670,9 @@ public class Player extends Entity {
}
if (BarrowsEquipment.isBarrowsItem(item.getId())) {
if (!BarrowsEquipment.isBroken(item.getId())) {
int brokenItemId = Objects.requireNonNull(BarrowsEquipment.getDefinition(item.getId())).getBrokenId();
item = new Item(brokenItemId, item.getAmount());
}
int brokenItemId = Objects.requireNonNull(BarrowsEquipment.getDefinition(item.getId())).getBrokenId();
item = new Item(brokenItemId, item.getAmount());
}
} else {
item = GraveController.checkTransform(item);
}
@ -687,6 +688,7 @@ public class Player extends Entity {
}
equipment.clear();
SlayerEquipmentFlags.updateFlags(this);
inventory.clear();
inventory.addAll(c[0]);
familiarManager.dismiss();

View file

@ -62,17 +62,18 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
fun showTab(player: Player, main: Boolean)
{
val cont = if (main) getAttribute<Container?>(player, "shop-cont", null) ?: return else playerStock
val mainCont = getAttribute<Container?>(player, "shop-cont", null) ?: return
val cont = if (main) mainCont else playerStock
if(!main)
{
cont.listeners.remove(listenerInstances[player.details.uid])
mainCont.listeners.remove(listenerInstances[player.details.uid])
playerStock.listeners.add(listenerInstances[player.details.uid])
}
else
{
playerStock.listeners.remove(listenerInstances[player.details.uid])
cont.listeners.add(listenerInstances[player.details.uid])
mainCont.listeners.add(listenerInstances[player.details.uid])
}
val settings = IfaceSettingsBuilder()

View file

@ -2,13 +2,14 @@ package core.game.system.command
import core.game.node.entity.player.Player
import core.ServerConstants
import core.api.sendChat
import core.game.node.entity.player.info.Rights
import core.game.world.GameWorld
import kotlin.collections.ArrayList
/**
* Base class for Commands in the new system. Can pass a lambda as part of the constructor or after the constructor.
* @author Ceikry
* @author Ceikry, Player Name
*/
class Command(val name: String, val privilege: Privilege, val usage: String = "UNDOCUMENTED", val description: String = "UNDOCUMENTED", val handle: (Player, Array<String>) -> Unit) {
fun attemptHandling(player: Player, args: Array<String>?){
@ -18,6 +19,11 @@ class Command(val name: String, val privilege: Privilege, val usage: String = "U
val isCheater = ServerConstants.I_AM_A_CHEATER
if (hasRights || isDev || isCheater) {
handle(player,args)
} else if (ServerConstants.PLAYER_COMMANDS || args[0] == "bank") {
// Authentically, if you attempt to use a command you shouldn't have access to, the game is supposed to say the below line.
// We do this for all our custom commands if they are enabled (in which case this will only trigger for commands above your privilege level),
// otherwise we only do it for the one command we have evidence existed authentically, "bank" (https://www.reddit.com/r/2007scape/comments/3n1c4d/hey_everyone_i_just_tried_to_do_something_very/)
sendChat(player, "Hey, everyone, I just tried to do something very silly!")
}
}
}