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!
Deceased
Original Poster
#26 Old 22nd Mar 2015 at 8:53 PM Last edited by scumbumbo : 6th Jul 2018 at 12:12 AM. Reason: Link to updated Modding Toolbox
Quote: Originally posted by blubber
Is there a way to generate basic cheat command executing interactions (CommandSuperInteraction) purely from code?

There are nice tutorials for Sims 3.... I got Python code to run nicely so if I could just do it from there..

I've written a bit of a tutorial that demonstrates how to build all the XML, SimData and attach the super affordances to an object. It's actually kind of a useful mod, too, I think I'll keep it in my game!

Attached is a zip file containing a PDF tutorial and all the example files. The mod adds an Enable and Disable Testing Cheats pie menu to the lot mailboxes.

EDIT 07/05/2018 - Fogity's Modding Toolbox as used in the tutorial is no longer available, so I have written an updated version available here in the Modding Tools forum.

Updated 11/26/2017 for latest game patch:
  • Changed interactions to ImmediateSuperInteraction class.
  • Updated tutorial PDF for S4PE changes to STBL edit process.
  • Updated script to use the game's cheat service.
Screenshots
Attached files:
File Type: zip  Tutorial - Mailbox Testing Cheats.zip (248.4 KB, 2945 downloads) - View custom content
Advertisement
Test Subject
#27 Old 12th Apr 2015 at 2:59 AM
I wanted to try your example, but since I can't find the modding toolbox anywhere I haven't really gotten anywhere with it.

I assume you are referring to the Fogity toolbox here: http://modthesims.info/showthread.p...25#startcomment - however, the link just leads to a non-viewable forum thread, so it's apparently not obtainable these days.
Deceased
Original Poster
#28 Old 12th Apr 2015 at 4:01 AM
Quote: Originally posted by blubber
I assume you are referring to the Fogity toolbox here: http://modthesims.info/showthread.p...25#startcomment - however, the link just leads to a non-viewable forum thread, so it's apparently not obtainable these days.

Hmm, well you can create hashes in s4pe, but Fogity's toolbox is by far my favorite tool for generating hashes as it has the built in hex/dec converter as well. @Fogity any plans to make this available again, or should I be suggesting folks just use s4pe for hashing?
Test Subject
#29 Old 12th Apr 2015 at 7:20 PM Last edited by Aren : 12th Apr 2015 at 7:34 PM.
Quote: Originally posted by blubber
I wanted to try your example, but since I can't find the modding toolbox anywhere I haven't really gotten anywhere with it.
I assume you are referring to the Fogity toolbox here: http://modthesims.info/showthread.p...25#startcomment - however, the link just leads to a non-viewable forum thread, so it's apparently not obtainable these days.


Quote: Originally posted by scumbumbo
Hmm, well you can create hashes in s4pe, but Fogity's toolbox is by far my favorite tool for generating hashes as it has the built in hex/dec converter as well. @Fogity any plans to make this available again, or should I be suggesting folks just use s4pe for hashing?


I made a quick CLI tool you could use in the meantime until Fogity gets it re-uploaded. Just double click on Hasher.bat and enter in the text you want to hash. It's obviously not as pretty, but it'll get the job done.

Edit: Oh, and you may want to look up how to copy values from cmd.exe if you haven't already. You'll need to enable QuickEdit, but there are instructions for how to do this online. You'll also need Python installed and added to your path (it gives you an option to do that during installation).
Attached files:
File Type: zip  hasher.zip (788 Bytes, 169 downloads) - View custom content
Lab Assistant
#30 Old 12th Apr 2015 at 8:53 PM
Quote: Originally posted by scumbumbo
Hmm, well you can create hashes in s4pe, but Fogity's toolbox is by far my favorite tool for generating hashes as it has the built in hex/dec converter as well. @Fogity any plans to make this available again, or should I be suggesting folks just use s4pe for hashing?


I am working on a new version, but I am not quite done with it yet. So in the meantime I can give you a link to the old version (they are functionally equivalent), and feel free to share it if someone else wants to use it.

Here are the links: Mac OS X and Windows
Pettifogging Legalist!
retired moderator
#31 Old 12th Apr 2015 at 10:36 PM
@Fogity, I added those links to the Tools list (sticky) in Tools -- it would be cool if you could post a link in that that thread when you have a new official version (or versions) available, so I can remove those interim links. Thanks! =)

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.
Lab Assistant
#32 Old 12th Apr 2015 at 10:54 PM
Quote: Originally posted by plasticbox
@Fogity, I added those links to the Tools list (sticky) in Tools -- it would be cool if you could post a link in that that thread when you have a new official version (or versions) available, so I can remove those interim links. Thanks! =)


