API:NPCs and AI

From WCell Wiki
Jump to: navigation, search

This article is related to development using the WCell API.

  • NPCs (Non-Player-Characters) represent all computer-controlled characters, vendors and creatures in the world
  • If you are interested into working with NPCs, you might want to start by considering the NPC Dump
  • Once you are familiar with the basics of NPCs and AI, you should take a look at the World API

Contents

NPCMgr

  • The NPCMgr (NPC manager) is a public static class that contains all static (not changing) NPC-related data after its been initialized
  • If WCell is setup to automatically load all content, the NPCMgr will initialize at startup, else it will start loading after using the Load NPCs command
  • NPC-customizations require to be done at any point after the NPCMgr has been initialized which means that you will need a public static method like this:
[Initialization]
[DependentInitialization(typeof(NPCMgr))]
public static void InitMyNPCs()
{
	// initialize my NPCs here
}

NPCEntry

  • Every NPC in the world is built from an NPCEntry which contains the definition of the specific NPC
  • You can get NPCEntry objects by using NPCMgr.GetEntry(NPCId):
var thrallEntry = NPCMgr.GetEntry(NPCId.Thrall);

Brains

  • Every NPC gets a new Brain when being created (which by default is an instance of MobBrain)
  • Brains implement IAICombatEventHandler which defines a set of events that one can override to customize the Brain's reaction during combat
  • The default Brain can be overridden by simply creating some CustomBrain class and overriding MobBrain (or any other Brain-class if you are not aiming to implement a mob in the classical sense)
  • The new Brain will be automatically created when we set NPCEntry.BrainCreator:
NPCMgr.GetEntry(NPCId.Thrall).BrainCreator = thrall => new ThrallBrain(thrall);

// ...

public class ThrallBrain : MobBrain 
{
	public ThrallBrain(NPC thrall) : base(thrall)
	{
			// ...
	}
	
	// ...
}

AIActions

  • AIActions define active behavior (unlike Brains which define reactions to events)
  • Every AIAction has at least three methods:
    • Start: When an Action is used the first time after others have been used, Start will be called
    • Update: While an Action is active, Update will be called continuously on every Region tick
    • Stop: When an Action is finished or another Action is being used or some other kind of change occured, Stop will be called (followed by another call to Start on the next action again)
  • Every NPC always has its CurrentAction set to the AIAction that is currently being performed (Update is being called continuously)

