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

Latest Site News

Nysha's New Creators for November - posted on 1st Dec 2017 at 10:00 AM
Replies: 77 (Who?), Viewed: 58579 times.
Page 1 of 4
Top Secret Researcher
Original Poster
#1 Old 26th Jan 2015 at 7:20 AM Last edited by scumbumbo : 4th Aug 2015 at 9:32 AM.
Default Adding Interactions to Sim/Object Super Affordance List via Python
There's been a lot of experimentation since this was originally posted, so I thought it might be nice to add a list of important posts from the thread:
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:

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.
Advertisement
Pettifogging Legalist!
staff: moderator
#2 Old 26th Jan 2015 at 10:31 AM
OOOOOOOOOOOOOh you are made of awesomeness! Thanks so much, this is I think what we were talking about at EA back in September or something!?

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 TS2TS3TS4 | 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.
Top Secret Researcher
Original Poster
#3 Old 26th Jan 2015 at 4:29 PM
Quote:
Originally Posted by plasticbox
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.
Test Subject
#4 Old 26th Jan 2015 at 5:23 PM Last edited by Aren : 2nd Feb 2015 at 12:53 PM.
Thanks so much! I've been working all day trying to figure out how to do exactly this. Took a break to see if by some miracle anyone else figured it out, and alas!

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).

Code:
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)
Pettifogging Legalist!
staff: moderator
#5 Old 26th Jan 2015 at 5:39 PM
Thank you, Aren =)

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 TS2TS3TS4 | 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.
Top Secret Researcher
Original Poster
#6 Old 26th Jan 2015 at 11:44 PM
Quote:
Originally Posted by Aren
Here's a slightly modified version to add interactions to any game object.


Superb - glad the same method worked out! Thanks!
Test Subject
#7 Old 28th Jan 2015 at 1:49 AM
ooooohh aaaaaahh
Lab Assistant
#8 Old 30th Jan 2015 at 3:50 AM
Quote:
Originally Posted by scumbumbo
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?
Top Secret Researcher
Original Poster
#9 Old 30th Jan 2015 at 5:25 AM
Quote:
Originally Posted by Vinathi
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.
Lab Assistant
#10 Old 31st Jan 2015 at 4:28 AM
Quote:
Originally Posted by scumbumbo
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)
Top Secret Researcher
Original Poster
#11 Old 31st Jan 2015 at 5:51 AM
Quote:
Originally Posted by Vinathi
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.
Lab Assistant
#12 Old 31st Jan 2015 at 6:14 AM
Quote:
Originally Posted by scumbumbo
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
Test Subject
#13 Old 2nd Feb 2015 at 3:50 AM
Please forgive my ignorance when it comes to Python, I must be missing something obvious... Before I tried to use this for a massive spread of things, I did a very simple one to one test. Part of one of my mods has me adding my own file instance 17302112734137328986 to the super affordances in the object_telescope file 14981. It's the only affordance that gets added to that file. When I do that just in the XML files, it works no problem.

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?
Top Secret Researcher
Original Poster
#14 Old 2nd Feb 2015 at 6:06 AM
Quote:
Originally Posted by 5th LMNt
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?
Test Subject
#15 Old 2nd Feb 2015 at 12:55 PM
Quote:
Originally Posted by scumbumbo
@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!
Field Researcher
#16 Old 20th Feb 2015 at 8:11 PM
I am sorry, but how to create XML package with Interaction definitions? Is there any guides for that?
Pettifogging Legalist!
staff: moderator
#17 Old 21st Feb 2015 at 5:42 PM
When trying to add custom interactions to gardening plants, it didnít work to just use the (adapted) code posted by Aren above; I got a bunch of exceptions like

Quote:
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:

Code:
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 TS2TS3TS4 | 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!
staff: moderator
#18 Old 21st Feb 2015 at 5:59 PM
Quote:
Originally Posted by ArtUrlWWW
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 TS2TS3TS4 | 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.
Top Secret Researcher
Original Poster
#19 Old 21st Feb 2015 at 7:50 PM Last edited by scumbumbo : 21st Feb 2015 at 8:01 PM.
Quote:
Originally Posted by plasticbox
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.
Code:
#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:
Code:
# 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
Pettifogging Legalist!
staff: moderator
#20 Old 21st Feb 2015 at 8:00 PM
Aaah thanks very much for the explanation =). The non-tupleness might have resolved itself if I had added the other interactions (which I didnt do since the whole thing didn't work) .. but if it had, Iíd never have realised what the issue was. Live and learn!

Stuff for TS2TS3TS4 | 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.
Top Secret Researcher
Original Poster
#21 Old 21st Feb 2015 at 8:22 PM
Quote:
Originally Posted by plasticbox
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!
Top Secret Researcher
Original Poster
#22 Old 13th Mar 2015 at 6:42 AM
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.
Test Subject
#23 Old 20th Mar 2015 at 12:57 AM
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? I tried figuring out the XML stuff, but no matter what I do, it won't accept my STBL strings and I can't get the interaction to show up. The game complains "T:\InGame\Gameplay\Scripts\Server\sims\self_interactions.py", line 560, in _get_interaction_name AttributeError: 'NoneType' object has no attribute 'localized_category' although I think my ids should be correct, but who knows. It's not exactly a specific error.. STBL debugging sucks..

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..
Top Secret Researcher
Original Poster
#24 Old 20th Mar 2015 at 5:25 AM
Quote:
Originally Posted by blubber
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?
It could probably be done, but it's easier with the XML. Basically the alternative would be to generate all the tuning in script which is not at all fun. I'm playing with that now to improve the text input dialog calls from Python as, for that case, doing it with code is easier than creating some kind of XML interaction to control the text inputs. I'm planning on posting where I got stuck with that later tonight, but I did make some good progress.

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.
Top Secret Researcher
Original Poster
#25 Old 21st Mar 2015 at 3:04 AM
Quote:
Originally Posted by scumbumbo
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.
Page 1 of 4
Back to top