The Sims 4 Modern Python Modding: Part 3 — Replacing Code

June Hanabi
The Startup
Published in
6 min readSep 22, 2020

Python has the ability to replace code with other code. This is an important and often easy skill that is frequently overlooked. It allows you to alter existing code in a way that wouldn’t be possible without replacing it which comes in handy with modding. This tutorial shows you how to do exactly that.

It’s worth noting that anytime you make a mod that replaces code with other code you need to tell players who download your mod about that. Replacing code means 2 mods cannot replace the same code without creating issues so players need to know this. It’s a very powerful and simple modding technique though so I urge everybody to try it out at least once.

What we’re going to do

So I began browsing through the files looking through what I want to do for this tutorial and I initially landed upon “Create A Sim” and was like, you know, I want to make some kind of Create a Sim scripted mod but after more digging I couldn’t really find what I wanted. Eventually my CAS search lead me to decide on making a voice mod and from there a voice pitch mod.

Now you can already change the voice pitches up with the tuning files, I wanted to do something you couldn’t do in tuning files so I settled on pitch randomization. It was perfect for python, next I was hoping it would be simple.

Make sure you start at part 1

The projects here assume your up-to-speed on how to mod for the Sims 4 using python scripting which I already covered in part 1 & part 2.

I needed to track down which file to hook into

Exactly which file and code was it that I needed to work with? What code control the voice pitch for each of the sims? Tracking it down wasn’t easy as it normally isn’t.

My first guess was audio.voice.voice_pitch because it makes the most sense and actually wasted about 3 hours before I realized the file isn’t the right file. I went through a ton of debugging and frustration figuring that out. I’m not sure what the file is used for, all I know is the code inside the file is never called or I never witnessed it being called.

Eventually I went back to the drawing board and did some more digging around and that’s when my discovery lead to some interesting results. distributor.ops.

I don’t fully understand it but when the game needs to do stuff it forwards whatever that is to the “distributor” which acts like a message board from what I can tell. Code sends “messages” all over the place to the “message board” and the “message board” intelligently sorts everything out, prioritizes it, and makes it happen. This could be anything from a sim speaking, to music, to light color, and even certain sim states like sleeping.

Now I might have misunderstood some parts of it, this is just a working hypothesis. Another thing I noticed is code is only called for each sim on the lot generally only once for efficiency reasons which was interesting and also not very message board like.

Getting our own copy of the code

The file is huge with many classes but inside the file is one particular class called “SetVoicePitch” which I’m presuming represents the ability for code to set or change the pitch of something or to use a certain pitch when playing speech.

There’s no way to tell the game to interact differently with this function so the easiest thing to do is just replace it with a very close copy but tweaked in our favor. Lets copy the function write to our own file named anything you want, I named mine main.py as usual.

def write(self, msg):
op = DistributorOps_pb2.SetVoicePitch()
op.voice_pitch = self.voice_pitch
self.serialize_op(msg, op, protocol_constants.SET_VOICE_PITCH)

Pretty much just a copy so far but it’s our own copy so we can now further modify it.

Making it work

By itself the code isn’t going to run, if you look at the code above you may see it using code named DistributorOps_pb2 and protocol_constants but we don’t have that code in our file… More so if you go back to the original file distributor.ops you’ll find it imports and creates them so we just need to copy that.

When copying code from another file, pay close attention if it uses code created elsewhere because you’ll need to copy that. This is the one thing I liked with Java programming using Netbeans is when copying code from elsewhere it would handle most of that for you.

Here’s a visual image of distributor.ops explaining what I mean

We’re copying the code at the bottom but it uses other code (DistributorOps_pb2 and protocol_constants) so we need to copy that as well and most of the time will be at the very top of the file.

from protocolbuffers import DistributorOps_pb2
protocol_constants = DistributorOps_pb2.Operation

There’s one last thing we need to do and that’s telling python to replace method created by EA with our own copy. This is super easy.

import distributor.ops
distributor.ops.SetVoicePitch.write = write

We import the file and then enter the class SetVoicePitch inside the file distributor.ops and replace the existing write method with our own. This magical little line allows us to remove the limits of modding. If EA’s code doesn’t allow something then be gone with it! We’ll make the code allow it.

Exercise restraint though, again, every code you replace can conflict if another mod also replaces that code which is why it’s important to let the users know so they can pay attention to that when downloading mods.

Changing our own copy

Lets just blantly randomize the pitch. Pitch values are between -2.0 and 2.0 so lets completely ignore the sims pitch and just pull random pitches out of the air.

We need to import random in order to do the randomization and the final code should look like this.

import random
import distributor.ops
from protocolbuffers import DistributorOps_pb2
protocol_constants = DistributorOps_pb2.Operationdef write(self, msg):
op = DistributorOps_pb2.SetVoicePitch()
op.voice_pitch = random.randrange(-200, 200) * 0.01
self.serialize_op(msg, op, protocol_constants.SET_VOICE_PITCH)
distributor.ops.SetVoicePitch.write = write

It looks like python can’t easily get a random range for a float value so instead I did a small workaround while keeping things simple. I asked python for a random integer value between -200 and 200 and converted the integer to a float between -2.0 and 2.0.

The end result

While I was hoping every sentence would come out at a different pitch, instead it just re-used the initial random pitch and while it randomized nicely, sometimes it didn’t seem to randomize. That was fine though and made for a good laugh, some of the sims sounded like chipmunks while others sounded like sleepy dragons lol and anytime I re-entered the lot the sims would re-randomize voice pitches which was great.

I could go on bug hunts and replace more functions but I can end it here for the tutorial. Maybe you can look further into tweaking, enhancing, and fixing potential bugs.

The reward was well worth it and fun and I hope you enjoyed reading through it. As always have fun with this, do more with this and stick around for more projects, tutorials, tips, and guides.

Addendum

I replaced a 2nd function to try and ensure voices get randomized.

def write_dialog_pitch(self, msg):
op = DistributorOps_pb2.SetOverrideDialogPitch()
op.pitch = random.randrange(-200, 200) * 0.01
self.serialize_op(msg, op, protocol_constants.SET_OVERRIDE_DIALOG_PITCH)
distributor.ops.SetOverrideDialogPitch.write = write_dialog_pitch

Publication to Mod the Sims

This has been published as a properly packaged mod to Mod The Sims as a mod you can download and use link here.

Next Part?

If you’re ready for the next tutorial, link here. Don’t rush if you don’t fully understand this or previous tutorials in this series.

--

--

June Hanabi
The Startup

I just love creating art, taking photographs, programming, and teaching new skills to people.