Loose Leaf Scripting Examples

This section holds loose leaf scripting examples that other creators have written, such as code pieces with no files, etc. You are free to use the codes here to create your mods; just remember to rename to your preference.

If you are looking for the main page to Scripting Examples, look here.

## Injecting Custom Crafting Ingredients into Tag Tuning (Scumbumbo)

```
from crafting.crafting\_ingredients import IngredientTuning
from sims4.collections import frozendict
from sims4.localization import TunableLocalizedStringFactory
from tag import TagCategory, Tag

                Tag.\_add\_new\_enum\_value(k, v)

# Add the tag elements, values are 32-bit hashes of the 'Func\_NAME' tags
TAG\_ELEMENTS = {'Func\_Ingredient\_Roadkill': 3814098884, 'Func\_Ingredient\_Pepper': 4087606450, 'Func\_Ingredient\_TreeBark': 3875362104}
add\_tag\_elements(TAG\_ELEMENTS)

# Convert the frozendict to a dict for updating
INGREDIENT\_TAG\_DISPLAY\_MAPPING = dict(IngredientTuning.INGREDIENT\_TAG\_DISPLAY\_MAPPING)

# Add the ingredient strings to the INGREDIENT\_TAG\_DISPLAY\_MAPPING for the newly created Tags
INGREDIENT\_TAG\_DISPLAY\_MAPPING[Tag.Func\_Ingredient\_Roadkill] = TunableLocalizedStringFactory().\_convert\_to\_value(0x6F5C78E1)  # 0x6F5C78E1 = "Any Roadkill"
INGREDIENT\_TAG\_DISPLAY\_MAPPING[Tag.Func\_Ingredient\_Pepper] = TunableLocalizedStringFactory().\_convert\_to\_value(0xCC5B8A72)  # 0xCC5B8A72 = "Any Peppers"
INGREDIENT\_TAG\_DISPLAY\_MAPPING[Tag.Func\_Ingredient\_TreeBark] = TunableLocalizedStringFactory().\_convert\_to\_value(0xF6E73813)  # 0xF6E73813 = "Any Tree Bark"

# Convert back to a frozendict and jam it painfully back into the IngredientTuning
IngredientTuning.INGREDIENT\_TAG\_DISPLAY\_MAPPING = frozendict(INGREDIENT\_TAG\_DISPLAY\_MAPPING)

```

## Injecting Custom Tags into Tag Tuning (Scumbumbo)

```
from tag import TagCategory, Tag

            TagCategory.\_add\_new\_enum\_value(k, v)

            Tag.\_add\_new\_enum\_value(k, v)

# Use 32-bit hashes of the names for the values
TAG\_CATEGORIES = {'DingoColor': 100001, 'WingSize': 100002, 'FlatulenceLevel': 100003}
TAG\_ELEMENTS = {'Func\_Hair\_Removal': 100001, 'Func\_NoseStraightener': 100002, 'Func\_ChickenPlucker': 100003}

add\_tag\_categories(TAG\_CATEGORIES)
add\_tag\_elements(TAG\_ELEMENTS)

```

## Injecting Tags (Frankk)

```
        Tag.\_add\_new\_enum\_value('TAG\_NAME', 123456)
        Tag.\_add\_new\_enum\_value('OTHER\_TAG', 654321)

do\_tag\_injections()

```

## Making a Configuration File for Your Mods (Frankk)

(The following was a transcript from the Discord Server: Creator Musings)

After battling this process for multiple hours, I finally figured out how to make and use a config file in a mod. If you don't know, a config file can be used to store settings and allow users to edit them on their own. Here are my tips if you're looking to do the same: 1. Create a `.cfg` file in your mod's directory. You can name it whatever you want, but `modname_settings.cfg` is probably a good idea. 2. Read about the syntax of the config file here if you aren't already familiar with it: https://docs.python.org/3/library/configparser.html (NOTE: Don't bother reading the configparser documentation. The Sims team wrote their own configparser.py that overrides this one. Just read the syntax of the config file, as it's the same for both) 3. Parse your config file in a script with the following:

