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!
Quick Reply
Search this Thread
Instructor
Original Poster
#1 Old 16th Aug 2021 at 2:59 AM
Default Take Practice Shots Mod (Help)
Hi everyone!

I'm working on a simple mod that would add a "Take Practice Shots" interaction to the camera. Sims would loop through the "Take Photo" animations and raise their photography skill, but no actual photos would actually be taken and the UI for photography wouldn't pop up.

I can't get the interaction to show up on the camera and I need a bit of help. I'll post the code below. Fair warning: it's sort of a mashup of snippets from Lyralei's tutorial, along with code from existing camera interactions.

Code:
namespace Sims3.Gameplay.Objects.Twinsimming
{
    public class Camera : GameObject
    {
        public class Camera_TakePracticeShots : Interaction<Sim, ICamera>
        {
            public class Definition : InteractionDefinition<Sim, ICamera, Camera_TakePracticeShots>
            {
                public override string GetInteractionName(Sim actor, ICamera target, InteractionObjectPair interaction)
                {
                    return "Take Practice Shots";
                }
                public override bool Test(Sim a, ICamera target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback)
                {
                    if (!target.InInventory)
                    {
                        return false;
                    }
                    a = (target.ItemComp.InventoryParent.Owner as Sim);
                    if (a == null)
                    {
                        return false;
                    }
                    if (!a.SimDescription.ChildOrAbove || !a.IsHuman)
                    {
                        return false;
                    }
                    return true;
                }
            }
            public static InteractionDefinition Singleton = new Definition();

            public override bool RunFromInventory()
            {
                Actor = (Target.ItemComp.InventoryParent.Owner as Sim);
                InteractionInstance entry = Target.TakePhotoSingleton.CreateInstance(Target, Actor, GetPriority(), isAutonomous: false, cancellableByPlayer: true);
                return Actor.InteractionQueue.AddNext(entry);
            }

