- Site Map >
- Modding and Creation >
- Sims 4 Creation >
- Modding Discussion >
- Adding Interactions to Sim/Object Super Affordance List via Python
- Site Map >
- Modding and Creation >
- Sims 4 Creation >
- Modding Discussion >
- Adding Interactions to Sim/Object Super Affordance List via Python
Posts: 2,671
Thanks: 62762 in 190 Posts
- Tutorial Post
- Deaderpool's Improved "Tuning" Method to add SAs to Objects
- Deaderpool Explains Script Injection
- Adding Social Mixer Interactions
- Most Recent Method of Addings SAs to Objects
Original Message
This should be a useful addition to the collective arsenal, allowing the ability to add interactions for sims without requiring overriding the _super_affordances tuning in the object_sim resource. This allows for multiple mods to add interactions for sims without conflict.
treelife brought this up recently in a thread, although I believe that was in reference to objects. This method works for just sims, but should be able to be adapted to objects as well. A sim is just a special object after all, the only difficulty with objects may be to identify the proper object to add the affordances to, but that shouldn't be too major. There had been a thread about this earlier, and although it was agreed that scripting might be a good idea to overcome conflicts, I don't know that anyone made much progress at it. It seemed straightforward, but without knowing how all the internals actually worked it wasn't at all.
After a lot of false tries, and a lot of poking through the source, and a lot of examing the outputs from vars() on objects, I finally had some success. Turns out it is actually fairly easy! The super affordance tuning classes available to a sim are stored as a tuple in the sim object called _super_affordances. All that remained was to find a way to query the affordance manager to get the tuning classes we want to add to the sim.
There may be a better way to do this, but I'm just relieved to have figured this method out in the short span of 10 or 15 hours worth of work.
First, create the XML for all the interactions, pie menu categories, etc. Create a tuple of all the instance ids for all of your interactions - the numbers you would normally add to the _super_affordances list in the XML. Then a little bit of Python, injected into the sims on_add() method will do the trick:
import services import injector YOURMODNAME_sa_instance_ids = (17608706005782878675, 13258313599595875556, ...etc for all the interactions you're adding) @injector.inject_to(sims.sim.Sim, 'on_add') def YOURMODNAME_add_super_affordances(original, self): original(self) sa_list = [] affordance_manager = services.affordance_manager() for sa_id in YOURMODNAME_sa_instance_ids: tuning_class = affordance_manager.get(sa_id) if not tuning_class is None: sa_list.append(tuning_class) self._super_affordances = self._super_affordances + tuple(sa_list)
That's all there is to it. You'll also need the injector.py script, but several script mods have this already to get a copy of - e.g. Always Start Lots Pause
Obviously, as much time as I've spent on getting this working, I'd appreciate credit for anyone who uses this.
Posts: 11,006
Thanks: 423237 in 1121 Posts
I'll try to use this for my Kindergardening stuff next time I have time to fiddle with that -- this is what I'd need to add interactions to the plants, without overriding them all, correct?
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.
Posts: 2,671
Thanks: 62762 in 190 Posts
this is what I'd need to add interactions to the plants, without overriding them all, correct? |
I haven't tried it yet but yes, I believe that pretty much the same technique should work attached to a method of script_object or game_object.
Posts: 47
Thanks: 1429 in 46 Posts
Here's a slightly modified version to add interactions to any game object. Just add the IDs of the objects you want to the object_ids tuple (for example, I included the IDs of all the computers in the game).
import services import objects.game_object import injector YOURMODNAME_sa_instance_ids = (17608706005782878675, 13258313599595875556, ...etc for all the interactions you're adding) YOURMODNAME_object_ids = (14845, 34680, 40340, 34682, 34684, 34679, 34678, 36369, 36370, 77507) @injector.inject_to(objects.game_object.GameObject, 'on_add') def YOURMODNAME_add_super_affordances(original, self): original(self) if not self.guid64 in YOURMODNAME_object_ids: return sa_list = [] affordance_manager = services.affordance_manager() for sa_id in YOURMODNAME_sa_instance_ids: tuning_class = affordance_manager.get(sa_id) if not tuning_class is None: sa_list.append(tuning_class) self._super_affordances = self._super_affordances + tuple(sa_list)
Posts: 11,006
Thanks: 423237 in 1121 Posts
I took the liberty to edit the thread title so it’s clear at one glance that this isn’t only about sims. And stickified, as well.
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.
Posts: 2,671
Thanks: 62762 in 190 Posts
Here's a slightly modified version to add interactions to any game object. |
Superb - glad the same method worked out! Thanks!
Posts: 146
This should be a useful addition to the collective arsenal, allowing the ability to add interactions for sims without requiring overriding the _super_affordances tuning in the object_sim resource. This allows for multiple mods to add interactions for sims without conflict. treelife brought this up recently in a thread, although I believe that was in reference to objects. This method works for just sims, but should be able to be adapted to objects as well. A sim is just a special object after all, the only difficulty with objects may be to identify the proper object to add the affordances to, but that shouldn't be too major. There had been a thread about this earlier, and although it was agreed that scripting might be a good idea to overcome conflicts, I don't know that anyone made much progress at it. It seemed straightforward, but without knowing how all the internals actually worked it wasn't at all. After a lot of false tries, and a lot of poking through the source, and a lot of examing the outputs from vars() on objects, I finally had some success. Turns out it is actually fairly easy! The super affordance tuning classes available to a sim are stored as a tuple in the sim object called _super_affordances. All that remained was to find a way to query the affordance manager to get the tuning classes we want to add to the sim. There may be a better way to do this, but I'm just relieved to have figured this method out in the short span of 10 or 15 hours worth of work. First, create the XML for all the interactions, pie menu categories, etc. Create a tuple of all the instance ids for all of your interactions - the numbers you would normally add to the _super_affordances list in the XML. Then a little bit of Python, injected into the sims on_add() method will do the trick:
Code:
import services import injector YOURMODNAME_sa_instance_ids = (17608706005782878675, 13258313599595875556, ...etc for all the interactions you're adding) @injector.inject_to(sims.sim.Sim, 'on_add') def YOURMODNAME_add_super_affordances(original, self): original(self) sa_list = [] affordance_manager = services.affordance_manager() for sa_id in YOURMODNAME_sa_instance_ids: tuning_class = affordance_manager.get(sa_id) if not tuning_class is None: sa_list.append(tuning_class) self._super_affordances = self._super_affordances + tuple(sa_list) That's all there is to it. You'll also need the injector.py script, but several script mods have this already to get a copy of - e.g. Always Start Lots Pause Obviously, as much time as I've spent on getting this working, I'd appreciate credit for anyone who uses this. |
is it possible for me to assign an interaction to the currently active sim to play on him/herself using this code?
Posts: 2,671
Thanks: 62762 in 190 Posts
is it possible for me to assign an interaction to the currently active sim to play on him/herself using this code? |
Well, the super affordance would get applied to all sims, but the XML can be written to test that the player is clicking the currently active sim.
Posts: 146
Well, the super affordance would get applied to all sims, but the XML can be written to test that the player is clicking the currently active sim. |
and could I get the "interaction" to play a python method rather than an animation? (like start/end a function)
Posts: 2,671
Thanks: 62762 in 190 Posts
and could I get the "interaction" to play a python method rather than an animation? (like start/end a function) |
Yeah, I just uploaded a mod that java7nerd and I made tonight, the Pregnancy Mega Mod that does just that using the CommandSuperInteraction tunings. It will send the script the target of the interaction, a sim ID or object ID as an argument. There's also a "do_command" tuning available to regular SuperInteractions which would allow for animation and so forth to be added. I'm planning on using that for a future lottery mod.
Posts: 146
Yeah, I just uploaded a mod that java7nerd and I made tonight, the Pregnancy Mega Mod that does just that using the CommandSuperInteraction tunings. It will send the script the target of the interaction, a sim ID or object ID as an argument. There's also a "do_command" tuning available to regular SuperInteractions which would allow for animation and so forth to be added. I'm planning on using that for a future lottery mod. |
you're seriously amazing! I have no idea how you all produce mods so fast and still do such an excellent job. in any case, thanks for helping me
Posts: 30
Thanks: 5756 in 8 Posts
So I stripped the 14981 file out of my XML package. I used the object code from this thread as such:
import services
import injector
TEST_sa_instance_ids = (17302112734137328986,)
TEST_object_ids = (14981,)
@injector.inject_to(objects.game_object.GameObject, 'on_add')
def TEST_add_super_affordances(original, self):
original(self)
if not self.guid64 in TEST_object_ids:
return
sa_list = []
affordance_manager = services.affordance_manager()
for sa_id in TEST_sa_instance_ids:
tuning_class = affordance_manager.get(sa_id)
if not tuning_class is None:
sa_list.append(tuning_class)
self._super_affordances = self._super_affordances + tuple(sa_list)
I included the injector file, I tried it with and without the final commas in the lists, and I tried it both with the .py files in Scripts subfolder and also with it all compiled and zipped. But it doesn't actually work in the game. Can someone tell me where I've gone wrong?
Posts: 2,671
Thanks: 62762 in 190 Posts
Please forgive my ignorance when it comes to Python, I must be missing something obvious... |
The obvious only becomes obvious with experience, so no forgiveness is necessary. The problem is that the injector.inject_to line doesn't see the GameObject object unless you import the file it's contained in. Add a line to the imports in your code above:
import objects.game_object
Everything should start to come together after that. And yes, since you're only using one interaction and one object, you are correct that you need the comma at the end of the lists in order to make them tuples - that threw me for a loop the first time I ran into it myself.
@Aren - Can you update the code in your post above to include that import line when you get the chance?
Posts: 47
Thanks: 1429 in 46 Posts
@Aren - Can you update the code in your post above to include that import line when you get the chance? |
Ah yeah, I forgot to add that in. Updated, thanks!
Posts: 133
Thanks: 1494 in 13 Posts
Posts: 11,006
Thanks: 423237 in 1121 Posts
File "C:\Users\User\Documents\Electronic Arts\The Sims 4\Mods\pbox_kindergarden\Scripts\pbox_kindergarden.py", line 17, in pbox_kindergarden_add_super_affordances for sa_id in pbox_kindergarden_sa_instance_ids: TypeError: 'int' object is not iterable |
and the objects in question also disappeared on load, no matter what they were (for testing purposes I also tried with other stuff but plants). Adding the same interaction to the same objects by way of a manual override (adding them in to the object XML and overriding that) did work, however.
When I use the script posted by @graycurse in this Feedback thread as a basis, it does work. That one seems to do some more elaborate error catching or something? .. I can’t say I really understand exactly WHY it works:
import services import injector import objects import types pbox_kindergarden_sa_instance_ids = (11045034422696481829) pbox_kindergarden_object_ids = (14927, 28885, 32065, 32083, 32085, 33322, 33386, 33387, 33388, 33389, 33390, 33759, 33760, 33761, 33762, 33763, 33764, 33765, 33766, 33767, 33768, 33769, 33770, 33771, 33772, 33924, 33925, 33926, 34532, 34533, 34534, 34539, 37689, 37692, 37693, 37696, 37697, 37698, 37700, 37701, 39337, 39338, 39339, 39340, 39341, 39342, 39343, 39594, 39603, 39605, 39606, 74240, 74569, 75984, 75991, 75992, 75993, 75994, 75995, 75996, 75997, 75998, 76066, 76067, 76068, 76070, 77116, 77117, 77118, 77119, 77120, 77123, 77124, 77125, 77126, 77127, 77128, 102303) def attachinteraction(sa_list, affordance_manager, sa_id): tuning_class = affordance_manager.get(sa_id) if not tuning_class is None: sa_list.append(tuning_class) @injector.inject_to(objects.game_object.GameObject, 'on_add') def pbox_kindergarden_add_super_affordances(original, self): original(self) if not self.guid64 in pbox_kindergarden_object_ids: return sa_list = [] affordance_manager = services.affordance_manager() if(isinstance(pbox_kindergarden_sa_instance_ids, int)): attachinteraction(sa_list, affordance_manager, pbox_kindergarden_sa_instance_ids) else: for sa_id in pbox_kindergarden_sa_instance_ids: attachinteraction(sa_list, affordance_manager, sa_id) self._super_affordances = self._super_affordances + tuple(sa_list)
Can someone perhaps explain where the crucial difference is?
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.
Posts: 11,006
Thanks: 423237 in 1121 Posts
I am sorry, but how to create XML package with Interaction definitions? Is there any guides for that? |
Make a new package in s4pe and then Import > From File.
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.
Posts: 2,671
Thanks: 62762 in 190 Posts
When I use the script posted by @graycurse as a basis, it does work. That one seems to do some more elaborate error catching or something? .. I can’t say I really understand exactly WHY it works Can someone perhaps explain where the crucial difference is? |
In Python, the parenthesis are not what makes a tuple a tuple, they are really just a decorative convention to kind of comment that "this is supposed to be a tuple". The commas are what make a tuple.
#in other words.... pbox_kindergarden_sa_instance_ids = (11045034422696481829) # is really just the same thing as pbox_kindergarden_sa_instance_ids = 11045034422696481829
Since the result is not a tuple, just a plain integer, it's not iterable.
graycurse adds a test to see if the instance_ids variable contains just an integer object and, if it does, skips the iteration of the tuple. That's one fix, and it's 100% perfectly acceptable. Another fix would be to just use the original code and make the instance_ids variable a true tuple containing a single integer by using the comma after the value:
# this is a true tuple and is iterable pbox_kindergarden_sa_instance_ids = (11045034422696481829, )
I feel this makes the code a little cleaner and easier to read as it avoids the need for the test and a separate function (the separate function is just to avoid code duplication and is a good thing for graycurse's method, it doesn't change how the assignment acts or anything, just encapsulates it rather than repeating the assignment twice in the if/else block).
So, use what you like - both should work fine! But if you do use graycurse's method, for clarity you should probably remove the parenthesis around the assignment of the single int to make it clear that it's just a single int and not a tuple.
EDIT - And since I'm planning on playing today and not doing any modding at all (yeah, that'll happen) I think I'm going to have my geeky programmer/author sim write a new mystery book, The Curse of the Missing Tuple
Posts: 11,006
Thanks: 423237 in 1121 Posts
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.
Posts: 2,671
Thanks: 62762 in 190 Posts
The non-tupleness... |
Ooh, I like that word!
Yeah, tupleness threw me for a loop as I was first learning to get this working, which bears the question "Why use a tuple at all, why not just a list?"
The primary reason is for speed. A tuple is immutable, you can't append an item to a tuple like you can a list, so it can be stored and used by Python a bit faster. Since this function is going to get called a LOT during the game (every single time for every single object that is put on a lot), speed is important. This is just one of the reasons why EA used a tuple to store all the SA instances (instanced objects, not instance ids - important difference) in the object, so it can be quickly searched - they didn't anticipate we'd be wanting to add items to it!
This is also why at the end we have to convert the sa_list (which had to be a list as we need to append to it) to a tuple in order to add it to the original tuple and then reassign the whole shebang over the original tuple. We can't just append to the tuple, we have to replace the whole thing. Read that through a few times and it should make sense!
Posts: 2,671
Thanks: 62762 in 190 Posts
Posts: 18
Your code seems pretty close, but it still requires the working XML for an interaction which I just can't figure out. There are nice tutorials for Sims 3: http://modthesims.info/wiki.php?tit...ocalized_Coding but as I wrote above, I can't figure it out for Sims 4 even if I set up my STBL stuff in a similar way to that tutorial. And I got Python code to run nicely so if I could just do it from there..
Posts: 2,671
Thanks: 62762 in 190 Posts
Is there a way to generate basic cheat command executing interactions (CommandSuperInteraction) purely from code? Does someone have some code lying around that does that as an example? |
But for what it sounds like what you're wanting, just calling a script command from the UI, it's easier with the XML. I'll try and come up with a good example and spend some time on doing a tutorial if folks think it would be helpful.
Posts: 2,671
Thanks: 62762 in 190 Posts
The technique for adding an affordance to the sim object also works for adding SAs to the _phone_affordances of a sim. Just change the last line of the script in the first post to reference self._phone_affordances If you need an example, check out the Change Career Branch mod which uses this tactic. |
Was looking through the sim.py file for something totally unrelated, but noticed something else that should also be useable: self._relation_panel_affordances should also be a target for adding SAs to the relationship panel. Haven't tried it, but it appears that it should work.
Who Posted
|