Illy's lite Companion scripting thread

Post » Fri May 27, 2011 9:26 pm

Thanks Cyrano for posting me the new script it worked beautifully - she took off from Caldera - made it past the end of the pathgrid with no problems - amused me no end with the animations for checking the signs and was pretty good at nagging me to keep up - best of all she dealt to all the annoying suicidal creatures that wanted to slow me down - take that cliffracer :) Melian i can hardly wait to try the companion script on her

Is there some way I can get her to stop at the signposts though and ask me if I'm ready to go on or to say there is a problem - ideally I want her to pause force a greeting and then after the conversation I can through dialogue use the 'come with me' topic to set her state to go the next post - so i guess i need a state for her to be in pause mode - is that right? I also wonder if the pause state would be useful for activating her during the quest - I clicked on her to see what would happen as she was walking and there was a pause before she got going again.

If the Naked Nord is still in the vicinity I would like to do a check and she can make a comment - I suspect it might use a GetLos function?
User avatar
Neil
 
Posts: 3357
Joined: Sat Jul 14, 2007 5:08 am

Post » Fri May 27, 2011 4:28 pm

I prefer 'Wall-and-Lava-Pit Inspection Mode', but I have to admit your description is more concise :P

Illuminiel, I don't think Cyrano mentioned this, but if GetCurrentAIPackage returns -1 that's when you need the combat fix (so it is detectable in script, and can be worked around). It's really only a big problem for creatures (b/c guards will attack any creature in combatmode).

I have another draft script (or set of scripts, now) for you - it's not tested (not even a quick compile) - just want to know what you think, what else needs doing, etc.

Companion local script:
Spoiler
begin illyCompanionLS

short followNow ;simple check for follow mode - if you have a follow variable
;that's set from dialogue it would be more reliable so replace this if you have one
short counter ; added b/c script is filling up a bit now -
; stuff that doesn't need to be done every frame can go at end of script
short oldCell ;type of previous cell, interior=1, exterior=0
short curCell ;type of current cell (as above)
short matchZ ;warp needed on z-axis? for levitation/swimming
short cmbtChk ;combat check
short wwChk ;do-once for taking waterwalking potion
short swimChk ;does the cell have water? (if so then will check for swimming)
short sick ;check for disease, 0=healthy 1=common disease 2=blight disease
short attrDmg
short doorFix ;state variable for load-door fix
short doOnce1 ;doonce vars for Grumpy's warping code/save warp destination points
short doOnce2
short tmpS ;just a temp var (can use in dialogue results calcs)
short storeDay ;simple day change check for medication once-per-day
; - remove this if doing it in dialogue instead


;potion storage vars
;these will need spells set up to apply the effects, except for
; restore health (which is better done in code alone)

;spells should all be type ability

