Jump to content

[LE] Cross referencing variables


Recommended Posts

I make a new thread to have a more clean one. I hope. :D

 

I did watch a video this morning about how to use Questvariables inside a quest. He did say that if we will be able to use the variable outside the script, we need to make it Conditional? Well I did that and made it into a property as he did.

 

Well this mod is meant to have 2 spell scripts that share ObjectReferences, so I need to store an array somewhere, and it will max hold 10 refereferences. So one spell writes references and another read them. So far I tried this:

 

QuestScript ( That is how we store common stuff in Oblivion for cross references at least )

Scriptname PekRefsSCR extends Quest conditional
{Holder for ObjectReferences}

 ObjectReference[] Property SummonAbleRefARR Auto conditional
 
Event OnInit()
	SummonAbleRefARR = New ObjectReference[10]
EndEvent

And the Marking spell, looks like this right now:

Scriptname PekMarkTargetRef extends ActiveMagicEffect  

Quest Property PekRefsQST  Auto   

Event OnEffectStart(Actor akTarget, Actor akCaster) 

	ObjectReference Testactor

	MiscUtil.PrintConsole ("Caster is: " + akCaster as String)
	MiscUtil.PrintConsole ("Target is: " + akTarget as String)

	PekRefsQST.SummonAbleRefARR[0] = akTarget
	Testactor = PekRefsQST.SummonAbleRefARR[0]
	
	MiscUtil.PrintConsole ("Target in Quest is: " + Testactor as String)	
	
EndEvent

The problem now is that I do not know how to write into the SummonAbleRefARR from the spell, as this does not work. Maybe I need to use the ScriptName instead of calling the quest in the spell? In Oblivion it is the Quest or Reference that holds the variables in game, not the scripts, so those names are never used in cross referencing, but in the other hand, we can have several scripts I did noticed in a single quest or reference in Skyrim, so...

 

Is there a better and easier way to do this in Skyrim? Globals only hold floats, but I also saw somewhere that we could translate the Hex reference number into a dec and store it as an int and vice verse? I do really want to learn how to store and read Quest variables from other sources really, no matter what. I have searched for tutorials, and those I find, so far did not make me brighter. So the penny must fall down.

 

Someone mentioned Alias, well Shered did. I did look it up in CK Wiki, and I did not get ny brighter.

 

I know Ref and Short, Long, Float from Oblivion and Arrays and Strings from OBSE, that is all we have in that game and coding without OBSE is out of the question for me as it really make life so much easier. :D

 

I know, forget about Oblivion, but that is what I know and can control right now. Oblivion + OBSE.

Edited by Pellape
Link to comment
Share on other sites

Making a variable conditional, basically just means you grant that variable permission to be accessed by the CK when building conditions for something. Like AI packs for example

 

You have to add the word Conditional to the script header string at the top, as well as append that word to each Property you want to promote to usable-by-CK status

 

From my experience there is rarely a situation where using VMScriptVariable conditions were "the way to go" but your mileage and preference may vary. I do recommend messing with them for a bit, if no other reason than to help understand how they tick. Kinda up to you if thats the method you settle on. I did notice Beth uses these in Fallout 4 a lot

Link to comment
Share on other sites

You want to assign the quest as a property or get it with GetFormFromFile and then cast it into the script attached to the quest. The variable that you will use to reference the script will need to be defined as the script. In the following modification of your code, we create a local variable PekQuestScript defined as the type PekRefsSCR. Then just inside the event we take the quest from the PekRefsQST property, cast it to PekRefsSCR and assign the result to the PekQuestScript variable. From there we use the PekQuestScript variable followed by a period to access any property or function that is on that script. Neither the quest script or properties need to be "conditional" under this scenario.

Scriptname PekMarkTargetRef extends ActiveMagicEffect  

Quest Property PekRefsQST  Auto   
PekRefsSCR PekQuestScript

Event OnEffectStart(Actor akTarget, Actor akCaster) 
  PekQuestScript = PekRefsQST as PekRefsSCR
  ObjectReference Testactor

  MiscUtil.PrintConsole ("Caster is: " + akCaster as String)
  MiscUtil.PrintConsole ("Target is: " + akTarget as String)

  If PekQuestScript.SummonAbleRefARR[0] = akTarget
    Testactor = PekQuestScript.SummonAbleRefARR[0]
  EndIf
	
  MiscUtil.PrintConsole ("Target in Quest is: " + Testactor as String)	
	
