Hi there! You are currently browsing as a guest. Why not create an account? Then you get less ads, can thank creators, post feedback, keep a list of your favourites, and more!
Test Subject
Original Poster
#1 Old 19th Jul 2018 at 5:33 AM
Default Script Override using an Injector - failed attempt - send help
Hi there!
I've just tapped into script modding veeery briefly like today. This is worse than xml files. I originally planned on doing something more elaborate, but I've given up on that already. What I want to do instead now is a simple override. Google says, things like that are done by using an “injector”, so I found THIS post.
The plan is to change the default baby skin tone that is used if the inherited skin tone is unnatural/custom content to something more visible than the original light. I would prefer to add my own skins, but I wouldn't even know where to start with that.
So, trying to follow Deaderpool's instructions I came up with this:

Code:
import sims.baby.baby_tuning
import sims.sim_info
import injector

@injector.inject_to(sims.baby.baby_tuning.BabyTuning, "get_baby_skin_tone_enum")
def inject_baby_skin(original, sim_info):
    original(sim_info)
    if sim_info.is_baby:
        skin_tone_id = sim_info.skin_tone
        for (skin_enum, tone_ids) in BabyTuning.BABY_SKIN_TONE_TO_CAS_SKIN_TONE.items():
            while skin_tone_id in tone_ids:
                return skin_enum
        return BabySkinTone.ALIEN_PURPLE
    return BabySkinTone.ADULT_SIM


I used THIS tutorial to set up my workspace and compile the script. I added the injector.py from Deaderpool's post.
Sadly, all this does is throw an error and have bassinet and baby vanish on delivery. And since I don't really know what I'm doing in the first place, I have now idea how to fix this. For my inapt eyes this looks just like the example in the post I mentioned.
Creating script mods is such a broad and complicated topic and tutorials seem to be really rare, so if anyone has mercy on me, any help would be appreciated.
Thanks in advance!
Advertisement
Deceased
#2 Old 19th Jul 2018 at 6:26 AM
You are getting errors as some of those values are not fully referenced so Python can't find them. The original code had direct access to the BabyTuning and the BabySkinTone classes, but your script doesn't as it's running from a different module. I'd suggest changing the code to use the from style of import, or you can just add sims.baby.baby_tuning in front of the references to BabyTuning and BabySkinTone as you did in the injector decorator. Using the from import style, it would look like
Code:
from sims.baby.baby_tuning import BabyTuning, BabySkinTone
import injector

@injector.inject_to(BabyTuning, "get_baby_skin_tone_enum")
def inject_baby_skin(original, sim_info):
    if sim_info.is_baby:
        skin_tone_id = sim_info.skin_tone
        for (skin_enum, tone_ids) in BabyTuning.BABY_SKIN_TONE_TO_CAS_SKIN_TONE.items():
            if skin_tone_id in tone_ids:
                return skin_enum
        return BabySkinTone.ALIEN_PURPLE
    return BabySkinTone.ADULT_SIM

Some additional notes:
  • You don't need to import sims.sim_info, that's only necessary if you're using something directly from the sim_info module; however, you're getting a direct reference to a sim_info object, so you can access parts of that object without any need to import.
  • You don't need to call the original, since you're completely overriding what it is doing there is no need for it to run before your code. You generally only need to call the original if you just want to do something else before or after it runs.
  • With the from import method shown above you can leave out the sims.baby.baby_tuning before BabyTuning in the injector line.
  • I don't have a couple that requires that default skin tone in my game, so I can't really test that your code works as you want - but it won't throw errors with these changes.
  • Finally, one helpful hint - it's easiest to get the code working without bothering to compile it. You can run .py scripts directly without compiling them by placing them into a sub-sub-folder named "Scripts", for example, I use the folder "Mods/Script Testing/Scripts" for a lot of simple script development. For bigger projects, I'll create a whole new subfolder like "Mods/NAMEofMOD/Scripts"

