Research:NPC AI Behaviour: Difference between revisions
Jump to navigation
Jump to search
Line 1: | Line 1: | ||
==General behaviour== | |||
===Decision scan=== | |||
{{Formula | |||
|AI decision check, every 5.0 seconds | |||
|The main point for actor behaviour selection | |||
| | |||
|{{StatusCol|orange|Early, incomplete research}}}} | |||
====Weight functions==== | |||
<syntaxhighlight lang="python"> | |||
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 | |||
</syntaxhighlight> | |||
====Actor scan==== | |||
<syntaxhighlight lang="python"> | |||
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 | |||
</syntaxhighlight> | |||
====Comments==== | |||
With the lookAt action, the NPC looks at its target's head. The anim controller blends the head pose over time. | |||
==Combat Behaviour== | ==Combat Behaviour== | ||
Revision as of 18:20, 23 July 2014
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.