Link Search Menu Expand Document

Entity Interactions

The interaction system refers to players or NPCs interacting with either map objects, ground items, other players or other NPCs. This includes item-on-entity as well as interface-component-on-entity(generally known as spell-on-entity) interactions. The interaction system falls into two separate categories - approachable and operable.


Table of contents

Operable Interactions

Operable interactions, referred to with the prefix op in runescript(e.g. opnpct, oplocu, opplayer2), require the player or NPC to have a line of walk to their target, as well as be standing next to the target. This is commonly referred to as being in “melee distance”, meaning standing to the east, west, south or north of the target. Diagonals are excluded in this case.

Approachable Interactions

Approachable interactions, referred to with the prefix ap in runescript(e.g. apnpct, aplocu, applayer2), require the player or NPC to have line of sight to the target and be within the interaction distance, which can be anywhere from 0 to 10 game squares. The default distance for approachable interactions is 10 game squares. If the script behind the approachable interaction sets the approach distance to -1, the approachable interaction is converted into an operable interaction.

Priority

The interactions follow a strict order of priority, which can be explained as:

  • Runescript operable interaction
  • Runescript approachable interaction
  • Default engine approachable interaction
  • Default engine operable interaction

The default engine versions can only execute in the absence of the runescript equivalent of it. In OldSchool RuneScape, the developers have the option to define the ap, op or both of these scripts. If they choose to only define one of those scripts, the other that they did not implement will end up as the respective default engine interaction. All that the default engine interactions do is send a chat message of “Nothing interesting happens.” with the message type of Engine.

Pseudocode

First off, we must define a base for our interaction system. There are a few variables that are tracked on a per-player or per-NPC basis.

var target: InteractableEntity? = null
var apRangeCalled: Boolean = false
var currentApRange: Int = 10
var apScript: InteractionScript? = null
var opScript: InteractionScript? = null
val persistent = false

/* This function would check if the player or npc is in "melee distance" of the target, and has line of walk to them. */
fun inOperableDistance(): Boolean = TODO("This method is a stub. Implementation should be provided by you.")
/* This function would check if the player or npc is within `currentApRange` of the target, and has line of sight to them. */
fun inApproachDistance(): Boolean = TODO("This method is a stub. Implementation should be provided by you.")

fun reset() {
    apScript = null
    opScript = null
    currentApRange = 10
    apRangeCalled = false
    target = null
    persistent = false
}

/**
 * The below example is made for player interactions, however the same concept applies for NPCs.
 * The only difference for NPCs is that the #containsModalInterface function does not exist,
 * nor do the two engine versions of the op and ap scripts. In addition to that, NPCs obviously cannot
 * send messages(referring to the "I can't reach that!" message).
 */
fun processInteractions() {
    var interacted = false
    this.apRangeCalled = false
    val opScript = this.opScript
    val apScript = this.apScript
    if (!delayed() && !containsModalInterface()) {
        when {
            opScript != null && inOperableDistance() && (target is Player || target is NPC) -> {
                opScript.execute()
                interacted = true
            }
            apScript != null && inApproachDistance() -> {
                apScript.execute()
                interacted = true
            }
            inApproachDistance() -> { /* no-op */ }
            inOperableDistance() && (target is Player || target is NPC) -> {
                engineMessage("Nothing interesting happens.")
                interacted = true
            }
        }
    }
    
    val location = this.location
    processMovement()
    val moved = this.location != location
    /* Set the last movement clock if the entity moved, this clock is used to determine p_arrivedelay() in scripts */
    if (moved) clocks[Clock.LAST_MOVEMENT] = gameClock() + 1
    
    if (!delayed() && !containsModalInterface()) {
        /* If the interacted boolean wasn't set to true, or if a script that executed above called aprange(n), process the second block. */
        if (!interacted || apRangeCalled) {
            when {
                opScript != null && inOperableDistance() && ((target is Player || target is NPC) || !moved) -> {
                    opScript.execute()
                    interacted = true
                }
                apScript != null && inApproachDistance() -> {
                    apRangeCalled = false
                    apScript.execute()
                    interacted = true
                }
                inApproachDistance() -> {
                    engineMessage("Nothing interesting happens.")
                    interacted = true
                }
                inOperableDistance() && ((target is Player || target is NPC) || !moved) -> {
                    engineMessage("Nothing interesting happens.")
                    interacted = true
                }
            }
        }
    }
    if (!delayed() && !containsModalInterface()) {
        if (!interacted && !moved && !hasSteps()) {
            engineMessage("I can't reach that!")
            reset()
        }
        if (interacted && !apRangeCalled && !persistent) reset()
    }
}

Interruptions

Interactions can only cancel(outside naturally finishing on their own) with hard interruptions, such as:

  • Launching another interaction.
  • Death and other similar events which manually cancel interactions.
  • Player-invoked teleportation(E.G. clicking on Varrock Teleport).
  • Certain interface buttons, such as the “View equipment stats” button on the equipment tab interface.
  • All item interactions within inventory. The same does not however apply when interacting from within the equipment tab.

Interactions will pause when:

  • A modal interface opens up.
  • A delay is active.

Interactions do not get interrupted or paused by the following:

  • Interface clicks(outside the exceptions mentioned above).

Media: TODO - Add Useful Media

Demonstrates a failed distanced interaction:

Line of sight failed interaction


Demonstrates a successful distanced interaction:

Line of sight interaction


Demonstrates an immobile interaction that hasn’t got a script associated with it. The player has to arrive by the object before the “Nothing interesting happens.” message can be sent:

Immobile interaction


Demonstrates a mobile interaction that hasn’t got a script associated with it. The “Nothing interesting happens.” message is sent immediately with a visible one square distance:

Mobile interaction


Demonstrates an interaction getting paused by a modal interface. After closing the interface, the interaction continues as normal:

Paused interaction