Automatically Assign Traits to Sims (Sims 4 Script Modding)
This is an updated version of a tutorial originally written on Sims 4 Studio.
This tutorial will show how you can automatically assign your traits to sims when they spawn into the world. It is assumed that you have already created your trait(s), and that you are at least somewhat familiar with Python scripting for The Sims 4.
This tutorial is meant for modded reward/hidden traits only. Do not attempt to use it with any CAS traits, and especially not with any vanilla traits.
What’s the point?
Mods can use traits to mark sims for particular gameplay purposes, and you may find it useful to add these traits to sims as soon as they spawn into the game. For example, my Language Barriers mod has native speaker traits for each of its languages, and these traits are automatically assigned to sims when they spawn into the world.
How do I do it?
This process heavily depends on your specific use case, and requires some knowledge of both Python and the TS4 codebase to perfect. But to get you started, I’ll walk you through a simplified version of L1 assignment in Language Barriers. For context, Language Barriers has 6 languages based on the worlds and occults in the game. This example will only consider two of them: Simlish and Sixami. All aliens should speak Sixami, and all humans should speak Simlish. Additionally, toddlers should not be assigned any language, since they are not old enough to have acquired their native language yet. Animals shouldn’t have a language either, since they’re, ya know, animals.
It’s important for the game to be able to distinguish between your script files, the game’s script files, and other modder’s script files, so be sure to create a python file with a unique name. A good way to come up with a unique name is to append your creator name and mod name to it, for example, if I were making a mod called “tutorial” and was using this script for automatically adding traits, I might call it
frankk_tutorial_autotraits.py . Note that the file name is the only thing that has to be unique, and your classes/functions can be named whatever you want, as long as they don’t conflict with anything you’re importing.
1) Add your traits to the script
The first thing you have to do is get your traits in the script. You can do this by using their decimal tuning IDs (the value after
s= in its tuning file), and using the trait instance manager to retrieve its instance. You could just do something like
get_instance_manager(Types.TRAIT).get(123456789) whenever you need to get the trait with tuning ID 123456789, but it’s better practice to keep your tuning IDs organized for readability, so I’ll show you one easy way to do that below.
Traits class is just an enum that holds the tuning IDs for your traits, and the
get_trait function will make retrieving your actual trait instances easy and readable. For example, to get the Simlish trait, you can simply use
get_trait(Traits.SIMLISH) . Using an enum for your traits comes with the added benefit of being able to iterate through them, which I’ll expand on later.
To make this work for your code, just replace the enum names with descriptive names for your own traits, and replace the number with their tuning IDs. You can add as many values to the enum as you’d like.
2) Define which traits are given to whom
There are many ways you can do this, but the simplest is just by creating a main function that adds traits to sims depending on the output of other helper functions. There is a lot of freedom here, and it depends heavily on what you’re doing. Here’s how I’d do it for the current example.
The function you’d use to assign a trait to a sim is
try_assign_language_trait — the other functions are just helpers for readability. It may be tempting to stuff all of your logic into one function, but I highly advise against it. Your future self will thank you for making the code easy to understand.
This function first checks if the sim needs a language by using the
sim_needs_a_language function. If they do, it will determine which language to add by using the Sixami trait’s tuning ID if
sim_should_speak_sixami is True, else it will use the Simlish trait’s tuning ID. It will then get the trait instance and add it to the sim. You will have to implement your own logic to suit your own needs, but this hopefully serves as a good starting point.
Another thing I want to note is the implementation of
sim_has_a_language . It is iterating over the
Traits enum, like I mentioned in the last section. This is a very useful function, because your code is going to be called every single time a sim loads into the world, which means your code may run on a single sim more than once — this ensures that the current sim has not already received one of your traits. If you’re assigning your traits randomly, not checking for this may result in your sims slowly having all your traits added to them, which you probably don’t want.
3) Inject to the Sim.on_add function
on_add function of the
Sim class is called every time a sim is instanced (i.e. when they load in the world). Injecting to this function will ensure that every sim you encounter in your game has had your code run on them at least once. The term “inject” is specific to Sims 4 script modding, but it essentially means “replace a game function with a function of your own.”
You can inject to
Sim.on_add with the following.
How exactly this works is a bit complex, and would be better explained in another tutorial. However, the gist of it is that
@inject_to(Sim, 'on_add') will replace the
Sim.on_add function with the function below it, and pass it the original
Sim.on_add function as the first argument. This is why we call
original(self, *args, **kwargs) and return the result, to ensure that the original functionality is preserved. All we care about is running our code whenever this function is triggered.
self argument of this function is equal to the
Sim object in which it was called, and in order to run our code, we need to call
try_assign_language_trait with the sim’s
sim_info , which we can get with
self.sim_info . We wrap this function call in a try/except clause to ensure that any faults in our code will not break the game. If you want to notice when something goes wrong, you can replace the
pass with a logger, or you can re-raise the exception for testing purposes only (but make sure you are not re-raising it in the published version!).
The completed script
Finally, here’s everything from above compiled into one file — although I am providing it as a single file for viewing convenience, copy-and-pasting the completed file will not do you much good. It’s better to create your script yourself by following the tutorial, since each use case is so unique.
Additionally, for larger mods, it’s recommended to split some of this logic into separate files, or even packages. For example, you may find it useful to put all of your tuning ID enums in one file, your injector in another, and import them where needed.