Jump to content

Serana AI vs other followers


nightscrawl

Recommended Posts

To start this off, I'll say that I've only used Marcurio extensively, so if other followers exhibit this same behavior, I'm not aware of it.

 

Since I'm currently doing Dawnguard, I decided to take Serana along instead of Marcurio. I've noticed that she is more animated in general. If we go to an inn, she will sit in a chair while I'm sitting, and other such idle activity. I really like this, as it makes her seem more like a real person, rather than some thoughtless drone. Normally, Marcurio will just stand around unless I tell him to wait (or sit) somewhere specific.

 

Before looking into it, I assumed that there was something in her AI packages that differentiated her from Marcurio (or other followers) that allowed this behavior. My plan was to see what this difference is and to copy it over to Marcurio. Unfortunately, her AI packages don't seem to be any different than his. She has one for Castle Volkihar, one for Castle Dawnguard, and one for hanging out in her coffin.

 

Anyone have any suggestions?

Link to comment
Share on other sites

I'm thinking that they put more time into her than the rest of the followers. I don't know if her ai packages are tied to the Dawnguard quests but I bet they are to some extent. I doubt it will work like you would like it to =/

Link to comment
Share on other sites

EFF, AFT, any Follower Mod worth any thing use's Serana Codes verbatim, it called the Mental Model, & works by monitoring the player, so Serana knows when do something immersive, & when to simply follow & be boring, by triggering (switching) her AI Packages

 

I use it myself, for my follower, how I how.

 

Take a look, hopefully you can understand it

Scriptname DLC1NPCMonitoringPlayerScript extends Quest  

DLC1_NPCMentalModelScript Property MM auto
ReferenceAlias Property RNPC auto

int Property UpdateInterval auto
float Property SettleRadius auto

int __historySize = 8 ; remember to update the declarations if necessary
float[] __playerPosX
float[] __playerPosY
float[] __playerPosZ


Function Setup()
	; history of player position over the last __historySize updates
	__playerPosX = new float[8]
	__playerPosY = new float[8]
	__playerPosZ = new float[8]

	; initialize the position histories with faraway junk datums
	;  so that we won't immediately assume the player is holding 
	;  still when the quest starts
	Actor _player = Game.GetPlayer()
	int count = 0
	while (count < __historySize)
		__playerPosX[count] = _player.X + 1000
		__playerPosY[count] = _player.Y + 1000
		__playerPosZ[count] = _player.Z + 1000
		count += 1
	endwhile

	RegisterForSingleUpdate(UpdateInterval)
EndFunction