EndEvent

While any script could technically be reached this way and papyrus won't complain about it either, one should not try obtaining property values or utilizing functions on short lived scripts such as those attached to magic effects. Instead those scripts need to reach out to something that will stick around for a longer period of time such as quest scripts or persistent object references.

 

Depending on the task at hand, another alternative route would be to utilize SKSE's custom mod events. One script sends the event when desired and any other script with that event registered will receive the event and then do something.

Link to comment
Share on other sites

Ahh, I think I understand more now IsharaMeridan. Well even if it is is just a short time lived script, it will do its job. I think I make a Messagebox in the quest script, with options where the player want to store the target. I guess there are tutorials for SKSE custom mod events? I will try to find one :D

 

That might be so Shered as the tutorial that I peeked at was for how to read the variable from within the quest dialogue, no to add something to an int that was in the quest script from the dialogue, and the Principe should be exactly the same.

 

Well i will remove Conditional, if it is not needed as I do not plan to add it in any UI made stuff, like packages not from dialogues in quests or whatever, but still it is always good to learn something new at least, as we never know if when I will need it. Well I think I make a temp Variable in the quest script, send the AkTarget to it, pop up a Messagebox with Info and 1-9 and then assign that Tempvariable to the Array.

 

Arrays can never be empty in OBSE, as we must fill all slots with something, well up to the point where we add more slots with a separate function that is, well I fill the ones I plan to use with junk if I want the array to be dynamic, with an Empty Ref mostly, well Ref 00000000 and then I can use the array in any order I want. I guess that is not the case in Skyrim? That we can use MyARR[4] immediately without filling 0-3 with something? It is something that made me furious and pissed off when I started to use arrays in OBSE, was that I forgot to initiate them properly, filling up the slots with junk and got a lot of nice error message in game as the compiler does not care for sure. :D Well that's the way the OBSE team made them. It is good to know, as otherwise I need to initiate the quest script with a loop, filling all slots up with junk.

Edited by Pellape
Link to comment
Share on other sites

With Papyrus arrays can have empty entries. They do not need to be filled with something at all times.

 

There isn't really a tutorial for using custom mod events from SKSE. There are some wiki pages about it. But it is probably better to just see a working example:

 

My Inventory Management System mod for SSE utilizes it for a couple of things. The most notable one is to send an event out to the player alias scripts associated with each category container to disable their current inventory event filter and enable a different filter in order to prevent the OnItemAdded and / or OnItemRemoved events from running on those scripts when items are transferred for use with crafting stations and merchants. And then another event to switch the inventory event filters back around when the transferring is complete.

 

 

The script containing the OnKeyDown and OnKeyUp events includes this snippet that sends the events (for just one of the scenarios)

 

If Ref == None
If ValidMerchant == True || ValidStation == True ;|| bSortOnClose == True
  SendModEvent("abim_ClearFiltersEvent")
  Debug.Notification(LoopInventoryAndTransferItems("IMS: Sorting items, please be patient"))
; bSortOnClose = false
  ValidFavor = false
  bPlanter = false
  bShowOnce = false
  TransferToggle = false
  ValidMerchant = false
  ValidStation = false
  SendModEvent("abim_RestoreFiltersEvent")
EndIf

The SKSE function is SendModEvent. The parameter is an internal name for the event. And if you were curious, that is a local function call inside the Notification statement. It works because it is designed to return a string for the notification statement once it has completed other stuff.

 

The scripts receiving the events have the following (among other things):

In their OnUpdate event triggered by the OnInit event and the OnPlayerLoadGame event:

 

  RegisterForModEvent("abim_ClearFiltersEvent","ClearFilters")
  RegisterForModEvent("abim_RestoreFiltersEvent","RestoreFilters")

RegisterForModEvent is the SKSE function. The first parameter is the internal name. The second parameter is the name of the custom event that Papyrus will be triggering.

 

And finally, the actual events

 

Event ClearFilters(string eventName, string strArg, float numArg, Form sender)
  RemoveAllInventoryEventFilters()
  AddInventoryEventFilter(TheRepItem)