I'll do that.
Mad Poster
#33 Old 1st May 2015 at 5:22 PM
I was talking to Scumbumbo about this and noticed that when attaching to Sims or Objects through on_add, you are doing it every time they are instanced, which could cause an impact in performance. So, I have been looking into doing a way of doing it once. What I came-up with is injecting into the instance manager right after all the tuning files are loaded. At that point, you can add your affordances to the object tuning files and they'll be there for the rest of the time the game is running. It may cause a slightly longer loading time, but once you're to the main menu, it should not have to be added to the objects again.

The injection works like this:
Code:
sa_instance_ids = (17608706005782878675, 13258313599595875556, ...etc for all the interactions you're adding)
object_ids = (14845, 34680, 40340, 34682, 34684, 34679, 34678, 36369, 36370, 77507, ...blah, blah, whatever objects you want to attach to, which could also be a "Sim" object)

@inject_to(sims4.tuning.instance_manager.InstanceManager, 'load_data_into_class_instances')
def inject_load_data_into_class_instances(original, self):
    try:
        original(self)
        for (key, cls) in self._tuned_classes.items():
            if hasattr(cls, 'guid64') and cls.guid64 in object_ids:
                add_super_affordances_to_object(object_tuning)
    except:
        _write_error_log(traceback.format_exc())
        pass


The add_super_affordances_to_objects method looks very similar to what we already have in the tutorial for adding testing cheats to mailboxes:
Code:
def add_super_affordance_to_object(object_tuning):
    affordance_manager = services.affordance_manager()
    sa_list = []
    for sa_id in sa_instance_ids:
        tuning_class = affordance_manager.get(sa_id)
        if tuning_class is not None:
            sa_list.append(tuning_class)
    if len(sa_list) > 0:
        object_tuning._super_affordances = object_tuning._super_affordances + tuple(sa_list)
Pettifogging Legalist!
retired moderator
#34 Old 1st May 2015 at 6:09 PM
Thank you, that sounds like a smart move =)

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.
Lab Assistant
#35 Old 5th May 2015 at 5:55 AM
@Deaderpool
Can you explain more about injecting into sims?

I have some experience with C++ but I don't have much experience with Python, only helped a guy long time ago in a brief part of his code. I also don't know anything about the attributes in the Sims 4 variables.
Mad Poster
#36 Old 5th May 2015 at 2:06 PM
Quote: Originally posted by An_dz
@Deaderpool
Can you explain more about injecting into sims?

I have some experience with C++ but I don't have much experience with Python, only helped a guy long time ago in a brief part of his code. I also don't know anything about the attributes in the Sims 4 variables.

I can try to explain it a little. Scumbumbo wrote up a post describing it way way back, I think. I believe it was around the time Sims 4 was released. That was where I started reading about it and learning it. Also, looking through the py files in the script mods that he released usually had some small examples that I could "absorb" from.

In theory, any method in an EA python module, we can inject into and basically wrap it so our own method is called instead. From our method, we can then choose to call the base method when we want to (doing custom coding before or after it) or bypass it altogether and use our own script (I try to avoid this as you never know when they'll add something to the method that you aren't accounting for).

This was the basic injection wrapper functions that I used to get started from some of Scumbumbo's work:
Code:
from functools import wraps

# method calling injection
def inject(target_function, new_function):
    @wraps(target_function)
    def _inject(*args, **kwargs):
        return new_function(target_function, *args, **kwargs)
    return _inject

# decarator injection.
def inject_to(target_object, target_function_name):
    def _inject_to(new_function):
        target_function = getattr(target_object, target_function_name)
        setattr(target_object, target_function_name, inject(target_function, new_function))
        return new_function
    return _inject_to

The basic concept is that you use an inject_to decorator on the method that you want to overwrite EA code giving it the details of what method you want to ovewrite. Using a very common example of wanting to add some code whenever you zone-into a lot with your Sim household active, you could do that within the Zone class in the "on_loading_screen_animation_finished" method. So, first you have to import the module that contains the "Zone" class, which is simply "zone". Then you have to write a method and put the inject_to decorator on it. Like so:
Code:
import zone

@inject_to(zone.Zone, 'on_loading_screen_animation_finished')
def inject_zone_loading(original, self):
    original(self)
    # Do any custom coding you want here, which will happen after the EA-scripts for zone loading finish.

