Papyrus FAQs

Revision as of 14:13, 25 May 2016 by imported>Digitalparanoid (→‎How do I create a dynamic array?)

Resources

Why doesn't the Creation Kit's script editor have syntax highlighting?

The Creation Kit allows you to open scripts with an external text editor from the context menu, so you should use an external text editor if you want syntax highlighting and other features.

Where can I find a list of functions and their syntax that I can use in my scripts?

You should refer to the wiki and ultimately the source scripts, like Actor.psc, ObjectReference.psc, and Utility.psc. These are the "classes" that your scripts typically "extend."

Where can I find examples of how to accomplish <something> or how to use a function?

Using a free third-party program named Agent Ransack, you can search through all of the base scripts almost instantly for examples, especially with a SSD.

To whom do I send fan/hate mail about Papyrus?

SmkViper created Papyrus and actively helps scripters in the official Fallout 4 Creation Kit forum.

First Steps

Where do I find the source scripts?

  1. In the Data\Scripts directory, there is a ZIP archive named Base.zip.
  2. Extract that file using 7-Zip, WinZip, or Windows' native ZIP handler to the same folder with paths.

Why can't the Creation Kit find the source scripts?

You should have the following folders in the Data\Scripts directory:

  • Data\Scripts\Source\Base
  • Data\Scripts\Source\User

In the Base folder, you should find all of the scripts you've extracted from Base.zip. The User folder is where all of your source scripts should and will be placed.

Where should compiled scripts be placed?

Compiled scripts do not belong anywhere in the Fallout 4\Data\Scripts\Source folder or subfolders. All compiled scripts should be placed in one of the following directories:

  • Data\Scripts
  • Data\Scripts\Fragments\*
  • Data\Scripts\<namespace>\*

Compiling

How else can I compile scripts?

  • You can compile scripts through the Script Manager. In the Script Manager, right-click the script and compile only that script.
  • You can compile scripts outside the Creation Kit by calling the compiler directly from the command shell.

If I change the source script, do I need to recompile that script?

Yes. If you make changes to a .psc (Papyrus Source) file and never recompile, the game will always use the older .pex (Papyrus Executable) file, and your changes will never take effect.

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, use fireundubh's Windows batch script (.bat file) for compiling scripts outside the Creation Kit.

  1. Save compile_fo4.bat to C:\Scripts. Create the directory if needed.
  2. Update the paths at the top of the .bat script as needed.
  3. Add C:\Scripts to your system's PATH environment variable. (Control Panel > System > Advanced System Settings > Environment Variables)

With this script, you can run the following commands from the command shell:

compile_fo4                       // compile all scripts in the current directory recursively and output the compiled scripts to the specified output directory  
compile_fo4 script.psc            // compile a single script in the current directory and output the compiled script to the specified output directory  
compile_fo4 script.psc --test     // the --test parameter will delete the compiled script after successfully compiling the script (useful for testing whether a script compiles)  
compile_fo4 C:\path\to\script.psc // compile a single script using the absolute path to the script and output the compiled script to the specified output directory  
compile_fo4 project.ppj           // compile the specified project file in the Projects directory  

What are all of the parameters for PapyrusCompiler.exe? What do they mean?

You can run PapyrusCompiler.exe on the command line without any parameters to get a list of all of the parameters and what they mean. That list has been replicated here.

Papyrus Compiler Version 2.8.0.4 for Fallout 4
Copyright (C) ZeniMax Media. All rights reserved.
Usage:
PapyrusCompiler <object, folder, or project> [<arguments>]

  object     Specifies the object to compile. (-all is not specified, with a
             .psc or no extension)
  folder     Specifies the folder to compile. (-all is specified)
  project    Specifies the project to compile. (Has a .ppj extension)
  arguments  One or more of the following (overrides project, if any):
   -debug|d
    Turns on compiler debugging, outputting dev information to the screen.
   -release|r
    Turns on release-mode processing, removing debug code from the script.
   -final
    Turns on final-mode processing, removing beta code from the script.
   -optimize|op
    Turns on optimization of scripts.
   -output|o=<string>
    Sets the compiler's output directory.
   -import|i=<string>
    Sets the compiler's import directories, separated by semicolons.
   -flags|f=<string>
    Sets the file to use for user-defined flags.
   -all|a
    Invokes the compiler against all psc files in the specified directory
    (interprets object as the folder).
   -norecurse
    Used with -all to tell the compiler not to recurse into subdirectories.
   -ignorecwd
    Tells the compiler to ignore the current working directory when searching
    for files. Otherwise it is implied to be first in the import list.
   -quiet|q
    Does not report progress or success (only failures).
   -noasm
    Does not generate an assembly file and does not run the assembler.
   -keepasm
    Keeps the assembly file after running the assembler.
   -asmonly
    Generates an assembly file but does not run the assembler.
   -?
    Prints usage information.

What is "debug code" and "beta code"?

Two new properties have been introduced in the Fallout 4 version of Papyrus: DebugOnly and BetaOnly. These properties can be declared at the function and script levels.

  • When release-mode processing is enabled, the compiler will remove DebugOnly functions from the compiled script. The source file will remain untouched.
  • When final-mode processing is enabled, the compiler will remove BetaOnlyfunctions from the compiled script. The source file will remain untouched.