; Debug.Trace("IMS:"+TheRepItem.GetName()+" ran ClearFilters event.")
EndEvent

Event RestoreFilters(string eventName, string strArg, float numArg, Form sender)
  If abim_IMS_ASGV.GetValue() != 0.0
    If Game.GetModByName("IMS_Patch.esp") != 255
      If RepItemIndex == 4
        If abim_IMS_StationItemList != None
          AddInventoryEventFilter(abim_IMS_StationItemList)
        EndIf
        If abim_IMS_TemperAStationItemList != None
          AddInventoryEventFilter(abim_IMS_TemperAStationItemList)
        EndIf
        If abim_IMS_TemperWStationItemList != None
          AddInventoryEventFilter(abim_IMS_TemperWStationItemList)
        EndIf
      Else
        If abim_IMS_StationItemList != None
          AddInventoryEventFilter(abim_IMS_StationItemList)
        Else
          AddInventoryEventFilter(abim_IMS_ItemList)
        EndIf
      EndIf
    Else
      AddInventoryEventFilter(abim_IMS_ItemList)
    EndIf
  EndIf
; Debug.Trace("IMS:"+TheRepItem.GetName()+" ran RestoreFilters event.")
EndEvent

Note that you can send more than just the event name. You can include a string and a float. The form parameter is auto-filled with the form holding the script that sent the event.

 

 

 

 

Going back to getting property variables from other scripts. You can also run functions on other scripts in the same manner. You can even pass information from one script to the other in this way. Let's say that you want to store an NPC to summon in an array on a quest script. Instead of reaching out to the quest script to get the property and manipulate everything on the magic effect script, you can call a function on the quest script and pass the NPC to it. The quest script would then handle storing it into the array.

Taking your previous code for use as an example:

 

 

Scriptname PekRefsSCR extends Quest
{Holder for ObjectReferences}
 
ObjectReference[] Property SummonAbleRefARR Auto
 
Event OnInit()
SummonAbleRefARR = New ObjectReference[10]
EndEvent
 
Function AssignActorsToArray(Actor myActor, Int index)
  If myActor ;make sure it has a valid value
    SummonAbleRefARR[index] = myActor
  EndIf
  MiscUtil.PrintConsole ("Target in Quest is: " + SummonAbleRefARR[index].GetName()) 
EndFunction
 
 
Scriptname PekMarkTargetRef extends ActiveMagicEffect  
 
;properties
Quest Property PekRefsQST  Auto
Int Property myIndex Auto
 
;local vars
PekRefsSCR PekQuestScript
 
Event OnEffectStart(Actor akTarget, Actor akCaster) 
  PekQuestScript = PekRefsQST as PekRefsSCR
  ObjectReference Testactor
 
  MiscUtil.PrintConsole ("Caster is: " + akCaster.GetName())
  MiscUtil.PrintConsole ("Target is: " + akTarget.GetName())
 
  PerQuestScript.AssignActorsToArray(akTarget,myIndex)
 
EndEvent

 

 

Edited by IsharaMeradin
Link to comment
Share on other sites

Yes!!! This looks awesome :smile: Mega Thanks for the examples IsharaMeradin as that is exactly what I needed. :smile: I also feel I understand more now about how Skyrim works, one slow step in a time :D

 

I did look through all event types yesterday, tried to find something similar to "Begin GameMode" from Oblivion but I start to get the impression that it is not needed? I will make a test to see if it is so really. Initiations for the scripts is always outside the Begin Blocks but everything else is inside one of all the different Begin ThisOrThat -> like events in Skyrim. I did try Event OnUpdate but nothing did happen, and we where also warned to use it as it can stack up stuff, the WIKI did claim. So if I want to run specifik checks every time the script is running, do I use a special event or nothing at all? I will peek in some Skyrim scrips today, to see if I can get wiser. Trial and error is always nice, but my game starts so bloody slow, you know... :wink: I make some changes, and then test them in game, and I stay there, playing, mostly for a while, until I decide to make a new change.

 

Well I was wrong. Added some code at the end of the script, which I was forced to move inside an event... :wink: So which Event is closest to Begin GameMode?

 

