Difference between revisions of "Ai Overview"

From Legends of Aria Admin and Modding Wiki
Jump to: navigation, search
(The Default Ai Settings or: How to tweak your mob)
(The Default Ai Settings or: How to tweak your mob)
Line 170: Line 170:
 
</MobileComponent>
 
</MobileComponent>
 
<ObjectVariableComponent>
 
<ObjectVariableComponent>
  <BoolVariable  Name="AI-CanFlee">true</BoolVariable>
+
 
  <BoolVariable  Name="AI-CanSeeBehind">false</BoolVariable>
 
 
   <BoolVariable  Name="AI-Leash">true</BoolVariable>
 
   <BoolVariable  Name="AI-Leash">true</BoolVariable>
 +
  <DoubleVariable Name="AI-LeashDistance">250</DoubleVariable>
 
   <BoolVariable  Name="AI-ScaleToAge">false</BoolVariable>
 
   <BoolVariable  Name="AI-ScaleToAge">false</BoolVariable>
  <BoolVariable  Name="CanBeTamed">true</BoolVariable>
 
  <DoubleVariable Name="AI-AggroRange">5.0</DoubleVariable>
 
 
   <DoubleVariable Name="AI-ChanceToNotAttackOnAlert">3</DoubleVariable>
 
   <DoubleVariable Name="AI-ChanceToNotAttackOnAlert">3</DoubleVariable>
 
   <DoubleVariable Name="AI-FleeChance">1</DoubleVariable>
 
   <DoubleVariable Name="AI-FleeChance">1</DoubleVariable>
 
   <DoubleVariable Name="AI-InjuredPercent">0.90</DoubleVariable>
 
   <DoubleVariable Name="AI-InjuredPercent">0.90</DoubleVariable>
   <DoubleVariable Name="AI-LeashDistance">250</DoubleVariable>
+
 
 +
   <BoolVariable  Name="CanBeTamed">true</BoolVariable>
 
   <DoubleVariable Name="BaseHealth">150</DoubleVariable>
 
   <DoubleVariable Name="BaseHealth">150</DoubleVariable>
 
   <DoubleVariable Name="ChanceToNotAttackOnAlert">10</DoubleVariable>
 
   <DoubleVariable Name="ChanceToNotAttackOnAlert">10</DoubleVariable>
Line 219: Line 218:
 
<br><code>AI.Settings.ChaseRange = 8</code>
 
<br><code>AI.Settings.ChaseRange = 8</code>
 
<br> You can override that by putting an ObjVar on your mob like  
 
<br> You can override that by putting an ObjVar on your mob like  
<code><DoubleVariable Name="AI-ChaseRange">10</DoubleVariable></code>
+
<br><code><DoubleVariable Name="AI-ChaseRange">10</DoubleVariable></code>
 
+
<br> in the template
  
 +
Lets compare.
  
 
{|Setting || Default Wolf || Feral Mastiff
 
{|Setting || Default Wolf || Feral Mastiff
 +
|-
 +
|Leash || false (default) || true
 +
|-
 +
|LeashDistance || 40 (default) || 250
 +
|-
 +
|ScaleToAge || true (default) || false
 +
|-
 +
|ChanceToNotAttackOnAlert || 3 (default) || 10
 
|-
 
|-
|
+
|FleeChance || 5 (default) || 1
 
|-
 
|-
 +
|InjuredPercent || 0.20 (default) || 0.90
 +
 
|}
 
|}
 +
  
  

Revision as of 17:32, 2 November 2016

Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft Draft

Ai Overview

This article is about helping to understand how the Ai in Shards Online works.

Some Basics:

The Artificial Intelligence for NPCs follows a general Ai Principle which is the cycle of

  • Sense
  • Think
  • Act

The State Machine

The model of implementation is a Finite State Machine(FSM). [Wikipedia Article Finite State Machine]

All AI files used by mobs require directly or indirectly the FSM and extend or alter it or its parameters to their needs.

To understand how the FSM would transition from one state (e.g. "Idle") to another state (e.g. "Combat") in Shards Online (SO) it is important to understand the first step: "Sense".

Sense

How does an NPC sense the world?

There are several ways:

Messages
Messages using the Sendmessage function tell the NPC someone or something sent it a message. To understand Messages the NPC needs to have the appropriate EventHandlers.
Views
Views are added to objects by using the AddView function. A view is basically a search function performed on a configurably regular basis which triggers an event if something matching that search function enters its configured range. Mobiles can carry around views with them.
FindObjects
The FindObjects function can be executed whenever an NPC needs to find something.

Each of these sensing mechanisms can call functions provided by the core Ai which lives in base_ai_state_machine.lua.