;to use these in dialogue:
;if using one of the NPC's potions (ie one stored in these vars):
;check that the relevant variable is > 0 (ie that she's actually got some) and if so:
; set variable to ( variable - 1 )
; AddSpell "spell ID"
; set cureTmr to 1
;(that last line will trigger the script to remove the ability once it's done its job)
;of course replace placeholder ids with the real ids

short pv_lev ;levitation
short pv_cureC ;cure common disease
short pv_health ;restore health
short pv_wwalk ;water walking
short pv_dispel ;dispel (note: beware mischievous players telling her to take a dispel potion while levitating/waterwalking!)
short pv_meds ;daily medicine (2 associated spells needed for this one: cure blight and restore all attributes)


float ax ;position vars for warping
float ay
float bx
float by
float bz
float cx
float cy
float cz
float t1 ;temp vars used in warping code
float t2 ;these "t*" vars can be reused as temp vars in dialogue results if needed
float t3
float t4
float coDist ;distance checks for warping
float coDist2
float pcDist ;distance to player - GetDistance is slow, it's cheaper to check once per frame
float warpTmr ;time out for warping
float wndrTmr ;time out for wander (door fix etc) - can be used in dialogue eg step aside option

;to use a step aside option, put in dialogue results:
; set wndrTmr to 3
; AIWander 1000 0 0 0 0 0 0 0 0 0 0 0
; goodbye
;the timer should (I hope) put her back in follow mode when it's done (if she was in follow
; mode to start with)

float sheathe ;timer for weapon sheathing after combat (otherwise sheathing would
; trigger when combat target is distant or when switching combat targets)
float levTmr ;levitation time-out
float cureTmr ;timer for cure/restore abilities to simulate potion use
float prevX ;check for change in x-position - more reliable than cellChanged
float currX
float swimLvl ;used to check if swimming
float playZ
float myZ ;current z-position
float tmpF ;just a temp var (can be used for calcs in dialogue results)


;heal and restore while sleeping, and
;check if NPC is dead/unconscious/paralysed -
;no point running the rest if they are!
if ( GetHealth < 1 )
return
elseif ( GetPCSleep ) ;I put this before menumode since otherwise the PC
ModCurrentHealth 999 ; has to sleep at least 2 hours for it to trigger
ModCurrentMagicka 999
ModCurrentFatigue 999
return
elseif ( GetPCTraveling )
set doorFix to 1 ;wander to get out of pile-up after fast travel
return
elseif ( MenuMode )
return
elseif ( GetFatigue < 1 )
return
elseif ( GetParalysis > 0 )
return
endif

if ( wndrTmr < 500 )
elseif ( doorFix != 0 )
elseif ( GetCurrentAIPackage == 3 )
set followNow to 1
elseif ( GetCurrentAIPackage != -1 ) ;if AI is invalid, this assumes the last valid package
set followNow to 0 ;is still the correct one
set warpTmr to 0
endif

;check for cell change - need to know if one or both
; cells are interior. This triggers the door-fix wander
;Problem is that on game load the x-pos is wrong value first time
; so also check for game load via startscript

set tmpS to 0
set prevX to currX
set currX to ( GetPos x )
set tmpF to ( currX - prevX )
if ( doorFix == -1 ) ;just warped, false alarm
set doorFix to 0
elseif ( doorFix == 0 )
if ( followNow == 0 )
elseif ( mel_reload == 1 ) ;game load = false alarm
elseif ( tmpF > 512 )
set tmpS to 1
elseif ( tmpF < -512 )
set tmpS to 1
endif
endif
if ( tmpS >= 1 )
set oldCell to curCell
set curCell to ( GetInterior )
set swimChk to 0
if ( oldCell == 1 ) ;if a cell is interior, do the fix
set doorFix to 1
elseif ( curCell == 1 )
set doorFix to 1
elseif ( tmpS == 1 ) ;this will kick in with e.g. scripted teleports
set doorFix to 1
endif
endif

set pcDist to ( GetDistance player )
set warpTmr to ( warpTmr + GetSecondsPassed )

if ( wndrTmr < 500 )
set wndrTmr to ( wndrTmr - GetSecondsPassed )
set warpTmr to 0
if ( wndrTmr <= 0 )
set wndrTmr to 512
if ( followNow == 1 )
AIFollow player 0 0 0 0
else
AIWander 256 0 0 30 35 5 10 5 10 5 0 0 ;replace idles and distance with your defaults/whatever you want
endif
endif
endif

if ( doorFix > 0 )
if ( mel_reload != 0 ) ;if it got past the first check, catch it here
set wndrTmr to 0.1 ;this should hopefully put her back in follow mode
set doorFix to 0
return
elseif ( doorFix > 7 )
if ( wndrTmr > 500 )
set doorFix to 0 ;all done
return
endif
elseif ( doorFix == 4 )
if ( pcDist < 800 ) ;don't need to warp
set doorFix to 6
elseif ( ScriptRunning illy_loaddoorfix == 0 )
set doorFix to 5
StartScript illy_loaddoorfix ;warp companion back to player if she's got lost
endif
elseif ( doorFix == 5 )
if ( pcDist > 800 ) ;wait for script to do its job
return
elseif ( ScriptRunning illy_loaddoorfix == 0 )
set doorFix to 6
endif
elseif ( doorFix == 6 )
AIWander 512 0 0 0 ;this also helps get companion out of the post-door pile-up
set wndrTmr to 2
set doorFix to 7
else
set doorFix to ( doorFix + 1 )
endif
set warpTmr to 4
return
endif

;remove spell abilities when done
if ( cureTmr >= 0 )
set cureTmr to ( cureTmr + GetSecondsPassed )
if ( cureTmr >= 2 )
set cureTmr to -2
RemoveSpell "illy_cureBlight"
RemoveSpell "illy_restoreAttribs"
RemoveSpell "illy_cureCommon"
RemoveSpell "illy_dispel"
endif
endif

;check health and run or teleport away if necessary
;just remove this block if you don't want it
if ( GetHealthGetRatio < 0.25 )
MessageBox "Insert flee-by-teleport message here"
PlaySound3d "swallow" ;pretend to take a potion
PlaySound3d "mysticism hit"
PositionCell 10 -58 131 0 "Caldera, Shenk's Shovel" ;change destination as you like
return
elseif ( GetHealthGetRatio < 0.5 )
if ( GetFlee < 90 )
SetFlee 100
MessageBox "Insert flee-by-running message here if you want one" ;or you could use say for a voice file instead
endif
elseif ( GetFlee > 90 )
SetFlee 10 ;change to whatever your default is
endif

;give her a health potion if health is low
;change health percent value to whatever you want
if ( GetHealthGetRatio < 0.7 )
if ( pv_health > 0 ) ;are these going to be Exclusive potions??
set pv_health to ( pv_health - 1 )
PlaySound3d "swallow"
PlaySound3d "restoration hit"
ModCurrentHealth 200 ;if not Exclusive, change this value to match potion total health increase
endif
endif

;this next part checks for combat so they don't warp in the middle of it
;and so can force weapon sheathing afterwards if they get stuck like that

;removed spellcast sound checks, so now there's room to put hit checks in same block

if ( cmbtChk )
set warpTmr to -2
set sheathe to ( sheathe - GetSecondsPassed )
if ( GetSoundPlaying "Weapon Swish" )
set sheathe to 5
elseif ( GetSoundPlaying "crossbowShoot" )
set sheathe to 5
elseif ( GetSoundPlaying "bowShoot" )
set sheathe to 5
elseif ( GetSoundPlaying "Health Damage" )
set sheathe to 5
elseif ( GetSoundPlaying "destruction hit" )
set sheathe to 5
elseif ( GetSoundPlaying "frost_hit" )
set sheathe to 5
elseif ( GetSoundPlaying "shock hit" )
set sheathe to 5
elseif ( GetSoundPlaying "alteration hit" )
set sheathe to 5
elseif ( GetSoundPlaying "illusion hit" )
set sheathe to 5
elseif ( GetSoundPlaying "Hand To Hand Hit" )
set sheathe to 5
elseif ( GetSoundPlaying "Hand to Hand Hit 2" )
set sheathe to 5
elseif ( sheathe <= 0 )
set cmbtChk to 0
if ( GetSpellReadied )
cast "mark" player
else
equip "chitin dagger" ;Change this if your NPC actually has one. You
RemoveItem "chitin dagger" 1 ; should use a 1-handed weapon they *don't* have
endif ; - otherwise it will remove their real weapon!
endif
elseif ( GetWeaponDrawn )
set cmbtChk to 1
set sheathe to 5
set warpTmr to -2
elseif ( GetSpellReadied )
set cmbtChk to 1
set sheathe to 5
set warpTmr to -2
endif

;check for attribute damage
;note this isn't all that reliable (but she'll get restored each day regardless so not a big deal)
if ( GetEffect sEffectRestoreAttribute )
set attrDmg to 0
elseif ( GetEffect sEffectDamageAttribute )
set attrDmg to 1
elseif ( attrDmg == 1 )
set attrDmg to 2
endif