Can it also be so the if we initiate a variable within an event of within a function, in an Object, Ref or Quest Script, that they are only saved in RAM during that Event and then deleted when the event did what it was supposed to do? I do start to get an impression that it might be so at least, just a hunch or an observation and speculation, nothing else... But if a variable is not needed anywhere else, it sure is convenient if it really is so, well consider all bloody variable that is in the game all together? The reason I ask is if I initiate temp variables as an example, they should not be initiated in the top of the script, but maybe inside each event and inside each function, as I seen in some scripts? I first thought it did look so odd and out of place, but maybe it is not?

 

By the way, all changes you did on my script works perfect. I did had to make some minor edits as the Questscript does not have any values to start with, as it is the mark script that is the only one adding values, well so far at least. :D So the mark script cannot check for anything that do not exist, before trying to present it to the player. :D

 

It also looks like I can turn this one into an ESL, at least so far, so I will try that. :D I will try it from Wrye Bash. If it fails, I do it from CK :wink:

Edited by Pellape
Link to comment
Share on other sites

Papyrus scripts do not sit there running throughout the entire game. Instead, they sit patiently waiting for the game to trigger an event or for one of the functions on the script to be called. In that sense there is nothing similar to "Begin GameMode".

 

Re Updates:

The OnUpdate event will not run unless the script is first registered to run the event. Think of it as something similar to SKSE's mod events explained earlier. But instead of triggering the event on scripts on other objects, it triggers the event on all scripts attached to the same object. Furthermore, there are two types of registers: single and continuous. RegisterForUpdate is continuous while RegisterForSingleUpdate runs just once. The continuous type have to be unregistered when no longer needed or they will build up and cause issues over time. The single type will fail once and stop should the script / mod associated with it be removed mid-game. Thus it is always best to use the single type and re-register as needed to simulate a continuous update cycle.

 

Re local variables:

A non-property variable defined in the empty state (at top, bottom or anywhere outside of the events and functions) will be usable throughout the entire script. Such variables defined inside of an event or function are only used within that event or function and only within conditional statements below it. Examples:

 

TotalCount and PCount are both local variables stored in the empty state and technically usable anywhere in the script. In this example, while not needed in the entire script it is necessary to initialize their value outside of the event or function.

Int TotalCount = 0
Int PCount = 0
 
Event OnActivate(ObjectReference akActivator)
  TotalCount += 1
  If akActivator == Game.GetPlayer()
    PCount += 1
  EndIf
  Debug.Trace(PCount+" of "+TotalCount+" triggers were done by the player")
EndEvent

Both index and Entry in this example are local variables used within the function. Neither can be used at a level higher than where they were defined.

Function ListOutAList(FormList myList)
  Int index = (myList.GetSize() - 1)
  While index >= 0
    Form Entry = myList.GetAt(index)
    If Entry.GetName()
      Display.Trace("Entry at index "+index+" is "+Entry.GetName())
    Else
      Display.Trace("Entry at index "+index+" is "+Entry)
    EndIf
    index -= 1
  EndWhile
EndFunction

 

 

 

And to be thorough...

Event and function parameters are "built in" variables that can only be used inside of the function or event. Typically, they are used to retrieve data passed into the event or function in order to do some sort of manipulation. Either checking if a passed in value matches something or to modify a value before sending it back.

 

Property variables can also be marked as hidden. Hidden property variables can still be accessed via other scripts and can have their values changed via script as necessary. However, they are hidden from display in the Creation Kit. This can be of benefit when working with what would ordinarily be a local variable but must be a property in order for another script to gain access to its value.

 

Link to comment
Share on other sites

I do think I understand some now, so if I want to call a Messagebox from the quest script, I put it inside a custom event, make sure something trigger that event like a spell or a button. In Oblivion, as the scripts runs all the time, ones every frame, except Quest scripts, that runs ones every 3rd or 5th second, we need to make sure the script is running in the background, waiting for interrupts, like when the player clicks a button, and GetBottonPressed() picks it up. So does a script just wait at that stage then in Skyrim, like normal programs do? :D Well, VB programs do not do anything really until the user clicks a button as far as I know, well except when it gets stuck in a loop. :D

 