ETA - I forgot to mention, I changed the "while" in the script to an "if". The script decompilers frequently will replace something that should be an "if" with a "while" instead and this is one of those cases. Although it doesn't hurt in this particular script as when the while is true it returns from the function, in some it will cause an infinite loop if a while is used incorrectly. Of course, not all whiles in the scripts are supposed to be ifs, some actually should be whiles - all you can do is read the code and evaluate what it should be doing whenever you run into a while loop. You can always ask here if you're in doubt about one you run across.
Test Subject
Original Poster
#3 Old 20th Jul 2018 at 1:01 AM
Thank you so much, scumbumbo! I just tested your changed script and it works just as planned. And what a nice and detailed answer, I understand some parts a little better now. Others not yet, but this gives me hope.
I wasn't sure how to identify what exactly to import for the script to run properly. At one point I even imported every file with a name that was similar enough to any of the words in my script.
And I was wondering what to change if I want to do a simple override instead of adding something to a script. The post didn't cover that and I didn't expect it to be as easy as just omitting the original part.
Using sub-subfolders works like a charm as well, that will make testing a lot faster.
I'll keep the while-if part in mind.
I really appreciate the time you spend to help me out here, this may seem minor to a veteran, but it's a big step forward for me (I consider myself being able to override stuff now, ha).
So all my thanks to you!

The next part almost borders on contemplating the meaning of life, now that I look at it, so I wouldn't really consider it an actual follow up question. But if someone is interested in a mod like this, is looking for a challenge or simply bored, feel free to add your two cents.

This is a mere workaround and for my current legacy it's good enough, but not exactly something to share with the world yet (I hope at least this thread can be helpful to someone though). Originally I wanted to add more baby skin tones for a more accurate representation of the parents, but I don't even understand how or where the game determines which skin to use. I've been looking at the files again and again before opting for the workaround, but it feels like I can see only half of what's going on.
For example in the script part I tried to overwrite: It checks for the skin_tone_id if it is in tone_ids. There is no mention whatsoever of any kind of id list named like that in any other file. So how does it look like? How are the ids calculated? There is a list of all the possible baby skin tones with numbers at the start of the script, but for ids they are pretty short, so that's probably not it. Or is it as easy as a number from 0 to 15 hidden somewhere inside the cas part of an adult skin tone? Is it hardcoded to the instance ids of the original EA skin tones?
I don't get it. Or maybe I just don't get it because I don't get scripts yet. I don't know. And I'm not even able to ask the right questions to somehow work my way up there. That's the actual frustrating part. Oh well. I'll be looking at stuff some more, from scripts to object tuning.
Until then, have a nice and successful day everyone!
Deceased
#4 Old 20th Jul 2018 at 7:46 AM Last edited by scumbumbo : 20th Jul 2018 at 7:53 AM. Reason: clarified the staticmethod note
Just a quick response at the moment, I'll touch on the latter part of your "meaning of life" question later when I have some more time.

Quote: Originally posted by godofallbeauties
I wasn't sure how to identify what exactly to import for the script to run properly. At one point I even imported every file with a name that was similar enough to any of the words in my script.


I pretty much started out in much the same way - anything that mentioned sim_info I figured I had to import the sim_info module. But what it boils down to makes a lot more sense -- you need to import anything that you don't otherwise have a reference to. Since an instance of a sim_info class was passed to your function, you can access anything from that sim_info without any other reference required. But when injecting or overriding a script from the game's scripts they may reference things that are not in your module. These may be in the original module, like in the case of the BabySkinTone enum class from your script - those are in the same method as that function had originally been defined in so it didn't need a reference to those. But since your module doesn't see that enum, you need to have a reference to it.

It could also be from another module entirely, in which case look at the imports at the top of the file you are modifying the function from and look to see what they are importing. If that BabySkinTone had been from another method, then somewhere at the top of that file they would've had a "from whatever import BabySkinTone" in the game script, so that would tell you what method it had come from and that you would need to do the same import in your script.

It'll make perfect sense in time, and when you forget to import something it should throw an exception into the LastException file that should make it clear that you are referencing something incorrectly. For example, if I tried to create a notification box from a script, but forgot to import the notification class (UiDialogNotification) from the module it is from (ui_dialog_notification), then the exception would tell me (along with the line of my code where the error occurred).
Code:
NameError: global name 'UiDialogNotification' is not defined


Quote:
And I was wondering what to change if I want to do a simple override instead of adding something to a script. The post didn't cover that and I didn't expect it to be as easy as just omitting the original part.


