Home | Download | Discussion | Help | Site Map | New Posts | Sign in

Latest Site News

MTS Movie Night #3 - posted on 20th Oct 2017 at 10:54 PM
Replies: 17 (Who?), Viewed: 15963 times.
Lab Assistant
Original Poster
#1 Old 26th Sep 2014 at 10:25 PM Last edited by plasticbox : 21st Oct 2014 at 1:17 AM. Reason: We have categories now!
Default For absolute beginners with no clue: Making a simple script that gets the town's population.
So, my last tutorial might have been a little complex. In lieu of this, I decided to write something a little more introductory to The Sims 4's scripting framework. Today, we're going to make a "Get Town Population" mod (for reference, you can see the full source code and mod here). Hopefully, when we're done with this tutorial, you can begin to understand how the framework.. uhm... works.

The main purpose of this tutorial is to accustom you to finding things within The Sims 4's own Python code. It's a giant web of objects, classes, and functions, so it's easy to get lost. This is the process I normally go through when finding different things I need.

What You'll Need
  • The Sims 4's core Python modules.
  • A slight idea of how Python works.

So, now, we're going to have take a peek at the Python modules provided in The Sims 4 (you should be looking at PY files, not PYO files). You can find helpful ways to get your hands on these core modules here and here. "base", "core", and "simulation" are each labels to module collections that are used in the game's intelligence engine. We're going to concern ourselves with "core" and "simulation". As I've noticed, "base" contains a very large amount of library modules useful for certain micro-operations within the game's code, but aren't necessarily things we will often have to look at. "core" and "simulation" are most important for modding the game.

To successfully complete our little mod here, we need a command that you can type. When it's typed, it will reveal the population of all the sims in your game. Now that we know our goal, we can proceed to hunt for the things we need.

Let's have a look at "core" first. Within the core collection, you'll find "google", "shared_commands", and "sims4". Within sims4, you'll find several other libraries related to the handling of modder-created Python modules, algorithms essential for the functioning of the game, and key aspects found within The Sims 4's user interface. This is probably the best place to look if we want to find out where there is a function in the game that allows us to register new cheat console (Ctrl+Shift+C) commands. And speak of the devil! There's a "commands.py" file in there! Let's have a look...

It seems there's a function there called Command:

Code:
def Command(*aliases, command_type=CommandType.DebugOnly, pack=None):


Great! That would give us the first line of code that we need! Let's start our script!

Code:
import sims4.commands


It is important that we import the module or else we will end up with errors because our own module will not recognize the things we declare.

Now, it's time to insert the call to sims4.commands.Command() as a decorator. Whatever function we define directly below the decorator will be its "subject" (the decorator will be applied to that function). But wait! Before we register the command, we need to be sure that we've nailed down the command type. In "commands.py", there's an enumerated list of command types:

Code:
class CommandType(enum.Int, export=False):
__qualname__ = 'CommandType'
DebugOnly = 1
Automation = 2
Cheat = 4
Live = 5


OK, so we're going to register our command type as a Live command. Remember the definition of the Command() function we saw earlier? It's already passing a command_type for us, and we need to override that in our own code:

Code:
@sims4.commands.Command('getpopulation', command_type=sims4.commands.CommandType.Live)


Good! Now, it's time to declare a function right below this beautiful decorator that gives some output back, giving the player that types "getpopulation" the town's population. First, we need to find out what we should call to output to the cheat console, then we need to find out how to grab the town's population. This is where things get a little more tricky.

So, let's get that output function.... I think we should look in "commands.py" a little more.

Ah, found it!

Code:
class CheatOutput(Output):
__qualname__ = 'CheatOutput'

def __call__(self, s):
cheat_output(s, self._context)


The "__call__" function tells us that we only need to write a string when calling the function. Now, let's focus on getting the town's population.

For the town population and all that other jazz, we need to get out of the core module collection and dive into "simulation". In the "sims" folder, there's a file called "sim_info_manager.py". That looks like a good place to start.

There's a class in that file called "SimInfoManager". It seems to hold the details of every sim. If we could count all of the sims in its list, then that will give us the population of all sims. While looking through the rest of the code in TS4's repository, I'm noticing a pattern: sim_info_manager is an object in services. Its attachment to services is found in __init__.py inside the services folder. More importantly, it has a function that's called sometimes inside of the code files called get_all(), which returns a list element. That seems to have solved our mystery!

OK, so let's recap a little. Here's our code so far:

Code:
import sims4.commands

@sims4.commands.Command('getpopulation', command_type=sims4.commands.CommandType.Live)


