Papyrus FAQs
ResourcesEdit
Why doesn't the Creation Kit's script editor have syntax highlighting?Edit
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?Edit
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?Edit
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?Edit
SmkViper created Papyrus and actively helps scripters in the official Fallout 4 Creation Kit forum.
First StepsEdit
Where do I find the source scripts?Edit
- In the
Data\Scripts
directory, there is a ZIP archive named Base.zip. - 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?Edit
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?Edit
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>\*
CompilingEdit
How else can I compile scripts?Edit
- 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?Edit
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!Edit
For the sake of simplicity, use fireundubh's Windows batch script (.bat file) for compiling scripts outside the Creation Kit.
- Save compile_fo4.bat to
C:\Scripts
. Create the directory if needed. - Update the paths at the top of the .bat script as needed.
- 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?Edit
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"?Edit
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
BetaOnly
functions 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?Edit
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.Edit
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?Edit
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:
- While in the Creation Kit: go to
File > Preferences
and find the Scripts tab. - In the box labeled
Default namespace
, enter the name of your namespace without any spaces or special characters. - 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!Edit
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\User</Import> <Import>D:\Steam\steamapps\common\Fallout 4\Data\Scripts\Source\Base</Import> </Imports> <Scripts> <Script>AutoLoot\Fragments\Terminals\TERM_dubhAutoLootMenuAdvance_0100272C.psc</Script> ... </Scripts> </PapyrusProject>
LoggingEdit
How do I enable the Papyrus log?Edit
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?Edit
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.
AdvancedEdit
Are data types distinct? Or are they ultimately floats as in the Oblivion scripting language?Edit
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?Edit
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?Edit
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:
- Instead of using a
While
loop, use anOnTimer()
event. TheOnTimer()
event is Fallout 4's version of Skyrim'sOnUpdate()
. Refer to the question and answer below for more information. - Avoid endless
While
loops that cannot terminate after the plugin has been removed. For example, aWhile Player.HasPerk(kPerk)
loop can terminate ifkPerk
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, ifkPerk
references a vanilla perk,HasPerk()
will always evaluate toTrue
and theWhile
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 howWhile
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
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. - 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?Edit
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?Edit
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?Edit
You can use the OnTimer event alongside the StartTimer and CancelTimer functions.
Are arrays limited to 128 elements?Edit
- Arrays that are created by scripts via the
New
operation orAdd()
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?Edit
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()
andRemove()
on the array to add and remove items.
How do I create key-value pairs?Edit
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?Edit
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?Edit
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?Edit
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()?Edit
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.
TroubleshootingEdit
What happens when Papyrus is "overloaded"?Edit
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?Edit
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.