These properties are useful when you want to run code during development that you don't want to run when your mod is in the wild.

Why doesn't my script output any debug messages to the Papyrus log?

The compiler is processing your script in release mode. The Debug script has the DebugOnly property, so all Debug functions (e.g., Debug.Notification, Debug.Trace) will be removed from your script when compiled.

The batch script provided above, and the Papyrus Project template provided below, enables release-mode and final-mode processing. To use DebugOnly and/or BetaOnly code, you'll need to modify these files as needed.

None of my scripts are running! / The Papyrus log indicates they're missing.

Add to %USERPROFILE%\Documents\My Games\Fallout4\Fallout4Custom.ini:

[Archive]    
bInvalidateOlderFiles=1

This will allow the game to load "loose files," which includes your scripts. Also, ensure that the paths to your compiled scripts are correct.

What is a namespace? How do I configure my mod to use a namespace?

For our purposes, a namespace is just a subfolder where your scripts are located for a specific mod in the User folder. Namespaces help you stay organized.

To set up a namespace, follow these instructions:

  1. While in the Creation Kit: go to File > Preferences and find the Scripts tab.
  2. In the box labeled Default namespace, enter the name of your namespace without any spaces or special characters.
  3. Click Apply and then Close.

From now on, any scripts you create with the Creation Kit will be placed in that namespace folder, and the ScriptName declaration in each script will be prefixed with the name of your namespace.

When I try to compile namespaced scripts outside the Creation Kit, my scripts always fail to compile. Help!

Namespaced scripts and scripts with paths, usually fragment scripts, should be compiled using a Papyrus Project file, which is a plain text XML file with the .ppj extension that the Papyrus compiler natively understands.

PapyrusProject.xsd in the Fallout 4\Papyrus Compiler directory defines the XML schema for Papyrus Project files. Here is an example of a Papyrus Project file:

<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>  
    <Import>D:\Steam\steamapps\common\Fallout 4\Data\Scripts\Source\Base</Import>  
    <Import>D:\Steam\steamapps\common\Fallout 4\Data\Scripts\Source\User</Import>  
  </Imports>  
  <Scripts>  
  <Script>AutoLoot\Fragments\Terminals\TERM_dubhAutoLootMenuAdvance_0100272C.psc</Script>  
  ...  
  </Scripts>  
</PapyrusProject>

Logging

How do I enable the Papyrus log?

You can enable the Papyrus log by adding the following lines to %USERPROFILE%\Documents\My Games\Fallout4\Fallout4Custom.ini:

[Papyrus]
fPostLoadUpdateTimeMS=500.0000
bEnableLogging=1
bEnableTrace=1
bLoadDebugInformation=1

Can the Papyrus log help me figure out why Fallout 4 is crashing?

The Papyrus log is not a crash log. There is a warning at the top of the log now that states this quite clearly. The Papyrus log is not likely to point at what was going on near the crash because if a crash occurs, the game will be unable to write to the log.

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?

According to SmkViper:

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.

How do I write save-safe scripts then?

According to SmkViper:

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.)

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:

  1. Instead of using a While loop, use an OnTimer() event. The OnTimer() event is Fallout 4's version of Skyrim's OnUpdate(). Refer to the question and answer below for more information.
  2. Avoid endless While loops that cannot terminate after the plugin has been removed. For example, a While Player.HasPerk(kPerk) loop can terminate if kPerk 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 kPerk references a vanilla perk, HasPerk() will always evaluate to True and the While 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 While loops and saves interact.
  3. 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 While 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.
  4. 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.
  5. 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?

You can emulate a Break statement like so:

Bool bBreak = False  
While (<primary_condition>) && !bBreak    
  // do stuff    
  If <secondary_condition>    
    bBreak = True    
  EndIf      
EndWhile

The alternative is to use Orvid's Caprica compiler, which allows you to use the For, ForEach, Break, and Continue 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?

According to SmkViper:

Scripts are not saved in your save game. Script variable values and currently running functions are saved so they can be resumed.

What's the alternative to OnUpdate, RegisterForUpdate, and RegisterForSingleUpdate?

You can use the OnTimer event alongside the StartTimer and CancelTimer functions.

Are arrays limited to 128 elements?

  • Arrays that are created by scripts via the New operation or Add() 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?

Dynamic arrays grow to fit the number of elements.

To create an empty dynamic array, pass 0 as the size.

ObjectReference[] dynamicArray = new ObjectReference[0]

Also:

  • You can pass in a variable or calculation for the array size.
  • You can call Add() and Remove() on the array to add and remove items.

How do I create key-value pairs?

According to SmkViper:

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.)

In the Papyrus log, why do the FindAllReferences* functions appear to return references in the player's inventory?

According to SmkViper:

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.

Can structs store arrays, other structs, and var types? Can arrays store arrays?

According to SmkViper:

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).

Can CallFunction() be used for reflection?

According to SmkViper:

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.

How do I use custom events instead of CallFunction()?

According to SmkViper:

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.

Troubleshooting

What happens when Papyrus is "overloaded"?

According to SmkViper:

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.

Does Papyrus affect framerate? Or "drop" function calls? Or "lose" entire scripts?

No. According to SmkViper:

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.