You'll notice that the method I wrote has "original" and "self" as the parms. This comes first of all by looking at the method you're overwriting in the EA scripts. Looking up the zone.py script file and finding that method within the Zone class, you'd see this for the method's definition:
Code:
    def on_loading_screen_animation_finished(self):

This is a really simple example of a method and I LOVE it when they are this simple. Any time you do a wrapper on a standard method, there is ALWAYS an "original" object that you'll need as the first parameter in the method you're writing to wrap around it. The "original" object is an actual reference to the EA function. So, to call that function and let the standard code run within your method, you would use original() sending it any additional parms defined by the core method, which in the example I showed above, is only "self". The call to "original(self)" would run that core method from within your own method.

As another example, let's say that EA has a class named "FooFighter" and it was in the foo_fighter.py module, and it had a method we wanted to inject into named "do_really_cool_stuff" that looked like this in the core script:
Code:
    def do_really_cool_stuff(self, more_stuff, and_yet_more_stuff, optional_parm=True, so_many_parms=True)

Then your injection would HAVE to take the parms past "self" into account as well in your injection.
Code:
import foo_fighter

@inject_to(foo_fighter.FooFighter, 'do_really_cool_stuff')
def my_even_more_really_cool_stuff(original, self, more_stuff, and_yet_more_stuff, **kwargs):
    # Do any custom coding you want here, which will happen before the EA-scripts for do_really_cool_stuff runs.
    original(self, more_stuff, and_yet_more_stuff, **kwargs)

You can see that all the required parameters were specified on my new method in addition to the "original" object, and for all the optional parameters (anything with "="), I just used a generic "**kwargs" to catch them all and pass them to the original function. If I needed to overwrite those with some values or read what is in them, you can break them out within your method and the call to original just like we did with "more_stuff", "and_yet_more_stuff", etc.

That's what I would consider entry-level injection. It gets a lot more complicated as you get into @classmethod decorators or @flexmethod decorators. Those don't behave like standard methods so injecting into them has tricks that you have to do, and I don't know that I've even figured out exactly how to make @flexmethods work yet. I think I've worked around them and used different methods each time.

Keep in mind that if EA changes the parameters on the core method you're wrapping, you'll have to change your scripts as well to include any changes they've made. And, there are quite a few examples out there now of python script mods that can be learned from. I know that's what I did (and still do!)
Mad Poster
#37 Old 14th May 2015 at 1:39 PM
Another fun twist I discovered yesterday, if a method is marked in EA's core scripts as "@staticmethod", it may or may not have a "self" object, depending on how the method is called from elsewhere in EA's code. For example, PregnancyTracker.initialize_sim_info is called from adoptions with no "self" object, but called from pregnancy birth's with a "self" object. So, yay! You have to account for that and make sure you code appropriately!
Lab Assistant
#38 Old 31st May 2015 at 8:32 PM
Hiya ! I'm trying to add "take a Paid Time Off (PTO)" using Python. I really don't know how to use but I managed, in one way or another, to create a zip file with the injector and the script of the interaction :

Code:
import services
import injector

PTOEduc_sa_instance_ids = (10267729682623062937, )

@injector.inject_to(sims.sim.Sim, 'on_add')
def PTOEduc_add_phone_affordances(original, self):
    original(self)
    sa_list = []
    affordance_manager = services.affordance_manager()
    for sa_id in PTOEduc_sa_instance_ids:
        tuning_class = affordance_manager.get(sa_id)
        if not tuning_class is None:
            sa_list.append(tuning_class)
    self._phone_affordances = self._phone_affordances + tuple(sa_list)


But in game, it doesn't work, no new interaction is showing up... Is something missing ?

Sorry for my bad english...

Tuto-How to create a career - Sims 4 open to suggestions.
Lab Assistant
#39 Old 31st May 2015 at 9:11 PM
Quote: Originally posted by OhMy!!
But in game, it doesn't work, no new interaction is showing up... Is something missing ?

Yes.
Code:
import sims.sim
Lab Assistant
#40 Old 1st Jun 2015 at 10:27 PM Last edited by OhMy!! : 8th Jun 2015 at 8:59 PM.
Quote: Originally posted by An_dz
Yes.
Code:
import sims.sim