```
import os
import configparser
from pathlib import Path

# Add one more `.parent` for each package that this file is nested in, if any
config\_dir = Path(\_\_file\_\_).resolve().parent.parent
config\_name = 'modname\_settings.cfg'  # change this to your file's name
file\_path = os.path.join(config\_dir, config\_name)

config = configparser.ConfigParser()
with open(file\_path) as file:
    config.read\_file(file)

```
And there you go! Now you have the config file loaded into your script. If your config file looks like this:

```
[header]
key = value
some other key = some other value

[another header]
key = another value

```
You can get the values like this:

```
config.get("header", "key")  # this returns "value"
config.get("header", "some other key")  # this returns "some other value"
config.get("another header", "key")  # this returns "another value"

```
Just as an additional example to show you what this can actually be used for, here is the config file that I will be including in Language Barriers:

```
[REGIONS]
Brindleton Bay     = Simlish
Britechester       = Simlish
Del Sol Valley     = Simlish
Evergreen Harbor   = Simlish
Forgotten Hollow   = Simlish
Glimmerbrook       = Simlish
Magnolia Promenade = Simlish
Newcrest           = Simlish
Oasis Springs      = Simlish
San Myshuno        = Simlish
Strangerville      = Simlish
Willow Creek       = Simlish

```
If a user wants to make Oasis Springs speak Selvadoradian, then they just need to replace that line with:

```
Oasis Springs      = Selvadoradian

```
This can be super helpful for people looking to give users customization options!

## Getting Client Cheat Commands to Run in XML (TwelfthDoctor1)

(The following was a transcript from the Discord Server: Creator Musings)

I just found out that inside `sims4.commands`, there is a output type called `client_cheat()`. `client_cheat()` is basically the console you use to type cheats in. Unlike `execute()`, `client_cheat()` is used to execute commands that are not in Simulation such as `bb.showhiddenobjects`. I found that in `developer_commands`, commands like `bb.showwipobjects` and `rr.toggletime 12` aren't found in Simulation. It could be in the Core, but I know that some files are not fully decompiled (well, based on one of my scripts when I decompile it from TS4SCRIPT it only got 2 of 4 commands.).

So based on the `developer_commands` code, I wrote up this:

```
@Command('td1devaccess.call\_client\_command')
def call\_client\_command(identifier:str, state:bool, \_connection=None):
    output = CheatOutput(\_connection)
    identifier\_lc = identifier.lower()
    result = get\_command\_and\_sort\_from\_list(identifier\_lc, state, \_connection)
    output('{}'.format(result))

def get\_command\_and\_sort\_from\_list(identifier, state, \_connection):
    if identifier is None:
        result = '[FAILURE] Command Identifier Missing. Please specify command.'
        return result
    if state is None:
        state = False
    for (enable\_cmd, disable\_cmd, is\_client) in commands\_list:
                    client\_cheat(enable\_cmd, \_connection)
                    result = '[SUCCESS] Client Cheat: ' + str(enable\_cmd) + ' is now enabled.'
                    return result
                else:
                    client\_cheat(disable\_cmd, \_connection)
                    result = '[SUCCESS] Client Cheat: ' + str(disable\_cmd) + ' is now disabled.'
                    return result
                    execute(enable\_cmd, \_connection)
                    result = '[SUCCESS] Command Cheat: ' + str(enable\_cmd) + ' is now enabled.'
                    return result
                else:
                    execute(disable\_cmd, \_connection)
                    result = '[SUCCESS] Command Cheat: ' + str(disable\_cmd) + ' is now disabled.'
                    return result
    result = '[FAILURE] Command: ' + str(identifier) + ' is not within TD1 Developer Access Panel Command List.'
    return result

```
Also there needs to be a list with a tuple along with it:

