foamyesque Posted September 19, 2017 Share Posted September 19, 2017 This is something of a weird corner case I've encountered in the course of building a fairly complicated script that employs multiple states. The intended state sequences, when the game is first loaded, are one of the following: Empty -> Preboot -> Initializing -> Restocking -> Active [the script's active and ready to go]Empty -> Preboot -> Initializing -> Inactive [the script is currently inactive, but set up correctly and can be turned on later]Empty -> Prebbot -> Initializing -> Error [an unrecoverable error was hit during initialization, and the script is now disabled] These flows work correctly on starting a new game, loading an old game if it's the first in session, or loading an old game if it's *not* the first in session but does not have a lot of scripting loading at the same time. However, it breaks in the event of loading an old game with a lot of script nonsense (I have a pretty dirty save I am using to test this) if that game is *not* the first one loaded in the session (works correctly if it is). In that case, all the state flows execute normally, according to traces in OnBeginState, until their terminal state, but then somehow seem wind up back in the empty state, and without triggering an OnEndState event for the state left or an OnBeginState for the empty state. Only functions and events defined in the empty state execute, not anything in what the traces claim was the last state the script was in. It's particularly confusing as the precise same code executes properly otherwise and appears to follow the exact same code path as far as the traces go until suddenly it doesn't. I've audited my code looking for bad GotoState statements and have not found any. Does anyone have any idea why this might be occurring and/or some way to prevent it? Link to comment Share on other sites More sharing options...
ReDragon2013 Posted September 19, 2017 Share Posted September 19, 2017 (edited) Maybe the next sample will be helpful. sample script Scriptname XYZ_Test extends ??? ; https://forums.nexusmods.com/index.php?/topic/6003658-oddity-with-states/ ; foamyesque wrote: "It is particularly confusing as the precise same code executes properly otherwise ; and appears to follow the exact same code path as far as the traces go until suddenly it does not." ; There are not so many vanilla scripts which are using both events in the same state. ; I found these "NorRotatingDoorLeverScript.psc", "DLC1VQ01PuzzleBrazierScript.psc" for example. ; -- EVENTs -- make sure you do not have used "auto state" anywhere EVENT OnInit() ; this is empty state gotoState(" Initializing") ; ### STATE ### RegisterForSingleUpdateGameTime(0.0) ENDEVENT ;====================== state Initializing ;================= EVENT OnBeginState() Debug.Trace(self+" [Initializing] OnBeginState() - has been reached..") ; do never use any gotoState() here !!! ; OnInit() event (the caller) is still running, script will be locked and no other event can be triggered until this event will be closed ENDEVENT EVENT OnUpdateGameTime() ; initialization code right here, OnInit() event is closed (fastest way) and does not lock the papyrus script anymore ;IF () ; gototstate("Inactive") ; ### STATE ### ; RegisterForSingleUpdate(1.0) ; RETURN ; - STOP - condition made script inactive ;ENDIF ;--------------------- ;IF () ; gototstate("Done") ; ### STATE ### ; RETURN ; - STOP - condition found error occured ;ENDIF ;--------------------- ; otherwise go to "active" state gototstate("Active") ; ### STATE ### RegisterForSingleUpdate(1.0) ENDEVENT EVENT OnEndState() Debug.Trace(self+" [Initializing] OnEndState() - has been reached..") ; do never use any gotoState() here !!! ENDEVENT ;======= endState ;====================== state Active ;=========== EVENT OnLoad() ; this event is not working for Quest scripts, works for ObjectReference you know ENDEVENT EVENT OnUpdate() ; your active code here ;IF () RegisterForSingleUpdate(1.0) ; do update chain depends on condition if you like ;ENDIF ENDEVENT ;======= endState ;====================== state Inactive ;============= ;EVENT OnLoad() ; you do not need to cover the same event in state "Active", if not used here ;ENDEVENT EVENT OnUpdate() ; your inactive code here or let this event empty RegisterForSingleUpdate(1.0) ; do update chain ENDEVENT ;======= endState ;====================== state Done ; normally do nothing here ;========= EVENT OnBeginState() Debug.Trace(self+" [Done] OnBeginState() - has been reached..") ENDEVENT ;======= endState Edited September 19, 2017 by ReDragon2013 Link to comment Share on other sites More sharing options...
foamyesque Posted September 19, 2017 Author Share Posted September 19, 2017 (edited) ReDragon: Something about executing the shifts in state *in conjunction* with a heavy script load *and* not loading that save game as the first in session produces a result that should, according to the documentation, be impossible. This is the state flow, working as intended, in a game started via coc whiterunorigin: [09/19/2017 - 03:31:34PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: EMPTY [09/19/2017 - 03:31:34PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: PREBOOT [09/19/2017 - 03:31:35PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: PREBOOT [09/19/2017 - 03:31:35PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: INITIALIZING [09/19/2017 - 03:31:35PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: INITALIZING [09/19/2017 - 03:31:35PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: RESTOCKING [09/19/2017 - 03:31:36PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: RESTOCKING [09/19/2017 - 03:31:36PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: ACTIVE [09/19/2017 - 03:31:51PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnActivate: State: ACTIVEThis is the state flow when it fails: [09/19/2017 - 03:36:36PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: EMPTY [09/19/2017 - 03:36:36PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: PREBOOT [09/19/2017 - 03:36:36PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: PREBOOT [09/19/2017 - 03:36:36PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: INITIALIZING [09/19/2017 - 03:36:37PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: INITALIZING [09/19/2017 - 03:36:37PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: RESTOCKING [09/19/2017 - 03:36:37PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnEndState: State: RESTOCKING [09/19/2017 - 03:36:37PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnBeginState: State: ACTIVE [09/19/2017 - 03:37:11PM] SM: StoreMannequinActor: SM_WR_Warmaidens_Actor01F: OnActivate: State: EMPTY*something* is breaking in the active state -- it reaches it, and the OnBeginState code executes, but then it *leaves* it somehow without a. triggering an OnEndState event from it *or* an OnBeginState in any other state, including the empty state. Some of my state changes are being driven by an external script on a control quest. I'll poke at that and see if there's some timing that's breaking.EDIT:Delaying the beginning of the state changes by thirty seconds avoids the problem, but doesn't tell me what the problem was to start with... Edited September 19, 2017 by foamyesque Link to comment Share on other sites More sharing options...
ReDragon2013 Posted September 20, 2017 Share Posted September 20, 2017 (edited) What is the empty state in your scripts, this "" or this "EMPTY"? You should try my approach and write a simple workflow for the whole script. Now you should find out who (external script, internal event, internal function) is changing the state in the script, here probably "StoreMannequinActor.psc" as I can see in the debug output above. I do not believe it is a problem with OnBeginState(). Do you have this event in your script inserted? ;===================== state ACTIVE ;=========== EVENT OnEndState() Debug.Trace("[ACTIVE] OnEndState() - has been reached..") ENDEVENT ;======= endStateYou wrote: "Delaying the beginning of the state changes by thirty seconds avoids the problem". What state? I cannot see your script code! Edited September 20, 2017 by ReDragon2013 Link to comment Share on other sites More sharing options...
foamyesque Posted September 21, 2017 Author Share Posted September 21, 2017 (edited) What is the empty state in your scripts, this "" or this "EMPTY"? You should try my approach and write a simple workflow for the whole script. Now you should find out who (external script, internal event, internal function) is changing the state in the script, here probably "StoreMannequinActor.psc" as I can see in the debug output above. I do not believe it is a problem with OnBeginState(). Do you have this event in your script inserted? ;===================== state ACTIVE ;=========== EVENT OnEndState() Debug.Trace("[ACTIVE] OnEndState() - has been reached..") ENDEVENT ;======= endStateYou wrote: "Delaying the beginning of the state changes by thirty seconds avoids the problem". What state? I cannot see your script code! The default fallback empty state, "". I just have the traces set to say "EMPTY" for ease of reading. I don't think seeing my script code would help much. StoreMannequinActor.psc hits the Creation Kit's maximum character count for scripts and does a lot of things, many of them tied into still other scripts. However, as best as I can tell, none of those are *happening* in the bad-behaviour system. The initial trigger out of the empty state and into initializing processes is called by a control script on a quest, that only sets up initialization if SKSE is present. This is necessary on account of how SKSE functions are absolutely critical to the functioning of the script and no feasible substitutes exist for much of it. I added a large delay to the call to StoreMannequinActor that triggers the shift into Preboot (all subsequent state changes in the log are driven internally), and I *had* thought that had fixed it, but further examination shows that it had not. What does fix it is if the mannequin the script is running on is fully loaded and in the same cell as the player. The rest remain broken. But on the other hand this requirement is not needed if a. the save in question is clean or b. I manually retrigger the entire code path (which I've set up the ability to do); in both of those scenarios, fully loaded or not, same cell or not, everything works. The mannequin actors in question are persistent and have persistence locations set appropriately. Edited September 21, 2017 by foamyesque Link to comment Share on other sites More sharing options...
foamyesque Posted September 21, 2017 Author Share Posted September 21, 2017 Update: I've set up a workaround by means of an OnLoad statement in the empty state that dumps the script back into a working state. In the ordinary course of execution it should be impossible to trigger -- it's overridden in all the others -- but it catches the weird glitch I'm experiencing and corrects it. ReDragon: If you are still interested in the code, the current version is here: [spoiler=Large script] Scriptname foam_SM_StoreMannequinActor extends Actor import utility ObjectReference Property SM_MiscEnableMarker Auto GlobalVariable Property SM_SKSEVersion Auto GlobalVariable Property SM_MannequinGenders Auto FormList Property SM_MannequinActorList Auto FormList Property SM_ShutdownMannequinActorList Auto FormList Property SM_MannequinGenderOverrideActorList Auto foam_SM_MerchantChestQuest Property SM_MerchantChestQuest Auto foam_SM_ControlQuest Property SM_ControlQuest Auto bool Property bDefaultGender = true Auto bool Property bReverseEnableStatus = false Auto Faction Property SM_StoreMannequinFaction Auto Faction Property JobMerchantFaction Auto Faction Property myOwningFaction = none Auto Actor[] Property PossibleOwners Auto int Property iSaleRadius Auto Spell Property SM_IsVendingFFSelf Auto Spell Property SM_IsTalkingFFSelf Auto Topic Property BarterTopic Auto FormList Property SM_TalkingActorList Auto FormList Property SM_VendingActorList Auto String Property sMyID = "" Auto FormList Property SM_EmptyFormList Auto ;Keyword Property SM_EnableParent Auto Keyword Property SM_PairedMannequin Auto foam_SM_StoreMannequinActor myPairedMannequin = none ;foam_SM_StoreMannequinActor Property myPairedMannequin = none Auto ObjectReference Property myEnableParent = none Auto ;Inventory control properties LeveledItem[] Property OutfitList Auto ;A list of what the mannequin should wear. Each array element is a different outfit. Quest[] Property QuestCompleteReqs Auto ;What quest needs to be completed for the matching outfit to be used. This array should never be larger than the OutfitList, but it can be shorter. bool[] Property bPurchaseFlags Auto ;Whether the items on the mannequin should be added to the merchant chest Faction[] Property RequiredFactions Auto ;Player must be in one of these factions to purchase from the mannequin (if none, no restriction) Faction[] Property ExcludedFactions Auto ;Player cannot buy if they are in any of these factions LeveledItem Property myOutfit = none Auto bool Property bPurchasable = false Auto Actor Property PlayerRef Auto ObjectReference myChest = none ObjectReference myMarker = none Faction Property CrimeFaction = none Auto foam_SM_MerchantChestAlias myChestAlias = none string sWaitMenu = "Sleep/Wait Menu" string sMapMenu = "MapMenu" int kVendorFlag =0x00004000 ;Code flow flags bool bInitialized = false bool bThieving = false bool bPopping = false bool bActivating = false bool bRestockQueued = false bool bRestockNoWait = false bool bGenderUpdateQueued = false bool bQueueActive = false bool[] Property bGenderUpdating Auto bool Property bUsePaired = false Auto Event OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: EMPTY") EndEvent Event OnEndState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnEndState: State: EMPTY") EndEvent ;polices states Event OnLoad() if SM_ShutdownMannequinActorList.Find(self) < 0 if GetEnableTargetStatus() EnableAI(true) MoveTo(myMarker) EnableAI(false) GotoState("Active") else GotoState("Inactive") endif else GotoState("Error") endif EndEvent Event OnInit() Debug.Trace("SM: StoreMannequinActor: OnInit: State: EMPTY") if self == none ShutDown() else if bDefaultGender bGenderUpdating = new bool[2] endif SM_MannequinActorList.AddForm(self) endif EndEvent Function StartUp() GotoState("Preboot") EndFunction Function ShutDown() RegisterForSingleUpdate(0.1) GotoState("Error") EndFunction ; Preboot State State PreBoot Event OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: PREBOOT") UnregisterForUpdate() RegisterForSingleUpdate(1.0) EndEvent Event OnLoad() EndEvent Event OnUpdate() UnregisterForUpdate() GotoState("Initializing") EndEvent EndState ; Initializing State State Initializing Event OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING") EnableAI(false) if GetBaseAV("CarryWeight") < 10000 SetAV("CarryWeight", 10000) endif IgnoreFriendlyHits() bool bInitSuccess = true myMarker = GetLinkedRef() if myMarker != none if sMyID == "" sMyID = myMarker.GetPositionX() as int + "_" + myMarker.GetPositionY() as int + "_" + myMarker.GetPositionZ() as int + "_" + GetActorBase().GetSex() as int endif else if sMyID == "" sMyID = self.GetPositionX() as int + "_" + self.GetPositionY() as int + "_" + self.GetPositionZ() as int + "_" + GetActorBase().GetSex() as int endif endif ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ID'ed") if bInitSuccess if myMarker == none Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: No linked marker!") bInitSuccess = false endif endif if bInitSuccess if SM_PairedMannequin == none Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: SM_PairedMannequin keyword not found!") bInitSuccess = false endif endif if bInitSuccess if myPairedMannequin == none myPairedMannequin = GetLinkedRef(SM_PairedMannequin) as foam_SM_StoreMannequinActor endif if myPairedMannequin == self Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: Paired mannequin is self!") bInitSuccess = false endif endif if bInitSuccess if myPairedMannequin == none if !bDefaultGender Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: No paired mannequin on non-default mannequin!") bInitSuccess = false else Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: No paired mannequin!") endif else if myPairedMannequin.GetLinkedRef(SM_PairedMannequin) != self Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: Paired mannequin doesn't pair back!") endif endif endif if bInitSuccess if myPairedMannequin != none if bDefaultGender && myPairedMannequin.bDefaultGender Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: Mannequin and paired mannequin both set to default!") elseif !bDefaultGender && !myPairedMannequin.bDefaultGender Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: Mannequin and paired mannequin both set to non-default!") elseif !bDefaultGender && myPairedMannequin.bDefaultGender bReverseEnableStatus = myPairedMannequin.bReverseEnableStatus myEnableParent = myPairedMannequin.myEnableParent CrimeFaction = myPairedMannequin.CrimeFaction myOwningFaction = myPairedMannequin.myOwningFaction iSaleRadius = myPairedMannequin.iSaleRadius PossibleOwners = myPairedMannequin.PossibleOwners bGenderUpdating = myPairedMannequin.bGenderUpdating if !OutfitList.Length OutfitList = myPairedMannequin.OutfitList endif if !QuestCompleteReqs.Length QuestCompleteReqs = myPairedMannequin.QuestCompleteReqs endif if !bPurchaseFlags.Length bPurchaseFlags = myPairedMannequin.bPurchaseFlags endif if !RequiredFactions.Length RequiredFactions = myPairedMannequin.RequiredFactions endif if !ExcludedFactions.Length ExcludedFactions = myPairedMannequin.ExcludedFactions endif endif endif endif if bInitSuccess if iSaleRadius <= 0 Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: Invalid sale radius!") bInitSuccess = false elseif iSaleRadius < 400 Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: Sale radius: " + iSaleRadius + ": Low!") elseif iSaleRadius > 4000 Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: Sale radius: " + iSaleRadius + ": High!") endif endif if bInitSuccess if myOwningFaction == none Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: No owning faction!") bInitSuccess = false endif endif if bInitSuccess if !myOwningFaction.IsFactionFlagSet(kVendorFlag) Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: Owning faction not vendor!") bInitSuccess = false endif endif if bInitSuccess myChest = myOwningFaction.GetMerchantContainer() if myChest == none Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: No merchant container!") bInitSuccess = false endif endif if bInitSuccess myChestAlias = SM_MerchantChestQuest.RegisterChest(myChest) as foam_SM_MerchantChestAlias if myChestAlias == none Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: No alias provided!") bInitSuccess = false endif endif if bInitSuccess if CrimeFaction == none Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: No crime faction!") else SetCrimeFaction(CrimeFaction) endif if !OutfitList.Length Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: WARNING: No inventory provided!") endif AddToFaction(SM_StoreMannequinFaction) SM_ControlQuest.IncrementMannequins() SM_ShutdownMannequinActorList.RemoveAddedForm(self) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: Initialized: Index: " + SM_MannequinActorList.Find(self)) if !GetEnableTargetStatus() GotoState("Inactive") else ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: Disabled: " + IsDisabled()) if IsDisabled() Enable() endif ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: Disabled: " + IsDisabled()) if Is3DLoaded() RegisterForMenu(sWaitMenu) RegisterForMenu(sMapMenu) endif GotoState("Restocking") endif endif if !bInitSuccess Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INITIALIZING: ERROR: Initalizing failed!") SM_MannequinActorList.RemoveAddedForm(self) ShutDown() endif EndEvent ;keeps any update calls until a working state can process or reject them Event OnUpdate() RegisterForSingleUpdate(0.5) EndEvent LeveledItem Function GetOutfit(bool abUsePaired = false) return none EndFunction bool Function GetPurchasable(bool abUsePaired = false) return false EndFunction Event OnLoad() EndEvent EndState ; Error State State Error Event OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: ERROR") EndEvent Event OnUpdate() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": CRITICAL ERROR: Disabling!") UnregisterForUpdate() UnregisterForUpdateGameTime() if self != none SM_ShutdownMannequinActorList.AddForm(self) SM_ControlQuest.DecrementMannequins() if SM_ControlQuest.bModActiveStatusTarget Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ERROR: Index: " + SM_MannequinActorList.Find(self) + ": Shutdown count: " + SM_ShutdownMannequinActorList.GetSize()) endif UnregisterForLOS(PlayerRef, self) if SM_SKSEVersion.GetValueInt() > 0 UnregisterForMenu(sWaitMenu) UnregisterForMenu(sMapMenu) endif DisableNoWait(false) RemoveFromFaction(SM_StoreMannequinFaction) endif RemoveAllInventoryEventFilters() bPurchasable = false bGenderUpdateQueued = false bRestockQueued = false bRestockNoWait = false EndEvent ;Overrides dangerous functions/events from empty state Event OnLoad() EndEvent Function RegisterForSingleUpdate(Float afInterval) EndFunction Function RegisterForSingleUpdateGameTime(Float afInterval) EndFunction Function RegisterForUpdate(Float afInterval) EndFunction Function RegisterForUpdateGameTime(Float afInterval) EndFunction Function UpdateList() EndFunction Actor Function GetOwner(bool bFullCheck = true) return none EndFunction Function PopToMarker() EndFunction Function EquipAll() EndFunction bool Function GetEnableTargetStatus() return false EndFunction bool Function QueueUpdateGender(bool abChangeGender = false) return false EndFunction LeveledItem Function GetOutfit(bool abUsePaired = false) return none EndFunction bool Function GetPurchasable(bool abUsePaired = false) return false EndFunction EndState ; Inactive State State Inactive Event OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: INACTIVE") bRestockQueued = false bRestockNoWait = false UnregisterForLOS(PlayerRef, self) UnregisterForMenu(sWaitMenu) UnregisterForMenu(sMapMenu) BlockActivation(true) if IsEnabled() PopToMarker() DisableNoWait() endif UpdateList() EndEvent Event OnEndState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnEndState: State: INACTIVE") EndEvent ;Fires even when disabled Event OnCellAttach() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnCellAttach: State: INACTIVE") if GetEnableTargetStatus() if IsDisabled() EnableNoWait() endif GotoState("Restocking") endif EndEvent Event OnUpdate() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: INACTIVE: Restock queued: " + bRestockQueued + ": Gender update queued: " + bGenderUpdateQueued) if bRestockQueued bRestockQueued = false bRestockNoWait = false endif if bGenderUpdateQueued if GetEnableTargetStatus() GotoState("Active") endif bGenderUpdating[bDefaultGender as int] = false bGenderUpdateQueued = false endif EndEvent Event OnLoad() EndEvent LeveledItem Function GetOutfit(bool abUsePaired = false) return none EndFunction bool Function GetPurchasable(bool abUsePaired = false) return false EndFunction Function EquipAll() EndFunction EndState ; Restocking State State Restocking Event OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: RESTOCKING") bUsePaired = false bRestockNoWait = false BlockActivation(true) RemoveAllItems() AddDisplayItems() EquipAll() bRestockQueued = false bQueueActive = true RegisterForSingleUpdate(0.1) EndEvent Event OnEndState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnEndState: State: RESTOCKING") EndEvent Event OnUpdate() if bQueueActive bQueueActive = false GotoState("Active") else if bGenderUpdateQueued RegisterForSingleUpdate(0.5) endif if bRestockQueued bRestockQueued = false bRestockNoWait = false endif endif EndEvent Event OnLoad() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnLoad: State: RESTOCKING") RegisterForMenu(sWaitMenu) RegisterForMenu(sMapMenu) EndEvent Event OnUnload() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUnLoad: State: RESTOCKING") UnregisterForLOS(PlayerRef, self) if SM_SKSEVersion.GetValueInt() > 0 UnregisterForMenu(sWaitMenu) UnregisterForMenu(sMapMenu) endif EndEvent Event OnMenuClose(string asMenuName) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnMenuClose: State: RESTOCKING") RegisterForSingleLOSGain(PlayerRef, self) EndEvent Event OnGainLOS(Actor akViewer, ObjectReference akTarget) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnGainLOS: State: RESTOCKING") PopToMarker() EndEvent ;Adds mannequin's default display items Function AddDisplayItems() myOutfit = GetOutfit() bPurchasable = GetPurchasable() SetFactionRank(SM_StoreMannequinFaction, bPurchasable as int) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID +": AddDisplayItems: State: RESTOCKING: Purchasable: " + (bPurchasable as int) + ": Faction rank: " + GetFactionRank(SM_StoreMannequinFaction)) ;Adds the items from the leveled list if myOutfit != none AddItem(myOutfit) endif ;Updates merchant container UpdateList() EndFunction EndState ; Active State State Active Event OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: ACTIVE") PopToMarker() if IsDisabled() Enable() endif PopToMarker() if Is3DLoaded() OnLoad() endif BlockActivation(false) if bRestockQueued || bGenderUpdateQueued OnUpdate() endif EndEvent Event OnEndState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnEndState: State: ACTIVE") EndEvent ;Fires even when disabled Event OnCellAttach() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnCellAttach: State: ACTIVE") if GetEnableTargetStatus() PopToMarker() if GetOutfit(bUsePaired) == myOutfit if bPurchasable != GetPurchasable(bUsePaired) bPurchasable = !bPurchasable SetFactionRank(SM_StoreMannequinFaction, bPurchasable as int) UpdateList() endif endif else GotoState("Inactive") endif EndEvent Event OnLoad() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnLoad: State: ACTIVE") RegisterForMenu(sWaitMenu) RegisterForMenu(sMapMenu) EndEvent Event OnUnload() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUnLoad: State: ACTIVE") UnregisterForLOS(PlayerRef, self) UnregisterForMenu(sWaitMenu) UnregisterForMenu(sMapMenu) EndEvent Event OnMenuClose(string asMenuName) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnMenuClose: State: ACTIVE") RegisterForSingleLOSGain(PlayerRef, self) EndEvent Event OnGainLOS(Actor akViewer, ObjectReference akTarget) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnGainLOS: State: ACTIVE") PopToMarker() EndEvent Event OnUpdate() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE") if bRestockQueued if !IsNearPlayer() || bRestockNoWait ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE: Triggering restock") if bGenderUpdateQueued RegisterForSingleUpdate(0.5) endif GotoState("Restocking") else ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE: Restock postponed, player near.") if bGenderUpdateQueued UpdateGender() else RegisterForSingleUpdate(2.0) endif endif elseif bGenderUpdateQueued UpdateGender() endif EndEvent ;Forces mannequin to its marker and opens the inventory screen for item management Event OnActivate(ObjectReference akTriggerRef) Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnActivate: State: ACTIVE") if !bActivating && !IsActivationBlocked() bActivating = true PopToMarker() if akTriggerRef == PlayerRef bThieving = false if PlayerRef.IsSneaking() bThieving = true endif if GetOutfit(bUsePaired) == myOutfit if bPurchasable != GetPurchasable(bUsePaired) bPurchasable = !bPurchasable ;Update list checks the purchase status, so set it first SetFactionRank(SM_StoreMannequinFaction, bPurchasable as int) UpdateList() endif endif if bThieving else Actor myOwner = GetOwner() if myOwner != none myOwner.Say(BarterTopic) else Debug.Notification("No merchant available") endif endif endif bActivating = false endif EndEvent Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) if akSourceContainer == none UpdateList() else ;if bThieving AddInventoryEventFilter(SM_EmptyFormList) if akItemReference != none RemoveItem(akItemReference, aiItemCount, true, akSourceContainer) else RemoveItem(akBaseItem, aiItemCount, true, akSourceContainer) endif RemoveInventoryEventFilter(SM_EmptyFormList) ;endif endif EndEvent Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Item: " + akBaseItem.GetName() + ": Removed") QueueNiNodeUpdate() UpdateList() EndEvent Function RemoveRegisteredItem(Form akRemoveForm = none, int aiCount = 1) if akRemoveForm AddInventoryEventFilter(SM_EmptyFormList) RemoveItem(akRemoveForm, aiCount, true) RemoveInventoryEventFilter(SM_EmptyFormList) QueueNiNodeUpdate() endif EndFunction bool Function UpdateGender() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE: Updating gender") bool bChanged = false bool bActive = GetEnableTargetStatus() bGenderUpdating[bDefaultGender as int] = false int iLoops = 0 while bGenderUpdating[0] || bGenderUpdating[1] iLoops+=1 ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: UpdateGender: Flags: " + bGenderUpdating[0] + " : " + bGenderUpdating[1] + " Loop count: " + iLoops) WaitMenuMode(0.1) endwhile if !bActive && myPairedMannequin != none if myPairedMannequin.GetEnableTargetStatus() iLoops = 0 while !myPairedMannequin.IsEnabled() iLoops += 1 ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": UpdateGender: Flags: " + bGenderUpdating[0] + " : " + bGenderUpdating[1] + " Loop count: " + iLoops) WaitMenuMode(0.1) endwhile TransferItems() myPairedMannequin.EquipAll() GotoState("Inactive") endif bChanged = true endif if bRestockQueued ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": UpdateGender: Restock flag: " + bRestockQueued) RegisterForSingleUpdate(0.5) endif bGenderUpdateQueued = false return bChanged EndFunction bool Function RestockInventory(bool abNoWait = false) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Restock: Called") if !GetEnableTargetStatus() GotoState("Inactive") else if GetOwner(false) == none Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Restock: WARNING: No owner found") else bRestockQueued = true bRestockNoWait = abNoWait ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Restock: Queued") RegisterForSingleUpdate(0.1) return true endif endif return false EndFunction bool Function TransferItems() if myPairedMannequin == none Debug.Trace("SM: StoreMannequinActor: TransferItems: WARNING: No paired mannequin!") return false else if self.IsDisabled() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": TransferItems: WARNING: Mannequin: " + sMyID + ": Disabled!") else if myPairedMannequin.IsDisabled() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": TransferItems: WARNING: Mannequin: " + myPairedMannequin.sMyID + ": Disabled!") else ;copies data to paired mannequin myPairedMannequin.bPurchasable = bPurchasable myPairedMannequin.SetFactionRank(SM_StoreMannequinFaction, bPurchasable as int) myPairedMannequin.myOutfit = myOutfit myPairedMannequin.bUsePaired = true bUsePaired = false ;disables OnItemAdded/Removed events, transfers items, then re-enables the events myPairedMannequin.AddInventoryEventFilter(SM_EmptyFormList) self.AddInventoryEventFilter(SM_EmptyFormList) RemoveAllItems(myPairedMannequin, false, true) self.RemoveInventoryEventFilter(SM_EmptyFormList) myPairedMannequin.RemoveInventoryEventFilter(SM_EmptyFormList) myPairedMannequin.UpdateList() self.UpdateList() return true endif endif endif EndFunction EndState ; Empty State Function EquipAll() Form[] myInventory = GetContainerForms() int iLength = myInventory.Length int i = iLength if iLength > 1 int[] iPriorityList = CreateIntArray(i) while i > 0 i-=1 iPriorityList[i] = CalcPriority(myInventory[i]) endwhile ;insertion sort i=1 while i < iLength int iTempPriority = iPriorityList[i] Form TempForm = myInventory[i] int j = i - 1 while j >= 0 && iPriorityList[j] > iTempPriority iPriorityList[j+1] = iPriorityList[j] myInventory[j+1] = myInventory[j] j-=1 endwhile iPriorityList[j+1] = iTempPriority myInventory[j+1] = TempForm i+=1 endwhile endif ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": EquipAll: Items to equip: " + i) bool myGender = GetActorBase().GetSex() while i > 0 i-=1 ;makes sure only armour is going to be equipped Armor testArmor = myInventory[i] as Armor if testArmor != none ;makes sure only armour with visible models are equipped int j = testArmor.GetNumArmorAddons() bool bPathsFound = true while j > 0 j-=1 ArmorAddon testAddon = testArmor.GetNthArmorAddon(j) if testAddon.GetModelPath(false, false) == "" if !myGender bPathsFound = false elseif testAddon.GetModelPath(false, true) == "" bPathsFound = false endif endif endwhile ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": EquipAll: Item: " + i + ": Equipping: " + myInventory[i].GetName()) if bPathsFound EquipItem(myInventory[i], true) endif endif endwhile EndFunction ;Moves the mannequin to its assigned XMarkerHeading Function PopToMarker() if !bPopping bPopping = true ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": PopToMarker: Disabled: " + IsDisabled() + ": 3DLoaded: " + Is3DLoaded()) MoveTo(myMarker) QueueNiNodeUpdate() bPopping = false endif EndFunction ;finds the mannequins owner(s) and, optionally, if they are available to sell merchandise. Actor Function GetOwner(bool bFullCheck = true) if myOwningFaction == none return none endif Actor myOwner = none int i = PossibleOwners.Length while i > 0 i-=1 if PossibleOwners[i] == none ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": GetOwner: Index: " + i + ": NULL") else string ownerName = PossibleOwners[i].GetDisplayName() if !PossibleOwners[i].IsDead() && !PossibleOwners[i].IsDisabled() if PossibleOwners[i].GetFactionRank(JobMerchantFaction) >= 0 && PossibleOwners[i].GetFactionRank(myOwningFaction) >= 0 if bFullCheck ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": GetOwner: Actor: " + ownerName + ": Activation blocked: " + PossibleOwners[i].IsActivationBlocked()) if !PossibleOwners[i].IsActivationBlocked() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": GetOwner: Actor: " + ownerName + ": Distance to player: " + PossibleOwners[i].GetDistance(PlayerRef)) if PossibleOwners[i].GetDistance(PlayerRef) < iSaleRadius ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": GetOwner: Actor: " + ownerName + ": In combat: " + PossibleOwners[i].IsInCombat()) if !PossibleOwners[i].IsInCombat() SM_IsVendingFFSelf.Cast(PossibleOwners[i], PossibleOwners[i]) SM_IsTalkingFFSelf.Cast(PossibleOwners[i], PossibleOwners[i]) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": GetOwner: Actor: " + ownerName + ": Is vending: " + SM_VendingActorList.HasForm(PossibleOwners[i])) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": GetOwner: Actor: " + ownerName + ": Is talking: " + SM_TalkingActorList.HasForm(PossibleOwners[i])) WaitMenuMode(0.05) if SM_VendingActorList.HasForm(PossibleOwners[i]) && !SM_TalkingActorList.HasForm(PossibleOwners[i]) myOwner = PossibleOwners[i] ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": GetOwner: Owner: " + ownerName) endif PossibleOwners[i].DispelSpell(SM_IsTalkingFFSelf) PossibleOwners[i].DispelSpell(SM_IsVendingFFSelf) endif endif endif else myOwner = PossibleOwners[i] endif endif endif endif endwhile return myOwner EndFunction ;sends mannequin inventory data to the merchant chest script Function UpdateList() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": UpdateList: Purchasable: " + bPurchasable) if bPurchasable Form[] myInventory = GetContainerForms() myChestAlias.UpdateLists(myInventory, self) else myChestAlias.UpdateLists(CreateFormArray(0), self) endif EndFunction ;Loops through the list of quests ;The last completed quest (or blank value in the list) will be used as the index number ;to determine which leveled list is added and/or purchase flag used. If no list exists, the index is zero int Function GetIndex() int outfit_index = 0 int i = 0 if QuestCompleteReqs.Length while i < QuestCompleteReqs.Length if QuestCompleteReqs[i] != none if QuestCompleteReqs[i].IsCompleted() outfit_index = i endif else outfit_index = i endif i+=1 endwhile endif return outfit_index EndFunction ;Calculates the priority of equipping a given item int Function CalcPriority(Form akForm) int iPriority = 0x000 if akForm == none return iPriority endif if akForm as Armor Armor testArmor = akForm as Armor int iSlotMask= testArmor.GetSlotMask() int iTestSlot = 0x01 while iTestSlot < testArmor.kSlotMask61 if Math.LogicalAnd(iTestSlot, iSlotMask) ;chest armour if iTestSlot == testArmor.kSlotMask32 iPriority+=0x800 endif ;head slots if iTestSlot == testArmor.kSlotMask30 || iTestSlot == testArmor.kSlotMask31 || iTestSlot == testArmor.kSlotMask41 || iTestSlot == testArmor.kSlotMask42 || iTestSlot == testArmor.kSlotMask43 iPriority+=0x080 endif ;slot count iPriority+= 0x010 endif iTestSlot *= 2 endwhile endif ;gold value as tiebreaker int iValue = (akForm.GetGoldValue() / 10) as int if iValue > 0x080 iValue = 0x080 endif iPriority += iValue return iPriority EndFunction bool Function GetEnableTargetStatus() bool bGenderMatch = false if !SM_MiscEnableMarker.IsEnabled() return false endif if myPairedMannequin == none bGenderMatch = true else int iMannequinGenders = (SM_MannequinGenders.GetValue() as int) if iMannequinGenders == 0 bool bOverride = SM_MannequinGenderOverrideActorList.HasForm(self) if (bDefaultGender && !bOverride) || (!bDefaultGender && bOverride) bGenderMatch = true endif elseif iMannequinGenders == 1 && GetActorBase().GetSex() == 0 bGenderMatch = true elseif iMannequinGenders == 2 && GetActorBase().GetSex() == 1 bGenderMatch = true endif endif if bGenderMatch == false return false endif if myEnableParent == none return true endif ;WaitMenuMode(0.1) if myEnableParent.IsEnabled() && !bReverseEnableStatus return true elseif !myEnableParent.IsEnabled() && bReverseEnableStatus return true endif return false EndFunction bool Function QueueUpdateGender(bool abChangeGender = false) while bGenderUpdateQueued WaitMenuMode(0.1) endwhile bGenderUpdateQueued = true bGenderUpdating[bDefaultGender as int] = true UnRegisterForUpdate() RegisterForSingleUpdate(0.1) return true EndFunction LeveledItem Function GetOutfit(bool abUsePaired = false) foam_SM_StoreMannequinActor testMannequin = self if abUsePaired testMannequin = myPairedMannequin endif int iQuestIndex = testMannequin.GetIndex() LeveledItem testOutfit = none if iQuestIndex < testMannequin.OutfitList.Length testOutfit = testMannequin.OutfitList[iQuestIndex] endif return testOutfit EndFunction bool Function GetPurchasable(bool abUsePaired = false) foam_SM_StoreMannequinActor testMannequin = self if abUsePaired testMannequin = myPairedMannequin endif bool bPurchaseFlag = false if !PlayerExcluded() if PlayerIncluded() int iQuestIndex = testMannequin.GetIndex() if iQuestIndex < testMannequin.bPurchaseFlags.Length bPurchaseFlag = testMannequin.bPurchaseFlags[iQuestIndex] else bPurchaseFlag = true endif endif endif return bPurchaseFlag EndFunction bool Function PlayerIncluded() int i = RequiredFactions.Length if i == 0 return true endif while i > 0 i-=1 ;Debug.Trace("SM: StoreMannequinActor: PlayerIncluded: Index: " + i + ": " + PlayerRef.IsInFaction(RequiredFactions[i])) if PlayerRef.IsInFaction(RequiredFactions[i]) return true endif endwhile return false EndFunction bool Function PlayerExcluded() int i = ExcludedFactions.Length while i > 0 i-=1 if PlayerRef.IsInFaction(ExcludedFactions[i]) return true endif endwhile return false EndFunction ;------------------------- ; Null functions ;------------------------- Function AddDisplayItems() EndFunction bool Function RestockInventory(bool abNoWait = false) Debug.Trace("SM: StoreMannequinActor: RestockInventory: State: EMPTY: NoWait flag: " + abNoWait) return false EndFunction Function RemoveRegisteredItem(Form akRemoveForm = none, int aiCount = 1) EndFunction bool Function UpdateGender() return false EndFunction bool Function TransferItems() Debug.Trace("SM: StoreMannequinActor: TransferItems: WARNING: Called outside of active state!") return false EndFunction It's something like twelve hundred lines of sparsely documented interdependent asynchronous spaghetti. Have fun :D Link to comment Share on other sites More sharing options...
foamyesque Posted September 21, 2017 Author Share Posted September 21, 2017 ... and apparently a. I goofed my spoiler tags and b. it's so long I can't edit my post to fix them :v Link to comment Share on other sites More sharing options...
cdcooley Posted September 22, 2017 Share Posted September 22, 2017 [ ...] if that game is *not* the first one loaded in the session [...] That's the problem. You're loading a saved game from somewhere other than the main menu right after the game starts. Don't do that if you value your saved games. Loading a save after you've already been playing will almost always corrupt your game in some way. Too many parts of the game simply aren't reinitialized when you do that so you end up with a weird merge of states of the game you were running and the one you are loading. The next time you save you're saving that corrupt mess. The fact that your script works in every situation except that problem case means it's not your script that's the problem it's the overall game state. Link to comment Share on other sites More sharing options...
foamyesque Posted September 22, 2017 Author Share Posted September 22, 2017 (edited) Unfortunately there was a flaw in my testing system -- I'd added a big enough delay that I could coc into a cell before the mannequins loaded in, which changed my results. I now get the same failure however the save game is loaded, though not with all save games. Workaround notwithstanding I'm still puzzled as to what could possibly be changing the state without tripping any of the state transition events. Edited September 22, 2017 by foamyesque Link to comment Share on other sites More sharing options...
ReDragon2013 Posted September 24, 2017 Share Posted September 24, 2017 Your script code is very complicated imho. It took me some time to understand what is going on here, additional you are using SKSE function sometimes without checking presence of SKSE, not so good.Maybe you can use next code to find the mistake. I didn't compiled the script. Do not forget my English is weak and ot really native written. foam_SM_StoreMannequinActor Scriptname foam_SM_StoreMannequinActor extends Actor {rewritten by ReDragon 2017} ; "Ordnung ist das halbe Leben!" foam_SM_ControlQuest PROPERTY SM_ControlQuest auto ; use the quest property !! foam_SM_MerchantChestQuest PROPERTY SM_MerchantChestQuest auto ; use the quest property !! foam_SM_MerchantChestAlias myChestAlias foam_SM_StoreMannequinActor myPairedMannequin Quest[] PROPERTY QuestCompleteReqs auto ; What quest needs to be completed for the matching outfit to be used. ; This array should never be larger than the OutfitList, but it can be shorter. LeveledItem PROPERTY myOutfit auto Hidden LeveledItem[] PROPERTY OutfitList auto ; A list of what the mannequin should wear. Each array element is a different outfit. GlobalVariable PROPERTY SM_SKSEVersion auto GlobalVariable PROPERTY SM_MannequinGenders auto ;Keyword PROPERTY SM_EnableParent auto ; removed by default Keyword PROPERTY SM_PairedMannequin auto Topic PROPERTY BarterTopic auto FormList PROPERTY SM_MannequinActorList auto FormList PROPERTY SM_ShutdownMannequinActorList auto FormList PROPERTY SM_MannequinGenderOverrideActorList auto FormList PROPERTY SM_TalkingActorList auto FormList PROPERTY SM_VendingActorList auto FormList PROPERTY SM_EmptyFormList auto Spell PROPERTY SM_IsVendingFFSelf auto Spell PROPERTY SM_IsTalkingFFSelf auto Faction[] PROPERTY RequiredFactions auto ; Player must be in one of these factions to purchase from the mannequin (if none, no restriction) Faction[] PROPERTY ExcludedFactions auto ; Player cannot buy if they are in any of these factions Faction PROPERTY SM_StoreMannequinFaction auto Faction PROPERTY JobMerchantFaction auto Faction PROPERTY myOwningFaction auto Hidden Faction PROPERTY CrimeFaction auto Hidden Actor[] PROPERTY PossibleOwners auto ;Actor PROPERTY PlayerRef auto ObjectReference PROPERTY SM_MiscEnableMarker auto ObjectReference PROPERTY myEnableParent auto Hidden ;ObjectReference myChest ;ObjectReference myMarker Bool[] PROPERTY bPurchaseFlags auto ; whether the items on the mannequin should be added to the merchant chest Bool[] PROPERTY bGenderUpdating auto Hidden Bool PROPERTY bUsePaired auto Bool PROPERTY bPurchasable auto Bool PROPERTY bDefaultGender = TRUE auto ; set TRUE Bool PROPERTY bReverseEnableStatus auto ;Code flow flags Bool bInitialized ; it is False by default ;Bool bThieving Bool bPopping Bool bActivating Bool bRestockQueued Bool bRestockNoWait Bool bQueueActive Bool bGenderUpdateQueued Int PROPERTY iSaleRadius auto ;Int kVendorFlag = 0x00004000 String PROPERTY sMyID auto ; "", empty by default ; -- EVENTs -- EVENT OnInit() IF self.GetBaseObject() ELSE shutdown() RETURN ; - STOP - ENDIF ;--------------------- Debug.Trace("SM: OnInit() - has been reached.. " +self) SM_MannequinActorList.AddForm(self) IF ( bDefaultGender ) bGenderUpdating = new Bool[2] ENDIF ENDEVENT EVENT OnCellAttach() ; Fires event only when disabled string s = self.GetState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnCellAttach: State: " +s) IF (s == "Inactive") IF GetEnableTargetStatus() IF self.IsDisabled() self.EnableNoWait() ; triggers OnLoad() in same state ENDIF gotoState("Restocking") ; ### STATE ### (R) myF_InitRestock() ENDIF RETURN ; - STOP - ENDIF ;--------------------- IF (s == "Active") IF GetEnableTargetStatus() ELSE gotoState("Inactive") ; ### STATE ### (I) myF_InitInactive() RETURN ; - STOP - ENDIF ; ---------------------- PopToMarker() IF (GetOutfit(bUsePaired) == myOutfit) IF (GetPurchasable(bUsePaired) == bPurchasable) ELSE bPurchasable = !bPurchasable self.SetFactionRank(SM_StoreMannequinFaction, bPurchasable as Int) UpdateList() ENDIF ENDIF ENDIF ENDEVENT EVENT OnLoad() int i = SM_ShutdownMannequinActorList.Find(self) IF (i >= 0) gotoState("Error") ; ### STATE ### - E - RETURN ; - STOP - already in shutdown list ENDIF ;--------------------- IF GetEnableTargetStatus() ELSE gotoState("Inactive") ; ### STATE ### (I) myF_InitInactive() RETURN ; - STOP - status disabled ENDIF ;--------------------- self.EnableAI(TRUE) self.MoveTo( self.GetLinkedRef() ) ; myMarker self.EnableAI(False) gotoState("Active") ; ### STATE ### (A) myF_InitActive() ENDEVENT ;;================================ ;state Preboot ; not really required ;;============ ;EVENT OnBeginState() ; Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: Preboot") ;ENDEVENT ;;======= ;endState ;================================ state Error ; prevent some events from running ;========== EVENT OnCellAttach() ENDEVENT EVENT OnLoad() ENDEVENT EVENT OnUpdate() ; maybe, maybe not? ENDEVENT FUNCTION shutDown() ; make sure to run this once only ENDFUNCTION ;======= endState ;------------------ FUNCTION shutDown() ;------------------ UnregisterForUpdate() ;;; UnregisterForUpdateGameTime() gotoState("Error") ; ### STATE ### - E - SM_MannequinActorList.RemoveAddedForm(self) Debug.Trace("SM: OnUpdate() [Initializing] failed!! shutDown for " +sMyID) IF ( self ) UnregisterForLOS(Game.GetPlayer(), self as ObjectReference) myF_RFM(False) SM_ShutdownMannequinActorList.AddForm(self) SM_ControlQuest.DecrementMannequins() self.DisableNoWait() self.RemoveFromFaction(SM_StoreMannequinFaction) IF ( SM_ControlQuest.bModActiveStatusTarget ) int i = SM_MannequinActorList.Find(self) int c = SM_ShutdownMannequinActorList.GetSize() Debug.Trace("SM: OnUpdate() [Initializing] Index: " +i+ ", Shutdown count: " +c+ " " +sMyID) ENDIF ENDIF self.RemoveAllInventoryEventFilters() bPurchasable = False bGenderUpdateQueued = False bRestockQueued = False bRestockNoWait = False ENDFUNCTION ;---------------------- FUNCTION myF_InitGoto() ;---------------------- IF GetEnableTargetStatus() ELSE gotoState("Inactive") ; ### STATE ### (I) myF_InitInactive() RETURN ; - STOP - ENDIF ;--------------------- IF self.IsDisabled() self.Enable() ; triggers OnLoad() immediately Debug.Trace("SM: OnUpdate() [Initializing] self has been enabled.. " +sMyID) Utility.Wait(0.1) ENDIF int i = 20 WHILE (i > 0) i = i - 1 IF self.Is3DLoaded() myF_RFM(TRUE) i = 0 ELSE Utility.Wait(0.1) ; 20 * 0.1 = 2.0 sec ENDIF ENDWHILE gotoState("Restocking") ; ### STATE ### (R) myF_InitRestock() ENFUNCTION ;--------------------------- FUNCTION myF_Fill_StringID() ;--------------------------- objectReference mRef = sef.GetLinkedRef() IF ( mRef ) ELSE mRef = self ENDIF int i = mRef.GetActorBase().GetSex() as Int sMyID = mRef.GetPositionX() as Int + "_" + mRef.GetPositionY() as Int + "_" + mRef.GetPositionZ() as Int + "_" + i ENDFUNCTION ;----------------- FUNCTION StartUp() ; called by external script ;----------------- UnregisterForUpdate() gotoState("Initializing") ; ### STATE ### Init RegisterForSingleUpdate(1.0) ENDFUNCTION ;================================ state Initializing ;================= EVENT OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: Initializing") ENDEVENT EVENT OnEndState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnEndState: State: Initializing") ENDEVENT EVENT OnLoad() ENDEVENT EVENT OnUpdate() IF ( bInitialized ) RegisterForSingleUpdate(0.5) ; keep updating until a working state can process or reject it RETURN ; - STOP - ENDIF ;--------------------- bInitialized = TRUE self.EnableAI(False) ; switch off AI self.IgnoreFriendlyHits() IF self.GetBaseActorValue("CarryWeight") < 10000) self.SetActorValue("CarryWeight", 10000) ENDIF IF (sMyID == "") myF_Fill_StringID() ENDIF string s = "SM: OnUpdate() [Initializing] " IF self.GetLinkedRef() ELSE Debug.Trace(s+"no linkedRef found.. " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- IF ( SM_PairedMannequin ) ELSE Debug.Trace(s+"missing keyword 'SM_PairedMannequin' " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- IF ( myPairedMannequin ) myPairedMannequin = self.GetLinkedRef(SM_PairedMannequin) as foam_SM_StoreMannequinActor ENDIF IF (myPairedMannequin == self) Debug.Trace(s+"myPairedMannequin is equal to self! " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- IF ( myPairedMannequin ) ELSE IF ( bDefaultGender ) Debug.Trace(s+"paired mannequin not found! " +sMyID) ELSE Debug.Trace(s+"No paired mannequin on non-default mannequin! " +sMyID") shutDown() RETURN ; - STOP - ENDIF ; ---------------------- ENDIF IF (myPairedMannequin.GetLinkedRef(SM_PairedMannequin) == self) ELSE Debug.Trace(s+"myPairedMannequin doesn't pair back! " +sMyID) ENDIF IF ( bDefaultGender ) IF myPairedMannequin.bDefaultGender Debug.Trace(s+"Mannequin and paired mannequin, defaultGender is TRUE! " +sMyID) ENDIF ELSE IF myPairedMannequin.bDefaultGender bReverseEnableStatus = myPairedMannequin.bReverseEnableStatus myEnableParent = myPairedMannequin.myEnableParent CrimeFaction = myPairedMannequin.CrimeFaction myOwningFaction = myPairedMannequin.myOwningFaction iSaleRadius = myPairedMannequin.iSaleRadius PossibleOwners = myPairedMannequin.PossibleOwners bGenderUpdating = myPairedMannequin.bGenderUpdating IF (OutfitList.Length == 0) OutfitList = myPairedMannequin.OutfitList ENDIF IF (QuestCompleteReqs.Length == 0) QuestCompleteReqs = myPairedMannequin.QuestCompleteReqs ENDIF IF (bPurchaseFlags.Length == 0) bPurchaseFlags = myPairedMannequin.bPurchaseFlags ENDIF IF (RequiredFactions.Length == 0) RequiredFactions = myPairedMannequin.RequiredFactions ENDIF IF (ExcludedFactions.Length == 0) ExcludedFactions = myPairedMannequin.ExcludedFactions ENDIF ELSE Debug.Trace(s+"Mannequin and paired mannequin, defaultGender is False! " +sMyID) ENDIF ENDIF IF (iSaleRadius <= 0) Debug.Trace(s+"Invalid sale radius! " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- IF (iSaleRadius < 400) || (iSaleRadius > 4000) Debug.Trace(s+"Sale radius is out of bounds! " +iSaleRadius+ " " +sMyID) ENDIF IF ( myOwningFaction ) ELSE Debug.Trace(s+"myOwningFaction is None! " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- IF myOwningFaction.IsFactionFlagSet(0x00004000) ; kVendorFlag ELSE Debug.Trace(s+"myOwningFaction has not the vendor flag! " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- objectReference oRef = myOwningFaction.GetMerchantContainer() IF ( oRef ) ; oRef = myChest ELSE Debug.Trace(s+"Merchant chest container does not exist! " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- myChestAlias = SM_MerchantChestQuest.RegisterChest(oRef) as foam_SM_MerchantChestAlias IF ( myChestAlias ) ELSE Debug.Trace(s+"No ChestAlias provided! " +sMyID) shutDown() RETURN ; - STOP - ENDIF ;--------------------- IF ( CrimeFaction ) self.SetCrimeFaction(CrimeFaction) ELSE Debug.Trace(s+"No crime faction! " +sMyID) ENDIF IF (OutfitList.Length == 0) Debug.Trace(s+"No Outfit provided! " +sMyID) ENDIF self.AddToFaction(SM_StoreMannequinFaction) SM_ControlQuest.IncrementMannequins() SM_ShutdownMannequinActorList.RemoveAddedForm(self) myF_InitGoto() ENDEVENT ;======= endState ;------------------------- FUNCTION myF_InitRestock() ;------------------------- bUsePaired = False bRestockNoWait = false self.BlockActivation(TRUE) self.RemoveAllItems() ;------------------------------------------ Adds mannequin's default display items ;AddDisplayItems() myOutfit = GetOutfit() bPurchasable = GetPurchasable() self.SetFactionRank(SM_StoreMannequinFaction, bPurchasable as Int) ; Adds the items from the leveled list IF ( myOutfit ) self.AddItem(myOutfit) ENDIF ; Updates merchant container UpdateList() ;------------------------------------------ EquipAll() bRestockQueued = False bQueueActive = TRUE RegisterForSingleUpdate(0.1) ENDFUNCTION ;================================ state Restocking ;=============== EVENT OnBeginState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnBeginState: State: RESTOCKING") ENDEVENT EVENT OnEndState() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnEndState: State: RESTOCKING") ENDEVENT EVENT OnLoad() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnLoad: State: RESTOCKING") myF_RFM(TRUE) ENDEVENT EVENT OnUpdate() IF ( bQueueActive ) bQueueActive = False gotoState("Active") ; ### STATE ### (A) myF_InitActive() RETURN ; - STOP - ENDIF ;--------------------- IF ( bRestockQueued ) bRestockQueued = False bRestockNoWait = False ENDIF IF ( bGenderUpdateQueued ) RegisterForSingleUpdate(0.5) ENDIF ENDEVENT EVENT OnUnload() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUnLoad: State: RESTOCKING") UnregisterForLOS(Game.GetPlayer(), self as ObjectReference) myF_RFM(False) ENDEVENT EVENT OnGainLOS(Actor akViewer, ObjectReference akTarget) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnGainLOS: State: RESTOCKING") PopToMarker() EndEvent EVENT OnMenuClose(String asMenuName) ; SKSE only ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnMenuClose: State: RESTOCKING") RegisterForSingleLOSGain(Game.GetPlayer(), self as ObjectReference) ENDEVENT ;======= endState ;------------------------ FUNCTION myF_InitActive() ;------------------------ IF self.IsDisabled() PopToMarker() self.Enable() ; trigger OnLoad() in same state ENDIF self.BlockActivation(False) PopToMarker() IF (bRestockQueued) || (bGenderUpdateQueued) UnRegisterForUpdate() RegisterForSingleUpdate(0.0) ENDIF ENDFUNCTION ;================================ state Active ;=========== EVENT OnBeginState() Debug.Trace("SM: " +sMyID+ ": OnBeginState() [Active]") ENDEVENT EVENT OnEndState() Debug.Trace("SM: " +sMyID+ ": OnEndState() [Active]") ENDEVENT EVENT OnLoad() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnLoad: State: ACTIVE") myF_RFM(TRUE) ENEVENT EVENT OnUpdate() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE") IF ( bRestockQueued ) ELSE UpdateGender() ; ** 1 ** RETURN ; - STOP - ENDIF ;--------------------- IF (bRestockNoWait) || !self.IsNearPlayer() ;; Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE: Triggering restock") gotoState("Restocking") ; ### STATE ### (R) IF ( bGenderUpdateQueued ) RegisterForSingleUpdate(0.5) ENDIF myF_InitRestock() RETURN ; - STOP - ENDIF ;--------------------- ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE: Restock postponed, player near.") IF ( bGenderUpdateQueued ) UpdateGender() ; ** 2 ** RETURN ; - STOP - ENDIF ;--------------------- RegisterForSingleUpdate(2.0) ; keep updating, but do not change the state ENDEVENT EVENT OnUnload() ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUnLoad: State: ACTIVE") UnregisterForLOS(Game.GetPlayer(), self as ObjectReference) myF_RFM(False) ENDEVENT EVENT OnGainLOS(Actor akViewer, ObjectReference akTarget) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnGainLOS: State: ACTIVE") PopToMarker() ENDEVENT EVENT OnMenuClose(string asMenuName) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnMenuClose: State: ACTIVE") RegisterForSingleLOSGain(Game.GetPlayer(), self as ObjectReference) ENDEVENT EVENT OnActivate(ObjectReference akActionRef) ; Forces mannequin to its marker and opens the inventory screen for item management ;; Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnActivate: State: ACTIVE") IF self.IsActivationBlocked() RETURN ; - STOP - ENDIF ;--------------------- IF ( bActivating ) RETURN ; - STOP - ENDIF ;--------------------- bActivating = TRUE ; *T* PopToMarker() IF (akActionRef == Game.GetPlayer() as ObjectReference) IF (GetOutfit(bUsePaired) == myOutfit) IF (GetPurchasable(bUsePaired) == bPurchasable) ELSE bPurchasable = !bPurchasable ; Update list checks the purchase status, so set it first self.SetFactionRank(SM_StoreMannequinFaction, bPurchasable as Int) UpdateList() ENDIF ENDIF IF (akActionRef as Actor).IsSneaking() ELSE actor aRef = GetOwner() ; aRef = myOwner IF ( aRef ) aRef.Say(BarterTopic) ELSE Debug.Notification("No merchant available") ENDIF ENDIF ENDIF bActivating = False ; *** ENDEVENT EVENT OnItemAdded(Form akBaseItem, Int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) IF ( akSourceContainer ) ELSE UpdateList() RETURN ; - STOP - ENDIF ;--------------------- self.AddInventoryEventFilter(SM_EmptyFormList) ; -------- IF ( akItemReference ) self.RemoveItem(akItemReference as Form, aiItemCount, TRUE, akSourceContainer) ; trigger OnItemRemoved() in same state ELSE self.RemoveItem(akBaseItem, aiItemCount, TRUE, akSourceContainer) ; trigger OnItemRemoved() in same state ENDIF ; ----------- self.RemoveInventoryEventFilter(SM_EmptyFormList) ENDEVENT EVENT OnItemRemoved(Form akBaseItem, Int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer) ;; Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Item: " + akBaseItem.GetName() + ": Removed") QueueNiNodeUpdate() UpdateList() ENDEVENT ;======= endState ;-------------------------- FUNCTION myF_InitInactive() ;-------------------------- bRestockQueued = False bRestockNoWait = False UnregisterForLOS(Game.GetPlayer(), self as ObjectReference) myF_RFM(False) self.BlockActivation(TRUE) IF self.IsDisabled() ELSE PopToMarker() self.DisableNoWait() ENDIF UpdateList() ENDFUNCTION ;================================ state Inactive ;============= EVENT OnBeginState() Debug.Trace("SM: " +sMyID+ ": OnBeginState() [Inactive]") ENDEVENT EVENT OnEndState() Debug.Trace("SM: " +sMyID+ ": OnEndState() [Inactive]") ENDEVENT EVENT OnLoad() ENDEVENT EVENT OnUpdate() ;; Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: INACTIVE: Restock queued: " + bRestockQueued + ": Gender update queued: " + bGenderUpdateQueued) IF ( bRestockQueued ) bRestockQueued = False bRestockNoWait = False ENDIF IF ( bGenderUpdateQueued ) bGenderUpdateQueued = False bGenderUpdating[bDefaultGender as Int] = False IF GetEnableTargetStatus() gotoState("Active") ; ### STATE ### (A) myF_InitActive() ENDIF ENDIF ENDEVENT ;======= endState ; -- FUNCTIONs -- 15 ;------------------------- FUNCTION myF_RFM(Bool bOK) ; internal helper ;------------------------- IF (SM_SKSEVersion.GetValue() == 0) RETURN ; - STOP - ENDIF ;--------------------- IF ( bOK ) RegisterForMenu("Sleep/Wait Menu") ; sWaitMenu RegisterForMenu("MapMenu") ; sMapMenu ELSE UnregisterForMenu("Sleep/Wait Menu") UnregisterForMenu("MapMenu") ENDIF ENDFUNCTION ;-------------------- FUNCTION UpdateList() ;-------------------- ; sends mannequin inventory data to the merchant chest script ;; Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": UpdateList: Purchasable: " + bPurchasable) IF ( myChestAlias ) ELSE RETURN ; - STOP - alias script is missing ENDIF ;--------------------- form[] a ; a = myInventory IF ( bPurchasable ) a = GetContainerForms() ; SKSE required !!! // Returns all base forms from the container into a new array (ObjectReference.psc) ELSE a = Utility.CreateFormArray(0) ; SKSE required !!! ENDIF myChestAlias.UpdateLists(a, self) ENDFUNCTION ;------------------------------------- Int FUNCTION CalcPriority(Form akForm) ; internal helper ;------------------------------------- ; calculates the priority of equipping a given item IF ( akForm ) ELSE RETURN 0 ; / 1 no priority ENDIF ;--------- int iPriority = 0 armor AR = akForm as Armor ; AR = testArmor IF ( AR ) int iSlotMask= AR.GetSlotMask() ; SKSE required !!! int i = 0x01 ; i = iTestSlot WHILE (iT < AR.kSlotMask61) IF Math.LogicalAnd(i, iSlotMask) ; SKSE required !!! IF (i == AR.kSlotMask32) iPriority += 0x800 ELSEIF (i == AR.kSlotMask30) || (i == AR.kSlotMask31) || (i == AR.kSlotMask41) || (i == AR.kSlotMask42) || (i == AR.kSlotMask43) ; head slots iPriority += 0x080 ENDIF iPriority += 0x010 ; additional slot counter update ENDIF i = i * 2 ENDWHILE ENDIF i = akForm.GetGoldValueInt() / 10 ; gold value as tiebreaker IF (i > 0x080) i = 0x080 ENDIF RETURN (iPriority + i) ; /2 gold priority ENDFUNCTION ;------------------ FUNCTION EquipAll() ;------------------ form[] a ; a = myInventory a = GetContainerForms() ; SKSE required !!! int j int i = a.Length ; iMax = iLength int iMax = i IF (i > 1) int[] b = CreateIntArray(i) ; SKSE required !!! ; b = iPriorityList WHILE (i > 0) i = i - 1 b[i] = CalcPriority( a[i] ) ENDWHILE i = 1 ; *** sort by insertion *** WHILE (i < iMax) int T = b[i] ; T = iTempPriority form fm = a[i] ; fm = TempForm ; ---------------------------- j = i - 1 WHILE (j >= 0) && (b[j] > T) b[j+1] = b[j] ; priority a[j+1] = a[j] ; inventory j = j - 1 ENDWHILE b[j+1] = T a[j+1] = fm ; ---------------------------- i = i + 1 ENDWHILE ENDIF bool bOK = self.GetActorBase().GetSex() as Bool ; bOK = myGender WHILE (i > 0) i = i - 1 armor AR = a[i] as Armor ; AR = testArmor IF ( AR ) ; makes sure only armour is going to be equipped bool bFound = TRUE j = AR.GetNumArmorAddons() ; makes sure only armour with visible models are equipped // SKSE required !!! WHILE (j > 0) j = j - 1 ArmorAddon ArA = AR.GetNthArmorAddon(j) ; ArA = testAddon IF (ArA.GetModelPath(False, False) == "") ; SKSE required !!! IF ( bOK ) IF (ArA.GetModelPath(False, TRUE) == "") bFound = False ENDIF ELSE bFound = False ENDIF ENDIF ENDWHILE IF ( bFound ) self.EquipItem(a[i], TRUE) ENDIF ENDIF ENDWHILE ENDFUNCTION ;---------------------------------------------- Actor FUNCTION GetOwner(Bool bFullCheck = TRUE) ;---------------------------------------------- ;finds the mannequins owner(s) and, optionally, if they are available to sell merchandise. IF ( myOwningFaction ) ELSE RETURN None ; /1 no faction, no actor ENDIF ;--------- actor myOwner objectReference playerRef = Game.GetPlayer() as ObjectReference int i = PossibleOwners.Length WHILE (i > 0) i = i - 1 actor aRef = PossibleOwners[i] IF (aRef) && !aRef.IsDead() && !aRef.IsDisabled() && (aRef.GetFactionRank(JobMerchantFaction) >= 0) && (aRef.GetFactionRank(myOwningFaction) >= 0) IF ( bFullCheck ) IF aRef.IsActivationBlocked() || aRef.IsInCombat() || (aRef.GetDistance(playerRef) > iSaleRadius) ELSE SM_IsVendingFFSelf.Cast(aRef as ObjectReference, aRef as ObjectReference) SM_IsTalkingFFSelf.Cast(aRef as ObjectReference, aRef as ObjectReference) Utility.WaitMenuMode(0.05) IF SM_VendingActorList.HasForm(aRef) && !SM_TalkingActorList.HasForm(aRef) myOwner = aRef ENDIF aRef.DispelSpell(SM_IsTalkingFFSelf) aRef.DispelSpell(SM_IsVendingFFSelf) ENDIF ELSE myOwner = aRef ENDIF ENDIF ENDWHILE RETURN myOwner ; /2 ENDFUNCTION ;---------------------- Int FUNCTION GetIndex() ;---------------------- ;Loops through the list of quests ;The last completed quest (or blank value in the list) will be used as the index number ;to determine which leveled list is added and/or purchase flag used. If no list exists, the index is zero int iMax = QuestCompleteReqs.Length IF (iMax == 0) RETURN 0 ENDIF ;--------- int n = 0 ; n = outfit_index int i = 0 WHILE (i < iMax) quest q = QuestCompleteReqs[i] IF (!q) || q.IsCompleted() n = i ENDIF i = i + 1 ENDWHILE RETURN n ENDFUNCTION ;------------------------------------------------------- LeveledItem FUNCTION GetOutfit(Bool abUsePaired = False) ;------------------------------------------------------- foam_SM_StoreMannequinActor ps = self ; ps = testMannequin IF ( abUsePaired ) ps = myPairedMannequin ENDIF int i = ps.GetIndex() ; i = iQuestIndex leveledItem LI ; LI = testOutfit IF (i < ps.OutfitList.Length) LI = ps.OutfitList[i] ENDIF RETURN LI ENDFUNCTION ;---------------------------------------------------- Bool FUNCTION RestockInventory(Bool abNoWait = False) ; called external only ;---------------------------------------------------- ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Restock: Called") IF (self.GetState() == "Active") ELSE Return False ; /1 state does not matching ENDIF ;--------- IF GetEnableTargetStatus() ELSE gotoState("Inactive") ; ### STATE ### (I) myF_InitInactive() Return False ; /2 ENDIF ;--------- IF GetOwner(false) ELSE Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Restock: WARNING: No owner found") Return False ; /3 ENDIF ;--------- Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": Restock: Queued, will be registered again") bRestockQueued = TRUE bRestockNoWait = abNoWait RegisterForSingleUpdate(0.1) ; be careful with this ! Return TRUE ; /4 well done! ENDFUNCTION ;----------------------------------------------------------------------- FUNCTION RemoveRegisteredItem(Form akRemoveForm = None, Int aiCount = 1) ; called external only ;----------------------------------------------------------------------- IF (akRemoveForm) && (self.GetState() == "Active") ELSE RETURN ; - STOP - form is None /or/ state does not matching ENDIF ;--------------------- self.AddInventoryEventFilter(SM_EmptyFormList) self.RemoveItem(akRemoveForm, aiCount, TRUE) self.RemoveInventoryEventFilter(SM_EmptyFormList) self.QueueNiNodeUpdate() ENDFUNCTION ;--------------------- FUNCTION PopToMarker() ;--------------------- IF ( bPopping ) RETURN ; - STOP - ENDIF ;--------------------- moves the mannequin to its assigned XMarkerHeading bPopping = TRUE ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": PopToMarker: Disabled: " + IsDisabled() + ": 3DLoaded: " + Is3DLoaded()) self.MoveTo( self.GetLinkedRef() ) ; myMarker self.QueueNiNodeUpdate() bPopping = False ENDFUNCTION ; #################### ; ### UpdateGender ############################################################################### ; #################### ;------------------------------------ Bool FUNCTION GetEnableTargetStatus() ;------------------------------------ IF (self.GetState() == "Error") Return False ; /1 ENDIF ;--------- IF (!SM_MiscEnableMarker) || SM_MiscEnableMarker.IsDisabled() Return False ; /2 ENDIF ;--------- bool bOK = False ; bOK = bGenderMatch IF ( myPairedMannequin ) int i = SM_MannequinGenders.GetValueInt() ; i = iMannequinGenders IF (i > 0) ; 1,2 bOK = (self.GetActorBase().GetSex() == (i - 1)) ; ((i == 1) && self.GetActorBase().GetSex() == 0) || ((i == 2) && self.GetActorBase().GetSex() == 1) ELSE i = SM_MannequinGenderOverrideActorList.Find(self as Form) IF ( bDefaultGender ) bOK = (i < 0) ; (bDefaultGender && !bOverride) ELSE bOK = (i >= 0) ; self found in formlist ; (!bDefaultGender && bOverride) ENDIF ENDIF ELSE bOK = TRUE ENDIF IF ( bOK ) ELSE Return False ; / 3 ; bGenderMatch == false ENDIF ;--------- IF ( myEnableParent ) ELSE Return TRUE ; / 4 ENDIF ;--------- IF (bReverseEnableStatus) && myEnableParent.IsDisabled() Return TRUE ; / 5 ENDIF ;--------- IF (!bReverseEnableStatus) && !myEnableParent.IsDisabled() Return TRUE ; / 6 ENDIF ;--------- Return False ; / 7 ENDFUNCTION ;--------------------------- Bool FUNCTION UpdateGender() ; state "Active" only ;--------------------------- ;; Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": OnUpdate: State: ACTIVE: Updating gender") IF ( bGenderUpdateQueued ) ; *T* ELSE Return False ENDIF ;--------- bool bActive = GetEnableTargetStatus() bGenderUpdating[bDefaultGender as Int] = False int i = 0 ; i = iLoops WHILE (bGenderUpdating[0]) || (bGenderUpdating[1]) i = i + 1 Utility.WaitMenuMode(0.1) ENDWHILE bool bOK = (myPairedMannequin) && (!bActive) ; bOK = bChanged IF (bOK) && myPairedMannequin.GetEnableTargetStatus() WHILE myPairedMannequin.IsDisabled() Utility.WaitMenuMode(0.1) Utility.Wait(0.1) ENDWHILE TransferItems() myPairedMannequin.UpdateList() UpdateList() myPairedMannequin.EquipAll() gotoState("Inactive") ; ### STATE ### (I) myF_InitInactive() ENDIF IF ( bRestockQueued ) ;Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": UpdateGender: Restock flag: " + bRestockQueued) RegisterForSingleUpdate(0.5) ENDIF bGenderUpdateQueued = False ; *** RETURN bOK ENDFUNCTION ;----------------------- FUNCTION TransferItems() ; internal helper // return Bool has been removed (ReDragon) ;----------------------- IF ( myPairedMannequin ) ELSE Debug.Trace("SM: StoreMannequinActor: TransferItems: WARNING: No paired mannequin!") ;;; Return False RETURN ; - STOP - ENDIF ;--------------------- IF self.IsDisabled() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": TransferItems: WARNING: Mannequin: " + sMyID + ": Disabled!") ;;; Return False ; missing !!! RETURN ; - STOP - ENDIF ;--------------------- IF myPairedMannequin.IsDisabled() Debug.Trace("SM: StoreMannequinActor: " + sMyID + ": TransferItems: WARNING: Mannequin: " + myPairedMannequin.sMyID + ": Disabled!") ;;; Return False ; missing !!! RETURN ; - STOP - ENDIF ;--------------------- ; now copy all data to paired mannequin myPairedMannequin.bPurchasable = bPurchasable myPairedMannequin.myOutfit = myOutfit myPairedMannequin.SetFactionRank(SM_StoreMannequinFaction, bPurchasable as Int) myPairedMannequin.bUsePaired = TRUE bUsePaired = False ; disables OnItemAdded/Removed events, transfers items, then re-enables the events myPairedMannequin.AddInventoryEventFilter(SM_EmptyFormList) self.AddInventoryEventFilter(SM_EmptyFormList) self.RemoveAllItems(myPairedMannequin, False, TRUE) ; remove all items, be careful with this method ! self.RemoveInventoryEventFilter(SM_EmptyFormList) myPairedMannequin.RemoveInventoryEventFilter(SM_EmptyFormList) ;;; Return TRUE ENDFUNCTION ;----------------------------------------------------------- Bool FUNCTION QueueUpdateGender(Bool abChangeGender = False) ; called external only ;----------------------------------------------------------- WHILE (bGenderUpdateQueued) Utility.WaitMenuMode(0.1) ENDWHILE bGenderUpdateQueued = TRUE UnRegisterForUpdate() bGenderUpdating[bDefaultGender as Int] = TRUE RegisterForSingleUpdate(0.1) Return TRUE ENDFUNCTION ; ################ ; ### Purchase ################################################################################### ; ################ ;----------------------------------------------------- Bool FUNCTION GetPurchasable(Bool abUsePaired = False) ;----------------------------------------------------- actor player = Game.GetPlayer() bool bOK = False faction fac int i = ExcludedFactions.Length WHILE (i > 0) && (!bOK) i = i - 1 fac = ExcludedFactions[i] IF (fac) && Player.IsInFaction(fac) bOK = TRUE ENDIF ENDWHILE IF ( bOK ) Return False ; /1 player is part of excluded faction ENDIF ;--------- i = RequiredFactions.Length bOK = (i == 0) ; empty array, player is included by default WHILE (i > 0) && (!bOK) i = i - 1 fac = RequiredFactions[i] IF (fac) && Player.IsInFaction(fac) bOK = TRUE ENDIF ENDWHILE IF ( bOK ) ELSE Return False ; /2 player is not required ENDIF ;--------- foam_SM_StoreMannequinActor ps = self ; ps = testMannequin IF ( abUsePaired ) ps = myPairedMannequin ENDIF i = ps.GetIndex() IF (i >= ps.bPurchaseFlags.Length) Return TRUE ; /3 ENDIF ;--------- bOK = ps.bPurchaseFlags[i] RETURN bOK ; /4 ENDFUNCTION Link to comment Share on other sites More sharing options...
Recommended Posts