That's just one way to do it, there's a bit more overhead involved with that method as it uses the injector (which internally is just sort of doing a fancy override while saving a copy of the original function) but that overhead is extremely minimal (maybe 1kb extra code and a few nanoseconds of extra cpu time). But the other way is simpler and more direct and that is to just directly overwrite the method from the original module. For example:

Code:
from sims.baby.baby_tuning import BabyTuning, BabySkinTone

# define your override function with no inject decorator
# and don't add "original" to the argument list

# *** and for this particular function DO include the staticmethod decorator from
# *** the original so it gets defined as a staticmethod
# *** (otherwise it would expect a "self" argument)
@staticmethod
def inject_baby_skin(sim_info):
    if sim_info.is_baby:
        etc. etc. etc.

# and overwrite the original with yours
BabyTuning.get_baby_skin_tone_enum = inject_baby_skin


If it looks a lot like just overwriting a variable that's because it kind of is. It works because everything in Python is an object even a class or method in another module, so we can nearly always write over it and hopefully do great things. Or possibly causing the game to go haywire, but you get used to that and learn to fix it (hopefully before releasing the mod, lol).

Quote:
Using sub-subfolders works like a charm as well, that will make testing a lot faster.


Hmm, did I mention that you can reload a uncompiled script that is in a folder like that without having to exit and restart the game? Considering keeping that a secret...

Nah... It's not really a secret, just probably some newer modders don't know because it was mentioned so long ago here. I keep a copy of that reload script in my script testing folder and it's probably saved me at least 100 hours of exit/reload time when playing with "what can I screw up next" ideas.
Deceased
#5 Old 20th Jul 2018 at 10:04 AM
Quote: Originally posted by godofallbeauties
Originally I wanted to add more baby skin tones for a more accurate representation of the parents, but I don't even understand how or where the game determines which skin to use. I've been looking at the files again and again before opting for the workaround, but it feels like I can see only half of what's going on.
For example in the script part I tried to overwrite: It checks for the skin_tone_id if it is in tone_ids. There is no mention whatsoever of any kind of id list named like that in any other file. So how does it look like? How are the ids calculated? There is a list of all the possible baby skin tones with numbers at the start of the script, but for ids they are pretty short, so that's probably not it. Or is it as easy as a number from 0 to 15 hidden somewhere inside the cas part of an adult skin tone? Is it hardcoded to the instance ids of the original EA skin tones?


Ok, back from what I needed to get done and able to type up a reply to this part, where does the tone_ids come from? The short answer is that it comes from the tuning. The long answers, well I have a feeling this is going to get long enough for a...
Test Subject
Original Poster
#6 Old 22nd Jul 2018 at 9:35 AM
I don't know how much more I should write in here, as the original question has been answered already and I don't want this to turn into spam, but I at least want to say thank you again.
Honestly, this thread is such a treasure. I've gotten a grasp on things already I never though I'd understand and after consulting with Google I have a feeling this may help others as well. It was unexpectedly fun using the SLE to look at different things and digging around the game files, too, I even made me a little diagram to better understand how everything ties together. While doing so I also managed to hit several walls already, (like how none of the skin tone ids match with any number, instance or otherwise, present in the TONE files, sadly, that would've made things a lot easier...) but I'm working on it.
When I posted this I really wasn't expecting someone to put this grade of detail and effort into answering me. And one of the veteran modders of the community no less. I've saved all the info faster than crashing my game with all the new playthings I got. So know that your time writing up those elaborate responses was not spend in vain and that my gratitude will be yours forever. (Or at least until I come to hate everything because nothing ever works. “Monkey Patching” is a great term, ha)
Thank you for your work!
Deceased
#7 Old 22nd Jul 2018 at 7:30 PM
Hmmm..... Try converting the skin tone ids from decimal to hexadecimal and then see what they match

Test Subject
Original Poster
#8 Old 23rd Jul 2018 at 11:10 AM
Quote: Originally posted by scumbumbo
Hmmm..... Try converting the skin tone ids from decimal to hexadecimal and then see what they match


Mind blown!
How do you come up with all this stuff? Is it magic? A sixth sense? Divine afflatus? Whatever it is, this definitely removes a black box from my diagram once again. Nice!
e3 d3 Ne2 Nd2 Nb3 Ng3
retired moderator
#9 Old 23rd Jul 2018 at 8:50 PM
*simsample cries*
This thread is a great example of why I love this site!
Back to top