```
commands\_list = [
    (
        # Testing Cheats
        'testingcheats true', 'testingcheats false', False
    ),
    (
        # Free Build Mode: For Generally Uneditable Lots
        'bb.enablefreebuild', '', True
    ),
    (
        # Move Objects
        'bb.moveobjects on', 'bb.moveobjects off', True
    ),
    (
        # Show Debug Objects
        'bb.showhiddenobjects', '', True
    ),
    (
        # Show Live Edit Objects
        'bb.showliveeditobjects', '', True
    ),
    (
        # Unlock Gameplay Objects
        'bb.ignoregameplayunlocksentitlement', '', True
    ),
    (
        # Show Work In Progress Objects: ???
        'bb.showwipobjects', '', True
    ),
    (
        # Shorten Crafting Phases
        'crafting.shorten\_phases on', 'crafting.shorten\_phases off', False
    ),
]

```
To make things easy, we start at the Command. `td1devaccess.call_client_command` has 2 params: identifier and state. identifier is the string command that is used to check against the enable command inside the list-tuple. (i.e. bb.enablefreebuild **contains** enablefreebuild). state is to determine if it is enabled or disabled. Now the list-tuple has 3 things: the enable command, disable command and the modifier to tell if it is a client command (basically not in Simulation). Each one will be used to test and be applied accordingly. Example: You want to run the `bb.showhiddenobjects` , and based from the list, it is a client\_cheat. So the identifier (bb.showhiddenobjects) will be matched against enable\_cmd, it will continue doing this until it matches for one or fails, in this case it finds it. So we check if it needs client\_cheat based on is\_client, if yes we check the state, True for enable, False for disable and then it will execute the command. (Note that non client is still the same just that the mode of execution is different, thats all.)
Actually if you want it **SIMPLE**, this will suffice:

```
@Command('td1devaccess.call\_client\_command')
def call\_client\_command(command:str, \_connection=None):
    output = CheatOutput(\_connection)
    client\_cheat(command, \_connection)
    output('Client Command: {} has been executed.'.format(command))

```
Well just saying that this quick command script here cannot tell you whether the command has worked. So you will need to double check before using it, most likely in XML.

## ConfigParser - Auto Making, Appending and Changing Values In-Game (TwelfthDoctor1)

**The reader should know some understanding of Python File Handling and ConfigParser functions before attempting this example.**

Config files is something commonly used by games and even less common by mods, unless it has certain functions that can be toggled. Originally, my "config file" was the Module Tuning, but with users needing S4S just to change an option, I had to figure out how to write my own config handler for my big mod.

Note: As the basic parts have been covered by Frankk, I will only cover the deeper components.

### Auto Writing a Config File

I am not really fond of self creating a config file and shipping that out with the mod, I want it to be auto created so that it makes it easier for me and the users.

```
config\_key\_list = [
    'disable\_notifs',
    'override\_debug\_menu',
    'force\_debug\_cheat\_menu',
]

preset\_modifiers = [
    "False",
    "True",
    "True"
]

def config\_prep\_file():
 """
 Configures and creates a brand new Config File for use.
 :return:
 """
    with open(get\_config\_dir(), "w") as config\_file:
        config\_file.write("[TD1 Developer Access Panel Config Settings]")
        # Incrementor Int by default is presumed as 0 and increments from there.
        # List Assigns start off as 0 as well.
        for incrementor in range(len(config\_key\_list)):
            config\_file.write("\n" + config\_key\_list[incrementor] + " = " + preset\_modifiers[incrementor])

        config\_file.close()

```
It is possible to create a file from a script, you will need to know Python's File Handling functions in order to implement it. In the case of a config file, you need the section or header and the options with their values.

As a time saver, it is marginally easier to use lists with a for loop to write each option with their value. Having the write function in each line can be wasteful, confusing and not efficient, especially in the long run.

### Appending and Auto Checking

I also had to consider each update, especially if I were to add new options to toggle. Not only that, with updates, people may not have that option available, and I'd rather not ship a config file update.

```
def config\_init\_check():
 """
 Runs an initial check on the config file for any missing options.
 Prudent on newer versions of the mod with config implementations.
 :return:
 """
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)
        for incrementor in range(len(config\_key\_list)):
            if config.has\_option(main\_header, config\_key\_list[incrementor]) is False:
                append\_option(incrementor)

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("Config List Check Success.", owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("Config List Check Success.", owner="TwelfthDoctor1")

def append\_option(identifier):
 """
 Appends missing options into the config file.
 :param identifier:
 :return:
 """
    option = config\_key\_list[identifier]
    modifier = preset\_modifiers[identifier]
    with open(get\_config\_dir(), "a") as config\_file:
        config\_file.write("\n" + option + " = " + modifier)
        config\_file.close()

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("Option Key: {0} has been appended into Config File.".format(option), owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("Option Key: {0} has been appended into Config File.".format(option), owner="TwelfthDoctor1")

    with open(get\_config\_dir(), "r") as config\_file:
        return config\_file

```
So, config\_init\_check will run and with sieve through all the options to see if it exists on the list. If it doesn't, it will call append\_option which will append the missing option into the config file.

