Difference between revisions of "Papyrus FAQs"

9,025 bytes added ,  01:47, 7 June 2016
m
Fixed indentation on script elements
imported>Digitalparanoid
(created / still populating from forum)
 
imported>Kicoax
m (Fixed indentation on script elements)
 
(18 intermediate revisions by 2 users not shown)
Line 16: Line 16:
=== To whom do I send fan/hate mail about Papyrus? ===
=== To whom do I send fan/hate mail about Papyrus? ===


[https://community.bethesda.net/people/SmkViper SmkViper] created Papyrus and actively helps scripters in the official Fallout 4 Creation Kit forum.
[https://community.bethesda.net/people/SmkViper SmkViper] created Papyrus and actively helps scripters in the [https://community.bethesda.net/community/fallout/fallout-4/creation-kit-beta official Fallout 4 Creation Kit forum.]


== First Steps ==
== First Steps ==
Line 55: Line 55:
=== When I try to compile scripts outside the Creation Kit, my scripts always fail to compile. I'm sure my syntax is correct!  ===
=== When I try to compile scripts outside the Creation Kit, my scripts always fail to compile. I'm sure my syntax is correct!  ===


For the sake of simplicity, I recommend using my Windows batch script (.bat file) for compiling scripts outside the Creation Kit.
For the sake of simplicity, use fireundubh's Windows batch script (.bat file) for compiling scripts outside the Creation Kit.


# Save [https://gist.github.com/fireundubh/667b685ad9f5e3358a9b596b42036949 compile_fo4.bat] to <code>C:\Scripts</code>. Create the directory if needed.
# Save [https://gist.github.com/fireundubh/667b685ad9f5e3358a9b596b42036949 compile_fo4.bat] to <code>C:\Scripts</code>. Create the directory if needed.
Line 70: Line 70:
compile_fo4 project.ppj          // compile the specified project file in the Projects directory   
compile_fo4 project.ppj          // compile the specified project file in the Projects directory   
</pre>
</pre>
If you have the old version of this script (previously posted here), I recommend updating!


=== What are all of the parameters for PapyrusCompiler.exe? What do they mean? ===
=== What are all of the parameters for PapyrusCompiler.exe? What do they mean? ===
Line 169: Line 167:
<PapyrusProject xmlns="PapyrusProject.xsd" Flags="Institute_Papyrus_Flags.flg" Output="D:\Steam\steamapps\common\Fallout 4\Data\Scripts" Optimize="true" Release="true" Final="true">   
<PapyrusProject xmlns="PapyrusProject.xsd" Flags="Institute_Papyrus_Flags.flg" Output="D:\Steam\steamapps\common\Fallout 4\Data\Scripts" Optimize="true" Release="true" Final="true">   
   <Imports>   
   <Imports>   
    <Import>D:\Steam\steamapps\common\Fallout 4\Data\Scripts\Source\Base</Import> 
     <Import>D:\Steam\steamapps\common\Fallout 4\Data\Scripts\Source\User</Import>   
     <Import>D:\Steam\steamapps\common\Fallout 4\Data\Scripts\Source\User</Import>   
    <Import>D:\Steam\steamapps\common\Fallout 4\Data\Scripts\Source\Base</Import>   
   </Imports>   
   </Imports>   
   <Scripts>   
   <Scripts>   
  <Script>AutoLoot\Fragments\Terminals\TERM_dubhAutoLootMenuAdvance_0100272C.psc</Script>   
    <Script>AutoLoot\Fragments\Terminals\TERM_dubhAutoLootMenuAdvance_0100272C.psc</Script>   
  ...   
    ...   
   </Scripts>   
   </Scripts>   
</PapyrusProject>
</PapyrusProject>
Line 198: Line 196:
   
   
== Advanced ==
== Advanced ==
=== Are data types distinct? Or are they ultimately floats as in the Oblivion scripting language? ===
In the Oblivion scripting language, bools were integers and integers were floats; the bool and integer data types were therefore clarifying abstractions and ultimately floats. However, in Papyrus, each data type is distinct, but although each data type is distinct, the compiler will automatically cast certain types to other types that are determined to be safe.


=== Are While loops safe to use for persistent or continuous effects? ===
=== Are While loops safe to use for persistent or continuous effects? ===
According to SmkViper:
<pre>
Saves operate generally the same way they did with Skyrim. Any running threads
are saved so they can be resumed when the save is loaded. This means any while
loops running will be saved into the save game, because of course they need to
continue running when the save is loading. However because they are saved, any
patch to the function that you release in a mod can't be applied to that thread
until it exits the function (which means the while loop has to end).
While loops also keep the thread alive, which consumes some memory, as the
thread and all its stack data and calling functions stack data need to stick
around. If you end up with >100 threads running at once for over a few seconds,
Papyrus assumes something is wrong and starts spitting out stack dumps to the
log so you can find and fix the problem. If the while loops aren't sleeping (via
Wait or some other latent call) they can contribute to script lag as the VM has
to take time to process the thread in addition to everything else it's running.
Timers replace OnUpdate partially in response to the problems with continual
update event registrations (they are equivalent to single update registrations).
And because it's sometimes really handy to have multiple timers going at once on
different intervals.
So yes, if you need something running over a long period of time, you should be
using timer events.
All that being said, F4's version of Papyrus is much more aggressive about
cleaning out dead data if things go... missing. Not that any self-respecting
modder would do that.
</pre>
=== How do I write save-safe scripts then? ===
=== How do I write save-safe scripts then? ===
According to SmkViper:
<pre>
Removing mods and continuing with a save that relies on that mod is not and
never has been supported. Proceed at your own risk. Demons may fly out of your
monitor and spray you with silly string. (You think I'm kidding - have you heard
of Bob the modder? No? Exactly.)
</pre>
However, here are some evolving guidelines that can be followed to reduce the incidence of uninstallation issues and/or shift the blame to user error:
# Instead of using a <code>While</code> loop, use an <code>OnTimer()</code> event. The <code>OnTimer()</code> event is Fallout 4's version of Skyrim's <code>OnUpdate()</code>. Refer to the question and answer below for more information.
# Avoid endless <code>While</code> loops that cannot terminate after the plugin has been removed. For example, a <code>While Player.HasPerk(kPerk)</code> loop can terminate if <code>kPerk</code> references a perk unique to the plugin. Removing the plugin removes the perk, and so when the player loads a save, the thread can then exit. However, if <code>kPerk</code> references a vanilla perk, <code>HasPerk()</code> will always evaluate to <code>True</code> and the <code>While</code> loop will continue iterating over instructions that may require objects which no longer exist, and therefore keep the thread alive, consuming memory, contributing to script lag, and producing errors. Refer to the above question for more information about how <code>While</code> loops and saves interact.
# Always finish your code as fast as possible. This ultimately means making optimizations to both your scripts and your systems. For example, if you need a <code>While</code> loop to iterate through a large formlist or array, chunk the formlist or array, and in between each chunk, give the loop an opportunity to exit. You might also want to ensure that there are enough conditions in place that your script can run only when needed, do its job, and get out when the job's done.
# Provide the user with the ability to "uninstall" your mod while in the game. This can be done by offering users a "Shutdown" option in a holotape menu that forcibly exits all continuously running scripts. Some users may not take the time to take advantage of this feature, so it remains important to ensure that you've taken the necessary steps to write save-safe scripts.
# Finally, warn users that uninstalling mods and continuing with saves that rely on those mods is unsupported and unsafe.
=== How would I emulate a Break statement in a While loop? ===
=== How would I emulate a Break statement in a While loop? ===
You can emulate a <code>Break</code> statement like so:
<pre>
Bool bBreak = False 
While (<primary_condition>) && !bBreak   
  // do stuff   
  If <secondary_condition>   
    bBreak = True   
  EndIf     
EndWhile
</pre>
The alternative is to use [https://github.com/Orvid/Caprica Orvid's Caprica compiler], which allows you to use the <code>For</code>, <code>ForEach</code>, <code>Break</code>, and <code>Continue</code> statements.
Please remember that Caprica is an unofficial, alternative compiler that is not supported by Bethesda Softworks. Any questions or issues regarding Caprica should be directed to Orvid.
=== Are scripts saved in save games? ===
=== Are scripts saved in save games? ===
According to SmkViper:
<pre>
Scripts are not saved in your save game. Script variable values and currently running functions are saved so they can be resumed.
</pre>
=== What's the alternative to OnUpdate, RegisterForUpdate, and RegisterForSingleUpdate? ===
=== What's the alternative to OnUpdate, RegisterForUpdate, and RegisterForSingleUpdate? ===
You can use the [[OnTimer - ScriptObject|OnTimer]] event alongside the [[StartTimer - ScriptObject|StartTimer]] and [[CancelTimer - ScriptObject|CancelTimer]] functions.
=== Are arrays limited to 128 elements? ===
=== Are arrays limited to 128 elements? ===
* Arrays that are created by scripts via the <code>New</code> operation or <code>Add()</code> function '''are''' limited to 128 elements.
* Arrays returned by native functions, and editor-filled array properties, are '''not''' limited to 128 elements.
=== How do I create a dynamic array? ===
=== How do I create a dynamic array? ===
Dynamic arrays grow to fit the number of elements.
To create an empty dynamic array, pass <code>0</code> as the size.
<code>
ObjectReference[] dynamicArray = new ObjectReference[0]
</code>
Also:
* You can pass in a variable or calculation for the array size.
* You can call <code>Add()</code> and <code>Remove()</code> on the array to add and remove items.
=== How do I create key-value pairs? ===
=== How do I create key-value pairs? ===
=== When I call Activate() on a stack of items dropped by the player, only one item in the stack is returned. ===
 
=== Why do the FindAllReferences* functions appear to return references in the player's inventory? ===
According to SmkViper:
=== Does the FindAllReferencesWithKeyword function accept a Formlist as a parameter? ===
 
<pre>
You can basically fake a map using structs and the new FindStruct function. Just
make one struct variable your key, and the other the value, and then run Find on
the key. (Unlike maps, duplicates are permitted since it's just an array.)
</pre>
 
=== In the Papyrus log, why do the FindAllReferences* functions appear to return references in the player's inventory? ===
 
According to SmkViper:
 
<pre>
When a persistent reference is moved into a container, the container basically
ends up pointing to the original, now mostly-deleted, reference in the world.
FindAllReferences* is simply finding that mostly-dead reference in world and
returning it. If you were to pick up the item, and run far enough away,
FindAllReferences* likely will not find it, because the original reference is
nowhere near your location.
 
The inventory system is not designed to be iterated over in such a manner, and
Papyrus is generally is set up to discourage that kind of access by not providing
it at all, and (in F4) preventing you from receiving inventory events without
filters in place.
 
The good news is that several Papyrus functions can now interact more reliably
with the inventory in F4 so, for example, GetBaseObject will always work, no
matter if the object is in the world, in a container, persistent, or not persistent.
</pre>
 
=== Can structs store arrays, other structs, and var types? Can arrays store arrays? ===
=== Can structs store arrays, other structs, and var types? Can arrays store arrays? ===
According to SmkViper:
<pre>
Structs are intentionally forbidden from storing arrays, other structs, and var
types to eliminate possible sources of circular references.
Arrays cannot store arrays for the same reason (and because it requires more
work on the part of the type syntax).
</pre>
=== Can CallFunction() be used for reflection? ===
=== Can CallFunction() be used for reflection? ===
According to SmkViper:
<pre>
CallFunction is designed for inter-mod communication and does not have type
information as a result. It is very finicky to use and the compiler can't tell
you if you're doing it wrong. It also is going to be more expensive than a
normal function call both in terms of how fast it runs and the amount of memory
used because you're going through the abstraction of a var array. Therefore -
avoid using it unless you absolutely have to. It's there to solve a very
particular problem, not be a general catch-all for reflection.
</pre>
=== How do I use custom events instead of CallFunction()? ===
=== How do I use custom events instead of CallFunction()? ===
=== I can't figure out what scripts govern the supply lines! Halp. ===
 
According to SmkViper:
 
<pre>
You can pass your script as one of the parameters of the custom event and they can call
a function on your script in return to give the value back. The big advantage of custom
events is they can dispatch in parallel, greatly speeding up processing.
</pre>
 
== Troubleshooting ==
 
=== What happens when Papyrus is "overloaded"? ===
 
According to SmkViper:
 
<pre>
The only thing "overloading" the script system will ever do will be to slow down all
script processing. You could theoretically run your system out of memory at some point
but the game would be unplayable long before then.
</pre>
 
=== Does Papyrus affect framerate? Or "drop" function calls? Or "lose" entire scripts? ===
 
No. According to SmkViper:
 
<pre>
Papyrus never affects framerate, nor does it "drop" function calls. It doesn't "drop
scripts" either, whatever the heck that would mean) You wouldn't have an "overload"
problem if it did, because then it would just throw things out instead of slow down,
but that way lies madness - randomly not running function calls is just silly.
</pre>
Anonymous user