NPCs, Horses, and you
From The Elder Scrolls Construction Set Wiki
This article has been bylined by a contributor. Current rules do not allow bylines in mainspace articles, but articles written before August 2007 may have been bylined prior to the rules. If you are the original author, please comment on this in the Talk page.
If you are not the bylined author, please treat this article with respect. While this is a Wiki and this article is considered open for editing, courtesy is expected. If at all possible, please contact the bylined author about any changes you would like to make.
As always, see the Edit History to see who has contributed to the page, and use the Talk page if you have any questions regarding its content.
This how-to was not designed for beginners but for people who have been banging their head against the same bloody wall since they created their first NPC ... When the phrases "MOUNT DA$#@#!" or the equally agitated 'GET OFF THE D^@# HORSE, B#@^H!" issued forth in abundance. If this is you, as it was me, then perhaps this HOW-TO may grant you a measure of sanity.
Contents |
Purpose
The purpose of this how-to is...
- To get your NPC to mount when you mount.
- To get your NPC to dismount when you dismount.
It is not:
- The best solution to the problem, I'm sure.
- A particular fast or optimized solution.
- Nor, I'm sure, is it the best conceptual or compliant solution.
With this in mind, proceed at your own risk....
Important Factors
After hours of tinkering I was able to infer a few things about how horses and NPCs interact. I expect there may be some inaccuracies or perhaps they are simply wrong; regardless, for me they were the secrets to unlocking the mysteries of NPCs, horses, and you. ;)
- NPCs respond to the TRAVEL package with the 'Use Horse' flag correctly and mount on the horse. The FOLLOW package with the 'Use Horse' flag did not appear to cause the NPC to mount the horse.
- Likewise, NPCs respond to the TRAVEL package without the horse flag correctly and dismount, if mounted; Again, the FOLLOW package seemingly does not have this effect.
- Unlike mounting, dismounting cannot be triggered with the addScriptPackage function. Instead, the dismount has to be triggered by an evaluatePackage (evp) command.
The Packages
You need to create four AI packages for your NPC, as follows:
- PACKAGE 1 - TRAVEL
- NAME: 'travelToHorse'
- PURPOSE: To get the NPC moving to the horse. When the NPC is close enough to the horse, the USE HORSE bit will kick in and the NPC will mount.
- STORAGE: Outside the NPCs Package List
- FLAGS
- Use Horse
- LOCATION: HorseType (ex:E3Horse)
- PACKAGE 2 - FOLLOW
- NAME: 'followOnHorse'
- PURPOSE: After the NPC has mounted this package will be activated, causing the NPC to follow the player.
- STORAGE: Outside the NPCs Package List
- FLAGS
- Use Horse
- TARGET: ('Abandoned Mine','PlayerRef')
- DISTANCE: 350 (Main Quests use this)
- PACKAGE 3 - TRAVEL
- NAME: 'travelToPlayer'
- PURPOSE: Causes the NPC to dismount and travel to the player.
- STORAGE: First in NPCs package list
- CONDITION: getScriptVariable( CELL , dismountFlag )==1 and isRidingHorse==1
- Where CELL is the initial cell the NPC was created in, view the USE INFO for the NPC to see where that is.
- LOCATION: Player
- PACKAGE 4 - FOLLOW
- NAME: 'followOnFoot'
- PURPOSE: Causes the NPC to follow the player on foot.
- STORAGE: Outside the NPCs Package List
- TARGET: Player
- DISTANCE: 0 (default)
The Script
This script should be incorporated into your NPC's primary script.
float mountTimer
short isMountTimerOn
short mounted
short dismountFlag
begin gamemode
;dismount section
if mounted == 1
if Player.IsRidingHorse == 0
if IsRidingHorse == 0
Message "NPC has dismounted"
set dismountFlag to 0
set mounted to 0
set isMountTimerOn to 0
set mountTimer to 0
addScriptPackage followOnFoot
else
if isMountTimerOn == 0
Message "Trying to get NPC to dismount"
set isMountTimerOn to 1
set mountTimer to 3
set dismountFlag to 1
evp
else
if mountTimer <= 0
Message "dismountTimer expired"
set mountTimer to 3
evp
else
set mountTimer to mountTimer - getSecondsPassed
endif
endif
endif
else ;Timing issues
if isRidingHorse == 0 ;NPC managed to dismount
Message "Player has mounted out of turn, NPC dismounted"
addScriptPackage followOnFoot
set isMountTimerOn to 0
set mountTimer to 0
set dismountFlag to 0
set mounted to 0
else
if isMountTimerOn == 1 ;NPC still trying to dismount
Message "Player has mounted out of turn, in the timer."
removeScriptPackage
addScriptPackage followOnHorse
set isMountTimerOn to 0
set mountTimer to 0
set dismountFlag to 0
endif
endif
endif
endif
;mount section
if mounted == 0
if Player.isRidingHorse==1
if isRidingHorse==1
Message "NPC is Mounted"
set mounted to 1
set isMountTimerOn to 0
set mountTimer to 0
addScriptPackage followOnHorse
else
if isMountTimerOn == 0
Message "Trying to get NPC to Mount"
set isMountTimerOn to 1
set mountTimer to 3
addScriptPackage travelToHorse
else
if isMountTimerOn <= 0
Message "mountTimer expired"
set mountTimer to 3
addScriptPackage travelToHorse
else
set mountTimer to mountTimer - getSecondsPassed
endif
endif
endif
else ;Timing issues
if isRidingHorse == 1 ;NPC managed to mount
Message "Player has dismounted out of turn, NPC mounted"
addScriptPackage followOnHorse
set isMountTimerOn to 0
set mountTimer to 0
set mounted to 1
else
if isMountTimerOn == 1 ;caught while waiting for NPC to mount
Message "Player has dismounted out of turn, in the timer."
removeScriptPackage
addScriptPackage followOnFoot
set isMountTimerOn to 0
set mountTimer to 0
endif
endif
endif
endif
End
Caveats and Notes
Caveats
- This script assumes that the NPC's horse is nearby, you could be in for long wait if its far away. You could use the getDistance function to determine if the NPC bothers to mount or not.
- Obviously the NPC must own the horse either by faction or direct ownership.
- I used a Unique Horse ID 'E3Horse' so the NPC couldn't be confused by other horses nearby.
- Do not use 'continue when PC is near' in 'Package 2' or the NPC will never dismount.
Notes
- The 'mounted' variable in the script is of type short created at the beginning and set to 0. Its just a toggle so that I know to change the package.
- While mounted is used for mounting and dismounting, the dismountFlag is a special indicator to cause only a particular package to get evaluated, and is only used when dismounting.
- You may wonder why I have a timer for mounting and dismounting...hehe...its because the darn NPCs keep forgetting what their supposed to be doing. Sometimes you can actually see that they will move for a bit and then stop and then when the "timer up" message appears they will start moving again. Think of it as pretty little cattle prod designed just for NPC fannies. ;)
Hope this was helpful. -- Breave Apr 26, 2006
Another way to get horse and NPC following
The solution above inspired me to work on a, let's say, more reliable solution using a slightly different coding approach. As said before, this is not really for beginners, as you should know, how to use quest topics to set script variables amongst other things.
Introduction
The following how-to will lead to an NPC that will follow riding or walking, depending on the players action. In combat, the NPC will leave his horse, if he is attacked. You may need quest topics, that set the appropriate flags in the script. The NPC has also the ability to call his horse, if it is not near.
- The FollowPlayer script variable
- Setting the FollowPlayer script variable to 1, will cause the NPC to follow by horse or walking, depending on the players action. Setting it to 2, will cause the NPC to follow always walking. Setting it to 3 will make the NPC wait for the player.
- The SneakAsPlayer script variable
- Setting the SneakAsPlayer script variable to 1, will cause the NPC to sneak, whenever the player is sneaking. Setting it to 2 will cause the NPC to sneak always.
- The CallHorse script variable
- Setting the CallHorse script variable to 1 will cause the NPC to call his horse.
The other related objects I've used where a01HorseC01 (with reference a01HorseC01Ref) for the horse and a01Companion01 (with reference a01C01Ref) for the companion.
The Packages
The packages, directly related to the NPC need to have conditions set, to avoid, that they overrule the follow packages. For the wander package I have set the condition IsInCombat == 1. The eat and sleep packages I have set to FollowPlayer == 0 OR FollowPlayer == 3 (using GetScriptVariable, Reference a01Companion01). You may set an additional wander package to the same conditions.
Now lets move to the follow packages:
- a01AIC01FollowWalking - FOLLOW package type
- Flags set: Skip Fallout Behavior, Allow Swimming, Allow Falls, Defensive Combat
- Conditions: NONE
- Locations: NONE
- Target: Player, Distance 150
- Package Location: Not related to any object
- a01AIC01FollowRiding - FOLLOW package type
- Flags set: Skip Fallout Behavior, Use Horse, Allow Swimming, Allow Falls, Defensive Combat
- Conditions: NONE
- Locations: NONE
- Target: Player, Distance 350
- Package Location: Not related to any object
- a01AIC01MountHorse - TRAVEL package type
- Flags set: Skip Fallout Behavior, Use Horse, Allow Swimming, Allow Falls, Defensive Combat
- Conditions: NONE
- Locations: HorseRef
- Target: NONE
- Package Location: Not related to any object
- a01AIC01DismountHorse - TRAVEL package type
- Flags set: Skip Fallout Behavior, Allow Swimming, Allow Falls, Defensive Combat
- Conditions: NONE
- Locations: NONE
- Target: Player
- Package Location: Not related to any object
- a01AIC01Wait - WANDER package type
- Flags set: Skip Fallout Behavior, Allow Swimming, Allow Falls, Defensive Combat
- Conditions: NONE
- Locations: Near Current Location 350
- Target: NONE
- Package Location: Not related to any object
The Script
ScriptName a01ScrCompanion01
; Script for companion with follow functions.
;
; The companion should follow the player and mimic his actions, e.g.
; walk when player is walking, ride when player is riding, sneak when
; player is sneaking
;==============================================================================
;==============================================================================
; After lots of testing and despair I came to the conclusion, that script
; execution is prevented, if a follow package is running and relying on
; script variables in the condition. The script is run once, and, if the
; conditions are met for a follow package it will run and never allow the
; script to be executed again.
; So, first, the AI-Packages should not be related directly to script variables
; Second, the running quest package script runs ALWAYS every 5 seconds, if the
; fQuestDelayTime var is not set to another value
; Third, thus the Quest script can be used as an control instance to avoid
; package overruling script execution due to variables
; Fourth, the most secure option is to use the AI packages to be loaded
; WITHOUT ANY condition, while the direct related packages have conditions
; This is considered for AI-Packages directly related to the NPC
; thus the NPC should only have a wander as well as an eat and sleep package.
; If you add other packages, you have to test, if they interfere with the
; follow packages. Every package, directly related to the NPC should have proper
; conditions to avoid problems with the follow packages
;==============================================================================
;==============================================================================
; This AI packages are needed for follow functionality
; a01AIC01FollowWalking
; a01AIC01FollowRiding
; a01AIC01MountHorse
; a01AIC01DismountHorse
; a01AIC01Wait
; If we want the companion to follow on horse, he should have a
; horse. This horse is referenced in this script to get the distance
; and on need, activate the beam function (using MoveTo).
; Current EditorID is a01HorseC01Ref, NPC is a01C01Ref
; C01 stands for Companion01
; init vars
short DoOnce
; Follow function vars
short FollowPlayer
; can be 0 (no follow action)
; 1 (follow player)
; 2 (follow player always walking)
; 3 (wait for player)
short SneakAsPlayer
; can be 0 (no sneak action)
; 1 (sneak, when player's sneaking)
; 2 (always sneak when walking)
short FollowAction
; can be 0 (no follow action)
; 1 (follow walking)
; 2 (follow riding)
; 3 (mount horse)
; 4 (dismount horse)
; 5 (wait for player)
; 6 (call horse action - special handling)
short LoadAction
; can be 0 (no load action)
; 1 (load a01AIC01FollowWalking)
; 2 (load a01AIC01FollowRiding)
; 3 (load a01AIC01MountHorse)
; 4 (load a01AIC01DismountHorse)
; 5 (load a01AIC01Wait)
; 6 (activate MoveTo of horse - special handling as no
; LoadedPackage exists, comparing the FollowAction 6
; to LoadedPackage will always be false and thus executed)
short LoadedPackage
; can be 0 (not identified)
; 1 (loaded a01AIC01FollowWalking)
; 2 (loaded a01AIC01FollowRiding)
; 3 (loaded a01AIC01MountHorse)
; 4 (loaded a01AIC01DismountHorse)
; 5 (loaded a01AIC01Wait)
short SneakAction
; can be 0 (no action)
; 1 (start sneaking)
; 2 (stop sneaking)
short CallHorse
; can be 0 (don't call horse)
; 1 (call horse)
short WasInCombat
; can be 0 (no combat detected)
; > 0 (combat detected and working as a timer to discover
; the end of combat - as this state may change from
; one frame to another)
short HorseTimer
; can be 0 (no call for horse)
; > 0 (horse called and timer started to finish process)
long HorseDistance
Begin GameMode
; ====================Init section====================
If ( DoOnce == 0 )
Set DoOnce To 1
Set FollowPlayer To 0
Set SneakAsPlayer To 0
Set UseTorch To 0
Set FollowAction To 0
Set LoadAction To 0
Set LoadedPackage To 0
Set SneakAction To 0
Set TorchAction To 0
Set CallHorse To 0
Set WasInCombat To 0
Set HorseTimer To 0
Set HorseDistance To 0
EndIf
; ====================Start Follow player section====================
If ( FollowPlayer > 0 )
; ====================get status and decide what to do====================
; this vars are always reset if follow player is active
Set LoadAction To 0
Set LoadedPackage To 0
Set HorseDistance To 0
Set SneakAction To 0
; loaded package needs to be identified
If ( GetIsCurrentPackage "a01AIC01FollowWalking" )
Set LoadedPackage To 1
ElseIf ( GetIsCurrentPackage "a01AIC01FollowRiding" )
Set LoadedPackage To 2
ElseIf ( GetIsCurrentPackage "a01AIC01MountHorse" )
Set LoadedPackage To 3
ElseIf ( GetIsCurrentPackage "a01AIC01DismountHorse" )
Set LoadedPackage To 4
ElseIf ( GetIsCurrentPackage "a01AIC01Wait" )
Set LoadedPackage To 5
EndIf
; this might include riding
If ( FollowPlayer == 1 )
; decide, if we should ride also
If ( Player.IsRidingHorse )
; we should only ride, if not in combat
If ( IsInCombat || Player.IsInCombat || ( WasInCombat > 0 ) )
; first time, we notice combat situation
If ( WasInCombat == 0 )
Set FollowAction To 1
Set WasInCombat To 1
; wait for combat to finish (combat state may flicker!!!)
Else
Set WasInCombat To WasInCombat + 1
; check combat state
If ( WasInCombat == 60 )
If ( ( IsInCombat == 0 ) && ( Player.IsInCombat == 0 ) )
Set WasInCombat To 0
Set FollowAction To 2
EndIf
ElseIf ( WasInCombat == 120 )
If ( ( IsInCombat == 0 ) && ( Player.IsInCombat == 0 ) )
Set WasInCombat To 0
Set FollowAction To 2
EndIf
ElseIf ( WasInCombat == 180 )
; too long, reset combat status
Set WasInCombat To 0
Set FollowAction To 2
EndIf
EndIf
; no combat, normal processing
Else
; we are riding, that's fine
If IsRidingHorse
Set FollowAction To 2
Set HorseTimer To 0
Set CallHorse To 0
; we have to convince the NPC to ride
Else
; we may need this information
Set HorseDistance To ( GetDistance "a01HorseC01Ref" )
; if calling the horse, we need special processing
If CallHorse
If ( HorseTimer == 0 )
Set HorseTimer To 1
Set FollowAction To 6
Else
; wait until horse arrives
If ( GetInSameCell "a01HorseC01Ref" )
Set HorseTimer To HorseTimer + 1
If ( HorseTimer == 30 )
; set LoadedPackage to -1 to force package reload
Set LoadedPackage To -1
If ( HorseDistance > 350 )
Set FollowAction To 3
Else
Set FollowAction To 2
EndIf
ElseIf ( HorseTimer == 60 )
; set LoadedPackage to -1 to force package reload
Set LoadedPackage To -1
If ( HorseDistance > 350 )
Set FollowAction To 3
Else
Set FollowAction To 2
EndIf
ElseIf ( HorseTimer == 90 )
; waited long enough
; set LoadedPackage to -1 to force package reload
Set LoadedPackage To -1
If ( HorseDistance > 350 )
Set FollowAction To 3
Else
Set FollowAction To 2
EndIf
Set HorseTimer To 0
Set CallHorse To 0
EndIf
Else
; freeze HorseTimer until Horse arrived
Set HorseTimer To 2
EndIf
EndIf
; not calling horse
Else
; check if horse is near
If ( HorseDistance < 500 )
; activate the follow package immediately
Set FollowAction To 2
ElseIf ( ( HorseDistance > 499 ) && ( HorseDistance < 2000 ) )
; try to mount first
Set FollowAction To 3
ElseIf ( HorseDistance > 1999 )
; to avoid any load action
; set the FollowAction to LoadedPackage for this frame
Set FollowAction To LoadedPackage
; ask player, if we should call the horse
; quest topics should set either CallHorse to 1
; or FollowPlayer to 2
StartConversation Player "0poQMC01CallHorse"
EndIf
EndIf ; end condition CallHorse or not
EndIf ; end condition IsRidingHorse or not
EndIf ; end condition combat or not
; we should walk
Else
; clear CallHorse and Combat state
Set CallHorse To 0
Set HorseTimer To 0
Set WasInCombat To 0
; we are riding, that's bad
If IsRidingHorse
; first dismount
Set FollowAction To 4
Else
; we're walking as it should be
Set FollowAction To 1
EndIf
; check if sneak mode is on
If ( SneakAsPlayer != 0 )
;normal sneak mode, sneak like player
If ( SneakAsPlayer == 1 )
If Player.IsSneaking
; player sneaks but not me
If ( IsSneaking == 0 )
Set SneakAction To 1
EndIf
Else
; player doesn't sneak but I
If IsSneaking
Set SneakAction To 2
EndIf
EndIf
ElseIf ( SneakAsPlayer == 2 )
; always sneak
If ( IsSneaking == 0 )
Set SneakAction To 1
EndIf
EndIf
EndIf
EndIf ; end condition ride or walk
; determine LoadAction
If ( FollowAction != LoadedPackage )
; if not loaded, so load it!
Set LoadAction To FollowAction
EndIf
; this means always walking
ElseIf ( FollowPlayer == 2 )
; if for any reason riding, we should dismount
If IsRidingHorse
Set FollowAction To 4
Else
Set FollowAction To 1
; check if sneak mode is on
If ( SneakAsPlayer != 0 )
;normal sneak mode, sneak like player
If ( SneakAsPlayer == 1 )
If Player.IsSneaking
; player sneaks but not me
If ( IsSneaking == 0 )
Set SneakAction To 1
EndIf
Else
; player doesn't sneak but I
If IsSneaking
Set SneakAction To 2
EndIf
EndIf
ElseIf ( SneakAsPlayer == 2 )
; always sneak
If ( IsSneaking == 0 )
Set SneakAction To 1
EndIf
EndIf
EndIf
EndIf
; determine LoadAction
; if we have loaded the correct package
If ( FollowAction != LoadedPackage )
Set LoadAction To FollowAction
EndIf
; clear any riding state
Set WasInCombat To 0
Set HorseTimer To 0
; this means waiting for the player
ElseIf ( FollowPlayer == 3 )
; if for any reason riding, we should dismount
If IsRidingHorse
Set FollowAction To 4
Else
Set FollowAction To 5
EndIf
; determine LoadAction
; if we have loaded the correct package
If ( FollowAction != LoadedPackage )
Set LoadAction To FollowAction
EndIf
; clear any riding state
Set WasInCombat To 0
Set HorseTimer To 0
EndIf ; Follow Player analysis
; in follow mode, decide which package to load, if any
; RemoveScriptPackage is only used, if one of the special
; packages are loaded - to avoid removing standard packages
If ( LoadAction == 1 )
If ( ( LoadedPackage != 0 ) && ( LoadedPackage != 1 ) )
RemoveScriptPackage
EndIf
AddScriptPackage "a01AIC01FollowWalking"
EvaluatePackage
ElseIf ( LoadAction == 2 )
If ( ( LoadedPackage != 0 ) && ( LoadedPackage != 2 ) )
RemoveScriptPackage
EndIf
AddScriptPackage "a01AIC01FollowRiding"
EvaluatePackage
ElseIf ( LoadAction == 3 )
If ( ( LoadedPackage != 0 ) && ( LoadedPackage != 3 ) )
RemoveScriptPackage
EndIf
AddScriptPackage "a01AIC01MountHorse"
EvaluatePackage
ElseIf ( LoadAction == 4 )
If ( ( LoadedPackage != 0 ) && ( LoadedPackage != 4 ) )
RemoveScriptPackage
EndIf
AddScriptPackage "a01AIC01DismountHorse"
EvaluatePackage
ElseIf ( LoadAction == 5 )
If ( ( LoadedPackage != 0 ) && ( LoadedPackage != 5 ) )
RemoveScriptPackage
EndIf
AddScriptPackage "a01AIC01Wait"
EvaluatePackage
ElseIf ( LoadAction == 6 )
; get horse closer
"a01HorseC01Ref".MoveTo "a01C01Ref" 200 0 0
"a01HorseC01Ref".EvaluatePackage
EndIf
; check sneak actions
If ( SneakAction == 1 )
SetForceSneak 1
ElseIf ( SneakAction == 2 )
SetForceSneak 0
EndIf
EndIf ; end condition follow mode active
; ====================End Follow player section====================
End
Caveats and Notes
- The MoveTo command is pretty dangerous. It was necessary to freeze the timer until GetInSameCell function reports that the horse has arrived. If you try to load any package before the horse arrives in the cell, Oblivion will in most of the cases crash.
- The combat situation needed special treatment, to avoid, that the NPC can't decide, if he should load a package or start to fight.
- The timer used, do not count seconds, they count every time, the script runs. If the NPC is near the player, the scripts runs usually according to the frame rate, that means about 30 times every second. You may play with the timer settings, to get the NPC acting more directly.
- The NPC starts to act, with a little delay, as I favorized the Follow packages instead of the travel packages. I use them only, if the follow packages don't work. You may change the order to your favors. This means for my version, that you have to move riding up to a distance of 350 until the NPC mounts the horse and follows.
- The script tried to minimize function calls, whereever possible, as checking conditions with variables is usually faster. You may delete the comments in your final version, to get the interpreter run the script faster.
AdiBeiElderScrolls 14:33, 15 November 2006 (EST)