I think i will watch a messagebox Video really. I did read a short tutorial at Nexus but a video can always be nice as well. :D I am trying to put my Oblivion thinking a side, but it is not easy doing so really as I see how it all flows around all the time in my mind. TES 3 and 4 GameMode and MenuMode blocks was something new to me 2005 as well, as normal programs do act in a similar way as the UI does in Skyrim it seems. I really thought back then that the code stopped and waited at GetButtonPressed, like Input do in basic, no way... :D So confusing... :wink: Input in old basic or in MS-DOS, halts everything, until it gets a carriage return. :wink: PLING.

Edited by Pellape
Link to comment
Share on other sites


Not everything has to be inside custom events. If a stock event works for what you need, you can use that. Due to the inheritance of scripts a lower level script can modify the behavior of an event for a specific object or class of objects without affecting the behavior of other objects using the same event.
Assume you have designed a message box that you want to appear when a button is pressed, a lever is switched or a chain pulled.
One route would be to assign the same script to each of the three references, adjust any property values according to the object and have the message box code on the same script. No quest involved. Best used when the objects involved are all new references added by the mod.

ScriptName ButtonChainLeverMessage Extends ObjectReference
 
String Property myMessage = "Hey, you activated me!" Auto
;value should be changed in the CK i.e.
;"Hey, you pushed me!" -- button
;"Hey, you switched me!" -- lever
:"Hey, you pulled me!" -- chain
 
Event OnActivate(ObjectReference akActivator)
  If akActivator == Game.GetPlayer()
     Debug.MessageBox(myMessage)
  EndIf
EndEvent

Another route would be to assign the three references to quest aliases and assign the same script to each of the aliases, adjust any property values as needed among the three and have the message box code on that same script. A quest is involved but only to avoid incompatibilities with mods that might change stats or appearance of the objects in use. Best used when working with stock object references.

ScriptName ButtonChainLeverMessage Extends ReferenceAlias
 
String Property myMessage = "Hey, you activated me!" Auto
;value should be changed in the CK i.e.
;"Hey, you pushed me!" -- button
;"Hey, you switched me!" -- lever
:"Hey, you pulled me!" -- chain
 
Event OnActivate(ObjectReference akActivator)
  If akActivator == Game.GetPlayer()
     Debug.MessageBox(myMessage)
  EndIf
EndEvent

An overkill route would be to put the message box code on a single quest script and have separate scripts on the three objects or aliases call up a function on the quest script that runs the message box code.

ScriptName ButtonChainLeverMessage Extends ObjectReference ;ReferenceAlias depending on method
 
Quest Property myQuest Auto
Int Property myType Auto
;1 button, 2 chain, 3 lever
 
Event OnActivate(ObjectReference akActivator)
  If akActivator == Game.GetPlayer()
    myQuestScript MQS = myQuest as myQuestScript
    MQS.DisplayMyMessageBox(myType)
  EndIf
EndEvent
ScriptName myQuestScript Extends Quest
 
String myButtonText = "Hey, you pushed me!"
String myLeverText = "Hey, you switched me!"
String myChainText = "Hey, you pulled me!"
 
Function DisplayMyMessageBox(Int num)
  If num == 1
    Debug.MessageBox(myButtonText)
  ElseIf num == 2
    Debug.MessageBox(myLeverText)
  ElseIf num == 3
    Debug.MessageBox(myChainText)
  EndIf
EndFunction

All are valid but the last one is in most circumstances a bit too much and not to mention harder to follow. Only reason to do something like that is if you are already hooking into the script for other purposes.

Link to comment
Share on other sites

Cool. Thanks for your nice examples. :) Yes, i did solved it when I watched a video, message object with MessageBox checked [x]. I also tried to figure out how to send a custom string to the textarea inside a Message Object, but that does not seems possible, as I found such threads at the Bethesda forums. But I add the custom string to a debug.MessageBox(MyString) and I also got reminded of \n, for new line so I can list the references to the player. I did use a healing script, and right now, the only refs I can add to the scripts are actors. I do want to be able to add other objects, like chests and other stuff we would like to be able to summon... I will make a new spell, base it on the Alteration Open spell, that opens doors, well the player should never try to summon a door as that is very nasty for sure. That door will get useless, well I did a lot of testing with this in Oblivion and ended up with activators, as those we can summon and move in any way we like. :D

 

So far so good. I feel happy now when I understand this much better, as I feel the penny fell down, at least for now :D Thanks again for all nice help. :)

Link to comment
Share on other sites

  • Recently Browsing   0 members

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