            public override bool Run()
            {
                // We ask the game to give/initiate the skill on the actor that's currently using the object. Even if they're level 10 or don't know the skill yet, this will be added. 
                Photography photographySkill = base.Actor.SkillManager.AddElement(SkillNames.Photography) as Photography;
                if (photographySkill == null)
                {
                    // For good practise on a first run, make sure to add an in-game notification here to debug whether the skill has been loaded or not
                    return false;
                }
                // Starts skill gaining. The Skilling bar will appear above the sim's head now.
                photographySkill.StartSkillGain(30);
                base.StandardEntry();
                base.EnterStateMachine("TakePhoto", "Enter", "x");
                base.SetActor("camera_object", base.Target);
                base.EnterState("x", "Enter");
                base.BeginCommodityUpdates();
                base.AnimateSim("Taking Photo");
                // EA uses the 'Flag' variable for this. But when looping, it's important on each loop to return either 'true' or 'false'. An exit reason can return a false or true 
                bool GainSkillOnLoop = DoLoop(ExitReason.Default, LoopInfinite, null);
                base.AnimateSim("Exit");
                base.EndCommodityUpdates(true);
                // Because we're exiting, we're also stopping skillgaining here.
                photographySkill.StopSkillGain();
                base.StandardExit();
                // This is the only instance in a run() function to return a variable like this. Merely because of the 'DoLoop' function.
                return GainSkillOnLoop;

            }
            // Our loop function. See how in: bool GainSkillOnLoop = DoLoop(ExitReason.Default, LoopInfinite, null); We call a function in the second parameter? 
            public void LoopInfinite(StateMachineClient smc, LoopData loopData)
            {
                //2 things here, if either we as the player cancel the animation OR the game notices that the sim is super hungry/has no fun/has to go to the toilet, we'll add the exit reasons. 
                if (Actor.HasExitReason(ExitReason.UserCanceled) || (Actor.HasExitReason(ExitReason.MoodFailure)))
                {
                    // AddExitReason so that we 'base.AnimateSim()' will be initiated
                    base.Actor.AddExitReason(ExitReason.UserCanceled);
                    base.Actor.AddExitReason(ExitReason.MoodFailure);
                }
            }
        }
        public override void OnStartup()
        {
            AddInventoryInteraction(Camera_TakePracticeShots.Singleton);
        }
    }
}
Advertisement
Senior Moderator
staff: senior moderator
#2 Old 16th Aug 2021 at 1:12 PM
What you're actually doing here is creating a new "Camera" object, and adding this interaction to it. Which would work if you made a new camera object, by cloning a camera and changing the OBJK to Sims3.Gameplay.Objects.Twinsimming.Camera.
But that would mean you have a custom type of camera with this interaction, not the existing camera with this interaction added.
So instead you only need to write the code for the interaction and use Pure Modding (see here http://www.simlogical.com/ContentUp..._script_mod.pdf) to add it to cameras.
Hope this helps
Instructor
Original Poster
#3 Old 19th Aug 2021 at 6:51 AM
Quote: Originally posted by zoe22
What you're actually doing here is creating a new "Camera" object, and adding this interaction to it. Which would work if you made a new camera object, by cloning a camera and changing the OBJK to Sims3.Gameplay.Objects.Twinsimming.Camera.
But that would mean you have a custom type of camera with this interaction, not the existing camera with this interaction added.
So instead you only need to write the code for the interaction and use Pure Modding (see here http://www.simlogical.com/ContentUp..._script_mod.pdf) to add it to cameras.
Hope this helps

Thanks! That was a big help. I think I've got most of the instantiator worked out, with the exception of one part. On the line "Camera[] objects = Queries.GetObjects<Camera>();" I get the following error: CS0308 The non-generic method 'Queries.GetObjects(Type)' cannot be used with type arguments. How should I go about fixing this? I tried changing "Camera" out for "GameObject", but that didn't work.

Code:
using Sims3.SimIFace;
using System;
using System.Text;
using Sims3.Gameplay.Objects.Miscellaneous;
using Sims3.Gameplay.Actors;
using Sims3.Gameplay.EventSystem;
using Sims3.UI;
using Sims3.Gameplay.Abstracts;
using Sims3.Gameplay.DreamsAndPromises;
using Sims3.Gameplay.ActorSystems;
using Sims3.Gameplay.Utilities;
using Sims3.Gameplay.TuningValues;
using Sims3.Gameplay.Autonomy;
using Sims3.Gameplay.Interactions;
using Sims3.Gameplay.Skills;
using Sims3.SimIFace.SACS;
using Sims3.Gameplay.Roles;
using System.Collections.Generic;
using Sims3.Gameplay.Interfaces;
using Sims3.Gameplay.Objects.Decorations.Mimics;
using Sims3.Gameplay.Objects.Electronics;
using Sims3.Gameplay.Objects.HobbieSkills.Camera.Twinsimming;
using Sims3.Gameplay.Objects.HobbieSkills.Camera;
using Sims3.Gameplay.Objects.HobbiesSkills;

public class Instantiator
{
    [Tunable]
    protected static bool kInstantiator = false;

    static Instantiator()
    {
        World.sOnWorldLoadFinishedEventHandler += new EventHandler(OnWorldLoadFinished);
    }

    private static void OnWorldLoadFinished(object sender, EventArgs e)
    {
        Camera[] objects = Queries.GetObjects<Camera>();
        foreach (Camera camera in objects)
        {
            AddInteractions(camera);
        }
        EventTracker.AddListener(EventTypeId.kBoughtObject, OnObjectChanged);
        EventTracker.AddListener(EventTypeId.kInventoryObjectAdded, OnObjectChanged);
        EventTracker.AddListener(EventTypeId.kObjectStateChanged, OnObjectChanged);
    }
    private static void AddInteractions(Camera camera)
    {
        foreach (InteractionObjectPair interaction in camera.Interactions)
        {
            if (interaction.InteractionDefinition.GetType() == TakePracticeShots.Singleton.GetType())
            {
                return;
            }
        }
        camera.AddInteraction(TakePracticeShots.Singleton);
    }
    private static ListenerAction OnObjectChanged(Event e)
    {
        try
        {
            Camera camera = e.TargetObject as Camera;
            if (camera != null)
            {
                AddInteractions(camera);
            }
        }
        catch (Exception)
        {
        }
        return ListenerAction.Keep;
    }
}
Space Pony
#4 Old 19th Aug 2021 at 2:00 PM
Quote: Originally posted by twinsimming
Thanks! That was a big help. I think I've got most of the instantiator worked out, with the exception of one part. On the line "Camera[] objects = Queries.GetObjects<Camera>();" I get the following error: CS0308 The non-generic method 'Queries.GetObjects(Type)' cannot be used with type arguments. How should I go about fixing this? I tried changing "Camera" out for "GameObject", but that didn't work.,


You're getting that error because you're using Sims3.SimIFace.Queries instead of Sims3.Gameplay.Queries. You can fix it either by fully qualifying the method call, like so:

Code:
Camera[] objects = Sims3.Gameplay.Queries.GetObjects<Camera>();


Or you can use my preferred method of adding an alias directive to your "using" statements at the top of the file:

Code:
using Queries = Sims3.Gameplay.Queries;

// Additional code cut for clarity

Camera[] objects = Queries.GetObjects<Camera>();

"The Internet is the first thing that humanity has built that humanity doesn't understand, the largest experiment in anarchy that we have ever had." - Eric Schmidt

If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Instructor
Original Poster
#5 Old 20th Aug 2021 at 9:03 AM
Quote: Originally posted by gamefreak130
You're getting that error because you're using Sims3.SimIFace.Queries instead of Sims3.Gameplay.Queries. You can fix it either by fully qualifying the method call, like so:

Code:
Camera[] objects = Sims3.Gameplay.Queries.GetObjects<Camera>();


Or you can use my preferred method of adding an alias directive to your "using" statements at the top of the file:

Code:
using Queries = Sims3.Gameplay.Queries;

// Additional code cut for clarity

Camera[] objects = Queries.GetObjects<Camera>();

Thank you! That got rid of the error and I was able to successfully build the solution. Unfortunately, the interaction still doesn't show up on the camera.

I've attached the project file and package file below. If someone could take a look, I'd appreciate it.
Attached files:
File Type: rar  twinsimming_TakeManyPhotos.rar (7.97 MB, 5 downloads)
Space Pony
#6 Old 21st Aug 2021 at 5:16 PM
Quote: Originally posted by twinsimming
Thank you! That got rid of the error and I was able to successfully build the solution. Unfortunately, the interaction still doesn't show up on the camera.

I've attached the project file and package file below. If someone could take a look, I'd appreciate it.


The name/hash of your instantiator XML must match the fully qualified name of the class in which the tuning variable is contained. In this case, your XML's name is "Twinsimming.TakePracticeShots.Common.Instantiator", but your instantiator class's fully qualified name would just be "Instantiator". To fix this, you can wrap the class around a namespace with the rest of the name:

Code:
namespace Twinsimming.TakePracticeShots.Common
{
    public class Instantiator
    {
        // Instantiator code
    }
}


For future reference, it's always best practice to put all of your code into namespaces to organize it. Having objects in the global namespace is generally not a good idea.

At this point you'll also need to add a static using directive for your interaction class; otherwise, the compiler will confuse the TakePracticeShots class with the TakePracticeShots namespace:
Code:
using static Sims3.Gameplay.Objects.HobbieSkills.Camera.Twinsimming.TakePracticeShots;


Now the game will be able to inject your tuning and call your static constructor, but if you try it out in-game the interactions still won't show up. This is because the game maintains two separate lists of interactions for each object: one for objects in a Sim's inventory, and one for objects not in an inventory. Your interaction is being added to the latter but not the former, and since your test method disallows usage of the interaction unless the camera is in the selected Sim's inventory, it will never appear.

To fix this, simply change
Code:
camera.AddInteraction(Singleton);


To
Code:
camera.AddInventoryInteraction(Singleton);


Then your interaction will be added to the inventory list instead of the non-inventory list, and it will appear in-game.

"The Internet is the first thing that humanity has built that humanity doesn't understand, the largest experiment in anarchy that we have ever had." - Eric Schmidt

If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Instructor
Original Poster
#7 Old 22nd Aug 2021 at 7:55 AM
Quote: Originally posted by gamefreak130
The name/hash of your instantiator XML must match the fully qualified name of the class in which the tuning variable is contained. In this case, your XML's name is "Twinsimming.TakePracticeShots.Common.Instantiator", but your instantiator class's fully qualified name would just be "Instantiator". To fix this, you can wrap the class around a namespace with the rest of the name:

Code:
namespace Twinsimming.TakePracticeShots.Common
{
    public class Instantiator
    {
        // Instantiator code
    }
}


For future reference, it's always best practice to put all of your code into namespaces to organize it. Having objects in the global namespace is generally not a good idea.

At this point you'll also need to add a static using directive for your interaction class; otherwise, the compiler will confuse the TakePracticeShots class with the TakePracticeShots namespace:
Code:
using static Sims3.Gameplay.Objects.HobbieSkills.Camera.Twinsimming.TakePracticeShots;


Now the game will be able to inject your tuning and call your static constructor, but if you try it out in-game the interactions still won't show up. This is because the game maintains two separate lists of interactions for each object: one for objects in a Sim's inventory, and one for objects not in an inventory. Your interaction is being added to the latter but not the former, and since your test method disallows usage of the interaction unless the camera is in the selected Sim's inventory, it will never appear.

To fix this, simply change
Code:
camera.AddInteraction(Singleton);


To
Code:
camera.AddInventoryInteraction(Singleton);


Then your interaction will be added to the inventory list instead of the non-inventory list, and it will appear in-game.

It works! The interaction shows up, the sim plays through the animations, and gains the skill like they're supposed to. One final hiccup is that when I cancel the interaction, the sim resets and Error Trap throws a script error.

The log reads (in part): "Interaction Sims3.Gameplay.Objects.HobbieSkills.Camera.Twinsimming.TakePracticeShots left an object in the Sim's hand with no carry state machine"

This problem isn't present in other camera interactions and there's no base.Cleanup(); line to go off of.
Back to top