Research:NPC AI Behaviour
Jump to navigation
Jump to search
General behaviour
Decision scan
Actions affected | AI decision check, every 5.0 seconds |
Description | The main point for actor behaviour selection |
Implementation status | |
Analysis status | Early, incomplete research |
Weight functions
actionGreetWeighting :: (npc, actor) -> weight
if npc is creature: return 0
if actor is not player: return 0
if player is incapacitated: return 0
if player is invisible or player.chameleon >= 75: return 0
if npc.hello == 0: return 0
if npc.currentAIPackage not in { AIWander, AITravel, AIPursue }: return 0
x = iGreetDistanceMultiplier * npc.hello
dist = distance(npc, actor)
if npc has already greeted and dist > 2 * x:
npc resets greeting flag
npc.target = none
elif npc has not greeted and dist < x:
npc.target = actor
return 100
else:
return 0
actionCrimeWeighting :: (npc, actor) -> weight
if npc is creature: return 0
if npc is incapacitated: return 0
if npc.alarm == 0: return 0
if not npc.isGuard: return 0
if actor is not player: return 0
if player is incapacitated: return 0
if player is invisible or player.chameleon >= 75: return 0
if player is resisting arrest: return 0
if bounty < iCrimeThreshold: return 0
weight = npc.alarm + 0.1 * bounty
if weight < 100: return 0
return weight
actionFightWeighting :: (npc, actor) -> weight
if actor is in combat with an npc's escort: return 100
if actor is in combat with an actor escorted by npc: return 100
fightTerm = npc.fight
if actor.isWerewolf or (actor is player and player.isKnownWerewolf): fightTerm += iWerewolfFightMod
dist = distance(npc, actor) # ignores vertical distance if actor is flying or swimming
weight = iFightDistanceBase - fFightDistanceMultiplier * distance + fFightDispMult * (50 - disp)
if weight < 100: return 0
return weight
actionLookAtWeighting :: (npc, actor) -> weight
if actor is incapacitated: return 0
if actor is invisible or player.chameleon >= 75: return 0
if npc current AI package prevents proximity check: return 0 # unclear conditions
if distance(npc, actor) >= 640: return 0
if actor is an npc's escort: return 0
if actor is being escorted by npc: return 0
return 100
Actor scan
actionWeight = 0
selectedAction = none
for each actor in environment: # player is examined first
if actor is dead: continue
if distance(npc, actor) > 7168: continue
# 5-10 more conditions
greetWeight = 0
crimeWeight = 0
if actor is player:
if npc meets condition x: # under research
greetWeight = actionGreetWeighting(npc, actor)
if npc meets condition y: # under research
crimeWeight = actionCrimeWeighting(npc, actor)
fightWeight = actionFightWeighting(npc, actor)
lookAtWeight = actionLookAtWeighting(npc, actor)
action = none
if lookAtWeight > npc.actionWeight:
npc.target = actor
actionWeight = int(lookAtWeight)
action = lookAt
if greetWeight > npc.actionWeight:
npc.target = actor
actionWeight = int(greetWeight)
action = greet
if fightWeight > npc.actionWeight:
npc.target = actor
actionWeight = int(fightWeight)
action = fight
if crimeWeight > npc.actionWeight:
npc.target = actor
actionWeight = int(crimeWeight)
action = uphold the law
if actionWeight != 0 and action != none and npc.target != none:
if some 20 page function: # includes expensive functions like line of sight and sneak
npc.target = 0
actionWeight = 0
action = none
if action != none: selectedAction = action
Comments
With the lookAt action, the NPC looks at its target's head. The anim controller blends the head pose over time.
Combat Behaviour
NPC Awareness Check
Actions affected | NPC AI |
Description | |
Implementation status | implemented |
Analysis status | Verified |
This check runs every 5 seconds for each NPC. It occurs whether you are sneaking or not, but isn't the same as the combat distance check.
Player side
if sneaking:
sneakTerm = fSneakSkillMult * sneak + 0.2 * agility + 0.1 * luck + bootWeight * fSneakBootMult
else:
sneakTerm = 0
fatigueTerm = fFatigueBase - fFatigueMult*(1 - normalisedFatigue)
where normalisedFatigue is a function of fatigue. empty fatigue bar -> 0.0, full fatigue bar -> 1.0
distTerm = fSneakDistanceBase + fSneakDistanceMultiplier*dist
x = sneakTerm * distTerm * fatigueTerm + chameleon (+ 100 if invisible)
NPC side
npcTerm = npcSneak + 0.2 * npcAgility + 0.1 * npcLuck - npcBlind
npcFatigueTerm = fFatigueBase - fFatigueMult * (1 - normalisedFatigue)
using NPC normalisedFatigue
if PC is behind NPC (180 degrees):
y = npcTerm * npcFatigueTerm * fSneakNoViewMult
else:
y = npcTerm * npcFatigueTerm * fSneakViewMult
Final check
target = x - y
roll 100, win if roll < target
Comments
Appears straightforward and bug-free. NPCs can take up to five seconds to notice you even if you are not sneaking. This function precedes the combat distance check. I have not identified if there is a line of sight check occuring before or after.
According to this formula, NPCs can still detect you when invisible? That seems wrong. Scrawl (talk) 00:25, 7 January 2014 (CET)
AI Tendencies
Fight
Affected by calm and frenzy spells.
Flee
Affected by rally and demoralize spells.