Nutulator Posted August 19, 2016 Share Posted August 19, 2016 (edited) Hey guys, I would like some advice. How can I increase the follower slots from the vanilla one to five? I've tried editing FollowersScript but it's not working out exactly as I planned. I did that under "NOTE ABOUT COMPANION AND DOGMEAT FUNCTIONS." I don't know if this is only on my end or not but I can't add anything to that script in the CK, you can delete code but you can't write anything in it. Here is the original script: Scriptname FollowersScript extends Quest Conditional {Script for handling follower and companion systems: FOLLOWERS, COMPANIONS, COMMANDS LOITERING COMPANION AFFINITY SITUATION AWARENESS } ;/ This script is broken into sections for each "system" - each with its own properties, variables, and functions defined under the section heading: *** FOLLOWERS, COMPANIONS, COMMANDS *** LOITERING *** COMPANION AFFINITY *** SITUATION AWARENESS Events and Remote Events however are kept together at top, as events are used by multiple systems. Questions/concerns/changes: --speak with jduvall /; ;******************************************************************************************************* ;************************************** STATES ************************************************** ;******************************************************************************************************* string HandlingOnHit = "HandlingOnHit" State HandlingOnHit Event OnHit(ObjectReference RemoteSource, ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked, string asMaterialName) EndEvent EndState ;******************************************************************************************************* ;************************************** EVENTS ************************************************** ;******************************************************************************************************* ;************************************** OnInit() ************************************************** Quest Property Tutorial Auto Const Quest Property Achievements Auto Const WorkshopParentScript Property WorkshopParent Auto LocationAlias Property DismissMessageLocation const auto Message Property FollowersCompanionDismissMessage const auto Message Property FollowersDogmeatCompanionDismissMessage const auto Message Property FollowersDogmeatCompanionDismissMessageNameUnknown const auto GlobalVariable Property PlayerKnowsDogmeatName const auto keyword Property workshopItemKeyword const auto Event OnInit() playerRef = game.GetPlayer() ;used throughout ;**** used by "LOITERING" ***** LoiterBufferSize PlayerPosX = New float[LoiterBufferSize] PlayerPosY = New float[LoiterBufferSize] PlayerPosZ = New float[LoiterBufferSize] InitializeLoiterBuffer() ;to make game boot instant debugging easier, not really a practical in game need startTimer(TimerInterval_LoiterSample, iTimerID_LoiterSample) ;****************************** ;**** used by Autonomy **** AutonomyDisallowedByObjects = new ScriptObject[0] RegisterForRemoteEvent(playerRef, "OnLocationChange") ; used by: "SITUATION AWARENESS" RegisterForRemoteEvent(playerRef, "OnCombatStateChanged") ; used by: "SITUATION AWARENESS" RegisterForHitEvent(playerRef) ; used by: "SITUATION AWARENESSS" ; & "FOLLOWER, COMPANION, COMMANDS" RegisterForRemoteEvent(playerRef, "OnPlayerFireWeapon") ;used by the Affinity Event system RegisterForRemoteEvent(playerRef, "OnPlayerFallLongDistance") ;used by the Affinity Event system RegisterForRemoteEvent(playerRef, "OnPlayerHealTeammate") ;used by the Affinity Event system RegisterForRemoteEvent(playerRef, "OnCripple") ;used by the Affinity Event system RegisterForRemoteEvent(Companion, "OnCripple") ;used by the Affinity Event system RegisterForRemoteEvent(PlayerRef, "OnItemEquipped") ;used by the Affinity Event system (for getting in Power Armor) RegisterForRemoteEvent(PlayerRef, "OnPlayerEnterVertibird") ;used by the Affinity Event system and to start scene to have companion get in RegisterForRemoteEvent(PlayerRef, "OnPlayerUseWorkBench") ;used by the Affinity Event system RegisterForRemoteEvent(PlayerRef, "OnPlayerSwimming") ;used by the Affinity Event system RegisterForRadiationDamageEvent(PlayerRef) ;used by the Affinity Event system RegisterForRemoteEvent(PlayerRef, "OnPlayerModArmorWeapon") ;used by the Affinity Event system RegisterForRemoteEvent(DogmeatCompanion, "OnEnterBleedout") ;used by the Affinity Event system RegisterForPlayerSleep() ;used to add the perk for sleeping near companions that are infatuated RegisterForRemoteEvent(Companion, "OnCommandModeGiveCommand") ;used to start quests for specific behavior RegisterForRemoteEvent(DogmeatCompanion, "OnCommandModeGiveCommand") ;used to change his archetype based on commands given RegisterForRemoteEvent(DogmeatCompanion, "OnCommandModeCompleteCommand") ;used to change his archetype based on commands given RegisterForMagicEffectApplyEvent(PlayerRef, akCasterFilter = none, akEffectFilter = CA_AddictionEffect) RegisterForPlayerTeleport() trace(self, "OnInit() finished.") EndEvent ;************************************** TIMERS ************************************************** int iTimerID_LoiterSample = 1 const ;TimerID ;NOTE: Timer Interval is TimerInterval_LoiterSample int iTimerID_GameTime_SAECombatantExpiry = 3 const ;timer id float TimerInterval_SAECombatantExpiry = 0.25 const ;in gametime hours int iTimerID_GameTime_SAC_LastCombat = 4 const ;timer id float TimerInterval_SAC_LastCombat = 0.25 const ;in gametime hours -- NOTE: this should be smaller than the smallest change between numbers in SAC_LastCombat_Times Event OnTimer(int aiTimerID) if aiTimerID == iTimerID_LoiterSample HandleTimer_LoiterSample() endif EndEvent Event OnTimerGameTime(int aiTimerID) if aiTimerID == iTimerID_GameTime_SAECombatantExpiry HandleTimer_GameTime_SAECombatantExpiry() elseif aiTimerID == iTimerID_GameTime_SAC_LastCombat Handletimer_GameTime_SAC_LastCombat() endif EndEvent ;******************************************************************************************************* ;************************************** REGISTERED REMOTE EVENTS ************************************** ;******************************************************************************************************* bool DebugTrace_OnHit = false bool DebugTrace_OnCombatStateChanged = false Event OnMagicEffectApply(ObjectReference akTarget, ObjectReference akCaster, MagicEffect akEffect) if akTarget == PlayerRef && akEffect == CA_AddictionEffect followersScript.SendAffinityEvent(self, AddictionKeyword) endif EndEvent Event Actor.OnLocationChange(Actor akSender, Location akOldLoc, Location akNewLoc) if akSender == playerRef trace(self, "Player OnLocationChange()") StartCompanionInteractionScene(CIS_Type_EnterNewLocation, Playerref, akNewLoc) SetSituationAwarenessBasedOnLocation(akNewLoc, SetSASData = true, SetSAEClutterData= false, SettingObject = akNewLoc) endif EndEvent Event OnHit(ObjectReference RemoteSource, ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked, string asMaterialName) GoToState(HandlingOnHit) if DebugTrace_OnHit debug.traceFunction() endif Actor CompanionActor = Companion.GetActorReference() if RemoteSource == CompanionActor || RemoteSource == playerRef HandleCombatMessageToSituationAwareness(akAggressor as Actor) ;ultimately causes SitationalAwareness variables to update based on the actor we're in combat with endif GoToState(None) ; listen for the next hit against the player or companion RegisterForHitEvent(playerRef) RegisterForHitEvent(Companion) ; may want to track if we are currently listening for companion hits and, if so, not re-register EndEvent Event Actor.OnCombatStateChanged(Actor RemoteSource, Actor akTarget, int aeCombatState) trace(self, "Actor.OnCombatStateChanged() RemoteSource: " + RemoteSource + ", akTarget:" + akTarget + ", aeCombatState:" + aeCombatState) if DebugTrace_OnCombatStateChanged debug.traceFunction() endif actor CompanionActor = Companion.GetActorReference() if RemoteSource == CompanionActor || RemoteSource == playerRef if akTarget HandleCombatMessageToSituationAwareness(akTarget as Actor) ;ultimately causes SitationalAwareness variables to update based on the actor we're in combat with endif endif EndEvent Event Actor.OnPlayerFireWeapon(Actor akSender, Form akBaseObject) trace(self, "OnPlayerFireWeapon() akBaseObject:" + akBaseObject) float radiusToLookForActors = 3000 ;trace(self, "OnPlayerFireWeapon") ;check location keywords for settelements ;find all ActorTypeNPC refs nearby, see if they are all non-hostile ;then send the dischargeweapon event location locToTest = akSender.GetCurrentLocation() if akBaseObject && akBaseObject.HasKeyword(Game.GetCommonProperties().WeaponTypeMelee1H) == false && akBaseObject.HasKeyword(Game.GetCommonProperties().WeaponTypeMelee2H) == false && akBaseObject.HasKeyword(Game.GetCommonProperties().WeaponTypeUnarmed) == false if locToTest.HasKeyword(Game.GetCommonProperties().LocTypeSettlement) || locToTest.HasKeyword(Game.GetCommonProperties().LocTypeWorkshopSettlement) ;trace(self, "OnPlayerFireWeapon - player in settlement area") ObjectReference[] NPCs = akSender.FindAllReferencesWithKeyword(Game.GetCommonProperties().ActorTypeNPC, radiusToLookForActors) bool AnyHostiles = CommonArrayFunctions.IsActorInArrayHostileToActor(akSender, NPCs) if AnyHostiles == false trace(self, "OnPlayerFireWeapon - no hostiles in the area, will send the CA_DischargeWeapon event") SendAffinityEvent(self, DischargeWeapon) endif endif endif endEvent Event Actor.OnPlayerFallLongDistance(Actor akSender, float afDamage) trace(self, "OnPlayerFallLongDistance") SendAffinityEvent(self, JumpFromHeight) endEvent Event Actor.OnPlayerHealTeammate(Actor akSender, Actor akTeammate) trace(self, "OnPlayerHealTeammate akSender:" + akSender + " akTeammate:" + akTeammate) if akTeammate == Companion.GetActorReference() SendAffinityEvent(self, HealCompanion) elseif akTeammate == DogmeatCompanion.GetActorReference() SendAffinityEvent(self, HealDogmeat) endif EndEvent Event Actor.OnCripple(Actor akSender, ActorValue akActorValue, bool abCrippled) HandleOnCrippleEvent(akSender, akActorValue, abCrippled) EndEvent Event ReferenceAlias.OnCripple(ReferenceAlias akSender, ActorValue akActorValue, bool abCrippled) HandleOnCrippleEvent(akSender, akActorValue, abCrippled) EndEvent Event ReferenceAlias.OnEnterBleedout(ReferenceAlias akSender) if akSender == DogmeatCompanion FollowersScript.SendAffinityEvent(self, DogmeatBleedoutKeyword) endif endEvent Event Actor.OnPlayerEnterVertibird(Actor askSender, ObjectReference akVertibird) trace(self, "OnPlayerEnterVertibird() " + akVertibird) LastPlayerVertibird.ForceRefTo(akVertibird) HandleVertibirdEvent(akVertibird) EndEvent Event Actor.OnPlayerUseWorkBench(Actor akSender, ObjectReference akWorkBench) trace(self, "OnPlayerUseWorkBench()" + akWorkBench) HandleOnWorkbench(akWorkBench) EndEvent Event Actor.OnPlayerSwimming(Actor akSender) trace(self, "OnPlayerSwimming()" + Game.GetPlayer().GetCurrentLocation()) HandleOnSwimming(Game.GetPlayer().GetCurrentLocation()) EndEvent Event OnRadiationDamage(ObjectReference akTarget, bool abIngested) trace(self, "OnRadiationDamage()") if Game.GetPlayer().IsInCombat() == false && abIngested == false HandleOnRadiation(akTarget.GetCurrentLocation()) endif RegisterForRadiationDamageEvent(PlayerRef) ; Re-register so we get the next event that's sent endEvent Event Actor.OnPlayerModArmorWeapon(Actor akSender, Form akBaseObject, ObjectMod akModBaseObject) trace(self, "OnPlayerModArmorWeapon() akBaseObject:" + akBaseObject + ", akModBaseObject: " + akModBaseObject) HandleOnMod(akBaseObject) endEvent Event Actor.OnItemEquipped(Actor akSender, Form akBaseObject, ObjectReference akReference) trace(self, "OnItemEquipped()" + akSender + ", " + akBaseObject + ", " + akReference) HandleOnItemEquipped(akSender, akBaseObject) EndEvent Event OnPlayerSleepStart(float afSleepStartTime, float afDesiredSleepEndTime, ObjectReference akBed) trace(self, "OnSleepStart akBed:" + akBed) HandleOnSleepStart(akBed) EndEvent Event OnPlayerSleepStop(bool abInterrupted, ObjectReference akBed) HandleOnSleepEnd() EndEvent Event OnPlayerTeleport() trace(self, "OnPlayerTeleport, calling AllowAutonomyOnTeleport()") AllowAutonomyOnTeleport() EndEvent ;Companion command interface commands Event ReferenceAlias.OnCommandModeGiveCommand(ReferenceAlias akSender, int aeCommandType, ObjectReference akTarget) ; aeCommandType: Type of Command that is given, which is one of the following: ; ; 0 - None ; 1 - Call ; 2 - Follow ; 3 - Move ; 4 - Attack ; 5 - Inspect ; 6 - Retrieve ; 7 - Stay ; 8 - Release ; 9 - Heal trace(self, "OnCommandModeGiveCommand()" + akSender + aeCommandType + akTarget) if akSender == Companion if (akTarget.GetBaseObject() is Container || akTarget.GetBaseObject() is Door) && akTarget.IsLocked() && akSender.GetActorReference().HasKeyword(Followers_Command_LockPick_Allowed) akSender.GetActorReference().EvaluatePackage(abResetAI=true) ;cancels the stay command Command_LockPick_Scene.stop() CommandActor.ForceRefTo(akSender.GetReference()) CommandTarget.ForceRefTo(akTarget) Command_LockPick_Scene.start() elseif akTarget.GetBaseObject() is Terminal && akTarget.IsLocked() && akSender.GetActorReference().HasKeyword(Followers_Command_HackTerminal_Allowed) akSender.GetActorReference().EvaluatePackage(abResetAI=true) ;cancels the stay command Command_HackTerminal_Scene.stop() CommandActor.ForceRefTo(akSender.GetReference()) CommandTarget.ForceRefTo(akTarget) Command_HackTerminal_Scene.start() endif elseif akSender == DogmeatCompanion && aeCommandType == 6 DogmeatIdles.BarkPlayful() DogmeatIdles.SetDogmeatAlert() endif EndEvent ;Companion command interface commands Event ReferenceAlias.OnCommandModeCompleteCommand(ReferenceAlias akSender, int aeCommandType, ObjectReference akTarget) ; aeCommandType: Type of Command that is given, which is one of the following: ; ; 0 - None ; 1 - Call ; 2 - Follow ; 3 - Move ; 4 - Attack ; 5 - Inspect ; 6 - Retrieve ; 7 - Stay ; 8 - Release ; 9 - Heal trace(self, "OnCommandModeCompleteCommand()" + akSender + aeCommandType + akTarget) if akSender == DogmeatCompanion && aeCommandType == 6 && !game.getplayer().isSneaking() ; if Dogmeat has just completed a fetch, get happy! Unless, of course, we're being sneaky. DogmeatIdles.BarkPlayful() DogmeatIdles.SetDogmeatPlayful(15) DogmeatIdles.FaceHappy() endif EndEvent ;******************************************************************************************************* ;************************************** GENERIC FUNCTIONS ******************************** ;******************************************************************************************************* FollowersScript Function GetScript() global ;used for making functions global return (Game.GetFormFromFile(0x000289E4, "Fallout4.esm") as Quest) as FollowersScript ;this script attached to Followers quest EndFunction bool Function Trace(ScriptObject CallingObject, string asTextToPrint, int aiSeverity = 0) global debugOnly ;we are sending callingObject so we can in the future route traces to different logs based on who is calling the function string logName = "Followers" debug.OpenUserLog(logName) RETURN debug.TraceUser(logName, CallingObject + ": " + asTextToPrint, aiSeverity) EndFunction bool Function TraceConditional(ScriptObject CallingObject, string asTextToPrint, bool ShowTrace, int aiSeverity = 0) global debugOnly ;we are sending callingObject so we can in the future route traces to different logs based on who is calling the function string logName = "Followers" if ShowTrace debug.OpenUserLog(logName) RETURN debug.TraceUser(logName, CallingObject + ": " + asTextToPrint, aiSeverity) else return false endif EndFunction ;******************************************************************************************************* ;************************************** FOLLOWERS, COMPANIONS, COMMANDS ******************************** ;******************************************************************************************************* ;/ Handles various Follower / Companion behavior. Much of which is setting variables used by FollowPlayer template based packages. Functions are exposed that can be called on any actor following the player. There is also a temporary messagebox menu interface for setting commands. See also: PACKAGES - FollowPlayer Template Sandboxes at "Go Home Sandbox Location" package data if: A) AllFollowerState var == iFollower_Com_GoHome, OR B) ActorValue == iFollower_Com_GoHome Travels to self (ie "Waits") if: A) AllFollowerState var = iFollower_Com_Wait, OR B) ActorValue == iFollower_Com_Wait Sandboxes around player if Loitering (see LOITERING section for details) If not sandboxing: Flees if player gets to close. Follows closely if: ActorValue == iFollowerDistNear Follows normally if: ActorValue == iFollowerDistMedium Follows distantly if: ActorValue == iFollowerDistFar /; ReferenceAlias Property Companion Auto Const ReferenceAlias Property DogmeatCompanion Auto Const RefCollectionAlias Property ActiveCompanions const auto ReferenceAlias Property AvailableMessageActor const auto Message Property FollowersCompanionAvailableMessage const auto ReferenceAlias Property SleepCompanion Auto Const ReferenceAlias Property SleepCompanionBed Auto Const keyword Property FollowersCompanionSleepNearPlayerFlag const auto Faction property CurrentCompanionFaction Auto Const Faction property HasBeenCompanionFaction Auto Const Faction property DisallowedCompanionFaction Auto Const ReferenceAlias Property CommandActor const auto ReferenceAlias Property CommandTarget const auto GlobalVariable Property PlayerHasActiveCompanion const auto GlobalVariable Property PlayerHasActiveDogmeatCompanion const auto ReferenceAlias Property LastPlayerVertibird Auto Const Group CommandLockHackProperties keyword Property Followers_Command_LockPick_Allowed const auto {autofill - used to start the lockpick scene if you command an actor to inspect a locked door or container} scene Property Command_LockPick_Scene const auto {autofill - scene used for commanded lock pick scenes} keyword Property Followers_Command_HackTerminal_Allowed const auto {autofill - used to start the hack terminal scene if you command an actor to inspect a locked terminal} scene Property Command_HackTerminal_Scene const auto {autofill - scene used for commanded hack terminal scenes} actorvalue Property ValentineFailedToHack const auto ;actor value to put on TERMINAL when Valentine fails to hack it, so he won't try again. int property CommandUnlockAttempts auto hidden conditional ;how many attempts to cait make to pick a lock? bool Property CommandUnlockSuccess auto hidden conditional MiscObject Property BobbyPin const auto Sound Property NPCHumanLockpickingPickBreak const auto Sound Property NPCHumanLockpickingUnlock const auto idle Property IdleLockPickingLowHeight const auto idle Property IdleLockPickingMediumHeight const auto idle Property IdleStop const auto sound Property NPCHumanHackingPasswordBad const auto sound Property NPCHumanHackingPasswordGood const auto EndGroup Function CommandUnlockStartNewAttempt() debug.trace(self + "CommandUnlockStartNewAttempt()") CommandUnlockSuccess = false CommandUnlockAttempts = 0 EndFunction Function CommandUnlockPlayAnim() debug.trace(self + "CommandUnlockPlayAnim()") actor source = CommandActor.GetActorReference() objectReference target = CommandTarget.GetReference() ;TO DO -- check if door, if so play medium, if not, check z offset if target.GetBaseObject() is Door source.playidle(IdleLockPickingMediumHeight) else float zOffset = target.Z - source.Z trace(self, "CommandUnlockPlayAnim() zOffset" + zOffset) if zOffset > 50 source.playidle(IdleLockPickingMediumHeight) else source.playidle(IdleLockPickingLowHeight) endif endif EndFunction Function CommandUnlockAttempt() debug.trace(self + "CommandUnlockAttempt()") objectReference source = CommandActor.GetReference() objectReference target = CommandTarget.GetReference() int lockLevel = target.GetLockLevel() debug.trace(self + "lockLevel: " + lockLevel) int roll = Utility.RandomInt(0, 110) debug.trace(self + "roll: " + roll) CommandUnlockSuccess = roll > lockLevel debug.trace(self + "CommandUnlockSuccess: " + CommandUnlockSuccess) if CommandUnlockSuccess target.unlock() if target.GetBaseObject() is Terminal NPCHumanHackingPasswordGood.play(source) else NPCHumanLockpickingUnlock.play(source) endif else if target.GetBaseObject() is Terminal NPCHumanHackingPasswordBad.play(source) else NPCHumanLockpickingPickBreak.play(source) CommandActor.GetActorReference().RemoveItem(BobbyPin) endif endif CommandUnlockAttempts += 1 EndFunction Function CommandUnlockFailedHack() debug.trace(self + "CommandUnlockFailedHack()") objectReference target = CommandTarget.GetReference() target.setvalue(ValentineFailedToHack, 1) EndFunction int Property AllFollowerState = 1 auto conditional hidden ;/ iFollower_Com_Follow -- "allowed to follow" individually determined by each actor's ActorValue iFollower_Com_Wait FollowersSayCommandGoHome /; ;holds cached values for these actor values when the actor becomes a companion, so we can change them and set them back float Property CachedIdleChatterTimeMin hidden auto float Property CachedIdleChatterTimeMax hidden auto float CompanionIdleChatterTimeMin = 240.0 float CompanionIdleChatterTimeMax = 300.0 ;************************************** COMMAND PROPERTY DATA ************************************************** ;/ Commands are made up of three parts (which should be STRUCTS in future implementations of Papyrus) 1) String Property AV_<CommandName> - a string which is used as a switch for which command data to set - in part this is because when implementing I didn't have appropriate actorvalues and had to map to other actorvalues (see TTP#3505) -*** IF ANY OF THE STRINGS SHOULD CHANGE BECAUSE THE ACTOR VALUE NAMES CHANGE (which they will when TTP#3505)) -*** YOU MUST CHANGE THE CONDITIONS IN FollowPlayer Template tree!!! 2) GlobalVariable Property iFollower_Com_<CommandName> - a GlobalVariable whose value acts like an Enum and is used to make conditions in packages human readable and more flexible 3) Topic Property FollowersSayCommand<CommandName> - a Topic that the follower will say when being switched into this command Some commands have additional GlobalVariables for settings, such as: -follow distance: near, medium, far -stance: aggressive, defensive /; ;THESE ARE USED BY FollowPlayer package template (and possibly others) in condition functions. Changes here will have ramifications in packages. ;Not sure if these are going to their own actor values or what, so setting things up as string/value pairs will hopefully make it less painful to change later if needed Group FollowerControl Collapsed Message Property FollowersCompanionMenuMain const auto Message Property FollowersCompanionMenuTactics const auto Message Property FollowersCompanionMenuWait const auto Message Property FollowersCompanionMenuFollow const auto ;NOTE: some of these actor values are remapping old ones that will be renamed. (Ex: "Fame" will be renamed "Follow") ;Follow State: GlobalVariable Property iFollower_Com_Follow const auto GlobalVariable Property iFollower_Com_Wait const auto GlobalVariable Property iFollower_Com_GoHome const auto Topic Property FollowersSayCommandFollow const auto Topic Property FollowersSayCommandWait const auto Topic Property FollowersSayCommandGoHome const auto ;Follow Distance: GlobalVariable Property iFollower_Dist_Near const auto GlobalVariable Property iFollower_Dist_Medium const auto GlobalVariable Property iFollower_Dist_Far const auto Topic Property FollowersSayCommandDistanceNear const auto Topic Property FollowersSayCommandDistanceMedium const auto Topic Property FollowersSayCommandDistanceFar const auto ;Stance: GlobalVariable Property iFollower_Stance_Aggressive const auto GlobalVariable Property iFollower_Stance_Defensive const auto Topic Property FollowersSayCommandStanceAggressive const auto Topic Property FollowersSayCommandStanceDefensive const auto ;Trade: Topic Property FollowersSayCommandTrade const auto ;Favor: Topic Property FollowersSayCommandFavor const auto ;Stance Combat Override (this is short lived flag for defensive stance companions that turns on/off their combat override package) GlobalVariable Property iFollower_Stance_CombatOverride_True const auto GlobalVariable Property iFollower_Stance_CombatOverride_False const auto ;"Aggression" actorValue Reminder: ;0 = Unaggressive Will not initiate combat. ;1 = Aggressive Will attack Enemies on sight. ;2 = Very Aggressive Will attack Enemies and Neutrals on sight. ;3 = Frenzied Will attack anyone on sight. Actors are rarely set to Frenzied by default; this is usually the result of some other spell or effect (like the Frenzy spell). Endgroup CustomEvent CompanionChange ;register for this to listen for companion changes ;### Status Functions ### bool Function IsCompanion(Actor ActorToCheck) return Companion.GetActorReference() == ActorToCheck EndFunction bool Function IsPossibleCompanion(Actor ActorToCheck) return ActorToCheck as CompanionActorScript EndFunction bool Function IsFollowing(Actor ActorToCheck) return ActorToCheck.GetValue(Game.GetCommonProperties().FollowerState) == iFollower_Com_Follow.value EndFunction bool Function IsCompanionAndFollowing(Actor ActorToCheck) return IsCompanion(ActorToCheck) && IsFollowing(ActorToCheck) EndFunction Function TracePlayerFollowers() global debug.messagebox(Game.GetPlayerFollowers()) trace(GetScript(), "TracePlayerFollowers(): " + Game.GetPlayerFollowers()) EndFunction ;### Companion Change Event ### Function SendCompanionChangeEvent(Actor ActorThatChanged, bool IsNowCompanion) Var[] args = new Var[2] args[0] = ActorThatChanged args[1] = IsNowCompanion trace(self, "SendCompanionChangeEvent()" + args) sendCustomEvent("CompanionChange", args) EndFunction Function SetAvailableToBeCompanion(Actor ActorToMakeAvailable) ActorToMakeAvailable.AddToFaction(HasBeenCompanionFaction) if ActorToMakeAvailable.IsInFaction(CurrentCompanionFaction) == false AvailableMessageActor.ForceRefTo(ActorToMakeAvailable) FollowersCompanionAvailableMessage.show() endif EndFunction ;************** NOTE ABOUT COMPANION AND DOGMEAT FUNCTIONS ****************** ;Originally there were two slots for companion types, you could have both Dogmeat and a single human companion. ;That is why there are two aliases and two seperate functions. ;Now we are only letting you have either dogmeat or a single human companions. ;Rather than risk breaking everything by trying to unifying everything into a single alias/function call ;I am going to just put hooks in SetCompanion and SetDogmeatCompanion to auto dismiss the other ;### SetCompanion #### ;There is only ever one companion at a time ;This function handles "promoting" an actor to companion status, and "demoting" previously installed companions ;Much of the companion behavior and data is set up using the Companion alias. ;ALSO CALLED BY Actor.psc's SetCompanion() function Actor[] AchievementCompanions ;add companions and dogmeat to this when hired for the first time, used to setstage on achievement quests Function ManageAchievementCompanions(actor CompanionActor) if AchievementCompanions == None AchievementCompanions = New Actor[0] endif if AchievementCompanions.find(CompanionActor) < 0 AchievementCompanions.add(CompanionActor) Achievements.setStage(320) endif EndFunction Function SetCompanion(Actor ActorToMakeCompanion, bool SetCompanion = true, bool FillCompanionAlias = true, bool SuppressDismissMessage = false) ; Joel - Adding hook for Follower/Command Mode tutorial if !tutorial.getStageDone(600) tutorial.setStage(600) endif Actor CurrentCompanionActor = Companion.GetActorReference() if SetCompanion == false if CurrentCompanionActor == ActorToMakeCompanion DismissCompanion(ActorToMakeCompanion, ShowLocationAssignmentListIfAvailable = true, SuppressDismissMessage = SuppressDismissMessage) endif RETURN endif ;dismiss dogmeat - you can only have a single human companion or dogmeat DismissDogmeatCompanion(ShowLocationAssignmentListIfAvailable = true, SuppressDismissMessage = false) if FillCompanionAlias ;this is almost certainly going to be true ;if we are changing companions, dismiss the previous companion so player get's a message knowing that's happened if CurrentCompanionActor && ActorToMakeCompanion.IsInFaction(CurrentCompanionFaction) == false DismissCompanion(Companion.GetActorReference(), ShowLocationAssignmentListIfAvailable = true) endif Companion.ForceRefTo(ActorToMakeCompanion) ;if ActorToMakeCompanion isn't already in there, add him to the ActiveCompanions refcollection if ActiveCompanions.find(ActorToMakeCompanion) < 0 ActiveCompanions.addRef(ActorToMakeCompanion) endif endif CompanionDataToggle(true, canDoFavor = true) ;canDoFavor is param that forces the no-dialogue command mode ;hook for achievements ManageAchievementCompanions(ActorToMakeCompanion) ActorToMakeCompanion.addToFaction(HasBeenCompanionFaction) ;used primarily to conditionalize dialogue FollowerFollow(ActorToMakeCompanion) FollowerSetDistanceMedium(ActorToMakeCompanion) ;used to handle companion in defensive stance responding to his or player's getting hit RegisterForHitEvent(playerRef) SendCompanionChangeEvent(ActorToMakeCompanion, IsNowCompanion = True) PlayerHasActiveCompanion.SetValue(1) CompanionActorScript CompanionActor = ActorToMakeCompanion as CompanionActorScript if CompanionActor.ShouldGivePlayerItems CompanionActor.StartHasItemTimer() endif EndFunction Function DismissCompanion(Actor CompanionToDismiss, bool ShowLocationAssignmentListIfAvailable = true, bool SuppressDismissMessage = false) trace(self, "DismissCompanion()" + CompanionToDismiss) CompanionActorScript CAS = Companion.GetActorReference() as CompanionActorScript if CAS == CompanionToDismiss ;is actor we are trying to dismiss actually the current companion? if SuppressDismissMessage == false trace(self, "PlayerOwnsAWorkshop: " + WorkshopParent.PlayerOwnsAWorkshop) trace(self, "ShowLocationAssignmentListIfAvailable: " + ShowLocationAssignmentListIfAvailable) location WorkshopHome if ShowLocationAssignmentListIfAvailable && WorkshopParent.PlayerOwnsAWorkshop WorkshopHome = WorkshopParent.AddPermanentActorToWorkshopPlayerChoice(Companion.GetActorReference()) endif trace(self, "DismissCompanion() will show message") if WorkshopHome == None WorkshopHome = (CompanionToDismiss.GetLinkedRef(workshopItemKeyword) as WorkshopScript).myLocation endif trace(self, "DismissCompanion() WorkshopHome: " + WorkshopHome) if WorkshopHome CAS.HomeLocation = WorkshopHome endif DismissMessageLocation.ForceLocationTo(CAS.HomeLocation) FollowersCompanionDismissMessage.Show() endif trace(self, "DismissCompanion() will ClearCompanion()") ClearCompanion() else trace(self, CompanionToDismiss + " is NOT the current companion, so not dismissing.") endif EndFunction Function DismissDogmeatCompanion(bool ShowLocationAssignmentListIfAvailable = true, bool SuppressDismissMessage = false) trace(self, "DismissDogmeatCompanion()") DogmeatActorScript DAS = DogmeatCompanion.GetActorReference() as DogmeatActorScript trace(self, "DismissDogmeatCompanion() DAS = " + DAS) trace(self, "PlayerOwnsAWorkshop: " + WorkshopParent.PlayerOwnsAWorkshop) trace(self, "ShowLocationAssignmentListIfAvailable: " + ShowLocationAssignmentListIfAvailable) if DAS if SuppressDismissMessage == false location WorkshopHome if ShowLocationAssignmentListIfAvailable && WorkshopParent.PlayerOwnsAWorkshop WorkshopHome = WorkshopParent.AddPermanentActorToWorkshopPlayerChoice(DAS, bWaitForActorToBeAdded = false) endif trace(self, "DismissDogmeatCompanion() DAS.HomeLocation = " + DAS.HomeLocation) if WorkshopHome == None WorkshopHome = (DAS.GetLinkedRef(workshopItemKeyword) as WorkshopScript).myLocation endif if WorkshopHome DAS.HomeLocation = WorkshopHome endif DismissMessageLocation.ForceLocationTo(DAS.HomeLocation) ;TEST FOR IF PLAYER KNOWS NAME if PlayerKnowsDogmeatName.GetValue() == 1 FollowersDogmeatCompanionDismissMessage.Show() else FollowersDogmeatCompanionDismissMessageNameUnknown.Show() endif endif ClearDogmeatCompanion() endif EndFunction function SetDogmeatCompanion(Actor DogmeatRef = None) ;NOTE: We allow dismissal of Dogmeat in the same way you dismiss any follower.(Oct 6 2014) ;WE ASSUME THERE IS ONLY EVER ONE DOGMEAT THAT CAN BECOME YOUR COMPANION (debug testing versions aside) ; Joel - Adding hook for Follower/Command Mode tutorial if !tutorial.getStageDone(602) tutorial.setStage(602) endif if DogmeatRef.GetRace() != Game.GetCommonProperties().DogmeatRace Game.Warning(self + "SetDogmeatCompanion() DogmeatRef is not DogmeatRace. " + DogmeatRef) DogmeatRef = None endif if DogmeatRef == none DogmeatRef = Game.GetCommonProperties().DogmeatRef endif trace(self, "SetDogmeatCompanion(): " + DogmeatRef + "; will dismiss current companion") Actor CompanionActor = Companion.GetActorReference() if CompanionActor DismissCompanion(CompanionActor, ShowLocationAssignmentListIfAvailable = true, SuppressDismissMessage = false) endif ;hook for achievements ManageAchievementCompanions(DogmeatRef) DogmeatCompanion.ForceRefTo(DogmeatRef) DogmeatRef.IgnoreFriendlyHits() DogmeatRef.SetPlayerTeammate(abTeammate = true, abCanDoFavor = true, abGivePlayerXP = true) DogmeatRef.AddtoFaction(HasBeenCompanionFaction) FollowerFollow(DogmeatRef) FollowerSetDistanceMedium(DogmeatRef) ;start the AO dogmeat find story manager ping AOScript.GetScript().DogmeatLoading() ;ADDED FOR SURVIVAL MODE - though it probably should have been in for the base game, no one apparently needed it until now SendCompanionChangeEvent(DogmeatRef, IsNowCompanion = True) PlayerHasActiveDogmeatCompanion.SetValue(1) EndFunction ;If you are trying to clear a particular companion, call SetCompanion with the false parameter instead. Function ClearCompanion() ;check if there is a companion before doing any of this to avoid throwing errors Actor actorToClear = Companion.GetActorReference() Trace(self, "ClearCompanion() actorToClear: " + actorToClear) if actorToClear CompanionDataToggle(false, false) actorToClear.StopCombatAlarm() Companion.Clear() SendCompanionChangeEvent(actorToClear, IsNowCompanion = False) PlayerHasActiveCompanion.SetValue(0) ;** Question: ;Should I also remove the companion from the ActiveCompanions RefAliasCollection? At the moment I am not, but ;down the road dependning on what all uses ActiveCompanions collection, I might want to. endif EndFunction ;Reminder this is primarily called via function on Actor script Function DisallowCompanion(Actor ActorToDisallow, bool SuppressDismissMessage = false) debug.trace(self + "DisallowCompanion()" + ActorToDisallow) ActorToDisallow.AddToFaction(DisallowedCompanionFaction) if ActorToDisallow.GetRace() == Game.GetCommonProperties().DogmeatRace DismissDogmeatCompanion(SuppressDismissMessage = SuppressDismissMessage) else DismissCompanion(ActorToDisallow, SuppressDismissMessage = SuppressDismissMessage) endif EndFunction ;Reminder this is primarily called via function on Actor script Function AllowCompanion(Actor ActorToAllow, bool MakeCompanionIfNoneCurrently = true, bool ForceCompanion = false) debug.trace(self + "AllowCompanion()" + ActorToAllow + ", MakeCompanionIfNoneCurrently:" + MakeCompanionIfNoneCurrently + ", ForceCompanion:" + ForceCompanion ) ActorToAllow.RemoveFromFaction(DisallowedCompanionFaction) if (PlayerHasActiveCompanion.GetValue() == 0 && PlayerHasActiveDogmeatCompanion.GetValue() == 0 && MakeCompanionIfNoneCurrently) || ForceCompanion if ActorToAllow.GetRace() == Game.GetCommonProperties().DogmeatRace SetDogmeatCompanion(ActorToAllow) else SetCompanion(ActorToAllow) endif endif EndFunction ;Note: you are probably looking for DismissDogmeatCompanion() instead Function ClearDogmeatCompanion() ;check if there is a companion before doing any of this to avoid throwing errors Actor actorToClear = DogmeatCompanion.GetActorReference() Trace(self, "ClearDogmeatCompanion() actorToClear: " + actorToClear) if actorToClear actorToClear.IgnoreFriendlyHits(false) actorToClear.SetPlayerTeammate(false) DogmeatCompanion.Clear() SendCompanionChangeEvent(actorToClear, IsNowCompanion = False) PlayerHasActiveDogmeatCompanion.SetValue(0) endif EndFunction Function CompanionDataToggle(bool toggleOn = true, bool canDoFavor = true, bool givePlayerXP = true, bool SetNotShowOnStealthMeter = true) CompanionActorScript CompanionActor = Companion.GetActorReference() as CompanionActorScript CompanionActor.IgnoreFriendlyHits(toggleOn) ; ***THIS SHOULD PROBABLY BE A FLAG ON ALIASES CompanionActor.SetPlayerTeammate(toggleOn, canDoFavor, givePlayerXP) ; ***THIS SHOULD PROBABLY BE A FLAG ON ALIASES ;reminder Companion is the alias, not the reference ;cache the idle timer actor values ActorValue IdleChatterTimeMin = Game.GetCommonProperties().IdleChatterTimeMin ActorValue IdleChatterTimeMax = Game.GetCommonProperties().IdleChatterTimeMax if toggleOn if SetNotShowOnStealthMeter CompanionActor.SetNotShowOnStealthMeter(true) endif ;IDLE CHATTER - cache and set them CachedIdleChatterTimeMin = CompanionActor.GetValue(IdleChatterTimeMin) CachedIdleChatterTimeMax = CompanionActor.GetValue(IdleChatterTimeMax) CompanionActor.SetValue(IdleChatterTimeMin, CompanionIdleChatterTimeMin) CompanionActor.SetValue(IdleChatterTimeMax, CompanionIdleChatterTimeMax) ;ADD KEYWORDS ;we also add keywords directly on the Companion alias ;this is for keywords that some but all companions should have ;Example: playerCanStimpak should be added to human companions, but not robot ones int i = 0 while (i < CompanionActor.KeywordsToAddWhileCurrentCompanion.length) CompanionActor.AddKeyword(CompanionActor.KeywordsToAddWhileCurrentCompanion[i]) i += 1 endwhile RegisterForHitEvent(Companion) RegisterForRemoteEvent(CompanionActor, "OnCombatStateChanged") else CompanionActor.SetNotShowOnStealthMeter (false) ;IDLE CHATTER - set them back CompanionActor.SetValue(IdleChatterTimeMin, CachedIdleChatterTimeMin) CompanionActor.SetValue(IdleChatterTimeMax, CachedIdleChatterTimeMax) ;REMOVE KEYWORDS int i = 0 while (i < CompanionActor.KeywordsToAddWhileCurrentCompanion.length) CompanionActor.RemoveKeyword(CompanionActor.KeywordsToAddWhileCurrentCompanion[i]) i += 1 endwhile UnregisterForAllHitEvents(Companion) UnRegisterForRemoteEvent(CompanionActor, "OnCombatStateChanged") endif EndFunction Function TryToTeleportCompanion(ObjectReference TeleportDestinationRef, bool ShouldPlayTeleportInEffect = true, bool ShouldPlayTeleportOutEffect = true) debug.trace(self + "TryToTeleportCompanion()") Actor CompanionActor = Companion.GetActorReference() if CompanionActor TeleportActor(CompanionActor, TeleportDestinationRef, ShouldPlayTeleportInEffect, ShouldPlayTeleportOutEffect) endif EndFunction Function TryToTeleportDogmeat(ObjectReference TeleportDestinationRef, bool ShouldPlayTeleportInEffect = true, bool ShouldPlayTeleportOutEffect = true) debug.trace(self + "TryToTeleportDogmeat()") Actor DogmeatActor = DogmeatCompanion.GetActorReference() if DogmeatActor TeleportActor(DogmeatActor, TeleportDestinationRef, ShouldPlayTeleportInEffect, ShouldPlayTeleportOutEffect) endif EndFunction Function TeleportActor(Actor ActorToTeleport, ObjectReference TeleportDestinationRef, bool ShouldPlayTeleportInEffect, bool ShouldPlayTeleportOutEffect) debug.trace(self + "TeleportActor() ActorToTeleport: " + ActorToTeleport + ", TeleportDestinationRef: " + TeleportDestinationRef) TeleportActorScript TeleportActor = ActorToTeleport as TeleportActorScript if ShouldPlayTeleportOutEffect if TeleportActor TeleportActor.TeleportIn() else game.Warning("FollowerScript: TeleportFollowers() actor doesn't have TeleportActorScript attached, can't play fx! " + ActorToTeleport) endif endif utility.wait(5) debug.trace(self + "TeleportActor() calling MoveTo()" + TeleportDestinationRef) TeleportActor.MoveTo(TeleportDestinationRef) debug.trace(self + "TeleportActor() done with moveto") if ShouldPlayTeleportInEffect if TeleportActor TeleportActor.TeleportIn() else game.Warning("FollowerScript: TeleportFollowers() actor doesn't have TeleportActorScript attached, can't play fx! " + ActorToTeleport) endif endif debug.trace(self + "TeleportActor() done") EndFunction ;********************************* FOLLOWER COMMANDS *************************************************** Function FollowerWait(Actor Follower) SetFollowerActorValue(Follower, Game.GetCommonProperties().FollowerState, iFollower_Com_Wait) EndFunction Function FollowerFollow(Actor Follower) SetFollowerActorValue(Follower, Game.GetCommonProperties().FollowerState, iFollower_Com_Follow) EndFunction Function FollowerSetDistanceNear(Actor Follower) SetFollowerActorValue(Follower, Game.GetCommonProperties().FollowerDistance, iFollower_Dist_Near) EndFunction Function FollowerSetDistanceMedium(Actor Follower) SetFollowerActorValue(Follower, Game.GetCommonProperties().FollowerDistance, iFollower_Dist_Medium) EndFunction Function FollowerSetDistanceFar(Actor Follower) SetFollowerActorValue(Follower, Game.GetCommonProperties().FollowerDistance, iFollower_Dist_Far) EndFunction Function SetFollowerActorValue(Actor Follower, ActorValue WhichValueToSet, GlobalVariable iFollower_Global) Follower.SetValue(WhichValueToSet, iFollower_Global.GetValue()) EndFunction ;******************************************************************************************************* ;************************************** SCENES ************************************************** ;******************************************************************************************************* ;/ Companions will have various scenes that they are in, triggered by: Attraction Object events Actor Hello Events Change Location Events Quest Stage result scripts To make things simple, they are all going to be script event quests started by a common function call that those other quest types will call in their start up phase quest stage. /; Group SceneData keyword Property CIS_Companion_Scene_Start const auto {Keyword used to start Companion Interaction Scenes} GlobalVariable Property CIS_Type_EnterNewLocation const auto {AUTOFILL} EndGroup ;Reminder: ;This sends out to story manager Script Event ;Look for "CIS" nodes in those to set timers and conditions. bool Function StartCompanionInteractionScene(GlobalVariable SceneTypeGlobal, objectReference SceneRef, location SceneLoc, Actor CompanionActor = None) global ;SceneTypeGlobal is a global that represents the scene type, which is the numeric variable we send with sendStoryEvent function call ;SceneRef is the primary reference/actor in the scene with the companion ;SceneLoc is the location where the scene is happening ;CompanionActor default = whoever is in the Companion alias FollowersScript selfScript = GetScript() if CompanionActor == None CompanionActor = selfScript.Companion.GetActorReference() endif trace(selfScript, "StartCompanionInteractionScene()" + SceneTypeGlobal + ", " + SceneRef + ", " + SceneLoc + ", " + CompanionActor ) RETURN selfScript.CIS_Companion_Scene_Start.SendStoryEventAndWait(akLoc = SceneLoc, akRef1 = CompanionActor, akRef2 = SceneRef, aiValue1 = SceneTypeGlobal.GetValue() as int) ;*** SEE ALSO: CIS_ManagerScript *** EndFunction ;******************************************************************************************************* ;************************************** LOITERING ************************************************** ;******************************************************************************************************* ;/ Used to get anyone running the FollowPlayer package to Sandbox around the player if the player is loitering. Adapted by sLiesegang's DLC1NPCMonitoringPlayerScript from Dawnguard (adding check against all buffered positions, not just the last) See also: PACKAGES - FollowPlayer Template Sandboxes around the Player if: 1) AllowLoitering Package Data is True, and 2) isPlayerLoitering is True (variable set by this script) Otherwise Follows the player /; CustomEvent Loitering bool DebugTrace_Loitering = false bool DebugDropMarkers = false ;SHOULD ALWAYS BE SET TO FALSE IN RELEASE ARCHIVES - set to true if you want to drop debug markers to help visualize the buffer (need to TMK in console to see them) ObjectReference[] debugMarkers Actor playerRef Bool Property isPlayerLoitering conditional auto hidden ;player isn't moving far from where he was a little while ago float LoiterRadius = 1500.0 const ;in units - isPlayerLoitering = true if he has not gone farther than this from his previously buffered sampled position float TimerInterval_LoiterSample = 1.0 const ;in seconds int LoiterBufferSize = 10 ;* How many LoiterSample's to store. ;* Each new sample shifts previous samples into higher index. ;* Larger LoiterBufferSize means it takes longer from traveling (in a straightline) to loitering when resting ; and triggers traveling again faster if resting for brief period float StandardLoiterCoolDownTime = 30.0 ;minimum amount of time before being allowed to loiter again float FollowCommandLoiterCoolDownTime = 90.0 ; after a follower gets a follow command we make the cool down longer float CurrentLoiterCoolDownTime ;These Arrays are buffers of players last positions ;index(0) is the most recent, index(LoiterBufferSize -1) is the oldest float[] PlayerPosX float[] PlayerPosY float[] PlayerPosZ Function SendLoiteringEvent(bool isNowLoitering) Var[] args = new Var[2] args[0] = isNowLoitering ;true = started loitering, false = stopped loitering args[1] = Companion ;the companion alias - note: you usually want the alias itself (not the actor in it), in case it changes out from under you traceconditional(self, "Sending LoiteringEvent. " + args, DebugTrace_Loitering) sendCustomEvent("Loitering", args) EndFunction Function InitializeLoiterBuffer() float PlayerX = PlayerRef.X float PlayerY = PlayerRef.Y float PlayerZ = PlayerRef.Z int i = 0 while (i < LoiterBufferSize - 1) BufferCurrentPosition(i, PlayerX, PlayerY, PlayerZ) i += 1 endwhile EndFunction Function HandleTimer_LoiterSample() float FurthestDistance = GetFurthestDistanceAndPromoteSamples() traceconditional(self, "FurthestDistance = " + furthestDistance, DebugTrace_Loitering) if DebugDropMarkers == true ; THIS SHOULD ALWAYS BE FALSE IN RELEASE ARCHIVES DropDebugMarkers() EndiF bool LoiterCoolingDown CurrentLoiterCoolDownTime -= TimerInterval_LoiterSample LoiterCoolingDown = CurrentLoiterCoolDownTime > 0 bool Traveled = FurthestDistance > LoiterRadius bool InCombat = PlayerRef.IsInCombat() bool Sneaking = PlayerRef.IsSneaking() bool Sprinting = PlayerRef.IsSprinting() bool WeaponOut = PlayerRef.IsWeaponDrawn() bool NotSitting = PlayerRef.GetSitState() != 3 if DebugTrace_Loitering Trace(self, "SetIsPlayerLoitering():") Trace(self, "LoiterCoolingDown: " + LoiterCoolingDown) Trace(self, "Traveled: " + Traveled) Trace(self, "InCombat: " + InCombat) Trace(self, "Sneaking: " + Sneaking) Trace(self, "Sprinting: " + Sprinting) Trace(self, "WeaponOut: " + WeaponOut) Trace(self, "NotSitting: " + NotSitting) endif bool shouldEVPFollowers if ( Traveled || LoiterCoolingDown || InCombat || Sneaking || Sprinting || WeaponOut ) && NotSitting if (isPlayerLoitering == true) traceconditional(self, "*** PLAYER NO LONGER LOITERING ***", DebugTrace_Loitering) SendLoiteringEvent(isNowLoitering = false) shouldEVPFollowers = true SetLoiterCoolDown(StandardLoiterCoolDownTime) endif isPlayerLoitering = false else if (isPlayerLoitering == false) traceconditional(self, "*** PLAYER IS NOW LOITERING ***", DebugTrace_Loitering) SendLoiteringEvent(isNowLoitering = true) shouldEVPFollowers = true endif isPlayerLoitering = true endif if isPlayerLoitering traceconditional(self, "* LOITERING *", DebugTrace_Loitering) else traceconditional(self, "* NOT LOITERING *", DebugTrace_Loitering) endif ;**** TO DO ADD EVALUATE PACKAGE LOGIC HERE**** ;can certainly do this for companion, not sure if we'll have a way to get all the normal followers if Companion.GetActorReference() && shouldEVPFollowers Companion.GetActorReference().EvaluatePackage() endif if DogmeatCompanion.GetActorReference() && shouldEVPFollowers ;**** TO DO ADD EVALUATE PACKAGE LOGIC HERE**** ;can certainly do this for companion, not sure if we'll have a way to get all the normal followers DogmeatCompanion.GetActorReference().EvaluatePackage() endif startTimer(TimerInterval_LoiterSample, iTimerID_LoiterSample) EndFunction float Function GetFurthestDistanceAndPromoteSamples() ;check all positions against player's current poistion then ;cycle all positions up one notch in the history arrays float furthestDistance float bufferedDistance float PlayerX = PlayerRef.X float PlayerY = PlayerRef.Y float PlayerZ = PlayerRef.Z int i = LoiterBufferSize - 1 while (i >= 0) if i > 0 ;promote lower buffer into me PlayerPosX[i] = PlayerPosX[i - 1] PlayerPosY[i] = PlayerPosY[i - 1] PlayerPosZ[i] = PlayerPosZ[i - 1] else ;i'm the first buffer, so fill me with the player postion BufferCurrentPosition(0, PlayerX, PlayerY, PlayerZ) endif bufferedDistance = GetDistanceFromSamplePosition(i, PlayerX, PlayerY, PlayerZ) if bufferedDistance > furthestDistance furthestDistance = bufferedDistance endif ; trace(self, "Buffer#" + i + " distance: " + bufferedDistance) i -= 1 endwhile return furthestDistance EndFunction Function BufferCurrentPosition(int bufferToSet, float X, float Y, float Z) PlayerPosX[bufferToSet] = X PlayerPosY[bufferToSet] = Y PlayerPosZ[bufferToSet] = Z EndFunction ;Called in SetIsPlayerLoitering() ;**Also called with a longer cool down whenany follower receives a follow command ) Function SetLoiterCoolDown(float CoolDownTime) CurrentLoiterCoolDownTime = CoolDownTime EndFunction int function GetLastBufferIndex() return LoiterBufferSize - 1 EndFunction float Function GetDistanceFromSamplePosition(int BufferIndex, float X, float Y, float Z) ; calculate distance between buffered position and present ; sqrt((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2) ;thank you sLiesegang! :) float xFactor = (PlayerPosX[BufferIndex] - X) float yFactor = (PlayerPosY[BufferIndex] - Y) float zFactor = (PlayerPosZ[BufferIndex] - Z) xFactor = xFactor * xFactor yFactor = yFactor * yFactor zFactor = zFactor * zFactor return Math.sqrt(xFactor + yFactor + zFactor) EndFunction Function DropDebugMarkers() debugOnly if DebugDropMarkers == true Game.Warning(self + "WARNING!: DropDebugMarkers() THIS SHOULD NOT BE CALLED IN RELEASE BUILDS!") ;create the markers if this is the first time if debugMarkers == None debugMarkers = New ObjectReference[LoiterBufferSize] static markerToDrop = Game.GetFormFromFile(0x0003180C, "Fallout4.esm") as Static ; Red Flag int i = 0 while (i < debugMarkers.length - 1) debugMarkers[i] = playerRef.placeAtMe(markerToDrop) i += 1 endwhile EndiF ;move the markers to the locations in the position buffer int i = 0 while (i < debugMarkers.length - 1) debugMarkers[i].setPosition(PlayerPosX[i], PlayerPosY[i], PlayerPosZ[i]) i += 1 endwhile EndiF EndFunction ;******************************************************************************************************* ;************************************** COMPANION AFFINITY ********************************************* ;******************************************************************************************************* ;/ The Companion Affinity system is how we handle each Companion response to various events and player actions. And how they feel in total over time. Each event is more or less important. Each companion has a different feeling about each event. This information is supplied, and each event is handled by the scripts on the individual Companions. See: CompanionActorScript for details and implementation /; Struct AffinityEventData keyword EventKeyword {Keyword associated with this event. Used as the primary event identifier. Filter: "CA_Event_*"} GlobalVariable EventSize {Global representing the size of the event. Filter: "CA_Size_*" USUALLY: CA_Size_DialogueOnly for events that shouldn't change how the companion feels about the player.} GlobalVariable CoolDownDays ;in terms of game days passed {Global representing the cool down period for the event. Value in terms of GameDaysPassed. Filter: "CA_CoolDownDays_*"} keyword TopicSubType ;for dialogue to say {Keyword TopicSubType. Dialogue to say when this event happens. Filter: "CAT_*"} actorvalue AssociatedActorValue ;very often NONE {USUALLY: NONE -- when in doubt leave this one blank.) The AssociatedActorValue.} float NextDayAllowed hidden ;will store gamedayspassed + CoolDownDays when that event occurs EndStruct Group Affinity actorvalue property CA_Affinity const auto actorvalue property CA_CurrentThreshold const auto actorvalue property CA_WantsToTalk const auto {Used for conditions on affinity scene greetings: 0 = doesn't want to talk 1 = wants to talk, will forcegreet player 2 = wants to talk, will not forcegreet playe } actorvalue property CA_WantsToTalkRomanceRetry const auto ;Used by companion interjections ;actorvalue Property CA_LastDialogueBump const auto GlobalVariable Property CA_DialogueBump_Dislike const auto GlobalVariable Property CA_DialogueBump_Hate const auto GlobalVariable Property CA_DialogueBump_Like const auto GlobalVariable Property CA_DialogueBump_Love const auto Keyword Property CAT_DialogueBump_Disliked const auto Keyword Property CAT_DialogueBump_Hated const auto Keyword Property CAT_DialogueBump_Liked const auto Keyword Property CAT_DialogueBump_Loved const auto actorvalue property CA_AffinitySceneToPlay const auto actorvalue property CA_Custom const auto actorvalue property CA_HighestThreshold const auto actorvalue property CA_LowestThreshold const auto actorvalue property CA_LastChangePositive const auto actorvalue property CA_HighestReached const auto actorvalue property CA_LowestReached const auto actorvalue property CA_MurderSessionCount const auto {how many murder sessions did the companion witness} actorvalue property CA_MurderSessionDay const auto {The last murder session occured on this day (in terms of game days passed)} actorvalue property CA_MurderSessionVictimCount const auto {The last murder sessions victim count - might be less than actual if people died in rapid succession} Location[] Property LocationsToIgnoreMurderIn auto hidden {When it's okay to kill anyone in this location, add it to this list. For example, end game "attack XYZ faction" quests} actorvalue property CA_IsRomantic const auto actorvalue property CA_IsRomanceableNow const auto ;Used by AffinityEvent GlobalVariable Property SmallAffinityEvent const auto GlobalVariable Property NormalAffinityEvent const auto GlobalVariable Property LargeAffinityEvent const auto spell Property SleepNearInfatuatedCompanionBonus const auto {The spell to cast on the player if he sleeps near a companion who is infatuated with him.} MagicEffect Property CA_AddictionEffect const auto AffinityEventData[] property AffinityEvents auto EndGroup struct CrippleEventDatum ActorValue LimbConditionActorValue Keyword AffinityKeyword_Companion Keyword AffinityKeyword_Player EndStruct Group AffinityEventSending {Used by scripting to respond to game events.} keyword Property AskedForMoreCapsKeyword const auto {CA__Event_SpeechForMoreCaps} keyword Property GenerousKeyword const auto {CA__CustomEvent_Generous} keyword Property SelfishKeyword const auto {CA__CustomEvent_Selfish} keyword Property NiceKeyword const auto {CA__CustomEvent_Nice} keyword Property MeanKeyword const auto {CA__CustomEvent_Mean} keyword Property PeacefulKeyword const auto {CA__CustomEvent_Generous} keyword Property ViolentKeyword const auto {CA__CustomEvent_Generous} keyword Property AddictionKeyword const auto {CA_Event_ChemAddiction} keyword Property DogmeatBleedoutKeyword const auto {CA_Event_DogmeatBleedout} keyword Property VertibirdKeyword const auto {CA_Event_EnterVertibird{} keyword Property WorkbenchKeyword const auto {CA_Event_UseWorkbench{} keyword Property SwimmingKeyword const auto {CA_Event_Swim} keyword Property RadiationKeyword const auto {CA_Event_RadDamage} keyword Property ModArmorKeyword const auto {CA_Event_ModArmor} keyword Property ModWeaponKeyword const auto {CA_Event_ModWeapon} CrippleEventDatum[] Property CrippleEventData const auto {Holds mapping between actor values and affinity event keyword} keyword Property EnterPowerArmorKeyword const auto {CA_Event_EnterPowerArmor} keyword Property isPowerArmorFrame const auto {Pointer to Power Armor Furniture keyword so Companion can comment when you get into it.} keyword Property DischargeWeapon const auto {CA_Event_DischargeWeapon} keyword Property JumpFromHeight const auto {CA_Event_JumpFromHeight} keyword Property HealCompanion const auto {CA_Event_HealCompanion} keyword Property HealDogmeat const auto {CA_Event_HealDogmeant} EndGroup CustomEvent AffinityEvent CustomEvent PossibleMurderEvent Actor SleepCompanionActor ;GLOBAL VERSION bool Function SendAffinityEvent(scriptobject Sender, keyword EventKeyword, objectReference Target = None, GlobalVariable EventSizeOverride = None, bool CheckCompanionProximity = true, bool ShouldSuppressComment = false, bool IsDialogueBump = false, float ResponseDelay = 1.0) global return GetScript().LocalSendAffinityEvent(Sender, EventKeyword, Target, EventSizeOverride, CheckCompanionProximity, ShouldSuppressComment, IsDialogueBump, ResponseDelay) EndFunction ;LOCAL VERSION bool LockSendAffinityEvent bool Function LocalSendAffinityEvent(scriptobject Sender, keyword EventKeyword, objectReference Target = None, GlobalVariable EventSizeOverride = None, bool CheckCompanionProximity = true, bool ShouldSuppressComment = false, bool IsDialogueBump = false, float ResponseDelay = 1.0) while LockSendAffinityEvent utility.wait(0.1) endwhile LockSendAffinityEvent = true trace(self, "FollowersScript: SendAffinityEvent(" + EventKeyword + ", " + EventSizeOverride + ", " + CheckCompanionProximity + ") sender:" + sender) AffinityEventData CurrentAffinityEventData = GetAffinityEventData(EventKeyword) if !CurrentAffinityEventData LockSendAffinityEvent = false RETURN false endif if CurrentAffinityEventData.NextDayAllowed > Utility.GetCurrentGameTime() trace(self, "FollowersScript: SendAffinityEvent() NOT sending event as CheckAffinityEventCooldown() == False. NextDayAllowed: " + CurrentAffinityEventData.NextDayAllowed + ". Current GameDaysPassed:" + Utility.GetCurrentGameTime()) LockSendAffinityEvent = false RETURN false endif float NextDayAllowed = CurrentAffinityEventData.CoolDownDays.GetValue() + Utility.GetCurrentGameTime() Trace(self, "FollowersScript: SendAffinityEvent() NextDayAllowed for event = " + NextDayAllowed) CurrentAffinityEventData.NextDayAllowed = NextDayAllowed globalvariable EventSize if EventSizeOverride EventSize = EventSizeOverride else EventSize = CurrentAffinityEventData.EventSize endif Var[] args = new Var[8] args[0] = EventKeyword args[1] = EventSize ;args[7] = Target - see below args[2] = CheckCompanionProximity args[3] = CurrentAffinityEventData.AssociatedActorValue if ShouldSuppressComment == false args[4] = CurrentAffinityEventData.TopicSubType endif args[5] = IsDialogueBump args[6] = ResponseDelay args[7] = Target ;the reason this args number is out of order is because it was added later sendCustomEvent("AffinityEvent", args) LockSendAffinityEvent = false RETURN TRUE EndFunction AffinityEventData Function GetAffinityEventData(keyword EventKeyword) global AffinityEventData[] myAffinityEvents = GetScript().AffinityEvents int i = 0 while (i < myAffinityEvents.length) if myAffinityEvents[i].EventKeyword == EventKeyword RETURN myAffinityEvents[i] endif i += 1 endwhile game.error("WARNING: FollowersScript.GetAffinityEventData() didn't find EventKeyword in AffinityEvents: " + EventKeyword) EndFunction function SendPossibleMurderEvent(Actor ActorVictim, Actor ActorKiller, bool GameConsidersMurder) global followersScript thisScript = GetScript() var[] args = New Var[3] args[0] = ActorVictim args[1] = ActorKiller args[2] = GameConsidersMurder ;the Storymanager indicated the kill event was murder if thisScript.IsActorInLocationWhereMurderShouldBeIgnored(ActorKiller) debug.trace("FollowersScript.SendPossibleMurderEvent() ignoring murder IsActorInLocationWhereMurderShouldBeIgnored() == true") RETURN endif thisScript.sendCustomEvent("PossibleMurderEvent", args) EndFunction Function SetLocationForCompanionsToIgnoreMurder(location LocationToIgnoreMurderIn) if LocationsToIgnoreMurderIn == None LocationsToIgnoreMurderIn = new location[0] endif if LocationsToIgnoreMurderIn.find(LocationToIgnoreMurderIn) < 0 LocationsToIgnoreMurderIn.add(LocationToIgnoreMurderIn) endif EndFunction bool Function IsActorInLocationWhereMurderShouldBeIgnored(actor ActorToTest) int i = 0 while (i < LocationsToIgnoreMurderIn.Length) if ActorToTest.IsInLocation(LocationsToIgnoreMurderIn[i]) debug.trace(self + "IsActorInLocationWhereMurderShouldBeIgnored() actor is in location where murder should be ignored: " + ActorToTest + ", " + LocationsToIgnoreMurderIn[i]) RETURN true endif i += 1 endwhile RETURN false EndFunction Function HandleOnCrippleEvent(Var akSender, ActorValue akActorValue, bool abCrippled) ;sent by Remotely Registered OnCripple events if abCrippled ;if we're crippled not healed if (akSender as Actor) == PlayerRef SendAffinityCrippledEvent(akSender as Actor, akActorValue, isPlayer = true) elseif akSender as ReferenceAlias == Companion SendAffinityCrippledEvent((akSender as ReferenceAlias).GetActorReference(), akActorValue, isCompanion = true) endif endif EndFunction function HandleVertibirdEvent(ObjectReference akVertibird) ;*** We'll need to add handling for making sure companion and dogmeat get into the veribird if they are near ; Check that companion / dogmeat are nearby, if not ignore them ; Find the available seat keyword since we don't know where player is sitting ; Give the companion that keyword ; Shove the vertibird reference into an alias on followers quest ; Give companion and dogmeat package to mount the vertibird alias ; have a while loop that waits for them to be ismounted (with a bailout timer) ; expose a "CompanionsVertibirdAccountedFor" that can be checked in the scene to make the vertibird take off ; add a condition for "player is in vertibird" condition to the wait at self in the FollowPlayer package template ; when player dismounts vertibird teleport people who are following him but not loaded ; and remove the mount package from companion and dogmeat ;but for now just have them say something: SendAffinityEvent(akVertibird, VertibirdKeyword) EndFunction Function HandleOnWorkbench(objectReference akWorkBench) SendAffinityEvent(akWorkBench, WorkbenchKeyword) EndFunction Function HandleOnSwimming(Location akLocation) SendAffinityEvent(akLocation, SwimmingKeyword) EndFunction Function HandleOnRadiation(Location akLocation) SendAffinityEvent(akLocation, RadiationKeyword) EndFunction Function HandleOnMod(Form ModdedBaseObject) if ModdedBaseObject is Weapon SendAffinityEvent(ModdedBaseObject, ModWeaponKeyword) elseif ModdedBaseObject is Armor SendAffinityEvent(ModdedBaseObject, ModArmorKeyword) else trace(self, "HandleOnMod ModdedBaseObject is not a weapon or armor:" + ModdedBaseObject) endif EndFunction Function SendAffinityCrippledEvent(Actor akSenderActor, ActorValue akActorValue, bool isPlayer = false, bool isCompanion = false) keyword EventKeyword int i = 0 while (i < CrippleEventData.length) if CrippleEventData[i].LimbConditionActorValue == akActorValue if isCompanion EventKeyword = CrippleEventData[i].AffinityKeyword_Companion elseif isPlayer EventKeyword = CrippleEventData[i].AffinityKeyword_Player endif endif i += 1 endwhile if EventKeyword == none game.warning(self + "SendAffinityCrippledEvent() couldn't find LimbCondition event! LimbConditionActorValue: " + akActorValue) RETURN endif SendAffinityEvent(akSenderActor, EventKeyword) EndFunction Function HandleOnItemEquipped(ObjectReference akSender, Form akBaseObject) if akSender == Game.GetPlayer() && akBaseObject.HasKeyword(isPowerArmorFrame) SendAffinityEvent(akBaseObject, EnterPowerArmorKeyword) endif EndFunction actor Function GetNearbyInfatuatedRomanticCompanion() Actor NearbyInfatuatedActor CompanionActorScript CompanionActor = Companion.GetActorReference() as CompanionActorScript ;if your current companion is infatuated, then use that if CompanionActor && CompanionActor.IsInfatuated() && CompanionActor.IsRomantic() && CompanionActor.IsPlayerNearby() NearbyInfatuatedActor = CompanionActor else ;loop through active companions seeing who is nearby and infatuated, use the closest int i = 0 while (i < ActiveCompanions.GetCount()) CompanionActor = (ActiveCompanions.GetAt(i) as Actor) as CompanionActorScript if CompanionActor && CompanionActor.IsInfatuated() && CompanionActor.IsRomantic() && CompanionActor.IsPlayerNearby() if NearbyInfatuatedActor == false NearbyInfatuatedActor = CompanionActor else if NearbyInfatuatedActor.GetDistance(PlayerRef) > CompanionActor.GetDistance(PlayerRef) NearbyInfatuatedActor = CompanionActor endif endif endif i += 1 endwhile endif trace(self, "GetNearbyInfatuatedRomanticCompanion() NearbyInfatuatedActor: " + NearbyInfatuatedActor) return NearbyInfatuatedActor EndFunction Function HandleOnSleepStart(objectReference bedRef) ;if there's a nearby infatuated companion, put them in temprary alias to get them to sleep in the same bed as the player clicked on SleepCompanionActor = GetNearbyInfatuatedRomanticCompanion() if SleepCompanionActor SleepCompanionActor.AddKeyword(FollowersCompanionSleepNearPlayerFlag) SleepCompanion.ForceRefTo(SleepCompanionActor) SleepCompanionBed.ForceRefTo(BedRef) SleepCompanionActor.EvaluatePackage() ;actor will say custom dialogue subtype CAT_Event_SleepTogetherWake when package finishes endif EndFunction Function HandleOnSleepEnd() ;give the player an exp bonus if he slepted near a infatuated companion ;Note: we may decide later to only have romantic infatuated companions sleep in same bed, but we want to give plutonic buddies the exp bonus if SleepCompanionActor SleepNearInfatuatedCompanionBonus.Cast(PlayerRef, PlayerRef) SleepCompanionActor.RemoveKeyword(FollowersCompanionSleepNearPlayerFlag) endif SleepCompanion.Clear() ;make the actor "wake up" EndFunction ;******************** FUNCTIONS CALLED BY OTHER SCRIPTS *********************** ;these are global functions to make the calling PlayerSaidXXX.psc scripts not require properties so designers can just "tag" their dialogue with the script and move on. Function AskedForMoreCaps(scriptObject Sender) global SendAffinityEvent(Sender, GetScript().AskedForMoreCapsKeyword, ShouldSuppressComment = true) EndFunction Function PlayerSaidGenerous(TopicInfo CallingTopicInfo) global SendAffinityEvent(CallingTopicInfo, GetScript().GenerousKeyword, ShouldSuppressComment = true) EndFunction Function PlayerSaidSelfish(TopicInfo CallingTopicInfo) global SendAffinityEvent(CallingTopicInfo, GetScript().SelfishKeyword, ShouldSuppressComment = true) EndFunction Function PlayerSaidNice(TopicInfo CallingTopicInfo) global SendAffinityEvent(CallingTopicInfo, GetScript().NiceKeyword, ShouldSuppressComment = true) EndFunction Function PlayerSaidMean(TopicInfo CallingTopicInfo) global SendAffinityEvent(CallingTopicInfo, GetScript().MeanKeyword, ShouldSuppressComment = true) EndFunction Function PlayerSaidPeaceful(TopicInfo CallingTopicInfo) global SendAffinityEvent(CallingTopicInfo, GetScript().PeacefulKeyword, ShouldSuppressComment = true) EndFunction Function PlayerSaidViolent(TopicInfo CallingTopicInfo) global SendAffinityEvent(CallingTopicInfo, GetScript().ViolentKeyword, ShouldSuppressComment = true) EndFunction Function FlagCompanionChatEvent(actorvalue ActorValueRepresentingEvent) global ;get the player's current companion, check proximity, then flag them by setting this actor value to 1 CompanionActorScript CompanionActor = GetScript().Companion.GetActorReference() as CompanionActorScript if CompanionActor && CompanionActor.IsPlayerNearby() debug.trace("FollowersScript.FlagCompanionChatEvent() CompanionActor is nearby, setting ActorValue to 1: " + ActorValueRepresentingEvent + ", " + CompanionActor) CompanionActor.SetValue(ActorValueRepresentingEvent, 1) endif EndFunction ;******************************************************************************************************* ;************************************** SITUATION AWARENESS ******************************************** ;******************************************************************************************************* ;/ For overview see: http://institute.bethsoft.com/index.php?title=Category:Companions#Situational_Awareness The basic gist of the system is: There's arrays of structs of related data. When we need to set data because the player enters/leaves triggerbox, or encounters an actor We look for matching data in the struct array We then look to see if any of those keywords/factions are on the locations/triggerbox scripts/actors we are asking about If so we set appropriate SAS/SAE/SAC prefixed variables. The value we set them to, is: The value in the GlobalVariable held at the found keyword/faction's index in the related array. When the player: enters a triggerbox: SAS: we set SAS variable and global based on keywords supplied by triggerbox meaning the "type of location encompassed by this triggerbox" SAE: we set SAEClutter variable and global based on keywords supplied by triggerbox that mean the "type of enemy based on the clutter we can see from this triggerbox" leaves a triggerbox: SAS: we set variables back to data derived from keywords that are on the Location (we call the same function as when player changes location) SAE: we clear SAEClutter variable and global gets into combat : (presently handles as when he or his Companion is hit, or when his cCompanion changes combat state) SAE: We set SAECombatant variable and global based on actor's faction (which maps to location keyword) There is a timer that then clears these SAC: We also set a SAC "last time combat happened" variable and global A timer increments this data so that it "promotes" through recent, less recent, very ancient status While we really only need to store the Value of the associated GlobalVariables for all this data. (and it's only a float that is exposed via the "conditional" variable flag) We store the actual GlobalVariable itself as well, so there's something human readable in logs and console inspection see also: REGISTERED REMOTE EVENTS: - ObjectReference.OnHit temporarily sets SAE variables OTHER SCRIPTS/OBJECTS: - SituationAwarenessTriggerScript -- handles triggers sending messages to this script /; Struct SetDefinition keyword LocSetKeyword ;{Holds LocType Keywords} globalvariable LocSetGlobal ;{Holds Globals used as "enums" in dialogue conditions} EndStruct Struct EncDefinition keyword LocEncKeyword ;{Holds LocType Keywords} faction Associated_Faction ;{Holds Factions that correspond to LocEncKeywords} globalvariable LocEncGlobal ;{Holds Globals used as "enums" in dialogue conditions} EndStruct Struct LastCombatDefinition globalvariable SAC_Global float SAC_Time EndStruct Group SituationAwareness SetDefinition[] property SetDefinitions const auto {Holds definitions for a Location Set - Keyword on locations and an associated Global used in dialogue conditions} EncDefinition[] property EncDefinitions const auto {Holds definitions for a Location Encounter Type - keyword on locations, a faction associated with that keyword, and an associated Global used in dialogue conditions} Formlist property SA_CurrentSAEClutterTriggersList const auto {Autofill list of triggers the player is in see SAEClutterTriggerScript needed to make sure we know exactly which trigger box the player actually in, in cases where triggerboxes might over lap and player teeters in/out of them } Formlist property SA_CurrentSASTriggersList const auto {Autofill list of triggers the player is in see SASTriggerScript needed to make sure we know exactly which trigger box the player actually in, in cases where triggerboxes might over lap and player teeters in/out of them } LastCombatDefinition[] property LastCombatDefinitions const auto {Holds Globals that are human readable and associated floats which are in terms of GameDaysPassed Globals named like: SAC_LastCombat_ShortTimeAgo | MediumTimeAgo | LongTimeAgo Floats are in terms of GameDaysPassed (1 = 24 hours, 0.04166 = 1 hour) Suggested values: Short: 0.0, Medium: 0.2, Long: 3.0 IMPORTANT: Arrange in ascending order of distance Short time ago in index 0, very long time ago in last index Note: first value is always treated as 0.0 } Endgroup bool DebugTrace_SA = true ;S.A.S. - [S]ituation [A]warenes location [S]et type ; This is for follower comments talking about the "the place we are walking around" ; Primarily set by keywords on the location ; Secondarily set by triggerboxes Float SAS = -1.0 conditional ;used by conditions - holds a value (based on global variable "enums" in LocSetGlobals) GlobalVariable SAS_Global ;For ease of debugging in console: holds the Global who's value is stored in SAS var float SAS_TimeStamp ;in game days passed - for debugging purposes, when did we last set this scriptObject SAS_ScriptObject ;for debugging purpses, what was the last thing to set his ;S.A.E. - [S]ituation [A]warenes location [E]ncounter type ;'THINGS THAT LIVE HERE': Float SAEClutter = -1.0 conditional ;used by conditions - holds a value (based on global variable "enums" in LocEncGlobals) GlobalVariable SAEClutter_Global ;For ease of debugging in console: holds the Global who's value is stored in SAE var float SAEClutter_TimeStamp ;in game days passed - for debugging purposes, when did we last set this scriptObject SAEClutter_ScriptObject ;for debugging purpses, what was the last thing to set his ;'THING WE'VE RECENTLY FOUGHT': Float SAECombatant = -1.0 conditional ;used by conditions - holds a value (based on global variable "enums" in LocEncGlobals) GlobalVariable SAECombatant_Global ;For ease of debugging in console: holds the Global who's value is stored in SAE var float SAECombatant_TimeStamp ;in game days passed - for debugging purposes, when did we last set this scriptObject SAECombatant_ScriptObject ;for debugging purpses, what was the last thing to set his ;S.A.C. - [S]ituation [A]warenes location [C]ombat data ;holds information about how long ago the last combat was GlobalVariable SAC_LastCombat_Global ;For ease of debugging in console: holds the Global who's value is stored in SAC_LastCombat var float SAC_LastCombat = -1.0 ;how long ago was the last combat - uses SAC_LastCombat_xxx globals as enums for values float SAC_LastCombat_Timestamp ;as GameDaysPassed used when setting SAC_LastCombat variables Function ShowSA() string msg = "SITUATION AWARENESS:" + "\n" msg += "SAS = " + SAS + "\n" msg += "SAS_Global = " + SAS_Global + "\n" msg += "SAS_TimeStamp = " + SAS_TimeStamp + "\n" msg += "SAS_ScriptObject = " + SAS_ScriptObject + "\n" msg += "\n" msg += "SAEClutter = " + SAEClutter + "\n" msg += "SAEClutter_Global = " + SAEClutter_Global + "\n" msg += "SAEClutter_TimeStamp = " + SAEClutter_TimeStamp + "\n" msg += "SAEClutter_ScriptObject = " + SAEClutter_ScriptObject + "\n" msg += "\n" msg += "SAECombatant = " + SAECombatant + "\n" msg += "SAECombatant_Global = " + SAECombatant_Global + "\n" msg += "SAECombatant_TimeStamp = " + SAECombatant_TimeStamp + "\n" msg += "SAECombatant_ScriptObject = " + SAECombatant_ScriptObject + "\n" msg += "\n" msg += "SAC_LastCombat = " + SAC_LastCombat + "\n" msg += "SAECombatant_Global = " + SAECombatant_Global + "\n" msg += "SAC_LastCombat_Timestamp = " + SAC_LastCombat_Timestamp + "\n" trace(self, msg) debug.messagebox(msg) EndFunction SetDefinition Function GetSetDefinition(keyword KeywordToFind = None, location LocationToCheck = None) ;finds the first matching item int i = 0 while (i < SetDefinitions.length) if KeywordToFind && SetDefinitions[i].LocSetKeyword == KeywordToFind RETURN SetDefinitions[i] endif if LocationToCheck && LocationToCheck.HasKeyword(SetDefinitions[i].LocSetKeyword) RETURN SetDefinitions[i] EndIf i += 1 endwhile EndFunction EncDefinition Function GetEncDefinition(keyword KeywordToFind = None, location LocationToCheck = None, Faction FactionToCheck = None, Actor ActorToCheck = None) ;finds the first matching item int i = 0 while (i < EncDefinitions.length) if KeywordToFind && EncDefinitions[i].LocEncKeyword == KeywordToFind RETURN EncDefinitions[i] endif if LocationToCheck && LocationToCheck.HasKeyword(EncDefinitions[i].LocEncKeyword) RETURN EncDefinitions[i] endif if FactionToCheck && EncDefinitions[i].Associated_Faction == FactionToCheck RETURN EncDefinitions[i] endif if ActorToCheck && ActorToCheck.IsInFaction(EncDefinitions[i].Associated_Faction) RETURN EncDefinitions[i] endif i += 1 endwhile EndFunction int Function GetIndexOfLastCombatDefinition(GlobalVariable GlobalToFind = None) ;finds the first matching item int i = 0 while (i < LastCombatDefinitions.length) if GlobalToFind && LastCombatDefinitions[i].SAC_Global == GlobalToFind RETURN i endif i += 1 endwhile EndFunction Function SetSAS(keyword LocSetKeyword, scriptObject SettingObject) ;sets the conditional variables used by conditions to flavor dialogue about what type of location set they are walking through traceconditional(self, "SetSAS() LocSetKeyword:" + LocSetKeyword, DebugTrace_SA) ;find the appropriate definition SetDefinition FoundSetDefinition = GetSetDefinition(KeywordToFind = LocSetKeyword) if FoundSetDefinition == false traceconditional(self, "Caution: SetSAS() did not find keyword." + LocSetKeyword, DebugTrace_SA, 1) SAS_Global = None SAS = -1.0 else SAS_Global = FoundSetDefinition.LocSetGlobal SAS = SAS_Global.value endif SAS_TimeStamp = Utility.GetCurrentGameTime() SAS_ScriptObject = SettingObject traceconditional(self, "SetSAS() SAS_Global is now: " + SAS_Global, DebugTrace_SA) traceconditional(self, "SetSAS() SAS is now: " + SAS, DebugTrace_SA) EndFunction Function SetSAE(keyword LocEncKeyword, bool shouldSetSAEClutter, bool shouldSetSAECombatant, scriptObject SettingObject) if shouldSetSAEClutter SetSAEClutter(LocEncKeyword, SettingObject) endif if shouldSetSAECombatant SetSAECombatant(LocEncKeyword, SettingObject) endif EndFunction Function SetSAEClutter(keyword LocEncKeyword, scriptObject SettingObject) ;find the appropriate definition EncDefinition FoundEncDefinition = GetEncDefinition(KeywordToFind = LocEncKeyword) if FoundEncDefinition == false traceconditional(self, "Caution: SetSAEClutter() did not find keyword." + LocEncKeyword, DebugTrace_SA, 1) SAEClutter_Global = None SAEClutter = -1.0 else SAEClutter_Global = FoundEncDefinition.LocEncGlobal SAEClutter = FoundEncDefinition.LocEncGlobal.value endif SAEClutter_TimeStamp = utility.GetCurrentGameTime() SAEClutter_ScriptObject = SettingObject traceconditional(self, "SetSAEClutter() SAEClutter_Global is now: " + SAEClutter_Global, DebugTrace_SA) traceconditional(self, "SetSAEClutter() SAEClutter is now: " + SAEClutter, DebugTrace_SA) EndFunction Function ClearSAEClutter() SAEClutter_Global = None SAEClutter = -1.0 traceconditional(self, "ClearSAEClutter() SAEClutter_Global is now: " + SAEClutter_Global, DebugTrace_SA) traceconditional(self, "ClearSAEClutter() SAEClutter is now: " + SAEClutter, DebugTrace_SA) EndFunction Function SetSAECombatant(keyword LocEncKeyword, scriptObject SettingObject) ;find the appropriate definition EncDefinition FoundEncDefinition = GetEncDefinition(KeywordToFind = LocEncKeyword) if FoundEncDefinition == false traceconditional(self, "Caution: SetSAECombatant() did not find keyword." + LocEncKeyword, DebugTrace_SA, 1) SAECombatant_Global = None SAECombatant = -1.0 else SAECombatant_Global = FoundEncDefinition.LocEncGlobal SAECombatant = FoundEncDefinition.LocEncGlobal.value endif SAECombatant_TimeStamp = utility.GetCurrentGameTime() SAECombatant_ScriptObject = SettingObject traceconditional(self, "SetSAECombatant() SAECombatant_Global is now: " + SAECombatant_Global, DebugTrace_SA) traceconditional(self, "SetSAECombatant() SAECombatant is now: " + SAECombatant, DebugTrace_SA) ;start timer that will set the values back to permValues startTimerGameTime(TimerInterval_SAECombatantExpiry, iTimerID_GameTime_SAECombatantExpiry) EndFunction Function ClearSAECombatant() SAECombatant_Global = None SAECombatant = -1.0 traceconditional(self, "ClearSAECombatant() SAECombatant_Global is now: " + SAECombatant_Global, DebugTrace_SA) traceconditional(self, "ClearSAECombatant() SAECombatant is now: " + SAECombatant, DebugTrace_SA) EndFunction ;### SetSituationAwarenessBasedOnLocation() ;called by: ; FUNCTIONS: ; - HandleMessageFromSituationAwarenessTrigger() ; [ oringinating from: SituationAwarenessTriggerScript.OnTriggerEnter/Leave() ] ; ; REGISTERED REMOTE EVENTS: ; - Actor.OnLocationChange Function SetSituationAwarenessBasedOnLocation(Location LocationToCheck, bool SetSASData, bool SetSAEClutterData, scriptObject settingObject) ;**similar to SetSituationAwarenessBasedOnActor traceconditional(self, "SetSituationAwarenessBasedOnLocation() LocationToCheck:" + LocationToCheck, DebugTrace_SA) SetDefinition FoundSetDefinition = GetSetDefinition(LocationToCheck = LocationToCheck) EncDefinition FoundEncDefinition = GetEncDefinition(LocationToCheck = LocationToCheck) if SetSASData if FoundSetDefinition traceconditional(self, "SetSituationAwarenessBasedOnLocation() FoundSetDefinition:" + FoundSetDefinition, DebugTrace_SA) keyword LocSet = FoundSetDefinition.LocSetKeyword SetSAS(LocSet, LocationToCheck) else traceconditional(self, "SetSituationAwarenessBasedOnLocation() didn't find a matching SetDefinition", DebugTrace_SA) SetSAS(None, LocationToCheck) endif endif if SetSAEClutterData if FoundEncDefinition keyword LocEnc = FoundEncDefinition.LocEncKeyword SetSAE(LocEnc, shouldSetSAEClutter = true, shouldSetSAECombatant = false, settingObject = settingObject) else traceconditional(self, "SetSituationAwarenessBasedOnLocation() didn't find a matching EncDefinition", DebugTrace_SA) SetSAE(None, shouldSetSAEClutter = true, shouldSetSAECombatant = false, settingObject = settingObject) endif endif EndFunction ;### SetSituationAwarenessBasedOnActor() ;called by: ; FUNCTIONS: ; - HandleCombatMessageToSituationAwareness() ; [ oringinating from: ObjectReference.OnHit ] Function SetSituationAwarenessBasedOnActor(Actor ActorToCheck, bool shouldSetLastCombatTime, bool shouldSetSAEClutter, bool shouldSetSAECombatant, scriptObject SettingObject) ;**similar to SetSituationAwarenessBasedOnLocation traceconditional(self, "SetSituationAwarenessBasedOnActor() ActorToCheck:" + ActorToCheck, DebugTrace_SA) EncDefinition FoundEncDefinition = GetEncDefinition(ActorToCheck = ActorToCheck) if FoundEncDefinition Faction LocEncFaction = FoundEncDefinition.Associated_Faction SetSituationAwarenessBasedOnFaction(LocEncFaction, shouldSetSAEClutter, shouldSetSAECombatant, SettingObject) else traceconditional(self, "SetSituationAwarenessBasedOnActor() didn't find a matching EncDefinition", DebugTrace_SA) SetSituationAwarenessBasedOnFaction(None, shouldSetSAEClutter, shouldSetSAECombatant, SettingObject) endif if shouldSetLastCombatTime SAC_LastCombat_Timestamp = utility.GetCurrentGameTime() ;returns GameDaysPassed SAC_LastCombat_Global = LastCombatDefinitions[0].SAC_Global SAC_LastCombat = SAC_LastCombat_Global.value if DebugTrace_SA trace(self, "SetSituationAwarenessBasedOnActor() SAC_LastCombat_Timestamp: " + SAC_LastCombat_Timestamp) trace(self, "SetSituationAwarenessBasedOnActor() SAC_LastCombat_Global: " + SAC_LastCombat_Global) trace(self, "SetSituationAwarenessBasedOnActor() SAC_LastCombat: " + SAC_LastCombat) endif startTimerGameTime(TimerInterval_SAC_LastCombat, iTimerID_GameTime_SAC_LastCombat) endif EndFunction ;### SetSituationAwarenessBasedOnFaction() ;called by: ; FUNCTIONS: ; - SetSituationAwarenessBasedOnActor() Function SetSituationAwarenessBasedOnFaction(faction FactionToUse, bool shouldSetSAEClutter, bool shouldSetSAECombatant, scriptObject settingObject) trace(self, "SetSituationAwarenessBasedOnFaction() Faction:" + FactionToUse + ", shouldSetSAEClutter: " + shouldSetSAEClutter + ", shouldSetSAECombatant: " + shouldSetSAECombatant) Keyword FoundKeyword EncDefinition definition = GetEncDefinition(factionToCheck = FactionToUse) if definition FoundKeyword = definition.LocEncKeyword EndIf if FoundKeyword == False traceconditional(self, "Caution: SetSituationAwarenessBasedOnFaction didn't find faction: " + FactionToUse, DebugTrace_SA, 1 ) SetSAE(None, shouldSetSAEClutter, shouldSetSAECombatant, settingObject = settingObject) endif SetSAE(FoundKeyword, shouldSetSAEClutter, shouldSetSAECombatant, settingObject = settingObject) EndFunction ;### HandleMessageFromSAEClutterTrigger() ;called by: ; EVENTS: ; OnTimerGameTime() Function HandleTimer_GameTime_SAECombatantExpiry() ;called by Event OnGameTimer() ClearSAECombatant() EndFunction ;### HandleMessageFromSAEClutterTrigger() ;called by: ; OTHER OBJECTS: ; - HandleMessageFromSAEClutterTrigger.OnTriggerEnter/Leave() Function HandleMessageFromSAEClutterTrigger(SAEClutterTriggerScript triggerbox, bool AddMe) ;addMe false = remove me ;**** !!!! VERY SIMILAR TO HandleMessageFromSASTrigger !!! *** if AddMe SA_CurrentSAEClutterTriggersList.addForm(triggerbox) else SA_CurrentSAEClutterTriggersList.RemoveAddedForm(triggerbox) endif ;DEBUG DUMP CONTENTS bool debugMe = true if debugMe if SA_CurrentSAEClutterTriggersList.getSize() <= 0 traceconditional(self, "SA_CurrentSAEClutterTriggersList is EMPTY", DebugTrace_SA) endif int i = 0 while (i < SA_CurrentSAEClutterTriggersList.getSize()) traceconditional(self, "SA_CurrentSAEClutterTriggersList[" + i + "]-->" + SA_CurrentSAEClutterTriggersList.getAt(i), DebugTrace_SA) i += 1 endwhile endif ;If the list isn't empty get the first item's (triggerbox) keywords if SA_CurrentSAEClutterTriggersList.getSize() > 0 SAEClutterTriggerScript FirstInList = SA_CurrentSAEClutterTriggersList.getAt(0) as SAEClutterTriggerScript if FirstInList.LocEncKeyword SetSAE(FirstInList.LocEncKeyword, shouldSetSAEClutter = true, shouldSetSAECombatant = false, SettingObject = triggerbox) else traceconditional(self, "SA_CurrentSAEClutterTriggersList FirstInList.LocEncKeyword == None", DebugTrace_SA, 1) endif else ;the list is empty, so clear the variables ClearSAEClutter() endif EndFunction ;### HandleMessageFromSASTrigger() ;called by: ; OTHER OBJECTS: ; - HandleMessageFromSASTrigger.OnTriggerEnter/Leave() Function HandleMessageFromSASTrigger(SASTriggerScript triggerbox, bool AddMe) ;addMe false = remove me ;**** !!!! VERY SIMILAR TO HandleMessageFromSAEClutterTrigger !!! *** if AddMe SA_CurrentSASTriggersList.addForm(triggerbox) else SA_CurrentSASTriggersList.RemoveAddedForm(triggerbox) endif ;DEBUG DUMP CONTENTS bool debugMe = true if debugMe if SA_CurrentSASTriggersList.getSize() <= 0 traceconditional(self, "SA_CurrentSASTriggersList is EMPTY", DebugTrace_SA) endif int i = 0 while (i < SA_CurrentSASTriggersList.getSize()) traceconditional(self, "SA_CurrentSASTriggersList[" + i + "]-->" + SA_CurrentSASTriggersList.getAt(i), DebugTrace_SA) i += 1 endwhile endif ;If the list isn't empty get the first item's (triggerbox) keywords if SA_CurrentSASTriggersList.getSize() > 0 SASTriggerScript FirstInList = SA_CurrentSASTriggersList.getAt(0) as SASTriggerScript if FirstInList.LocSetKeyword SetSAS(FirstInList.LocSetKeyword, triggerbox) else traceconditional(self, "SA_CurrentSASTriggersList FirstInList.LocSetKeyword == None", DebugTrace_SA, 1) endif else ;the list is empty, so clear the variables SetSituationAwarenessBasedOnLocation(playerRef.GetCurrentLocation(), SetSASData = true, SetSAEClutterData = false, SettingObject = triggerbox) endif EndFunction ;### HandleCombatMessageToSituationAwareness ;Called by: ; REGISTED REMOTE EVENTS: ; - ObjectReference.OnHit Function HandleCombatMessageToSituationAwareness(Actor Enemy) SetSituationAwarenessBasedOnActor(Enemy, shouldSetLastCombatTime = true, shouldSetSAEClutter = false, shouldSetSAECombatant = true, settingObject = Enemy) EndFunction Function Handletimer_GameTime_SAC_LastCombat() ;called by Event OnGameTimer() trace(self, "OnTimerGameTime() checking SAC last combat values") ;check to see if the time stamp exceeds the next highest global/float pair. ;If so, set the new data accordingly ;If we're not at the end of the array, throw the timer again, otherwise don't int i = GetIndexOfLastCombatDefinition(SAC_LastCombat_Global) if i < 0 ;Note: this should be impossible since SAC_LastCombat_Global is set by finding an actual item out of the array it's checking for. trace(self, "WARNING: Handletimer_GameTime_SAC_LastCombat() could not find SAC_LastCombat_Global in the array:" + SAC_LastCombat_Global) RETURN endif if i + 1 >= LastCombatDefinitions.length ;Note: this out of range shouldn't be possible because of we don't throw the timer again if isLastItemInArrayCheck below trace(self, "WARNING: Handletimer_GameTime_SAC_LastCombat() -- i >= SAC_LastCombat_Globals.length. Bailing out.") RETURN endif float TimeForNextSetting = SAC_LastCombat_Timestamp + LastCombatDefinitions[i + 1].SAC_Time trace(self, "SAC_LastCombat_Global: " + SAC_LastCombat_Global) trace(self, "Time to change to Next: " + TimeForNextSetting) trace(self, "Current Time: " + utility.GetCurrentGameTime()) bool isLastItemInArray if utility.GetCurrentGameTime() >= TimeForNextSetting SAC_LastCombat_Global = LastCombatDefinitions[i + 1].SAC_Global SAC_LastCombat = SAC_LastCombat_Global.value isLastItemInArray = (LastCombatDefinitions.length - 1) <= i + 1 if DebugTrace_SA trace(self, "setting new data...: " + SAC_LastCombat_Global) trace(self, "SAC_LastCombat_Global: " + SAC_LastCombat_Global) trace(self, "SAC_LastCombat: " + SAC_LastCombat) endif endif if isLastItemInArray == false startTimerGameTime(TimerInterval_SAC_LastCombat, iTimerID_GameTime_SAC_LastCombat) trace(self, "isLastItemInArray == false. Restarting timer.") else ;we are at the longest time - do nothing trace(self, "isLastItemInArray == true. Not restarting timer.") endif EndFunction ;******************************************************************************************************* ;************************************** Autonomy ************************************************ ;******************************************************************************************************* ;used to help control when companions/dogmeat are allowed to "do their own thing" without direct input from player bool property AutonomyAllowed = true auto hidden conditional ;used to conditional scene manager nodes and quests ScriptObject[] property AutonomyDisallowedByObjects auto hidden ;array holding all the callers who requested to turn off autonomy - these same objects will need to turn it back on CustomEvent AutonomyDisallowed ;Registered by quests to shutdown if their primary actor loses autonomy, etc. Function SetAutonomy(ScriptObject CallingObject, bool AutonomousActivityAllowed = True) global GetScript().SetAutonomyPrivate(CallingObject, AutonomousActivityAllowed) EndFunction Function SetAutonomyPrivate(ScriptObject CallingObject, bool AutonomousActivityAllowed = True) trace(self, "SetAutonomy() CallingObject:" + CallingObject + ", AutonomousActivityAllowed:" + AutonomousActivityAllowed) int foundIndex = AutonomyDisallowedByObjects.find(CallingObject) if AutonomousActivityAllowed && foundIndex >= 0 ;if allowed and already in array, remove it AutonomyDisallowedByObjects.remove(foundIndex) elseif AutonomousActivityAllowed == false && foundIndex < 0 ;if not allowed and if not already in array, add it AutonomyDisallowedByObjects.add(CallingObject) endif ;set flag based on whether any objects still want to prevent autonomy if AutonomyDisallowedByObjects.length == 0 AutonomyAllowed = true else AutonomyAllowed = false ;Kill running autonomous activities who have registered for the event (likely via quest scripts and aliases) var[] akArgs = new var[3] akArgs[0] = CallingObject ;ScriptObject who doesn't want actor allowed autonomy akArgs[1] = Companion ;convenient pointer to Companion alias akArgs[2] = DogmeatCompanion ;convenient pointer to DogmeatCompanion alias SendCustomEvent("AutonomyDisallowed", akArgs) endif EndFunction Function AllowAutonomyOnTeleport() ;this is to prevent problems that occur if SetAutonomy() allow events come in before disallow events AutonomyDisallowedByObjects.clear() AutonomyAllowed = true; EndFunction Thanks. Edited August 19, 2016 by Nutulator Link to comment Share on other sites More sharing options...
Recommended Posts