Now my method here does not deal with duplicates and those out of order, because my presumption is that no one would modify the config file in terms of changing the position of each option. But you are always welcome to include that option in your scripts.

### Changing an Option In-Game

The reason why you want users to change the config options in game is that it makes it easier for them to understand, you would rather not ask them to edit the config file, they'd be confused if there isn't anything to help them understand what they are changing.

```
@Command('td1devaccess.set\_option', command\_type=CommandType.Live)
def set\_option(option, \_connection=None):
    output = Output(\_connection)
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)
        if config.has\_option(main\_header, option):
            orig\_value = config.get(main\_header, option)
            config\_file.close()

                    option\_toggle(config\_key\_list.index(key))

        else:
            output("Wrong Option Specified. Please refer to config list and try again.")
            config\_file.close()

def option\_toggle(identifier):
    client = services.client\_manager().get\_first\_client()
    option = config\_key\_list[identifier]

    if config.has\_option(main\_header, option) is False:
        append\_option(identifier)
    else:
        with open(get\_config\_dir(), "r") as config\_file:
            config.read\_file(config\_file)
            setting\_value = config.getboolean(main\_header, option)
            print("{0} {1}".format(option, setting\_value))

            if setting\_value is True:
                value = "False"
                config.set(main\_header, option, value)
                config.write(open(get\_config\_dir(), "w"))

            elif setting\_value is False:
                value = "True"
                config.set(main\_header, option, value)
                config.write(open(get\_config\_dir(), "w"))

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("Option: {0} has been set to {1}. (Formerly {2})".format(option, value, setting\_value), owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("Option: {0} has been set to {1}. (Formerly {2})".format(option, value, setting\_value), owner="TwelfthDoctor1")

```
By far, this is the biggest hurdle to get through, as instead writing directly to the config file, you NEED to use ConfigParser itself to write (denoted as config.write) to the file as the regular write has no clue on what to change and write. Also, the set function only sets the option, it does not set it directly, you still need to use the write function to change the option value or no changes will take effect.

Now don't ask me why why it had to be done this way, its how its done in the Python Docs.

At the end of all this, you could have a code like this:

```
config\_key\_list = [
    'disable\_notifs',
    'override\_debug\_menu',
    'force\_debug\_cheat\_menu',
]

preset\_modifiers = [
    "False",
    "True",
    "True"
]

main\_dir = Path(\_\_file\_\_).resolve().parent.parent

config = configparser.ConfigParser()

main\_header = 'TD1 Developer Access Panel Config Settings'

def get\_config\_dir():
 """
 Gets the Configuration File Directory.

 No Params Required.
 """

    log\_name = "TD1\_AccessPanel\_Settings.cfg"

    config\_dir = os.path.join(main\_dir, log\_name)

    return config\_dir

def config\_prep\_file():
 """
 Configures and creates a brand new Config File for use.
 :return:
 """
    with open(get\_config\_dir(), "w") as config\_file:
        config\_file.write("[TD1 Developer Access Panel Config Settings]")
        # Incrementor Int by default is presumed as 0 and increments from there.
        # List Assigns start off as 0 as well.
        for incrementor in range(len(config\_key\_list)):
            config\_file.write("\n" + config\_key\_list[incrementor] + " = " + preset\_modifiers[incrementor])

        config\_file.close()

if os.path.exists(get\_config\_dir()) is True:
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("TD1 DevAccessPanel Config File Found.", owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("TD1 DevAccessPanel Config File Found.", owner="TwelfthDoctor1")

else:
    config\_prep\_file()
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("TD1 DevAccessPanel Config File Missing. Creating New Config File...", owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("TD1 DevAccessPanel Config File Missing. Creating New Config File...", owner="TwelfthDoctor1")

@Command('td1devaccess.list\_config', command\_type=CommandType.Live)
def print\_config\_values(\_connection=None):
    output = Output(\_connection)
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)
        for incrementor in range(len(config\_key\_list)):
            value = config.get(main\_header, config\_key\_list[incrementor])
            output("{}: {}".format(config\_key\_list[incrementor], value))

@Command('td1devaccess.set\_option', command\_type=CommandType.Live)
def set\_option(option, \_connection=None):
    output = Output(\_connection)
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)
        if config.has\_option(main\_header, option):
            orig\_value = config.get(main\_header, option)
            config\_file.close()

                    option\_toggle(config\_key\_list.index(key))

        else:
            output("Wrong Option Specified. Please refer to config list and try again.")
            config\_file.close()

def config\_init\_check():
 """
 Runs an initial check on the config file for any missing options.
 Prudent on newer versions of the mod with config implementations.
 :return:
 """
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)
        for incrementor in range(len(config\_key\_list)):
            if config.has\_option(main\_header, config\_key\_list[incrementor]) is False:
                append\_option(incrementor)

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("Config List Check Success.", owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("Config List Check Success.", owner="TwelfthDoctor1")

config\_init\_check()

def option\_toggle(identifier):
    client = services.client\_manager().get\_first\_client()
    option = config\_key\_list[identifier]

    if config.has\_option(main\_header, option) is False:
        append\_option(identifier)
    else:
        with open(get\_config\_dir(), "r") as config\_file:
            config.read\_file(config\_file)
            setting\_value = config.getboolean(main\_header, option)
            print("{0} {1}".format(option, setting\_value))

            if setting\_value is True:
                value = "False"
                config.set(main\_header, option, value)
                config.write(open(get\_config\_dir(), "w"))

            elif setting\_value is False:
                value = "True"
                config.set(main\_header, option, value)
                config.write(open(get\_config\_dir(), "w"))

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("Option: {0} has been set to {1}. (Formerly {2})".format(option, value, setting\_value), owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("Option: {0} has been set to {1}. (Formerly {2})".format(option, value, setting\_value), owner="TwelfthDoctor1")

        message\_title\_config\_change = lambda \*\*\_: LocalizationHelperTuning.get\_raw\_text("Access Panel Option Change")
        message\_text\_config\_change = lambda \*\*\_: LocalizationHelperTuning.get\_raw\_text("The option: {} has been changed from {} to {}.\n\nA restart may be required for changes to take effect.".format(
            option, setting\_value, value
        ))

        notification\_config\_change = UiDialogNotification.TunableFactory().default(
            client.active\_sim,
            text=message\_text\_config\_change,
            title=message\_title\_config\_change,
            expand\_behavior=UiDialogNotification.UiDialogNotificationExpandBehavior.FORCE\_EXPAND,
            urgency=UiDialogNotification.UiDialogNotificationUrgency.URGENT
        )
        notification\_config\_change.show\_dialog()

def get\_option\_value(option):
 """
 Gets the value from the option (key) in the Config List.
 :param option:
 :return:
 """
    with open(get\_config\_dir(), "r") as config\_file:
        config.read\_file(config\_file)
        value = config.getboolean(main\_header, option)
        return value

def append\_option(identifier):
 """
 Appends missing options into the config file.
 :param identifier:
 :return:
 """
    option = config\_key\_list[identifier]
    modifier = preset\_modifiers[identifier]
    with open(get\_config\_dir(), "a") as config\_file:
        config\_file.write("\n" + option + " = " + modifier)
        config\_file.close()

        if apprentice\_logger is not None and apprentice\_logger is not False:
            apprentice\_logger.info("Option Key: {0} has been appended into Config File.".format(option), owner="TwelfthDoctor1")

        if master\_logger is not None and master\_logger is not False:
            master\_logger.info("Option Key: {0} has been appended into Config File.".format(option), owner="TwelfthDoctor1")

    with open(get\_config\_dir(), "r") as config\_file:
        return config\_file

```
Well...I cannot fully explain this as ConfigParser is not within my territory of full understanding. But hopefully whoever reads this may understand a little bit more on config files and ConfigParser.

Also Yes I know there is the RawConfigParser, it still depends on what you want to use, but you also note that RawConfigParser uses older and unsafe methods of changing values, it is recommended you use the ConfigParser instead.