SchedulePulse()
AI.StateMachine.Init(initialState)
AI.StateMachine.Shutdown()
AI.StateMachine.ChangeState(newState)
AI.StateMachine.ChangeSubState(newState)
AI.StateMachine.EndSubStates(success,reason)
HandleAiPulse()

Especially important is function AI.StateMachine.ChangeState(newState) which could be called when something happens our NPC needs to react to, e.g. a wolf entering the perception range of a turkey.

In base_ai_mob.lua we find the following line of code in the initialization part:

AddView("chaseRange",SearchMobileInRange(chaseViewRange,false,false,true))

And at the end of the file we find the Event Handler in case some Mobile enters that range and triggered the view:

--On the target coming into range
RegisterEventHandler(EventType.EnterView, "chaseRange", 
    function (objRef)
        HandleMobEnterView(objRef,false)
    end)

Think

So it seems that HandleMobEnterView is our candidate for doing the real stuff.
Below is a shortened version cleared from comments and DebugMessages, with added comments to better understand it.
Nothing of the core logic of the function is changed

function HandleMobEnterView(mobileObj,ignoreVision,forceAttack)
    -- Sanity Checks
    if (ignoreVision == nil) then ignoreVision = false end
    if not(AI.IsActive()) then return end
    -- This check makes mobs ignore admins as possible targets
    if (not AI.IsValidTarget(mobileObj)) then return end
    -- Is what we see frined of foe?
    local isFriend = IsFriend(mobileObj)
    if (forceAttack) then isFriend = false  end
    -- if it is foe either attack or be alert
    if (not isFriend) then
        AI.AddToAggroList(mobileObj,2)
        if (forceAttack) then
            this:SendMessage("AttackEnemy",mobileObj,true)  
        else
            AI.StateMachine.ChangeState("Alert")   
        end         
    end
end

In the last part there are two ways to react, either by a manual state change to the "Alert" state of the FSM or an indirect way by sending a message to attack, which in turn will find the appropriate combat state.

So - it seems we have at least half-way understood the sensing, and the function above is already "thinking", but what about the acting and what are these dreaded "states" which seem to be so important?

Act

A state in the SO statemachine is implemented as a collection of stuff inside a table.
Lets have a look at that table:

--Essential states of any mob 
AI.StateMachine.AllStates = {
    Disabled =           {--[[More Stuff in here]] },
    DecidingIdle =       {--[[More Stuff in here]] },
    Idle =               {--[[More Stuff in here]] },
    GoToLocation =       {--[[More Stuff in here]] },
    Pursue =             {--[[More Stuff in here]] },
    GoHome =             {--[[More Stuff in here]] },
    Alert =              {--[[More Stuff in here]] },
    WhereDidHeGo =       {--[[More Stuff in here]] },
    Wander =             {--[[More Stuff in here]] },
    DecidingCombat =     {--[[More Stuff in here]] },
    AttackSpecial =      {--[[More Stuff in here]] },
    AttackAbility =      {--[[More Stuff in here]] },
    Chase =              {--[[More Stuff in here]] },
    FleeToSafeLocation = {--[[More Stuff in here]] },
    Flee =               {--[[More Stuff in here]] },
    Dead =               {--[[More Stuff in here]] },
    Melee =              {--[[More Stuff in here]] },
}

So - we see a big table with a bunch of states, each with a descriptive name which gives us an idea about the state, but how do states work?

Anatomy of a statemachine state


Basically states are mini-programs consisting of predefined and custom functions used by the statemachine logic - so lets see how such a state functions:
This is a version of the GoToLocation state above, again - I removed DebugMessages an comments and added my own comments for better understanding

GoToLocation = {
        -- This defines how often AiPulse() will be called in ms (every 500 ms in this example)
        -- It has a big influence on how costly this script will be in terms of CPU cycles
        GetPulseFrequencyMS = function() return 500 end,

        -- This function always gets called first when entering a state
        OnEnterState = function()
            if CheckPathLocation(AI.Destination) then DecideIdleState() return end
            this:PathTo(AI.Destination,AI.GetSetting("ChargeSpeed"),"GoToLocation")
        end,

        -- The heartbeat of the state. 
        -- It gets called regularly as defined by GetPulseFrequencyMS()
        AiPulse = function ()
            if CheckPathLocation(AI.Destination) then DecideIdleState() return end
            this:PathTo(AI.Destination,AI.GetSetting("ChargeSpeed"),"GoToLocation")
        end,

       -- Gets called last, before we exit the state
        OnExitState = function()
            AI.Destination = nil
        end,
    }