All this searching and we've only written two lines of code! It's getting late and you're yawning, we've looked through all of TS4's code, and all we managed to finish is two lines! That's no problem, though, since we've found everything we need now to finish the mod!

We'll start by importing services (which will also import its __init__.py) at the top where we imported sims4.commands:

Code:
import services


Now, we need to go under the decorator and declare a function right there:

Code:
def getpop(_connection=None):


Don't worry about the _connection variable. I ripped that off of the maslow mod that EA wrote, and that's just become a habit now. You can call it fluffy_kittens if you'd like.

So, now that we know how to output to the cheat console (by calling CheatOutput), we should define a variable that calls it:

Code:
    output = sims4.commands.CheatOutput(_connection)


Now, all we have to do to output a string is to type output('blablabla'). But we don't want to output just 'blablabla'. We want to output the town's population. This is where our search for sim_info_manager in services pays off.

Code:
    output('Your town\'s population is {}'.format(len(services.sim_info_manager().get_all())))


You see, by measuring the length of the list we get when we call get_all() in services.sim_info_manager(), we get the count of all the sims in the universe! We've finished our mod!

This is what our final code looks like:

Code:
import services
import sims4.commands

@sims4.commands.Command('getpopulation', command_type=sims4.commands.CommandType.Live)
def getpop(_connection=None):
	output = sims4.commands.CheatOutput(_connection)
	output('Your town\'s population is {}'.format(len(services.sim_info_manager().get_all())))


Short, useful, succinct, elegant, and tastier than the cordon bleu I stole from my wife yesterday. This is the result of that code.



HINT: If you don't have Notepad++, GET IT. It's very useful for finding things within files. For example, I can use the "Find in Files" feature to search the entire Sims 4 repository for instances in which a particular function (like get_all() in this example) is called.
6 users say thanks for this. (Who?)
Advertisement
Instructor
#2 Old 26th Sep 2014 at 10:30 PM
This is awesome! I'm definitely going to go through all of this later! I've been wanting to learn how to do some of these things so that I could do my own tuning and things to customize the game for me but I had no idea of where to even start. Thank you! I'll be back later if I have questions!
Lab Assistant
Original Poster
#3 Old 26th Sep 2014 at 11:00 PM
Quote:
Originally Posted by melbrewer367
This is awesome! I'm definitely going to go through all of this later! I've been wanting to learn how to do some of these things so that I could do my own tuning and things to customize the game for me but I had no idea of where to even start. Thank you! I'll be back later if I have questions!


For tuning, I'd imagine you would have to deal with the game's ".package" files. Scripting strictly adds more functions into the game using by latching modules onto the game's code. One good way to think of it is that tuning modifies values in the game and adds tangible things to it (like objects, food, etc.) and scripting is more useful to modify how the game itself runs (i.e. my relationship decay mod)
Instructor
#4 Old 26th Sep 2014 at 11:39 PM
Quote:
Originally Posted by mgomez
For tuning, I'd imagine you would have to deal with the game's ".package" files. Scripting strictly adds more functions into the game using by latching modules onto the game's code. One good way to think of it is that tuning modifies values in the game and adds tangible things to it (like objects, food, etc.) and scripting is more useful to modify how the game itself runs (i.e. my relationship decay mod)


Right. Thank you for clarifying the difference. The scripting is what I meant like your relationship decay mod you mentioned (which I downloaded the same day you posted it!).
Lab Assistant
Original Poster
#5 Old 27th Sep 2014 at 12:32 AM
Quote:
Originally Posted by melbrewer367
Right. Thank you for clarifying the difference. The scripting is what I meant like your relationship decay mod you mentioned (which I downloaded the same day you posted it!).


I certainly hope you have the latest version. Earlier version had a glaring bug because of some of the game mechanisms (some of which I might explain in another tutorial, when I find the time to stop banging my head against my desk).

I'm happy to help if you ever need some guidance in making mods. The process becomes a bit intuitive once you get the hang of how the game's nuts and bolts clunk together.
Test Subject
#6 Old 3rd Oct 2014 at 4:31 PM
I did everything you said but it doesn't work
Maybe I saved it wrong or something?
I must admit I've never used python, more used to Java.
I really want to start making mods so if you could be kind enough to help me out a bit I would be thankful for ever :D
Test Subject
#7 Old 15th Oct 2014 at 5:33 AM
Default Compilation
Hi mgomez!

Thank you for the tutorial. It is very helpful, short and to the point. Great to get somebody started!

But I am trying to compile my own script and it doesn't seem to work. I think I am not creating the .py file in the correct directory. It complains about modules not existing.

What directory (core, base, or simulation) should the script be created in in order for compilation to finish successfully?

Thanks!