if ( followNow == 1 )
if ( GetForceSneak )
if ( cmbtChk ) ;take her out of sneak mode if in combat
ClearForceSneak
elseif ( player->GetEffect sEffectInvisibility )
elseif ( player->GetEffect sEffectChameleon )
elseif ( GetPCSneaking == 0 )
ClearForceSneak
endif
elseif ( cmbtChk )
elseif ( GetPCSneaking == 1 )
ForceSneak ;sneak when the player is sneaking, except in combat
endif

if ( player->GetEffect sEffectWaterWalking )
if ( wwChk == 1 )
elseif ( pv_wwalk > 0 )
set pv_wwalk to ( pv_wwalk - 1 ) ;take a waterwalking potion
set wwChk to 1
PlaySound3d "swallow"
PlaySound3d "alteration hit"
AddSpell "efb_bf_wwalkA"
endif
elseif ( wwChk == 1 )
RemoveSpell "efb_bf_wwalkA"
set wwChk to 0
endif

;this bit will make her sneak when the player uses chameleon or
;invisiblity spells - just remove it if you don't want it,
;it shouldn't affect anything else much

if ( cmbtChk )
elseif ( player->GetEffect sEffectInvisibility )
if ( GetForceSneak == 0 )
ForceSneak
endif
elseif ( player->GetEffect sEffectChameleon )
if ( GetForceSneak == 0 )
ForceSneak
endif
endif

