NPCs, Horses, and you
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.
Purpose[edit | edit source]
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[edit | edit source]
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[edit | edit source]
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[edit | edit source]
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[edit | edit source]
Caveats[edit | edit source]
- 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[edit | edit source]
- 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[edit | edit source]
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[edit | edit source]
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[edit | edit source]
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[edit | edit source]
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[edit | edit source]
- 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.