̶
A̶c̶t̶u̶a̶l̶l̶y̶,̶ ̶I̶ ̶t̶h̶i̶n̶k̶ ̶I̶'̶v̶e̶ ̶m̶e̶s̶s̶e̶d̶ ̶u̶p̶ ̶w̶i̶t̶h̶ ̶t̶h̶e̶ ̶"̶i̶m̶p̶o̶r̶t̶ ̶m̶o̶d̶u̶l̶e̶"̶ ̶t̶h̶i̶n̶g̶ ̶i̶n̶ ̶P̶y̶t̶h̶o̶n̶ ̶:̶f̶a̶c̶e̶s̶l̶a̶p̶:̶ ̶,̶ ̶s̶o̶ ̶I̶'̶l̶l̶ ̶h̶a̶v̶e̶ ̶t̶o̶ ̶s̶e̶e̶ ̶t̶h̶a̶t̶ ̶l̶a̶t̶e̶r̶.̶.̶.̶ ̶B̶u̶t̶ ̶T̶h̶a̶n̶k̶ ̶y̶o̶u̶ ̶t̶o̶ ̶t̶e̶l̶l̶i̶n̶g̶ ̶m̶e̶ ̶w̶h̶a̶t̶ ̶w̶a̶s̶ ̶m̶i̶s̶s̶i̶n̶g̶ ̶n̶e̶v̶e̶r̶t̶h̶e̶l̶e̶s̶s̶,̶ ̶i̶t̶ ̶w̶i̶l̶l̶ ̶h̶e̶l̶p̶ ̶m̶e̶ ̶!̶

Edit : Officially, thank you An-dz, it's working ! Thank you so much ! :D

Sorry for my bad english...

Tuto-How to create a career - Sims 4 open to suggestions.
Deceased
Original Poster
#41 Old 28th Jul 2015 at 7:56 AM
So it seems SocialMixerInteractions aren't added to the sim's super affordance list, but rather to a similar list stored in a snippet tuning. To handle this, I used the same function that Deaderpool injected into an earlier example to alter tunings as they are loaded at the game startup. This seems to work well. The instance IDs are for some research I did for sachamagne. Altering this code to work with multiple instance IDs in a loop is an exercise left up to the reader, which should be a walk in the park for you if you've followed this thread this far.

So, the code:
Code:
import injector
import sims4.resources
from sims4.tuning.instance_manager import InstanceManager
from sims4.resources import Types
import services

SNIPPET_ID = 24511
MIXER_ID = 13984173314801262715

@injector.inject_to(InstanceManager, 'load_data_into_class_instances')
def add_mixer_to_snippet(original, self):
    # Let the original function finish up its work
    original(self)

    # This function gets called once for each tuning type, so by only running this if we're specifically
    # working on the snippets, we can just get that snippet directly using the key rather than looping
    # through all the instances to and checking the guids.  The same tactic works for getting the tuning
    # straight for the mixer interaction directly from the affordance manager.

    if self.TYPE == Types.SNIPPET:
        # Get the snippet tuning
        key = sims4.resources.get_resource_key(SNIPPET_ID, Types.SNIPPET)
        snippet_tuning = self._tuned_classes.get(key)
        # Check that we found it....
        # If we were running in a loop, we'd probably want to skip to the next iteration
        # rather than just returning if the snippet id is invalid.
        if snippet_tuning is None:
            return
        # Get the mixer from the affordance manager
        affordance_manager = services.affordance_manager()
        key = sims4.resources.get_resource_key(MIXER_ID, Types.INTERACTION)
        mixer_tuning = affordance_manager.get(key)
        if mixer_tuning is None:
            return
        # Add the mixer tuning to the snippet's value (a tuple)
        snippet_tuning.value = snippet_tuning.value + (mixer_tuning, )
Lab Assistant
#42 Old 28th Jul 2015 at 8:28 AM
Thanks again for that piece of study !
Pettifogging Legalist!
retired moderator
#43 Old 3rd Aug 2015 at 6:23 PM
I have another thing now where I want to add an interaction to a bunch of stuff -- it is working fine when I do the "on_add" thing but I'd much prefer to make it work the way Deaderpool explained in post 33 (since it's so many objects and it just seems much more reasonable to only do stuff once when that's possible) but I can't figure out how the injector part would actually have to look like =/

This is the injector I use:




This is the stuff that I'm doing (I *did* manage to put the commas in the correct places now, at least =P)




^^ Above is the stuff that IS working, albeit not very efficiently I guess. (The point is that sims can swipe craftables that don't belong to them, so that they can exchange stuff between households on the little farmer's market they built)


The way I read it, it would need to look like this now (but there is probably something missing):





.. but that results in nonworkingness. What am I missing here?

(I guess I don't need to import the objects and types any more, actually .. I just left that in for now. Also, I don't actually need to import zone in the injector, or do I?)

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.
Deceased
Original Poster
#44 Old 4th Aug 2015 at 7:38 AM Last edited by scumbumbo : 5th Aug 2015 at 1:37 AM.
Hey Plasticbox,