Event OnUpdate()
	; cycle all positions down one notch in the history arrays
	int historyIndex = 0
	while (historyIndex < __historySize - 1)
		__playerPosX[historyIndex] = __playerPosX[historyIndex + 1]
		__playerPosY[historyIndex] = __playerPosY[historyIndex + 1]
		__playerPosZ[historyIndex] = __playerPosZ[historyIndex + 1]

		historyIndex += 1
	endwhile

	; set the most recent history as the current player position
	Actor _player = Game.GetPlayer()
	__playerPosX[__historySize - 1] = _player.X
	__playerPosY[__historySize - 1] = _player.Y
	__playerPosZ[__historySize - 1] = _player.Z


	; check current position against oldest history point if we're
	;   in follow mode
	if (MM.IsFollowing)
		bool switchedPackageConditions = false

		if (!MM.IsWillingToWait && RNPC.GetActorReference().GetActorValue("WaitingForPlayer") != 0)
			; she's not willing to wait for the player right now, but for
			;  some reason is waiting. Let's kick her out of this.
			RNPC.GetActorReference().SetActorValue("WaitingForPlayer", 0)
			switchedPackageConditions = true
		endif

		; calculate distance between history start and present
		;    sqrt((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2)
		float xFactor = (__playerPosX[0] - _player.X)
		xFactor = xFactor * xFactor
		float yFactor = (__playerPosY[0] - _player.Y)
		yFactor = yFactor * yFactor
		float zFactor = (__playerPosZ[0] - _player.Z)
		zFactor = zFactor * zFactor

		float distance = Math.sqrt(xFactor + yFactor + zFactor)

		; if the player has moved less than the defined settle radius,
		;   set the flag that the sandbox package is looking for.
		if (distance > SettleRadius)
			if (MM.PlayerSettled == true)
				switchedPackageConditions = true
			endif
			MM.PlayerSettled = false
		else
			if (MM.PlayerSettled == false)
				switchedPackageConditions = true
			endif
			MM.PlayerSettled = true
		endif

		; only do the EVP if we've actually changed the value
		if (switchedPackageConditions)
			if (MM.PlayerSettled)
				Debug.Trace("RNPC: Player settled; sandbox.")
			else
				Debug.Trace("RNPC: Player moving more than settle radius; resume follow.")
			endif
			RNPC.GetActorReference().EvaluatePackage()
		endif
	endif

	; do it all again
	RegisterForSingleUpdate(UpdateInterval)
EndEvent


Once ya get coding working, with Virtual Machine conditions, move on to AI packages to complete Follower AI behavior. Not that hard really since it is just coping Beth. Little script knowledge is required, but not much. By copying all & sundry, it will be standalone & not require Dawnguard, like the above mods.

Edited by PeterMartyr
Link to comment
Share on other sites

Peter, thank you for this. Is this how the "follower does/does not auto-relax" thing came about, in AFT?

 

EFF, AFT, any Follower Mod worth any thing use's Serana Codes verbatim, it called the Mental Model, & works by monitoring the player, so Serana knows when do something immersive, & when to simply follow & be boring, by triggering (switching) her AI Packages

 

Link to comment
Share on other sites

 

Peter, thank you for this. Is this how the "follower does/does not auto-relax" thing came about, in AFT?

 

EFF, AFT, any Follower Mod worth any thing use's Serana Codes verbatim, it called the Mental Model, & works by monitoring the player, so Serana knows when do something immersive, & when to simply follow & be boring, by triggering (switching) her AI Packages

 

 

Yes, EFF too, & every Follower that Auto-Relaxes, I am so guilty of plagiarizing her it too.

 

It how I know. :whistling: But copying Beth is how we learn & highly recommended. Nothing wrong with it. I do it all the time.

 

Another thingy is her Regard System. How much regard she hold for the player, it's got nothing to do with completing quest & killing things. But it's how you get the option to cure her. But it's a one way street, if you lose her respect it doesn't come back. There's no second prize. Curing her should be an achievement, if you do it without following a guide.

Link to comment
Share on other sites

I like it. Thank you. Not something I've ever understood very well, because scripting is far above my skill level. If I was to duplicate Serana to make another NPC, would they be that much more advance, or would I have to learn the scripting thing you showed above?

 

Just Do Mate Go FOR IT. Three years I never scripted a thing, I am self taught! I started with that scripted & created an Auto-Relaxing Follower, now she has her own (Thanks to Fore's) Exclusive Animations with Combat, Outfit persistence, Quest aware etc, etc. the last thing I add was SKSE Actorbase changing Hair & Hair Color, with Serana Vampire Cure Human Eyes Code to change her Eye Color! in the MCM. The first year will be hell, I s#*&#33; you not, but it does better from there. First you gotta do is get both Notepad++ & Sublime, I use them both but prefer Sublime for 70% of time.

 

Then teach them Papyrus

 

https://notepad-plus-plus.org/

https://www.creationkit.com/index.php?title=Notepad%2B%2B_Setup

 

https://www.sublimetext.com/

https://www.nexusmods.com/skyrim/mods/60810/?

 

I highly encourage you to try. The trick with that code is it is Conditional in another script that I didn't posts with PlayerSettled, which then allow you to use PlayerSettled true or false in CK has Condition for AI Package, so tho gist of the code is there. How it get used to trigger the Package is in

DLC1_NPCMentalModelScript 

Bool Property PlayerSettled Auto Conditional

You bring it together in one script, duplicate Sarana package to make standalone, if you do it right, xEdit will remove the useless Master Dawnguard, so the follower only has Skyrim as a Master. Yes I got it wrong the the first thing... many years ago.

Scriptname MonitoringPlayerScript extends Quest  Conditional
    {a gift I was bored}

Bool Property PlayerSettled Auto Conditional
{trigger for AI Package}

ReferenceAlias Property AliasFollower auto

Faction Property AutoRalaxingFaction Auto

int Property UpdateInterval auto
float Property SettleRadius auto

int __historySize = 8 ; remember to update the declarations if necessary
float[] __playerPosX
float[] __playerPosY
float[] __playerPosZ


Event OnInit()

    ; history of player position over the last __historySize updates
    __playerPosX = new float[8]
    __playerPosY = new float[8]
    __playerPosZ = new float[8]

    ; initialize the position histories with faraway junk datums
    ;  so that we won't immediately assume the player is holding
    ;  still when the quest starts
    Actor _player = Game.GetPlayer()
    int count = 0
    while (count < __historySize)
        __playerPosX[count] = _player.X + 1000
        __playerPosY[count] = _player.Y + 1000
        __playerPosZ[count] = _player.Z + 1000
        count += 1
    endwhile

    RegisterForSingleUpdate(UpdateInterval)
EndEvent

Event OnUpdate()

    ; cycle all positions down one notch in the history arrays
    int historyIndex = 0
    while (historyIndex < __historySize - 1)
        __playerPosX[historyIndex] = __playerPosX[historyIndex + 1]
        __playerPosY[historyIndex] = __playerPosY[historyIndex + 1]
        __playerPosZ[historyIndex] = __playerPosZ[historyIndex + 1]

        historyIndex += 1
    endwhile

    ; set the most recent history as the current player position
    Actor _player = Game.GetPlayer()
    __playerPosX[__historySize - 1] = _player.X
    __playerPosY[__historySize - 1] = _player.Y
    __playerPosZ[__historySize - 1] = _player.Z


    ; check current position against oldest history point if we're
    ;   in follow mode

    bool switchedPackageConditions = false

    ; calculate distance between history start and present
    ;    sqrt((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2)
    float xFactor = (__playerPosX[0] - _player.X)
    xFactor = xFactor * xFactor
    float yFactor = (__playerPosY[0] - _player.Y)
    yFactor = yFactor * yFactor
    float zFactor = (__playerPosZ[0] - _player.Z)
    zFactor = zFactor * zFactor

    float distance = Math.sqrt(xFactor + yFactor + zFactor)

    ; if the player has moved less than the defined settle radius,
    ;   set the flag that the sandbox package is looking for.
    if (distance > SettleRadius)
        if (PlayerSettled == true)
            switchedPackageConditions = true
        endif
        PlayerSettled = false
    else
        if (PlayerSettled == false)
            switchedPackageConditions = true
        endif
        PlayerSettled = true
    endif

    ; only do the EVP if we've actually changed the value
    if (switchedPackageConditions)
        if (PlayerSettled)
            Debug.Trace("AliasFollower: Player settled; sandbox.")
        else
            Debug.Trace("AliasFollower: Player moving more than settle radius; resume follow.")
        endif
        AliasFollower.GetActorReference().EvaluatePackage()
    endif

    if (AliasFollower.GetReference() as actor).IsInFaction(AutoRalaxingFaction)
        self.RegisterForSingleUpdate(UpdateInterval) ;to stop the updates
    endIf
EndEvent

For Follower Dialogue, "Relax when able" or "Follower me Closely"

(GetOwningQuest() as CustomFollowerDialogue).AddToAutoRalaxingFaction(akSpeaker)

Which link to this, & this link to the above.

Scriptname CustomFollowerDialogue extends Quest
{Start of your independent follower system} 

MonitoringPlayerScript Property PlayerMoniter Auto 

Faction Property AutoRalaxingFaction Auto

Function AddToAutoRalaxingFaction(actor akSpeaker)

	If(akSpeaker.IsInFaction(AutoRalaxingFaction))
		AkSpeaker.RemoveFromFaction(AutoRalaxingFaction)
	else
		akSpeaker.SetFactionRank(AutoRalaxingFaction, 0)
		PlayerMoniter.RegisterForSingleUpdate(1)
	EndIf
EndFunction
	

So far we have two Quests, I leave to you work out Packages & Topic Info Frag, Alias, etc, etc, etc. Dialogue...... minor details.. Factions.....

 

Have Fun. I don't want to make it toooooooooooooo easy. The fun is in the learning. You will soon have Auto-Relaxing Follower. & the base model of an Advance Follower.

 

Please don't disappoint me by not trying...

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...