Quick Reply
Search this Thread
Imagine Wonderfully!
staff: trainee moderator
Original Poster
#1 Old 29th Nov 2023 at 12:34 AM Last edited by TheSweetSimmer : 30th Nov 2023 at 12:08 AM.
Default [Tutorial] Making A New Interaction From Start to Finish
Are you new to modding and want to make your very own interactions for the game? Well you've come to the right place! In this tutorial I will be showing you how to make a simple interaction that makes a sim wave at another sim from start to finish and help you understand to the best of my ability how it works.

Requirements:
  • Visual Studio or SharpDevelop (for writing the script)
  • S3PE (to assemble everything for the game to read)
  • Knowledge of the coding language C# isn't required, but it's always better if you know a few things about it

In this tutorial I'll be using SharpDevelop, which personally I like better than VS because it's more simple and easy but you can still follow along if you want to use Visual Studio.

Step 1: Setting up your code editor for TS3 Modding

Before you can go ahead and start writing anything, you'll need to set up Visual Studio or SharpDevelop so your project can be compatible with TS3.

• If you're using Visual Studio, follow this tutorial https://modthesims.info/wiki.php?ti..._Studio_project
• If you're using Sharp Develop, follow this tutorial by Battery https://modthesims.info/showthread.php?t=632267

If you've got any questions or need further help don't hesitate to ask!

Step 2: The Namespace, Class and it's static Constructor and Field

When starting out, you'll have something like or similar to this:
Code:
using System;
using System.Collections.Generic;

namespace Savanita.WaveAtTutorial
{
	/// <summary>
	/// Description of MyClass.
	/// </summary>
	public class MyClass
	{
		
	}
}

You must use a unique namespace so it doesn't conflict with other modders' code or EA's. The class name can be whatever you want it to be.

Now will also be a good time to add "using Sims3.SimIFace" below "using System.Collections.Generic" so we can access it's contents (and remember to add a semicolon ; afterwards, syntax is important!).