;levitation works on a timer, so she has time to get down
;after player's levitation runs out
;note: it's all in follow mode block, so if player tells her to wait
;while levitating she'll just stay there in the air indefinitely!
;this can be changed if you want (can add a slowfall ability and get
;down that way)

if ( player->GetEffect sEffectLevitate == 1 )
if ( levTmr > 1 )
set levTmr to 1
elseif ( levTmr <= 0 )
if ( pv_lev > 0 )
set pv_lev to ( pv_lev - 1 ) ;take a levitation potion
PlaySound3d "swallow"
PlaySound3d "alteration hit"
AddSpell "illy_levitateA"
set levTmr to 1
elseif ( GetEffect sEffectLevitate == 0 ) ;in case player casts on her or something
AIWander 0 0 0 ;don't try to follow if can't levitate
MessageBox "do you want a warning when she's out of levitation potions??"
endif
endif
elseif ( levTmr > 10 )
RemoveSpell "illy_levitateA"
set levTmr to 0
elseif ( levTmr > 0 )
set levTmr to ( levTmr + GetSecondsPassed )
endif

elseif ( followNow == 0 )
set warpTmr to 0
endif

if ( swimChk == 0 ) ;we don't know if there's water in this cell or not
if ( curCell == 0 ) ;exterior always has water
set swimChk to 1
elseif ( GetWindSpeed > 0.001 ) ;interior as exterior
set swimChk to 1
elseif ( GetSoundPlaying "FootWaterLeft" == 1 ) ;in true interiors, can only know if
set swimChk to 1 ;there's water when you've stepped in it
elseif ( GetSoundPlaying "FootWaterRight" == 1 )
set swimChk to 1
elseif ( GetSoundPlaying "DefaultLandWater" == 1 )
set swimChk to 1
endif
endif

;Grumpy's warping calcs
set playZ to ( player->GetPos z )
set myZ to ( GetPos z )
set ax to ( Player->GetPos x )
set ay to ( Player->GetPos y )

if ( doOnce1 == 0 )
set bx to ( Player->GetPos x )
set by to ( Player->GetPos y )
set bz to playZ
set doOnce1 to 1
endif

set t1 to ( ax - bx )
set t1 to ( t1 * t1 )
set t2 to ( ay - by )
set t2 to ( t2 * t2 )
set t1 to ( t1 + t2 )
set coDist to ( GetSquareRoot t1 )

if ( coDist > 360 )
set doOnce1 to 0
endif

if ( coDist > 180 )
if ( doOnce2 == 0 )
set cx to ( Player->GetPos x )
set cy to ( Player->GetPos y )
set cz to playZ
set doOnce2 to 1
endif
endif

set t3 to ( ax - cx )
set t3 to ( t3 * t3 )
set t4 to ( ay - cy )
set t4 to ( t4 * t4 )
set t3 to ( t3 + t4 )
set coDist2 to ( GetSquareRoot t3 )

if ( coDist2 > 360 )
set doOnce2 to 0
endif

;the actual warping bit
if ( warpTmr > 6 )
if ( pcDist > 680 )
if ( coDist > 350 )
set doorFix to -1
set warpTmr to 0
SetPos x bx
SetPos y by
SetPos z bz
AIFollow Player 0 0 0 0
return
elseif ( coDist2 > 350 )
set doorFix to -1
set warpTmr to 0
SetPos x cx
SetPos y cy
SetPos z cz
AIFollow Player 0 0 0 0
return
endif
endif
endif