BrainStates

  • All NPCs always have a BrainState which might change over time
  • Whenever an NPC is done performing a certain Action (for example after combat) it will return to its DefaultAction (which is usually Roam or Idle)
  • Every basic Brain has a set of default AIActions that it will execute when in is in a certain BrainState
  • You can set a Brain's state by using the command: ##s brain.state <state>
  • You can set a Brain's defaultstate by using the command: ##s brain.defaultstate <state>
  • Setting the CurrentAction of a Brain will not directly effect the State that it is in (example of this feature's usage can be found below)
    • This can be used to perform an action in between without losing the current state or the state's progress
    • The Brain will return to its default behavior for its current state, once it is done with performing a custom action (by calling StopCurrentAction() or setting CurrentAction to null)
  • You can get/set the AIAction of a certain state for a new Brain.

Example for default NPC behavior while not doing anything else (roaming):

var entry = NPCMgr.GetEntry(NPCId.Thrall);
entry.BrainCreator = thrall => {
		return new ThrallBrain(thrall);
};

entry.Activated += thrall => {
		thrall.Brain.Actions[BrainState.Roam] = new ThrallRoamAction(thrall);
};


// ...

/// <summary>
/// ThrallRoamAction defines what Thrall does while Roaming (when not being in combat)
/// </summary>
public class ThrallRoamAction : AIRoamAction 
{
	public ThrallRoamAction(NPC thrall) : base(thrall) 
	{
	}
	
	public override void Update() 
	{
		base.Update();
		
		if (!m_owner.IsFighting) 
		{
			// do something here on every tick
		}
	}
}

Combat AI

  • The AICombatAction is the default action for the Combat state
  • It only takes care of default threat-related target selection
  • The actual strategy on how to approach and fight the enemy is implemented in the nested AICombatAction.Strategy
  • The default Strategy is AIAttackAction
  • You can change the Strategy of Thrall like this:
entry.Activated += thrall => {
		var brain = (BaseBrain)thrall.Brain;
		var combatAction = (AICombatAction)brain.Actions[BrainState.Combat];
		combatAction.Strategy = new ThrallAttackAction(thrall);
};

// ...

public class ThrallAttackAction : AIAttackAction
{
	/// <summary>
	/// Spell to summon Thrall's wolves
	/// </summary>
	public static SpellId AngrySpell = SpellId.FeralSpirit_2;

	bool isAngry;

	public ThrallAttackAction(NPC thrall) : base(thrall) 
	{
	 	/* ... */ 
	}
	
	public override void Update() 
	{
		if (!isAngry && m_owner.Health < m_owner.MaxHealth / 2) 
		{
			// now Thrall becomes angry!
			TurnAngry();
		}
		else 
		{
			// only call base.Update if we did not start some new behavior
			// or else it would interfere
			base.Update();
		}
	}
	
	private void TurnAngry() 
	{
		isAngry = true;
		
		// stop moving
		m_owner.Movement.Stop();
		
		// scream
		m_owner.Yell("WAAARRRRGH!");
		
		// increase strength + size
		m_owner.ScaleX *= 1.2;
		m_owner.BaseStrength += 300;
		
		// summon Thrall's wolves
		m_owner.SpellCast.Start(AngrySpell, false);

		// wait one second for the Spell animation
		m_owner.Idle(1000);
	}
}


Controlling Units

  • Amongst others, the Unit class defines a set of methods to control AI behavior
  • They allow a very simple command of AI without interrupting any other AI-performance
  • They are being used in the made-up Ambusher Sample Quest
void MoveToThenExecute(Vector3 pos, UnitActionCallback actionCallback)
void MoveToThenExecute(Unit unit, UnitActionCallback actionCallback)
void MoveToThenExecute(Unit unit, UnitActionCallback actionCallback, int millisTimeout)
void MoveInFrontThenExecute(Unit unit, UnitActionCallback actionCallback)
void MoveInFrontThenExecute(Unit unit, UnitActionCallback actionCallback, int millisTimeout)
void MoveBehindThenExecute(Unit unit, UnitActionCallback actionCallback)
void MoveBehindThenExecute(Unit unit, UnitActionCallback actionCallback, int millisTimeout)
void MoveToThenExecute(Unit unit, float angle, UnitActionCallback actionCallback)
void MoveToThenExecute(Unit unit, float angle, UnitActionCallback callback, int millisTimeout)
void MoveToThenExecute(Unit unit, SimpleRange range, UnitActionCallback actionCallback)
void MoveToThenExecute(Unit unit, SimpleRange range, UnitActionCallback actionCallback, int millisTimeout)
void MoveToThenIdle(ref Vector3 pos)
void MoveToThenIdle(IHasPosition pos)
void MoveToThenEnter(ref Vector3 pos, BrainState arrivedState)
void MoveToThenEnter(IHasPosition pos, BrainState arrivedState)
void Idle(uint millis)
void IdleThenExecute(uint millis, Action actionCallback)


The Spawn System

NPCSpawnEntry

  • NPCSpawnEntry is a DataHolder
  • NPCSpawnPool has the property SpawnEntry
  • NPCEntry has the property SpawnEntries (list of SpawnEntry)
  • SpawnEntries defines where to spawn NPCs and define some additional properties to allow overriding of default NPC settings, such as flags or behavioral rules
  • The most important property of SpawnEntry is of course NPCEntry which is the DataHolder of every NPC
  • SpawnEntries define the Spawned event which will be triggered whenever a new NPC is spawned at a SpawnPoint that belongs to that SpawnEntry

NPCSpawnPoint

  • Instances of NPCSpawnEntries in the world are called NPCSpawnPoints
  • NPC has the property SpawnPoint
  • A SpawnPoint is considered active (property IsActive) if it either has already spawned an NPC or it's timer is running, else it's considered inactive
  • Every NPCSpawnPoint:
    • Is built from an NPCSpawnEntry
    • Has a timer that is controlled by it's NPCSpawnPool

Pools

  • Pools allow to group NPCs together
  • Every NPC spawned from the same pool is in the same AIGroup
  • You can set a SpawnProbability to single entries and assign a MaxAmount to the pool
  • When an autospawning NPC of a pool dies, the pool will randomly select an inactive SpawnPoint, using the given probabilities of the pool as priorities
  • If the probability of an entry of one of the pool's SpawnPoints is 0, it automatically has average probability (1 / availableAmount)
  • For example:
    • If you have a pool with a MaxAmount of 2
    • And 10 autospawning spawnpoints, each with probability 0
    • And an NPC from the pool dies
    • Then all 9 remaining points (because one still is active since it's NPC is still alive or it's timer is running) each have the same probability to respawn the NPC

NPCSpawnPoolTemplate

NPCSpawnPool

  • Instances of NPCSpawnPoolTemplates in the world are called NPCSpawnPools
  • NPCSpawnPoint has the property Pool
  • They have a set of all NPCSpawnPoints that are in the pool
  • They also have the AIGroup to which every newly spawned NPC from this pool is added when spawned (and when resurrected)
  • The NPCSpawnPool has the job of selecting a new SpawnPoint to spawn a new NPC from, when one NPC of the pool died
  • It selects the SpawnPoint to spawn the new NPC immediately and starts it's timer which will expire after the respawn delay, which is defined by it's SpawnPoint's SpawnEntry


How to spawn new NPCs

There are generally 3 types of spawning:

  • Use the above-mentioned SpawnSystem to continuesly respawn mobs or group mobs into AIGroups (you can of course also manually put them into AIGroups)
  • An already existing NPC (usually a boss) spawns a new minion
    • Cast a spell that spawns the creature (this is how most minions in instances are supposed to be spawned) by using boss.SpellCast.Trigger, or -if the spell has a cast time and/or can be allowed to fail-, use boss.SpellCast.Start (see the Spell API for more information)
    • Use the NPC.SpawnMinion methods (it has overloads in Unit)
  • Put a single NPC into the world

Approach to testing custom NPCs

  • Go to and spawn NPCs individually:
    • (Clear your current region so other NPCs won't trigger the debugger: #rgn clear)
    • Go to NPC (make sure you are already within an Instance if its an Instance NPC): #npc goto mrsmite
    • Spawn closest NPC (since you stand right on his spawn point): #npc spawn -c
  • Use further commands to trigger reactions that you want to test:
    • For example: Health-based events can usually be triggered by simply reducing the health: #s health xxx
Personal tools