Probably the most important thing we need here when we make a pure script mod (which is what we're doing here) is a static constructer and field inside the class to make TS3 read our mod. Add this inside your class and remove the comments (the forward slashes and the text after them):
Code:
		[Tunable]
		protected static bool kActualize; //This is the field
		
		static MushroomClass() //This is the constructor
		{
			
		}

Quick tip: When adding the curly brackets, you can simply add the first one { then hit enter and it will automatically place the other one as well as indent the space between them so it's ready for writing in.

It's also important to add the [Tunable] attribute so the game can find our mod through an XML file which we will get into later. The field can be anything you want it to be, it's most commonly a boolean named "kInstantiator" or "Init" but I'll call it something different like "kActualize" for this tutorial. The k prefix is also not a requirement, it's simply to show visually that it is a static field (it doesn't actually make it static!).

The constructer must have the same name as the class. Once the game finds our tunable field, the constructer of the class is called and whatever is inside it will execute! So in order for our mod to do something, we will be instantiating a delegate and a method for it. Basically, this:
Code:
		static MushroomClass()
		{
			World.sOnWorldLoadFinishedEventHandler += new EventHandler(OnWorldLoadFinishedHandler); //This is the delegate
		}

		public static void OnWorldLoadFinishedHandler(object sender, EventArgs e) //This is the method
		{
			
		}

"sOnWorldLoadFinishedEventHandler" is a handler for when a world finishes loading. There are other handlers such as "sOnStartupAppEventHandler" or "sOnWorldLoadStartedEventHandler", but we don't need to use any of those for this tutorial. The method OnWorldLoadFinishedHandler() will run once the event handler fires when the world has finished loading, which is also where we will tell the game to add our interaction! But first, we actually need to write the new interaction

Step 3: Writing the interaction

Now we've reached the fun part! Let's get into how we can make our own interaction.

For this tutorial, I'll show you how to make an interaction for sims to wave at other sims, and this is what the code of it looks like:
Code:
		public sealed class WaveAt : Interaction<Sim, Sim>
		{
			public sealed class Definition : InteractionDefinition<Sim, Sim, WaveAt>
			{
				public override bool Test(Sim actor, Sim target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback)
				{
					if (actor.SimDescription.ChildOrAbove)
					{
						return actor != target;
					}
					return false;
				}
				
				public override string GetInteractionName(Sim actor, Sim target, InteractionObjectPair iop)
				{
					return "Wave at";
				}
			}
			
			public static readonly InteractionDefinition Singleton = new Definition();
			
			public override bool Run()
			{
				Actor.RouteTurnToFace(Target.Position);
				Actor.PlaySoloAnimation("a_react_waveA_standing_x");
				return true;
			}
		}

I'll break it down for you: Inside our MushroomClass we will add another class, which will be the interaction itself (WaveAt). The new class can be named whatever you like again, but you should name it according to what kind of interaction you're making. To make things easy and simple as well, we'll derive the class off of Interaction<TActor, TTarget> which basically means that our class will inherit it. So TActor is the actor, which will be a sim, and TTarget is the target in the interaction, which will be another sim, hence both being called "Sim". This makes it so we can utilize any functions inside it so we don't have to rewrite them ourselves. However, we must add the following libraries to access these contents:
Code:
using Sims3.Gameplay.Actors;
using Sims3.Gameplay.Autonomy;
using Sims3.Gameplay.Interactions;

Inside our interaction class, we'll need a definition class so we can define the interaction.
Code:
			public sealed class Definition : InteractionDefinition<Sim, Sim, WaveAt>
			{
				public override bool Test(Sim actor, Sim target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback)
				{
					if (actor.SimDescription.ChildOrAbove)
					{
						return actor != target;
					}
					return false;
				}
				
				public override string GetInteractionName(Sim actor, Sim target, InteractionObjectPair iop)
				{
					return "Wave at";
				}
			}

Above is our definition class that is derived off InteractionDefinition<TActor, TTarget, TInteraction>, with TActor being the actor (Sim), TTarget being the target sim (Sim) and TInteraction being our interaction (WaveAt).

The method Test() which is part of the class we derived off, is basically the conditions that have to be met in order for the interaction to show up in the pie menu. It is a bool which means it has to return either True or False, so if you don't want any conditions you can just write "return true;". To give an example though, I'll make it so only sims aged children and above can use the interaction, and that the actor sim cannot target themselves. Keep in mind that it will be hard coded, which means even if you tried to enable it for babies or toddlers in the interaction tuning file (which we will also get to), it will not show up for them.

The method GetInteractionName() is what the name of the interaction will be in the pie menu. This is a string, which means text so we put it between quotation marks.

If you're wondering what all the contents inside the parentesis mean, they're to use if we want to reference a specific thing, so say we wanted the interaction to be autonomous only, we would write "return isAutonmous;" which refers to the bool "isAutonomous", and so if performed autonomously it will return true and if not then false.

For now this is all we need for the definition class.

Code:
public static readonly InteractionDefinition Singleton = new Definition();

Above is the instance of our interaction, which then can be referenced for when we add it inside the method used in the constructer from earlier.
Code:
			public override bool Run()
			{
				Actor.RouteTurnToFace(Target.Position); //This method makes the actor sim turn towards the target sim to face them.
				Actor.PlaySoloAnimation("a_react_waveA_standing_x"); //This makes the actor sim play an animation CLIP, which in our case is "a_react_waveA_standing_x". It will automatically play the child one for children despite the a_ prefix.
				return true; //Since the run method is a bool, it has to return either true or false. Returning true means the interaction was successful and will end (it will also end if you return false).
			}

The Run method is what will happen once the interaction fires up. Since we are making an interaction to wave at sims, all we really need is to make our actor sim face the target sim and then wave at them. What you write between the parenthesis of a method needs to match the type that references, so if it's a bool it has to be true or false, if it's an int then it needs to be a whole number, if it's a Sim, then you need to use the actor or target for example, etc.

Step 4: Adding the interaction to the sims

Now going back to our OnWorldLoadFinishedHandler() method, we shall add the newly created interaction class!
Code:
		public static void AddInteraction(Sim sim)
		{
			sim.AddInteraction(WaveAt.Singleton);
		}

Above is a method that we will create and add under OnWorldLoadFinishedHandler() (not inside it!) that adds the interaction to the referenced sim, which will be for each sim in the town that is loaded:
Code:
		public static void OnWorldLoadFinishedHandler(object sender, EventArgs e)
		{
			foreach (Sim sim in Sims3.Gameplay.Queries.GetObjects<Sim>())
			{
				if (sim != null)
				{
					AddInteraction(sim); // <--- The method that we just created, which adds the interaction for each sim in the world
				}
			}
		}

As you can see, inside the method that we talked about before, it looks for all sims in the world and checks if they aren't null, which means if they exist, and if so then the interaction gets added to them. One last thing we'll do is add a listener event for each sim that is instantiated, so if they age up or a new sim is created, the interaction can be added to them as well. A reminder again that syntax is very important! A misplaced curly bracket, or a missing one will cause errors and prevent you from building your project. Make sure to add "using Sims3.Gameplay.EventSystem;" at the top of your project so we can utilize the events and avoid errors.
Code:

public static ListenerAction OnSimInstantiated(Event e)
{
try
{
Sim sim = e.TargetObject as Sim;
if (sim != null)
{
AddInteraction(sim);
}
}
catch (Exception)
{
}
return ListenerAction.Keep;
}

We'll add this into our project and also to our OnWorldLoadFinishedHandler() so the listener can be activated
Code:

public static void OnWorldLoadFinishedHandler(object sender, EventArgs e)
{
EventTracker.AddListener(EventTypeId.kSimInstantiated, new ProcessEventDelegate(OnSimInstantiated)); // <--- Add this!
foreach (Sim sim in Sims3.Gameplay.Queries.GetObjects<Sim>())
{
if (sim != null)
{
AddInteraction(sim);
}
}
}

And we're done with the coding part! Now we'll build the project, import it to S3PE and assemble the package.
To build your project, go to Build -> Build [Project Name]

The final code should look like this (apart from namespaces and class names)
Code:

using System;
using Sims3.Gameplay.Actors;
using Sims3.Gameplay.Autonomy;
using Sims3.Gameplay.EventSystem;
using Sims3.Gameplay.Interactions;
using Sims3.SimIFace;
using System.Collections.Generic;

namespace Savanita.WaveAtTutorial
{
public class MushroomClass
{
[Tunable]
protected static bool kActualize;

static MushroomClass()
{
World.sOnWorldLoadFinishedEventHandler += new EventHandler(OnWorldLoadFinishedHandler);
}

public static void OnWorldLoadFinishedHandler(object sender, EventArgs e)
{
EventTracker.AddListener(EventTypeId.kSimInstantiated, new ProcessEventDelegate(OnSimInstantiated));
foreach (Sim sim in Sims3.Gameplay.Queries.GetObjects<Sim>())
{
if (sim != null)
{
AddInteraction(sim);
}
}
}

public static ListenerAction OnSimInstantiated(Event e)
{
try
{
Sim sim = e.TargetObject as Sim;
if (sim != null)
{
AddInteraction(sim);
}
}
catch (Exception)
{
}
return ListenerAction.Keep;
}

public static void AddInteraction(Sim sim)
{
sim.AddInteraction(WaveAt.Singleton);
}

public sealed class WaveAt : Interaction<Sim, Sim>
{
public sealed class Definition : InteractionDefinition<Sim, Sim, WaveAt>
{
public override bool Test(Sim actor, Sim target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback)
{
if (actor.SimDescription.ChildOrAbove)
{
return actor != target;
}
return false;
}

public override string GetInteractionName(Sim actor, Sim target, InteractionObjectPair iop)
{
return "Wave at";
}
}

public static readonly InteractionDefinition Singleton = new Definition();

public override bool Run()
{
Actor.RouteTurnToFace(Target.Position);
Actor.PlaySoloAnimation("a_react_waveA_standing_x");
return true;
}
}
}
}

Step 5: Assembling the package file

Create a new file in S3PE, then go to Resource -> Add...



It will bring up a new menu as shown above. First we'll import the DLL file of our code project, so for "Type" insert S3SA and select the resource it gives you. Set the group to 0 and give it a name, this can be whatever you want but it should be unique. Hit the FNV64 button to create the instance, which is the name but in hash code. It should look like this (except for the name):



Hit "OK" and right click the new script resource you created, then select "Import DLL" and navigate to the DLL file of your project.

• For SharpDevelop, Documents\SharpDevelop Projects\[Name of your project]\[Name of your project again]\bin\Debug.

• For Visual Studio, Documents\Visual Studio\Projects\[Name of your project]\[Name of your project again]\bin\Release.

Once imported, commit the changes. Now we'll add the XML so the game can read the package. Go to Resource -> Add... and insert _XML then select the first option it gives you (type _XML 0x0333406C). Set the group to 0 and for the name, you have to insert the namespace of your project and the name of the class, which for me would be "Savanita.WaveAtTutorial.MushroomClass", make sure to separate them with a dot. Then hit FNV64 again and select OK.

Open up the XML file by right clicking on it and selecting "Notepad". Paste this into it:

Code:
<?xml version="1.0" encoding="utf-8"?>
<base>
<Current_Tuning>
<kActualize value="True" />
</Current_Tuning>
</base>


You have to name the value depending on what you named your tunable value in the code, which for me was kActualize. Now let's save it, close and commit changes

Now for the last thing we'll add is an ITUN file (interaction tuning), which is optional but we'll add it so we can tell our sims that waving at other sims will increase their social a little bit, and that it's fun (which will make them more likely to perform the interaction) and also make it so friendly sims are more likely to wave at other sims. Keep in mind though that if you don't want to make an ITUN file for your interaction it would be better to add the [DoesntRequireTuning] attribute above your interaction definition so the game knows not to go looking for one, but I won't get into in this tutorial.

Once again, go to Resource -> Add... then type in ITUN and select the option it gives you. Set the group to 0 and name it whatever you like, but it has to be unique to avoid conflicts. Hit FNV64 and select OK.

Open up the file in Notepad and paste this:
Code:
<?xml version="1.0"?>
<base>
<Interaction name="" />
<Object name="Sims3.Gameplay.Actors.Sim" />
<CodeVersion name="BaseGame" considerCodeVersion="False" />
<Current_Tuning>
<Disallow DisallowAutonomous="False" DisallowUserDirected="False" DisallowPlayerSim="False" />
<BaseAvailability>
<AgeSpeciesAvail AgeSpeciesValue="C,T,Y,A,E" />
<MotiveThreshold MotiveThresholdType="None" MotiveThresholdValue="0" MotiveBelowCheck="False" />
<MoodThreshold MoodThresholdType="None" MoodThresholdValue="0" />
<SkillThreshold SkillThresholdType="None" SkillThresholdValue="0" />
<CareerThreshold CareerThresholdType="Undefined" CareerThresholdValue="0" IncludePastCareers="False" />
<Lot AllowNonGreetedSimsIfObjectOutside="False" AllowNonGreetedSimsIfObjectOutsideUserDirected="True" AllowGreetedSims="True" AllowOnCommunityLots="True" AllowOnAllLots="False" />
<World RestrictionType="None" Types="" Names="" />
<Room AllowInTombRoomAutonomous="False" AllowEvenIfNotAllowedInRoomAutonomous="False" />
<Misc DisallowedIfPregnant="False" DisallowedFromInventory="False" />
</BaseAvailability>
<Check type="All" value="0" />
<Tradeoff name="">
<Localization autoGenerate="True" />
<Time value="1" addRoute="True" />
<Exit funExit="False" stressExit="False" interruptible="False" />
<RouteLeadIn allowed="True" />
<AskJoinInteraction joinable="False" />
<AllowAutonomousReinforcement allowPraise="False" allowScold="False" />
<ScoringFunction alwaysChooseBest="False" name="" specificCommodity="None" />
<ActionTopic name="" ActionTopicUnavailableAfterActionFinishes="False" />
<Output>
<Change type="Fun" advertised="20" locked="True" actual="10" updateType="ImmediateDelta" timeDependsOn="False" updateEvenOnFailure="False" updateAboveAndBelowZero="Either" />
<Change type="Social" advertised="10" locked="True" actual="10" updateType="ContinuousFlow" timeDependsOn="False" updateEvenOnFailure="False" updateAboveAndBelowZero="Either" />
<Change type="TraitFriendly" advertised="200" locked="True" actual="200" updateType="ImmediateDelta" timeDependsOn="False" updateEvenOnFailure="False" updateAboveAndBelowZero="Either" />
</Output>
</Tradeoff>
<Notes Notes="" DesignerNotes="" LastChange="" />
</Current_Tuning>
</base>

Fill in the interaction name at the top with your namespace, class name and interaction class name. Mine would be this "Savanita.WaveAtTutorial.MushroomClass+WaveAt". You only need to separate the namespace and the first class with a dot, but for each class nested in another you must separate them with a plus sign. If you have multiple namespaces though, you would separate those with a dot instead. Add in the name of your interaction in the "Tradeoff name" section.

As I mentioned before, I want this interaction to increase a sim's social and fun bar, and make it so friendly sims are also more likely to perform this interaction too.

• The advertised output tells the sim how much fun or social they will receive after doing this interaction, while the actual output is the actual amount that will fill the fun or social bar. So yes, you can rip your sims off by telling them this interaction will be very fun, which in actual fact it wouldn't be!
• The updateType can either be ImmediateDelta, which sims will receive 10 fun after the interaction has finished, or ContinuousFlow which will give sims 10 fun over the course of the interaction (this option is better for looping interactions).

This is just the basics of the ITUN file, if you wanted you could make it so your sims had to be in a certain posture (like sitting) to be able to perform the interaction, or if sims learnt a skill throughout the interaction or if they even had to be in a specific world.

Now all you need to do is save the ITUN file, save your package and put it into your mods folder, and test it out in the game!





• And that's it! Now you can go make your sims wave at anyone you want them to
In the next part I will talk about making a new jazz script for your interaction, so you can use animations in a fancier and more efficient way, as well as looking at different things you can do in an interaction.
---
If you have any questions, or need further explanation, please let me know!
Screenshots

- When one gets inspired by the other, the one inspires another - Anything is Possible.

You can view some of my WIPs and other stuff for TS3 on my Twitter here ---> https://twitter.com/SweetSavanita
Advertisement
Imagine Wonderfully!
staff: trainee moderator
Original Poster
#2 Old 29th Nov 2023 at 12:36 AM
[Reserved]

- When one gets inspired by the other, the one inspires another - Anything is Possible.

You can view some of my WIPs and other stuff for TS3 on my Twitter here ---> https://twitter.com/SweetSavanita
Forum Resident
#3 Old 29th Nov 2023 at 3:07 AM
Well-formatted tutorials bring me so much joy
Excellent work, thank you so much for sharing this!
Back to top