So - lats start at the beginning and stop at the end:
OnEnterState: The code is doing a check:
CheckPathLocation(AI.Destination)) and if that check is positive it calls a function (DecideIdleState()) to return to idle state - so the mob will stop moving.
Otherwise it will try to path to its target location:
this:PathTo(AI.Destination,AI.GetSetting("ChargeSpeed"),"GoToLocation")
The same check is happeneing in the regularly called AiPulse() function.
The OnExitState simply deletes the Pathing Destination, since we are no longer pathing.

Easy, isn't it? So where is the problem with the Ai? The statemachine is a simple construct - it jusmps between states and has a logic coded in each state which tells it how to transition to ther states. And there are interruptions from sensing mechanisms, like messages or views which could trigger a state change.
The real crux, I think, is to learn all the different utility functions we can already use to develop a working Ai, we need more documentation of these, hopefully this wikk will help with that.
A second issue is the mass of Ai Settings - I think most people have not yet really gone into these, and that's what the next section is about:

The Default Ai Settings or: How to tweak your mob

For most basic application you simply have to do a very basic thing:

  1. Find the Mob or NPC which is most close to what you want to do
  2. Look at its AI file and Ai Settings (=check its template)
  3. Copy the examples template to your new template
  4. Tweak the variables.

A real world example from Arcanima:


I once wanted to create a new canine type of animal, a type of dog that has left home and gone feral.
I called them the "Feral Mastiff". Here's the template:

<ObjectTemplate>
<ClientId>264</ClientId>
<Hue>B37B46</Hue>
<Name>Feral Mastiff</Name>
<MobileComponent>
  <BaseRunSpeed>2.0</BaseRunSpeed>
  <MobileType>Animal</MobileType>
</MobileComponent>			
<ObjectVariableComponent>

  <BoolVariable   Name="AI-Leash">true</BoolVariable>
  <DoubleVariable Name="AI-LeashDistance">250</DoubleVariable>
  <BoolVariable   Name="AI-ScaleToAge">false</BoolVariable>
  <DoubleVariable Name="AI-ChanceToNotAttackOnAlert">3</DoubleVariable>
  <DoubleVariable Name="AI-FleeChance">1</DoubleVariable>
  <DoubleVariable Name="AI-InjuredPercent">0.90</DoubleVariable>

  <BoolVariable   Name="CanBeTamed">true</BoolVariable>
  <DoubleVariable Name="BaseHealth">150</DoubleVariable>
  <DoubleVariable Name="ChanceToNotAttackOnAlert">10</DoubleVariable>
  <StringVariable Name="MobileTeamType">Wolves</StringVariable>
  <StringVariable Name="NaturalEnemy">Prey</StringVariable>
  <StringVariable Name="NaturalWeaponName">Fangs</StringVariable>
  <StringVariable Name="NaturalWeaponType">Cutlass</StringVariable>
</ObjectVariableComponent>
<ScriptEngineComponent>
  <LuaModule Name="ai_wolf">
    <Initializer>				
      { Stats  = { Str=8, Agi=10, Int=10, },
        Skills = { Melee=10, Slashing=5, Regeneration=50, },
      }
    </Initializer>
</LuaModule>
<LuaModule Name="animal_parts">
    <Initializer>
      { MeatCount = 1,					
        AnimalParts =
          { { ResourceType="LeatherHide", RarityPct=25, Count=1 }, },
          }
    </Initializer>
</LuaModule>
</ScriptEngineComponent>
</ObjectTemplate>

I have reduced this template to the most basic stuff and removed settings specific to our server.
What you immediately see is, that there is a bunch of AI-something Variables. These Variables control to a great extent how the Ai works and how these mobs behave. Without changing a single line of code!

These variables override settings from the ai_wolf script, which means a feral mastiff simply is a specially tailored wolf concerning its Ai. Lets compare the Feral Mastiff to the Default Wolf. There is one thing to understand though:
Ai Settings are stored in a a table:
AI.Settings
But there is a conversion mechanism, which translates between Settings like AI.Settings.ChaseRange to the ObjVar "AI-ChaseRange"
Settings in ObjVars override theior reespective counterparts which have been set in the AI-Script
So if you find a something in the Ai Script (ai_wolf here) like:
AI.Settings.ChaseRange = 8
You can override that by putting an ObjVar on your mob like
<DoubleVariable Name="AI-ChaseRange">10</DoubleVariable>
in the template

Lets compare.

Leash false (default) true
LeashDistance 40 (default) 250
ScaleToAge true (default) false
ChanceToNotAttackOnAlert 3 (default) 10
FleeChance 5 (default) 1
InjuredPercent 0.20 (default) 0.90