if ( cmbtChk != 0 )
elseif ( doorFix != 0 )
elseif ( followNow == 0 )
elseif ( wndrTmr < 500 )
elseif ( GetEffect sEffectLevitate ) ;check geteffect again in case the pc casts on her
set matchZ to 1
elseif ( swimChk == 1 )
set swimLvl to ( ( GetWaterLevel ) - 119.7 ) ;check if actually *in* the water, not just wading
if ( myZ <= swimLvl ) ;we're swimming
set swimLvl to ( swimLvl - 12 )
set tmpF to ( swimLvl - 20 )
if ( myZ <= swimLvl ) ;companion is underwater
set matchZ to 1
elseif ( playZ < tmpF ) ;player underwater
set matchZ to 1
endif
endif
endif

;this bit moves the companion gradually towards the player's z-axis position
;still has same issue as Grumpy's companions in that it can't detect collision
;so can end up stuck in stairs etc - but that fixes itself soon enough
if ( matchZ == 1 )
set matchZ to 0
set tmpF to ( myZ - playZ )
if ( tmpF != 0 )
if ( tmpF >= 5 )
set myZ to ( myZ - 5 )
elseif ( tmpF <= -5 )
set myZ to ( myZ + 5 )
else
set myZ to playZ
endif
SetPos z myZ
endif
endif

if ( cmbtChk == 1 )
set counter to 0
return
elseif ( counter < 20 ) ; ---------- COUNTER ----------
set counter to ( counter + 1 )
return
endif
set counter to 0

if ( GetPCCell "cell where you want potions to restock" ) ;need to add id here!
; or if you prefer dialogue for this just delete the block
; all the if-checks are in case you ever want to set the vars higher
; this way they won't be reduced, only increased
if ( pv_lev < 3 )
set pv_lev to 3
endif
if ( pv_cureC < 3 )
set pv_cureC to 3
endif
if ( pv_health < 3 )
set pv_health to 3
endif
if ( pv_wwalk < 3 )
set pv_wwalk to 3
endif
if ( pv_dispel < 3 )
set pv_dispel to 3
endif
if ( pv_meds < 5 )
set pv_meds to 5
endif
endif

;in theory Day should be less accurate here than DaysPassed
; - but Day doesn't require any particular master file (don't know
; if this is tribunal-dependent?)
; - plus the inaccuracy is very unlikely to happen, and the player probably
; won't notice even if it does!

if ( pv_meds == 0 )
;don't do anything yet - this will let her take a potion as soon as she gets one
elseif ( storeDay != Day )
set storeDay to Day
set pv_meds to ( pv_meds - 1 )
AddSpell "illy_cureBlight" ;placeholder ids
AddSpell "illy_restoreAttribs"
set cureTmr to 1
if ( pv_meds == 1 )
MessageBox "insert message warning player there's only 1 dose left"
endif
endif