Zar
Test Subject
#8 Old 21st Oct 2014 at 5:31 PM
Could this process be used to mod NPC behaviors as a whole town? say like getting sims to go to work,and have and raise their own offspring?
Test Subject
#9 Old 26th Oct 2014 at 9:11 PM
So I've followed the other tutorials to decompile the pyo files and I'm running into an issue that has been asked before, but I haven't found any answers. When I call TheHologramMan’s unpyc3.py with a .pco file I ALWAYS get back the "USAGE: C:\Python33\unpyc3.py <filename.pyc>" message, as if I left off the file parameter...but I didn't. This happens even when I don't use the batch script - to test, I copied abc.pyo from base.zip to my Python dir (Python33) so that it's in the same folder as unpyc3.py, cd'd to that folder (even though it's in my PATH so I shouldn't have to), and called exactly this: unpyc3.py abc.pyo

It didn't work. Suggestions?
Test Subject
#10 Old 7th Apr 2015 at 9:36 PM
In this tutorial, you've said that we should be looking at .py files, and not .pyo files. However, the decompiler bat script only generates .pyo files.
Test Subject
#11 Old 9th Sep 2015 at 10:20 AM Last edited by Lynire : 17th Dec 2015 at 4:00 PM.
After you get your .py file, in order to get it to work you need to make a .pyo file and put that along with your .py file into a zip file. The zip file is what goes into your Sims 4 Mods folder. To make a .pyo file, you can create a batch file named something like PythonOptimizeCompile.bat and put this in it:

Code:

@echo off
if %1x==x goto usage
%~d1
cd "%~p1"
echo on
python -O -c "import py_compile;py_compile.compile('%~n1%~x1','%~n1.pyo')"
@echo off
goto end
:usage
echo drag+drop the .py file onto this to compile to a .pyo file.
:end
pause


If you have more than one Python version installed and the version found by your path is not the version used by Sims 4 (3.3.5) then you will need to put the full path in for the python command rather than just "python" or change your path variable.

Then when you drag+drop your .py file onto that batch file it will create a .pyo file with the same base name as the .py file in the same folder, provided python is in your path. This applies to Windows.
One horse disagreer of the Apocalypse
#12 Old 31st Jan 2016 at 11:14 AM
Testing this in game, I got "Unable to execute command". Obviously there is no clue where it went wrong. It appeared to compile ok.

"You can do refraction by raymarching through the depth buffer" (c. Reddeyfish 2017)
Pettifogging Legalist!
staff: moderator
#13 Old 31st Jan 2016 at 11:24 AM
Do you see anything in lastException.txt? "Unable to Execute" is a good sign, it means that your script is basically working and the game only does not understand what you're trying to tell it.

Maybe this thread helps: http://forums.thesims.com/en_US/dis...-the-game-files -- this is when I made my first script attempt, and SGEugi basically walked me through it step by step. I believe about 50% of all things one could possibly do wrong are covered in that thread

Stuff for TS2 · TS3 · TS4 | Please do not PM me with technical questions – we have Create forums for that.

In the kingdom of the blind, do as the Romans do.
One horse disagreer of the Apocalypse
#14 Old 31st Jan 2016 at 12:21 PM
I think my compiling is wrong as the .py works ok as a loose file, just the .pyo not working now.

"You can do refraction by raymarching through the depth buffer" (c. Reddeyfish 2017)
Lab Assistant
#15 Old 14th Nov 2016 at 11:52 PM
Awesome. But what is the _connection=None argument for?(I know you said i don't have to worry about it... But it's intriguing) And where is the get_all() command declared?
Test Subject
#16 Old 15th Nov 2016 at 9:17 PM
So this is a fairly old thread and may not even be the right place to ask but ... I cannot even get past downloading python :/ I go through the installation wizard and it says it can't be completed because there is no software to install. I was hoping someone here had heard of this problem before, or is it strictly something I need to contact the python website about. Thanks
Test Subject
#17 Old 30th Jan 2017 at 2:32 PM
Can this method override an existing pyo in the game, or can it only supplement?
Test Subject
#18 Old 18th Mar 2017 at 6:45 PM
Hey I tried this tutorial out for myself and it worked after a bit of troubleshooting and diffing.

What I noticed is that in your final code piece listed at the bottom of your original post you have:

Code:
def getpop(_connection=None)


but in your source code for your actual mod you just have:

Code:
def getpop(_connection)


I compiled and tested both versions of this with the =None and without, and found that just having def getpop(_connection), the command would work in game.

Was setting it equal to None a typo?

(p.s. I'm a total noob at python, hopefully this might help someone else that's running thru this tutorial and having problems at the end)
Back to top