RPG Library: Who, When and How NPCs Attack - ThePix/QuestJS GitHub Wiki

Handling when an NPC will choose to attack is complicated because there are so many ways you might want to do it.

NPCs have a "performAttack" function attribute that takes a target object. Calling this will make the NPC attempt one attack, though generally you are better leaving Quest to call it.

If the NPC is not in the same room as the target, no attack will happen, however, if it exists, the NPC's "pursueToAttack" function will be called, and they could be used to have the NPC move closer to the target. The function should return true if the pursuit is still on or false to abandon the attack.

  pursueToAttack:function(target) {
    const exit = w[this.loc].findExit(target.loc)
    if (!exit) return false  // not in adjacent room, so give up
    this.movingMsg(exit) 
    this.moveChar(exit)
    return true
  },

As this is a standard thing to want to do, it is actually built-in. Use like this:

  pursueToAttack:rpg.pursueToAttack,

Who To Attack

An NPC will attack if its "hostile" attribute is set to true and its target attribute is set to an object (usually the player!); it will attack that object.

Allegiance

By default, all NPCs are in the 'foe' allegiance, while the player is in 'friend'.

  allegiance:'friend',

Some skills/spells affect only foes, which actually means anyone not in the 'friend' allegiance. Currently allegiance has no other effect.

Signal groups

You can assign NPCs to a groups that can communicate. If the player attacks an NPC, any NPC is the same signal group will start to attack the player.

  signalGroups:['guards'],

This works for NPCs too. If an NPC attacks one guard, all the guards will attack that NPC.

isHostile(target)

To test if an NPC is hostile, use "isHostile", plus the target object. Hostile means the NPC will attack this round if in the same room as the player.

When To Attack

Antagonise

Note the English spelling!

Use "antagonise" on an NPC to change it from not attacking to attacking.

  npc.antagonise(player)

Using "antagonise" will cause other NPCs that share signal groups to start attacking too.

When Attacked

When a character (including the player) attacks another, "antagonise" will be automaticallycalled on the targeted character. Thus, if the player attacks any NPC, friend or foe, that NPC will respond in kind.

You can turn that off for a specific skill or spell by setting "suppressAntagonise" to true (you should do that for healing spells for example). Alternatively "suppressAntagonise" can be a function that returns true when "antagonise" should be suppressed.

Guarding a room or exit

Use "setGuard" to have an NPC guard an exit or location. In this example, the first paramter is false - the orc will guard the room it is in. When it enters, the response, the second parameter will given. In this case it is a string, so it will just be printed - it will do no more than that (the orc will not actually attack), and it will do it every turn the player is in the room.

  w.orc.setGuard(false, 'The orc eyes you suspiciously.')

This example has the first parameter set to a direction, so the orc is now guarding the exit, not the room. The second parameter is a function this time, so we could have it make an attack. The player will not use the exit either way.

  w.orc.setGuard("east", function(char, exit) {
    msg("It chucks a rock at you when you try to go east.")
  })

You would use "w.orc.unsetGuard()" to stop it guarding either the location or exit.

Agendas

You can use "antagonise" in an agenda too. This example waits until the "scenery" attribute of the tapestry is false - that is, the item is picked up - and then the orc will attack.

w.orc.agenda = ['waitUntilNow:tapestry:scenery:false:The orc draws his sword.', 'antagonise']

How To Attack

You can give an NPC attack options using its "attackPattern" attribute, an array of strings. Quest will pick one of these at random for each attack. If an attack sets the NPC's "nextAttack", the named skill or spell will be used instead.

In this example, four spells are set up (none of which do anything!). The orc is set to use one of the first three at random. The third, if used, will make the orc use the fourth in the next turn.

  new Spell("Test attack 1", {
    primarySuccess:"Test attack one was performed",
  })
  new Spell("Test attack 2", {
    primarySuccess:"Test attack two was performed",
  })  
  new Spell("Test attack 3A", {
    primarySuccess:"Test attack three was prepared",
    afterUse:function(attack) {
      attack.attacker.nextAttack = "Test attack 3B"
    }
  })  
  new Spell("Test attack 3B", {
    primarySuccess:"Test attack three was performed",
  })  
  
  w.orc.attackPattern = ['Test attack 1', 'Test attack 2', "Test attack 3A"]

You can further refine how a skill or spell is chosen by overriding the "performAttack" of the NPC. This shows the basics:

  res.performAttack = function(arr) {
    const target = w[this.target]
    if (target.dead) return true

    let skill
    skill = myCustomSkill  // modify as you please!
    Attack.createAttack(this, player, skill).apply().output()
    return target.dead
  }

This could allow the NPC to select an attack based on the situation.