Skip to content

Combat Interaction in RuneScript

Jordan edited this page Oct 28, 2023 · 9 revisions

The following code examples does not necessarily have to reflect the code for 2004scape. This is only to explain the general process of how this works. A typical combat interaction begins when a player does an op2/ap2 action.

  1. opnpc trigger only runs when the player does the action.
  2. ai_queue only runs one time when set.
  3. npc modes runs every tick when set.

opnpc2

Here is a script example for initiating the interaction in op range. This has an active player pointer.

[opnpc2,_]
// this means the npc is not avail to fight i.e dead
if (npc_stat(hitpoints) = 0) {
    return;
}

// this means we are currently unavailable to fight
if (%action_delay > map_clock) {
    p_opnpc(2); // TODO p_apnpc()
    return;
}

~p_combat_stat; // update combat varps before swinging

// flinching
if (add(%npc_action_delay, 8) < map_clock) {
    def_int $attackspeed = npc_param(attackrate);
    %npc_action_delay = add(map_clock, add(divide($attackspeed, 2), 1));
}

if (~combat_hit_chance = true) {
}

anim(%com_attackanim, 0); // our attack animation
sound_synth(%com_attacksound, 0, 0); // our attack sound
npc_queue(1, 0); // queue the npc on ai_queue1 for retaliation
npc_queue(2, 0); // queue the npc on ai_queue2 for damage
npc_anim(npc_param(defend_anim), 0); // delay npc this tick
sound_synth(npc_param(defend_sound), 0, 20); // delay 1 client tick for the hit queue
%npc_retaliation_pid = uid;
// set the skill clock depending on the weapon attack rate
def_obj $weapon = inv_getobj(worn, ^wearpos_rhand);
%action_delay = add(map_clock, oc_param($weapon, attackrate)); // set our action delay for our weapon speed
p_opnpc(2);

ai_queue1

When you set npc_queue(1, 0);, this is queueing an ai_queue1 on the npc with a delay of 0 (1 tick). ai_queue1 is specifically only used for combat retaliation for an npc by searching for a target and starting the combat ai mode. This does not have an active player pointer.

[ai_queue1,_] 
if (finduid(%npc_retaliation_pid) = true) { // give player pointer
    npc_setmode(opplayer2); // set ai mode to opplayer2
}

ai_queue2

When you set npc_queue(2, 0);, this is queueing an ai_queue2 on the npc with a delay of 0 (1 tick). ai_queue2 is specifically only used for applying damage on an npc and handling what happens to that npc when they take damage. This does not have an active player pointer.

[ai_queue2,_]
def_int $damage = 100; // the amount of damage to apply
// if the npc is already dead
if (npc_stat(hitpoints) = 0) {
    return;
}
// damage the npc
if ($damage = 0) {
    npc_damage(^hitmark_block, 0);
} else {
    npc_damage(^hitmark_damage, $damage);
}
if (npc_stat(hitpoints) > 0) {
    return;
}
// if the npc died from this damage
npc_setmode(none);
npc_queue(3, 0);

ai_queue3

When you set npc_queue(3, 0);, this is queueing an ai_queue3 on the npc with a delay of 0 (1 tick). ai_queue3 is specifically only used for making an npc die and remove from the world. This does not have an active player pointer.

[ai_queue3,_]
npc_anim(npc_param(death_anim), 0); // make the npc do a human death animation.
npc_delay(1); // delay the npc 2 ticks (how long the death animation should be)
npc_del; // remove the npc from the world
if (finduid(npc_findhero) = true) { // gives pointer to player
    obj_add(npc_coord, npc_param(death_drop), 1, 200); // drop the item
}

ai_opplayer1-5

This is a general example of an ai_opplayer2 which runs every tick when set on the npc mode. ai_opplayer2 is specifically used for an npc to defend from an enemy in combat for op range interactions. This is how you would code for an npc attacking the active player back for example. These types of npc modes has an active player pointer.

[ai_opplayer2,_]
if (%npc_action_delay > map_clock) return;
npc_anim(npc_param(attack_anim), 0);
anim(%com_defendanim, 0);
sound_synth(npc_param(attack_sound), 0, 0);
~npc_meleeattack;

// perform a basic melee attack from the npc to the player.
[proc,npc_meleeattack]
def_int $attack_roll;
def_int $maxhit_roll;
$attack_roll, $maxhit_roll = ~npc_meleedamage;

if (randominc($attack_roll) > randominc(~player_combat_defence_stat(npc_param(damagetype)))) {
    ~playerhit_n_melee(randominc($maxhit_roll), npc_param(attackrate));
    return;
}
~playerhit_n_melee(0, npc_param(attackrate));

[proc,playerhit_n_melee](int $damage, int $delay)
~damage_self($damage);
queue(playerhit_n_retaliate, 0, npc_uid); // this should be a queue* command
%npc_action_delay = add(map_clock, $delay);
%combat_logout_delay = add(map_clock, 17); // 10 seconds

[queue,playerhit_n_retaliate](npc_uid $nid)
def_boolean $retaliate = true;
if ($retaliate = true & p_finduid(uid) = true & npc_finduid($nid) = true) {
    // npc flinches player
    if (%action_delay < map_clock) {
        %action_delay = add(map_clock, divide(npc_param(attackrate), 2));
    }
    p_opnpc(2);
}

An Example Custom Combat Interaction (i.e boss/quest scripts)

[opnpc2,count_draynor]
gosub(player_melee_attack);
if (npc_stat(hitpoints) = 0) {
    @count_draynor_use_stake;
}

[ai_queue3,count_draynor] // do nothing
[ai_queue4,count_draynor] gosub(npc_death);
[ai_opplayer2,count_draynor] @count_draynor_attack;

[label,count_draynor_attack]
gosub(npc_default_attack);
if (inv_total(inv, garlic) > 0 & random(16) = 0) {
    // TODO garlic effects
    mes("The vampire seems to weaken.");
}

[label,count_draynor_use_stake]
if (inv_total(inv, stake) = 0) {
    mes("The vampire seems to regenerate!");
    npc_statheal(hitpoints, npc_basestat(hitpoints), 0);
    npc_setmode(opplayer2);
    return;
}
if (inv_total(inv, hammer) = 0) {
    mes("You're unable to push the stake far enough in!");
    mes("The vampire seems to regenerate!");
    npc_statheal(hitpoints, npc_basestat(hitpoints), 0);
    npc_setmode(opplayer2);
    return;
}
p_delay(1);
mes("You hammer the stake into the vampire's chest!");
inv_del(inv, stake, 1);
anim(human_stake, 0);
npc_setmode(none);
npc_queue(4, 0);
queue(quest_vampire_complete, 3);