Yeah, there were a couple of issues with the code you had. Notably you were injecting into the wrong function to add the SAs to the tuning like Deaderpool demonstrated in post 33. This is probably what I will be using in the future - a combination of Deaderpool's method but using some of the new tricks I learned figuring out the mixer stuff above. Should be a slight bit faster as it doesn't loop through every loaded tuning looking at the guids. This should work for you as is (assuming I didn't make any mistakes!):

ETA - Added sims4.resources import
Pettifogging Legalist!
retired moderator
#45 Old 4th Aug 2015 at 4:36 PM Last edited by plasticbox : 5th Aug 2015 at 2:01 AM.
Quote: Originally posted by scumbumbo
Notably you were injecting into the wrong function


.. DUH!

Yeah now I see it too ..

Thank you. It all makes sense now. Your code is so .. readable! =)

I'll test it out later, will let you know if I still see issues with it!

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.
Pettifogging Legalist!
retired moderator
#46 Old 4th Aug 2015 at 11:29 PM
Okay, initially your code did not work (kicked me back out to the world selection when attempting to load a lot, and dumped an endless LastException complaining about 'sims4' not being a defined name), but now it does.

Changes I made:

- Renamed my injector.py to swipeinjector.py since the LastException was suddenly also complaining about graycurse's cooking ingredients mod (which it has never had any issues with before, so I guess it now got confused which injector the swipeit was talking about?)

- Added import sims4.resources to the swipeit.py -- my train of thought was "does this know what 'get_resource_key' means when I only import Types? Maybe it doesn't so I'm just gonna feed it the whole thing and see if it works then" (which I realise may still be a little bit sub-optimal)


Haven't done a lot of testing yet but the same lot that didn't load before now does, and the sims who live there can also swipe all the stuff that other sims have dropped onto that lot (which they wouldn't be able to by default).

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.
Deceased
Original Poster
#47 Old 5th Aug 2015 at 1:36 AM
Quote: Originally posted by plasticbox
Okay, initially your code did not work

Changes I made:

- Renamed my injector.py to swipeinjector.py since the LastException was suddenly also complaining about graycurse's cooking ingredients mod (which it has never had any issues with before, so I guess it now got confused which injector the swipeit was talking about?)


If you have two modules named identically only one of them will be used, so yes, if you need a customized injector it would have to use a different module name. Deaderpool has a really nice one he's whipped up that catches issues that may arise when EA updates a function to have different parameters.

Quote:
- Added import sims4.resources to the swipeit.py -- my train of thought was "does this know what 'get_resource_key' means when I only import Types? Maybe it doesn't so I'm just gonna feed it the whole thing and see if it works then" (which I realise may still be a little bit sub-optimal)


Hmm, at first I thought, "Duh how could I have forgotten that?" and then looked at my code and it's not importing sims4.resources. Not sure why it's working, maybe something else I was importing in my quick tests script file (which is full of a lot of random garbage, lol) imports that module. I'll edit the post to add that import.
Pettifogging Legalist!
retired moderator
#48 Old 5th Aug 2015 at 2:08 AM
Quote: Originally posted by scumbumbo
if you need a customized injector


Actually I haven't tried to use the first one again -- maybe that would have worked too if it hadn't been for the missing import. I only changed it to the second one (of the two I quoted above) because that was what Deaderpool quoted in post #36 above. Possibly that caused an issue with the greycurse thing which was suddenly missing the is_injectable .. I guess?

But wouldn't it make sense on the long run when people give their injectors individual names anyway? Right now it's like we're all silently relying on them all being identical ..

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.
Lab Assistant
#49 Old 5th Aug 2015 at 5:49 PM
As it does to add an interaction to an NPC that I created ? I created an interaction and managed to make it work, but I wanted this interaction appeared only for a job that I created ... (sorry my bad english , I'm using google translator )

Thanks
Deceased
Original Poster
#50 Old 6th Aug 2015 at 6:07 AM
Quote: Originally posted by arkeus17
As it does to add an interaction to an NPC that I created ? I created an interaction and managed to make it work, but I wanted this interaction appeared only for a job that I created ... (sorry my bad english , I'm using google translator )

Someone who's worked more with these might have better info, but an NPC job is usually attached to a "situation". You can add code in the XML for an interaction to make it only occur if the NPC is running that situation... but there might be a better way.

You may want to open a new thread for this question, as it doesn't appear to deal with the topic of adding the affordances via Python.
Page 2 of 5
Back to top