if ( GetEffect sEffectCorpus ) ;note that GetBlightDisease will return true before GetEffect can detect corprus
RemoveEffects 132 ;effect ID for corprus - this needs testing btw, might have to use an ability-type spell instead
set sick to 0 ; (note: if the above doesn't work, SfD will need updating!)
elseif ( sick == 2 )
if ( ( GetJournalIndex "cure quest ID" ) >= value ) ;PLACEHOLDER - assuming here that cure quest (if you do one) will have a journal
AddSpell "illy_cureBlight" ;placeholder ID, change as you like
set cureTmr to 1
endif ;assuming nothing should be done here otherwise? (wait for daily medicine dose?)
elseif ( sick == 1 )
if ( pv_cureC > 0 )
set pv_cureC to ( pv_cureC - 1 )
PlaySound3d "swallow"
PlaySound3d "restoration hit"
AddSpell "illy_cureCommon" ;another placeholder ID
set cureTmr to 1 ; all these spells should all be type ability
endif
endif

;disease check - can be dealt with in script or dialogue or both as you like
if ( GetBlightDisease )
set sick to 2
elseif ( GetCommonDisease )
set sick to 1
else
set sick to 0
endif

;these are in case your NPC goes swimming - NPCs are too
;stupid to keep their heads above water :P
;acrobatics is mostly for teleport doors - prevents damage if NPC is slowfalling etc
if ( GetWaterBreathing != 1 )
SetWaterBreathing 1
endif
if ( GetAcrobatics < 199 )
SetAcrobatics 200
endif
ModCurrentFatigue 10 ;companions run a lot while in follow mode, this keeps their
;fatigue up (but won't take effect in combat)

;emergency warp straight to player position
;should only happen when there's no valid warp point stored

if ( followNow == 0 )
elseif ( pcDist > 1024 )
if ( warpTmr > 9 )
set doorFix to -1
SetPos x ax
SetPos y ay
SetPos z playZ
AIFollow player 0 0 0 0
return
endif
endif


end[/codebox]

Load door fix/warp (this is called from the companion local script when you go through a loaddoor, use fast travel, teleport with the companion, etc)
begin illy_loaddoorfix;quick warp, used for teleport door fix when companion ends up too far away (disappears);must be targeted script as local script setPos is not reliable here;this script can be used for other followers as well (but if there are many followers at;  once I'd have a few different scripts, since they'll have to wait til one's available)short statefloat timerfloat warpxfloat warpyfloat warpzif ( MenuMode == 1 )	returnelseif ( GetHealth < 1 )  ;mostly a check for script losing its target (GetHealth	set state to 0        ;   will return 0 if that happens)	set timer to 0	StopScript illy_loaddoorfix	returnelseif ( state == 0 )	if ( timer < 0.2 )		set timer to ( timer + GetSecondsPassed )		return	endif	set timer to 0	set warpx to ( player->GetPos x )	set warpy to ( player->GetPos y )	set warpz to ( player->GetPos z )	set state to 10elseif ( state == 10 )	SetPos x warpx	SetPos y warpy	SetPos z warpz	disable	enable	AIFollow player 0 0 0 0	set state to 20	returnelseif ( state == 20 )	if ( timer < 0.1 )		set timer to ( timer + GetSecondsPassed )		return	endif	set state to 0	set timer to 0	if ( ( GetDistance player ) > 2048 )  ;if still too far away, try again		if ( ( GetDistance player ) < 999999 )  ;but give up if actually in a different cell!			return		endif	endif	StopScript illy_loaddoorfixendifend


Game load detection (to stop companion wandering on game load as well). For this you need to add a global variable (called "mel_reload" here, you can use that or change it if you want). The variable should just have default settings (short, default 0).
begin mel_checkloadfloat timerif ( timer < 0.5 )	set timer to ( timer + GetSecondsPassed )	if ( mel_reload == 0 )		set mel_reload to 1	endifelse	set timer to 0	set mel_reload to 0	StopScript mel_checkloadendifend


So, let me know what you want done with it etc. And good luck with the escort!




Hi Melian - in the code you have a short variable follownow - is this what I can use for filtering in dialogue for when Lairah is with me? I think I need a global in case I want someone else to recognise that she is with me so how would I set her Global to the follow state if she is in follownow mode?
User avatar
Bigze Stacks
 
Posts: 3309
Joined: Sun May 20, 2007 5:07 pm

Post » Fri May 27, 2011 4:54 pm

For Jasmine, I used a global called jasfollowing that I set to 1 in dialog when she starts to follow you. If you tell her to leave (or she leaves on her own), it gets set back to 0. Setting the variable in dialog would be the easiest way and you could use it for a filter as well.
User avatar
Vicki Gunn
 
Posts: 3397
Joined: Thu Nov 23, 2006 9:59 am

Post » Fri May 27, 2011 11:35 pm

Sorry for the late reply - I'm on slow internet for a while :(

If you use a follow global, that would be good to use in the script too (more reliable than checking AI package). You could just set the global from dialogue results, and replace the followNow variable in the script with the global, since that's what it's trying to work out (whether or not she's supposed to be following).

This bit won't be needed if you use a global:
if ( wndrTmr < 500 )elseif ( doorFix != 0 )elseif ( GetCurrentAIPackage == 3 )    set followNow to 1elseif ( GetCurrentAIPackage != -1 ) ;if AI is invalid, this assumes the last valid package    set followNow to 0 ;is still the correct one    set warpTmr to 0endif


But you'll need to reset the warp timer if she's not in follow mode, something like this needs to be in the script somewhere instead:
if ( follow_global == 0 )    set warpTmr to 0endif


Actually, now that I think about it... "followNow" wasn't such a good idea for the variable id, because it's used by nearly all vanilla followers (all except Rabinna, I think?), so mods use it to identify followers for things (which you may not want done).

Edit: fixed code tag.
User avatar
Anna S
 
Posts: 3408
Joined: Thu Apr 19, 2007 2:13 am

Previous

Return to III - Morrowind