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!
Lab Assistant
Original Poster
#51 Old 3rd Jan 2021 at 4:06 AM Last edited by Alunn : 3rd Jan 2021 at 4:49 AM.
Alright, I've made some progress! I added the three yoga routines Jathom95 came up with to the code, but I'm not sure how to nest them in the "Do Yoga Routine" interaction. As for motive gains, gamefreak130 mentions here that they need to be put in a tuning file, but do I also need to mention them somewhere in the code? I've seen "[Tuneable Comment]" used in different interactions in the game's code, so I'm wondering if I need to do the same. Finally, I started on the custom skill using Inge's tutorial, but I'm a little lost on how to get the FNV32 hash in hex format and what to do with the rest of the CustomClassName section. Here's what I have so far:

<SkillList>
<SkillName>AlunnWellnessSkill</SkillName>
<SkillDescription>AlunnWellnessSkillDescription</SkillDescription>
<Hex>ExampleCustomSkill=0x0EDB3583</Hex>
<CustomClassName>Sims3.Gameplay.Alunn.ExampleCustomSkill, ExampleCustomSkillTut</CustomClassName>
<MaxSkillLevel>5</MaxSkillLevel>
<Commodity>SkillAlunnWellnessSkill</Commodity>
<Hidden>True</Hidden>
<AvailableAgeSpecies>T,Y,A,E</AvailableAgeSpecies>
<Version>1</Version>
</SkillList>
</Skills>

(Still debating between calling it the yoga skill or the wellness skill...)

Any help is appreciated.

Edit: I also don't know how to restrict the yoga interactions to teens and above. If someone could point me in the right direction on how to do this that'd be great.
Advertisement
Virtual gardener
staff: administrator
#52 Old 3rd Jan 2021 at 12:07 PM
Quote: Originally posted by Alunn
I added the three yoga routines Jathom95 came up with to the code, but I'm not sure how to nest them in the "Do Yoga Routine" interaction. 
It's actually not that difficult! But I do get starting out with the whole looping and animating that it's a bit confusing :p

Basically how I'd approach it is:

Code:
public bool run(){
// Bunch of the code you have already written...
            base.EnterStateMachine("PractiseYoga", "Enter", "x");
            base.SetActor("Yoga_Mat", base.Target);
            base.EnterState("x", "Enter");

            // DoLoop() will return a true/false whenever the if statements or anything returns true.
            bool Looping = DoLoop(ExitReason.Default, LoopYogaRoutine, null);
            return Looping;
}

public void LoopInfinite(StateMachineClient smc, LoopData loopData)
{
   if(Actor.HasExitReason(ExitReason.UserCanceled) || (Actor.HasExitReason(ExitReason.MoodFailure)) || (Actor.HasExitReason(ExitReason.Canceled)))
   {
                  base.Actor.AddExitReason(ExitReason.UserCanceled);
                  base.Actor.AddExitReason(ExitReason.MoodFailure);
   }
}
And then in the Jazz script, you basically follow my 'Looping' bit: https://modthesims.info/showthread....118#post5637118

Now, I do get the feeling you don't want it randomly looped (Which I wouldn't want either :p Having done Yoga myself, a routine usually exists out of one pose after the other that you do practically every time you do yoga, rather than it being randomised. In that case, I'd go as far as this:
Code:
public enum YogaRoutine {
   Pose1,
   Pose2,
   Pose3,
}

public bool run(){
// Bunch of the code you have already written... This is also the same code as above.
            base.EnterStateMachine("PractiseYoga", "Enter", "x");
            base.SetActor("Yoga_Mat", base.Target);
            base.EnterState("x", "Enter");

            // DoLoop() will return a true/false whenever the if statements or anything returns true.
            bool Looping = DoLoop(ExitReason.Default, LoopYogaRoutine, null);
            return Looping;
}

public void LoopInfinite(StateMachineClient smc, LoopData loopData)
{
   // Get our YogaRoutine Enum values and just put it in a variable.
   var values = Enum.GetValues(typeof(YogaRoutine));
         
   // Loop through them one by one and depending on the result, have the sim animate
   foreach (YogaRoutine routine in values)
   {
      switch (routine)
      {
            case YogaRoutine.Pose1:
         {
            base.AnimateSim("YogaRoutine1");
                 break;
         }
         case YogaRoutine.Pose2:
         {
            base.AnimateSim("YogaRoutine2");
                 break;
         }
         case YogaRoutine.Pose3:
         {
            base.AnimateSim("YogaRoutine3");
                 break;
         }
      }
   }
   // This foreach loop will continue on unless you've 'usercanceled it' or there is a moodfailure, or the game canceled it because of unexpected meteorites :p
   if(Actor.HasExitReason(ExitReason.UserCanceled) || (Actor.HasExitReason(ExitReason.MoodFailure)) || (Actor.HasExitReason(ExitReason.Canceled)))
   {   
      base.Actor.AddExitReason(ExitReason.UserCanceled);
      base.Actor.AddExitReason(ExitReason.MoodFailure);
   }
}
And then all you need to do is create multiple states. So no looping! But basically this:

Code:
State "Routine1" 
{
   Transitions to "Routine2"
   Play "CLIPNameHere" for "x"
}
State "Routine2" 
{
   Transitions to "Routine3"
   Play "CLIPNameHere" for "x"
}
State "Routine3" 
{
   Transitions to "Exit"
   Play "CLIPNameHere" for "x"
}
I actually think, because of the routine thing, you don't even need to do the whole switch statement and foreach. But just "base.AnimateSim("Routine1");" But that's your thing to test

Quote:
As for motive gains, gamefreak130 mentions here that they need to be put in a tuning file, but do I also need to mention them somewhere in the code?
The only place I can think of is that You'd need to make a ITUN file. But that already specifies your interaction, so you don't need to do the [Tunable] thingy  That's just useful for if you have an XML file that people can edit values in. The difference between GameFreaks reply and Consort, is that Consorts way is hardcoded, whereas Gamefreaks is more 'dynamic' and you can, as the downloader, change the values with Nraas overwatch

Quote:
Finally, I started on the custom skill using Inge's tutorial, but I'm a little lost on how to get the FNV32 hash in hex format and what to do with the rest of the CustomClassName section. Here's what I have so far:
The 32 hash thing is easy! In S3PE, go to Tools > FNVHash, Dump in the name of your skill and then copy the 32 hash from the 32 input field

To regards of the <CustomClassname> thing, that's pretty easy actually! It's just the Namespace, The DLL name.

So for the sewing table I had the following: 
<CustomClassName>Sims3.Gameplay.Skills.Lyralei.SewingSkill, GlobalOptionsSewingTable</CustomClassName>

SewingSkill was the class name I gave for the overall code for the sewingSkill, and GlobalOptionsSewingTable is the name of the DLL
Lab Assistant
Original Poster
#53 Old 4th Jan 2021 at 10:53 AM
I think I'll have multiple "Enter" states in the jazz script and put all of the looping there, just for my sanity's sake lol. I also did a bit more work on the code trying to replicate your post (thank you so much by the way, without help this project wouldn't have made it far off the ground), but I think I messed up somewhere.

I'm trying to have two interactions available when clicking on the yoga mat: "Practice Yoga" and "Do Yoga Routine". From there, if the user chooses "Do Yoga Routine", the three routines will show up. Kinda like how there are different social interaction categories and then once you pick a category (friendly, funny, romantic, etc.) the relevant interactions show up. Right now, all of the interactions are showing up when I first click on the mat. Children are also still able to use the mat even though I added this line after the Run function:
Code:
public override bool Run()
            {
                if (Actor.SimDescription.TeenOrAbove)
                {
                    return false;
                }

Here's what the latest version of the project looks like:
Attached files:
File Type: rar  Alunn_YogaMat (V4).rar (13.4 KB, 4 downloads)
Senior Moderator
staff: senior moderator
#54 Old 4th Jan 2021 at 8:35 PM Last edited by zoe22 : 4th Jan 2021 at 8:46 PM.
If you want the interactions to only show up for adults/teen, you'll need to put that check in the Test function in , and it should return true if they are TeenOrAbove, and return false otherwise.
What you have in Run(), that if statement will actually stop any sim who is Teen or older from completing the interaction

In fact, you can also do this with an ITUN file instead, and that's how motives can also be done. Tutorial here for that
i think before and after your animations you just need to add BeginCommodityUpdates(); and EndCommodityUpdates(true); 

Also, this tutorial should help with having the routines under one interaction, the section on Defining paths and submenus 
Lab Assistant
Original Poster
#55 Old 6th Jan 2021 at 2:41 AM
@zoe22 @Lyralei Thank you both! I got it working. The interactions all show up in the right place and sims gain motives now. I also filled out more of the SKIL file data:

<SkillList>
<SkillName>AlunnWellnessSkill</SkillName>
<SkillDescription>AlunnWellnessSkillDescription</SkillDescription>
<Hex>0xA8FBB335</Hex>
<CustomClassName>Sims3.Gameplay.Alunn.AlunnWellnessSkill, ExampleCustomSkillTut</CustomClassName>
<MaxSkillLevel>5</MaxSkillLevel>
<Commodity>0xCCD80930</Commodity>
<Hidden>True</Hidden>
<AvailableAgeSpecies>T,Y,A,E</AvailableAgeSpecies>
<Version>1</Version>
</SkillList>
</Skills>

I still can't quite make heads or tails of the actual coding of the skill yet, though. I'm using the example file from Inge's tutorial. Am I supposed to modify the ExampleBootstrapperClass class or just the ExampleCustomSkill class? Also, do I change the namespace to my own or leave it as it is? This is what I've done so far:
Code:
using System;
using Sims3.Gameplay.Abstracts;
using Sims3.Gameplay.Actors;
using Sims3.Gameplay.Skills;
using Sims3.Gameplay.Utilities;
using Sims3.SimIFace;
[assembly: Tunable] // Do not forget this line!!

namespace Sims3.Gameplay.IngeJones
{
    public class ExampleBootstrapperClass : GameObject
    {
        static bool HasBeenLoaded = false;

        [Tunable]
        protected static bool kInstantiator = false;

        static ExampleBootstrapperClass()
        {
            // gets the OnPreload method to run before the whole savegame is loaded so your sim doesn't find
            // the skill missing if they need to access its data
            LoadSaveManager.ObjectGroupsPreLoad += ExampleBootstrapperClass.OnPreload;
        }

        static void OnPreload()
        {
            try
            {
                if (HasBeenLoaded) return; // you only want to run it once per gameplay session
                HasBeenLoaded = true;

                // fill this in with the resourcekey of your SKIL xml
                XmlDbData data = XmlDbData.ReadData(new ResourceKey(0x96FE09AE3F7D7F1E, 0xA8D58BE5, 0x00000000), false);

                if (data == null)
                {
                    return;
                }
                SkillManager.ParseSkillData(data, true);

            }
            catch (Exception ex)
            {
                return;
            }
        }
    }

    public class AlunnWellnessSkill : Skill
    {
        public AlunnWellnessSkill(SkillNames guid) : base(guid)
        {
        }
        private AlunnWellnessSkill()
        {
        }
    }

    public class ExampleUsesClass : GameObject
    {
        private const SkillNames ExampleCustomSkillGuid = (SkillNames)0x0EDB3583;

        public ExampleUsesClass()
        {
        }

        public void ExampleUses (Sim s)
        {
            if (!s.SkillManager.HasElement(ExampleCustomSkillGuid))
            {
                s.SkillManager.AddElement(ExampleCustomSkillGuid);
            }
            s.SkillManager.AddSkillPoints(ExampleCustomSkillGuid, 3.0f);
            Skill sk = s.SkillManager.GetElement(ExampleCustomSkillGuid);
            float sl = sk.SkillPoints;

        }

    }

}
Senior Moderator
staff: senior moderator
#56 Old 6th Jan 2021 at 3:08 PM
So I haven't made a custom skill before but from what it looks like, the main thing you need is the ExampleBootstrapperClass which actually gets the skill loaded in to the game, and then your AlunnWellnessSkill  class, which is the skill itself. 
ExampleUsesClass  is just an example of how you might add skills to sims in game, but you are going to want to do that with your yoga mat code, so you could delete that part.
In the Bottstrapper class, you only need to edit the resource key in PreLoad() so it reads your xml, I think.

The namespace should be something unique to you, so you could just call it Alunn.YogaMod or something. I don't think you need the Sims3.Gameplay before that, because this part is basically just a pure mod, unlike your yoga mat which needs to have a namespace starting with Sims3.Gameplay.objects for it to recognise it as an object. But maybe there is some reason you need the Sims3.Gamelplay because it's a skill? Not sure.
You can also rename ExampleBootstrapperClass if you like, it doesn't really matter what it's called though. 
In fact I'm not sure why it's derived from GameObject?
I don't know if that's a mistake in the tutorial example, of if it's supposed to be there, but I'm pretty sure you don't need it because it's not an object... :p
Virtual gardener
staff: administrator
#57 Old 6th Jan 2021 at 4:49 PM
For global related things (So, anything a sim can't interact with but is sort of "in the air", like a skill, trait, buffs, etc), we don't indeed need a special namespace. I think the reason why Inge did decide to do it like that (Which is fine and will work, but just not necessary) was because back in 2010-ish, before the big Pets patch, script mods actually needed at *least* some sort of 'sims3. gameplay' in front of need. Obviously we're seeing better days for our namespaces :p

The:
[assembly: Tunable] // Do not forget this line!!

Actually needs to be inside the Assembly.cs, else it won't recognise our Tunables while compiling! So, keep that in mind  Especially with global mods (like your skill) it's important.

And indeed, GameObject is redundant. However, i would highly suggest that you use 'Skill' as a class to derive from. That way, you can easily get your overrides and guids (because doing that manually is a pain :p Zoe and I have both been there lol). But I guess I'll also go a bit in depth how i managed to do it for the sewing table's skill.

First, make a class that does things globally. I decided, for organisation and convenientness purposes, make a 'GlobalOptionsSewingTable' class that would handle ALLLLL the globals. Now, my preload() is filled with a buunch of code, but inside of your preload, the same code that Inge has in her onPreload function. In fact, just copy paste the onPreload function

Now, we make another class, like 'yogaSkill' (whether that's right underneath your 'global' class, or literally a new .cs file, that's both fine!) And make sure it looks as followed: SewingSkillExample

I didn't exactly add any comments or documentation here, but basically, the ItrackedStats are our journal's entry and what they need to do to win the 'challenge'. ILifetimeOpportunity however, is not a lifewish, but just really "If the sim completed their challenges, then they become a master at this thing". The handiness skill has it too, where if you 'master' it, then you get a discount on buy mode items

But what's most important (because after that, just ignore most of the functions. They're just there to check if we need any patterns and just stuff that is relevant to the mod) is this bit:

Code:
  public override void CreateSkillJournalInfo()        {
            mTrackedStats = new List<ITrackedStat>();
            mTrackedStats.Add(new FinishedProjects(this));
            mTrackedStats.Add(new PatternsDiscovered(this));
            mTrackedStats.Add(new PercentageOfPatternsDiscovered(this));
            mTrackedStats.Add(new BlogPostsWritten(this));
            mLifetimeOpportunities = new List<ILifetimeOpportunity>();
            mLifetimeOpportunities.Add(new PatternCollector(this));
            mLifetimeOpportunities.Add(new DIYBlogger(this));
            mLifetimeOpportunities.Add(new DoesNotTakeaVillage(this));
            mLifetimeOpportunities.Add(new MasterSewer(this));        
}
The merge travel data is also important, but for testing your skill you can leave it out of course
Lab Assistant
Original Poster
#58 Old 8th Jan 2021 at 5:34 AM Last edited by Alunn : 8th Jan 2021 at 5:47 AM.
I've read over all of the info you guys shared and it's a bit overwhelming for my skill level right now. I think I may set aside the custom skill for now and focus more on the animation progression for the moment. There's a reoccurring method across multiple athletic objects in the game's jazz scripts. It looks like you can outline different sets of animations per certain skill level parameters. This is from the workbench jazz script:

Code:
State "BenchPress"
    {
        Properties Public, Loop
        Transitions to "a2o_workoutBench_benchPress_stop_"
        Select on Parameter "fatigued"
        {
            Value "no"
            {
                Select on Parameter "skill"
                {
                    Value "Expert"
                    {
                        Play "a2o_workoutBench_benchPress_easy_workoutBench" for "WorkoutBench"
                        Play "a2o_workoutBench_benchPress_easy_x" for "x"
                    }
                    Value "normal"
                    {
                        Select Random
                        {
                            Weight 1
                            {
                                Play "a2o_workoutBench_benchPress_stressed_workoutBench" for "WorkoutBench"
                                Play "a2o_workoutBench_benchPress_stressed_x" for "x"
                            }
                            Weight 2
                            {
                                Play "a2o_workoutBench_benchPress_easy_workoutBench" for "WorkoutBench"
                                Play "a2o_workoutBench_benchPress_easy_x" for "x"
                            }
                        }
                    }
                    Value "novice"
                    {
                        Select Random
                        {
                            Weight 2
                            {
                                Play "a2o_workoutBench_benchPress_stressed_workoutBench" for "WorkoutBench"
                                Play "a2o_workoutBench_benchPress_stressed_x" for "x"
                            }
                            Weight 1
                            {
                                Play "a2o_workoutBench_benchPress_stressFail_workoutBench" for "WorkoutBench"
                                Play 0xE6D6082429AB955 for "x"
                            }
                        }
                    }
                    Value "poor"
                    {
                        Play "a2o_workoutBench_benchPress_stressFail_workoutBench" for "WorkoutBench"
                        Play 0xE6D6082429AB955 for "x"
                    }
                    Value "skilled"
                    {
                        Play "a2o_workoutBench_benchPress_easy_workoutBench" for "WorkoutBench"
                        Play "a2o_workoutBench_benchPress_easy_x" for "x"
                    }
                }
            }
            Value "yes"
            {
                Select Random
                {
                    Weight 2
                    {
                        Play "a2o_workoutBench_benchPress_stressFail_workoutBench" for "WorkoutBench"
                        Play 0xE6D6082429AB955 for "x"
                    }
                    Weight 1
                    {
                        Play 0x372CF404C80F7CAC for "WorkoutBench"
                        Play "a2o_workoutBench_benchPress_idle_x" for "x"
                    }
                }
            }
        }
    }

I tried to copy that with my jazz script and added this line to my code hoping to trigger the parameters:
Code:
		val.SetParameter("SkillLevel", (object)actor.SkillManager.GetSkillLevelParameterForJazzGraph(SkillNames.Athletic));

I changed it to this under the EnterState line, but unfortunately it didn't work.
Code:
base.StandardEntry();
                base.EnterStateMachine("YogaMatAnim", "Enter", "x");
                base.Actor.SwitchToOutfitWithSpin(Sim.ClothesChangeReason.GoingToWorkOut, OutfitCategories.Athletic);
                base.SetActor("YogaMat_object", base.Target);
                base.Actor.RouteToSlot(this.Target, (Slot)0x31229A4C);
                base.EnterState("x", "Enter");
                base.SetParameter("skill", (object)Actor.SkillManager.GetSkillLevelParameterForJazzGraph(SkillNames.Athletic));
                base.BeginCommodityUpdates();
                base.AnimateSim("Loop");
                // 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.
                athleticSkill.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;

            }

The only thing I can think to do to make the jazz scripts more similar is to remove the Explicit property on my Loop state in SmoothJazz, but that may not do anything either... I could be missing something script wise, though.

ETA: Okay I took out the previous line I mentioned and added this section, but I keep getting an error over the "EnterStateMachineAndInitParameters" section of the first line:
Code:
public override StateMachineClient EnterStateMachineAndInitParameters(Sim actor, bool needClothingChange)
                {
                    StateMachineClient val = StateMachineClient.Acquire((IHasScriptProxy)(object)actor, "YogaMatAnim", (AnimationPriority)(-2));
                    val.SetActor("x", (IHasScriptProxy)(object)actor);
                    val.SetParameter("skill", (object)actor.SkillManager.GetSkillLevelParameterForJazzGraph(SkillNames.Athletic));
                    return val;
                }

I do feel like I'm getting somewhere (hopefully), so that's encouraging
Virtual gardener
staff: administrator
#59 Old 8th Jan 2021 at 10:29 AM
I have zero ideas why the SetParameters wouldn't work actually, they should just work :/ Unless, of course, you didn't add the bench press state thing in your own code. I'm assuming you have, but it's a good thing to double-check

Now, for that second line that's not liking it, I'm not sure what the error is, but I'm almost assuming it's due to the way either animation priority has a -2 (I honestly don't know if AnimationPriority by itself is an int type. Currently not at my main computer) or whether it might be screwing up at the part where an actor is always being casted as an object (AND a IHasScriptProxy, which I'm not sure if that's right?). Which you shouldn't have to do, but if EA did this for a reason... then I guess they probably tested it :p 

Usually what I do though, in cases where I'm not quite sure if I copy-pasted the correct thing, is to simply check some more interactions  Sometimes you'll start noticing particular patterns. Keep in mind though that the base game objects code is usually quite different from after at least ambitions objects. 

Just a note for the future: It's a bit easier to help fixing any errors by knowing specifically what the error is
Lab Assistant
Original Poster
#60 Old 9th Jan 2021 at 12:28 AM
Quote: Originally posted by Lyralei
I have zero ideas why the SetParameters wouldn't work actually, they should just work :/ Unless, of course, you didn't add the bench press state thing in your own code. I'm assuming you have, but it's a good thing to double-check

Now, for that second line that's not liking it, I'm not sure what the error is, but I'm almost assuming it's due to the way either animation priority has a -2 (I honestly don't know if AnimationPriority by itself is an int type. Currently not at my main computer) or whether it might be screwing up at the part where an actor is always being casted as an object (AND a IHasScriptProxy, which I'm not sure if that's right?). Which you shouldn't have to do, but if EA did this for a reason... then I guess they probably tested it :p 

Usually what I do though, in cases where I'm not quite sure if I copy-pasted the correct thing, is to simply check some more interactions  Sometimes you'll start noticing particular patterns. Keep in mind though that the base game objects code is usually quite different from after at least ambitions objects. 

Just a note for the future: It's a bit easier to help fixing any errors by knowing specifically what the error is

Ah yeah, sorry about forgetting the error code. I did manage to fix it today. The problem was with my jazz script rather than the actual code, I had forgotten to copy over the transition statements from the note I was keeping them in. I reverted the code back to just the base line in my previous post and it works like a charm. The skill parameters function just like they do for other athletic objects. Now all that's left to do is outline the animation sequences for the yoga routines, add (or remove) buffs, and program the ability to "drag out" the interaction so sims can "Practice Yoga Until Leveling Up" and "Practice Yoga Until Tranquil" like with the bathtub. Hopefully that will go smoother
Lab Assistant
Original Poster
#61 Old 11th Jan 2021 at 5:56 AM
So making the routines in the jazz script isn't going as smoothly as I had hoped. The script won't recognize my additional Entry states, so the yoga routines use the same loop as my Practice Yoga interaction. This is part of the jazz script:

Code:
State "a_yoga_posture"
    {
        Transitions to "Exit"
        Play 0x400710791E58D436 for "x"
    }
    State "Enter"
    {
        Properties Public, Entry
        Transitions to "a_yoga_posture_getIn"
    }
    State "Enter1"
    {
        Properties Public, Entry
        Transitions to "a_yoga_posture_getIn1"
    }
    State "Enter2"
    {
        Properties Public, Entry
        Transitions to "a_yoga_posture_getIn2"
    }
    State "Enter3"
    {
        Properties Public, Entry
        Transitions to "a_yoga_posture_getIn3"
    }
    State "Exit"
    {
        Properties Public, Exit
    }
    State "EnergyCenteringPose1"
    {
        Transitions to "EnergyCenteringPose2"
        Play 0x4A93F0DD21EEBE26 for "x"
    }
    State "EnergyCenteringPose2"
    {
        Transitions to "EnergyCenteringPose3"
        Play "a_yoga_boatPose_x" for "x"
    }
    State "EnergyCenteringPose3"
    {
        Transitions to "a_yoga_posture"
        Play 0x6A97F3FDEEA86EE2 for "x"
    }
    State "StressRelievingPose1"
    {
        Transitions to "StressRelievingPose2"
        Play "a_yoga_treePose_x" for "x"
    }
    State "StressRelievingPose2"
    {
        Transitions to "StressRelievingPose3"
        Play 0xA68E070AC3B7B4E for "x"
    }
    State "StressRelievingPose3"
    {
        Transitions to "a_yoga_posture"
        Play "a_yoga_warrior" for "x"
    }
    State "MindBalancingPose1"
    {
        Transitions to "MindBalancingPose2"
        Play 0x736624679A08F680 for "x"
    }
    State "MindBalancingPose2"
    {
        Transitions to "MindBalancingPose3"
        Play "a_yoga_lordOfTheDancePose" for "x"
    }
    State "MindBalancingPose3"
    {
        Transitions to "a_yoga_posture"
        Play 0x78B3C9E4FB86A671 for "x"
    }

And this is the corresponding code:
Code:
public class EnergyCentering : Interaction<Sim, YogaMat>
        {
            public class Definition : InteractionDefinition<Sim, YogaMat, PracticeYoga>
            {
                public override string GetInteractionName(Sim actor, YogaMat target, InteractionObjectPair interaction)
                {
                    return "Energy Centering";
                }
                public override string[] GetPath(bool bPath)
                {

                    return new string[] { "Do Yoga Routine..." };

                }
                public override bool Test(Sim actor, YogaMat target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback)
                {
                    return true;
                }
            }
            public static InteractionDefinition Singleton = new Definition();
            public override bool Run()
            {
                if (!Actor.RouteToSlot(Target, (Slot)0x31229A4C))
                {
                    Actor.PlayRouteFailure();
                    return false;
                }
                // 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. 
                Athletic athleticSkill = base.Actor.SkillManager.AddElement(SkillNames.Athletic) as Athletic;
                if (athleticSkill == 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.
                athleticSkill.StartSkillGain(2);
                base.StandardEntry();
                base.EnterStateMachine("YogaMatAnim", "Enter1", "x");
                base.Actor.SwitchToOutfitWithSpin(Sim.ClothesChangeReason.GoingToWorkOut, OutfitCategories.Athletic);
                base.SetActor("YogaMat_object", base.Target);
                base.Actor.RouteToSlot(this.Target, (Slot)0x31229A4C);
                base.EnterState("x", "Enter1");
                base.SetParameter("skill", (object)Actor.SkillManager.GetSkillLevelParameterForJazzGraph(SkillNames.Athletic));
                base.BeginCommodityUpdates();
                base.AnimateSim("EnergyCenteringPose1");
                // 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.
                athleticSkill.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 class StressRelieving : Interaction<Sim, YogaMat>
        {
            public class Definition : InteractionDefinition<Sim, YogaMat, PracticeYoga>
            {
                public override string GetInteractionName(Sim actor, YogaMat target, InteractionObjectPair interaction)
                {
                    return "Stress Relieving";
                }
                public override string[] GetPath(bool bPath)
                {

                    return new string[] { "Do Yoga Routine..." };

                }
                public override bool Test(Sim actor, YogaMat target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback)
                {
                    return true;
                }
            }
            public static InteractionDefinition Singleton = new Definition();
            public override bool Run()
            {
                if (!Actor.RouteToSlot(Target, (Slot)0x31229A4C))
                {
                    Actor.PlayRouteFailure();
                    return false;
                }
                // 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. 
                Athletic athleticSkill = base.Actor.SkillManager.AddElement(SkillNames.Athletic) as Athletic;
                if (athleticSkill == 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.
                athleticSkill.StartSkillGain(2);
                base.StandardEntry();
                base.EnterStateMachine("YogaMatAnim", "Enter2", "x");
                base.Actor.SwitchToOutfitWithSpin(Sim.ClothesChangeReason.GoingToWorkOut, OutfitCategories.Athletic);
                base.SetActor("YogaMat_object", base.Target);
                base.Actor.RouteToSlot(this.Target, (Slot)0x31229A4C);
                base.EnterState("x", "Enter2");
                base.SetParameter("skill", (object)Actor.SkillManager.GetSkillLevelParameterForJazzGraph(SkillNames.Athletic));
                base.BeginCommodityUpdates();
                base.AnimateSim("StressRelievingPose1");
                // 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.
                athleticSkill.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 class MindBalancing : Interaction<Sim, YogaMat>
        {
            public class Definition : InteractionDefinition<Sim, YogaMat, PracticeYoga>
            {
                public override string GetInteractionName(Sim actor, YogaMat target, InteractionObjectPair interaction)
                {
                    return "Mind Balancing";
                }
                public override string[] GetPath(bool bPath)
                {

                    return new string[] { "Do Yoga Routine..." };

                }
                public override bool Test(Sim actor, YogaMat target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback)
                {
                    return true;
                }
            }
            public static InteractionDefinition Singleton = new Definition();
            public override bool Run()
            {
                if (!Actor.RouteToSlot(Target, (Slot)0x31229A4C))
                {
                    Actor.PlayRouteFailure();
                    return false;
                }
                // 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. 
                Athletic athleticSkill = base.Actor.SkillManager.AddElement(SkillNames.Athletic) as Athletic;
                if (athleticSkill == 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.
                athleticSkill.StartSkillGain(2);
                base.StandardEntry();
                base.EnterStateMachine("YogaMatAnim", "Enter3", "x");
                base.Actor.SwitchToOutfitWithSpin(Sim.ClothesChangeReason.GoingToWorkOut, OutfitCategories.Athletic);
                base.SetActor("YogaMat_object", base.Target);
                base.Actor.RouteToSlot(this.Target, (Slot)0x31229A4C);
                base.EnterState("x", "Enter3");
                base.SetParameter("skill", (object)Actor.SkillManager.GetSkillLevelParameterForJazzGraph(SkillNames.Athletic));
                base.BeginCommodityUpdates();
                base.AnimateSim("MindBalancingPose1");
                // 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.
                athleticSkill.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);
                }
            }
        }
Senior Moderator
staff: senior moderator
#62 Old 11th Jan 2021 at 10:36 AM
I don't think you need 3 different Entry states, and it probably just doesn't like it if it's not working :P
I think what you're supposed to do, is have the one the Enter state and have a Transitions To... line for each state it could go to after that. 
Then in the code, after you've done EnterStateMachine or whatever, you do AnimateSim to start the first correct animation. 

I'm just on my phone now so I can't find an example to make sure, but I think this is how other things do it.
Like where objects have one jazz script and so each interaction just uses a different path in the jazz script, but there's only one Enter. Maybe try looking at the fireplace if you want a reference? I think that one works like that  
Virtual gardener
staff: administrator
#63 Old 11th Jan 2021 at 11:21 AM
Quote: Originally posted by zoe22
I don't think you need 3 different Entry states, and it probably just doesn't like it if it's not working :P
I think what you're supposed to do, is have the one the Enter state and have a Transitions To... line for each state it could go to after that. 
Then in the code, after you've done EnterStateMachine or whatever, you do AnimateSim to start the first correct animation. 

I'm just on my phone now so I can't find an example to make sure, but I think this is how other things do it.
Like where objects have one jazz script and so each interaction just uses a different path in the jazz script, but there's only one Enter. Maybe try looking at the fireplace if you want a reference? I think that one works like that  
Seconding this! Also, keep in mind that most of EA's stateMachine handler code has "Enter" hardcoded in it. So Always start with 1 enter. you can always define in the code with animateSim() which animation it should play next
Lab Assistant
Original Poster
#64 Old 12th Jan 2021 at 7:35 PM
I got rid of the additional Enter states and defined the routines with their intended paths but it's still not working. Could it have something to do with the fact that I'm looping the gain athletic skill? This is the most up to date version of the code and the jazz script:
Attached files:
File Type: rar  Alunn_YogaMat (V6).rar (7.97 MB, 4 downloads)
Senior Moderator
staff: senior moderator
#65 Old 12th Jan 2021 at 8:27 PM
I had a quick look, and I think the problem might be that you're saying DoLoop(...) but for the routines there aren't any loops happening in the jazz script.
So I think you can just get rid of that line, because the actual starting and stopping of  skill gain happens outside of that.
Though I'm not 100% sure, there might be something else going on but that's worth a shot :P

Also nice to see how much progress you've made! Looking forward to seeing it complete
Lab Assistant
Original Poster
#66 Old 13th Jan 2021 at 10:08 AM
Quote: Originally posted by zoe22
I had a quick look, and I think the problem might be that you're saying DoLoop(...) but for the routines there aren't any loops happening in the jazz script.
So I think you can just get rid of that line, because the actual starting and stopping of  skill gain happens outside of that.
Though I'm not 100% sure, there might be something else going on but that's worth a shot :P

Also nice to see how much progress you've made! Looking forward to seeing it complete

I took out the DoLoop section, but no dice unfortunately. There has to be something I'm over looking somewhere...

PS: I'm also excited to share it with everyone!
Senior Moderator
staff: senior moderator
#67 Old 13th Jan 2021 at 11:46 AM
When you say it's not working, what's happening exactly? Is none of it working at all or is just going through the PracticeYoga states rather than the selected routine? And does PracticeYoga work as it should?

editing immediately after posting :P:
I think the problem is actually in your definitions for the routine interactions! 
Code:
 public class EnergyCentering : Interaction<Sim, YogaMat>
        {
            public class Definition : InteractionDefinition<Sim, YogaMat, *PracticeYoga*>
In the definition, the interaction should be EnergyCentering, and likewise for the other routines too. I've definitely done that before...multiple times :P
I really hope that fixes it because otherwise I'm really not sure what it could be 
Lab Assistant
Original Poster
#68 Old 14th Jan 2021 at 1:13 PM Last edited by Alunn : 14th Jan 2021 at 1:46 PM.
Quote: Originally posted by zoe22
When you say it's not working, what's happening exactly? Is none of it working at all or is just going through the PracticeYoga states rather than the selected routine? And does PracticeYoga work as it should?

editing immediately after posting :P:
I think the problem is actually in your definitions for the routine interactions! 
Code:
 public class EnergyCentering : Interaction<Sim, YogaMat>
        {
            public class Definition : InteractionDefinition<Sim, YogaMat, *PracticeYoga*>
In the definition, the interaction should be EnergyCentering, and likewise for the other routines too. I've definitely done that before...multiple times :P
I really hope that fixes it because otherwise I'm really not sure what it could be 

My bad, I really need to work on explaining things better. The yoga routines were just cycling through the Practice Yoga states, as you said.

Thanks for catching my slip up on the definitions! I fixed them, but now the animations won’t play until I cancel the interaction, so I can’t even say for sure if they’re cycling through the correct states. The Practice Yoga interaction still works as its supposed to.

Edit: Alright scratch that, the Practice Yoga interaction is borked too. My sim still cycles through the lower level animations despite having level 7 athletic skill *sigh*
Senior Moderator
staff: senior moderator
#69 Old 14th Jan 2021 at 3:30 PM
Could you attach the package with everything? I can't see just from looking what the problem might be, but it might help to test different changes to get to the bottom of what's going on 
Lab Assistant
Original Poster
#70 Old 15th Jan 2021 at 12:20 AM
Sure thing. I also noticed that the tuning seems to be broken as well
Attached files:
File Type: rar  Zen Again Yoga Mat [by Alunn].rar (4.48 MB, 1 downloads)
Senior Moderator
staff: senior moderator
#71 Old 15th Jan 2021 at 1:20 PM
I'll try and have a look some time this weekend

Though if you want some of my endless wisdom () the best thing to do when coding and debugging is one thing at a time, especially if there are multiple issues happening.
It might be a pain to do this, but if you make a copy of your jazz script and just get the PracticeYoga interaction working again, then get the ITUN to work again, and after that try and add in 1 routine to the jazz script and the object too see if you can get it to work, it's really the best way to figure out how it all works.
You can keep all your current code as a backup, for you to use when you're ready so you don't have to write it from scratch, but it's basically so you don't have large amounts of code that may have any number of issues in, and really there's no way to know if there are issues until you test in game, so that's why you only want to deal with one problem at once.

Because otherwise, you're trying to find the problem with the routines but there are several of them in the jazz script with like 200 states, and you don't know if the problem is with the jazz script or the c# code, or which interactions are causing problems...

It's way easier to take it one step at a time, while making sure things work at each step and only needing to figure out ONE thing that's not working, instead of having several things going, several issues, and no idea what's causing them :P 
And so that's the same for writing new stuff, and going back and fixing things where everything has suddenly gone wrong because, well we've all been there 
Lab Assistant
Original Poster
#72 Old 16th Jan 2021 at 4:30 AM
Your suggestion worked! I took everything apart, slowly put it back together, and now all of the interactions function as they're supposed to. I didn't even have to reformat the tuning files.

The last two things I want to do before I upload the package for testing is add a moodlet and the ability to "Work Out Until Athletic Skill Improves". I don't want the moodlet to appear every time a sim finishes using the yoga mat, only occasionally (like a random percent chance). Is there a way to do that?

I've also been combing through the game files looking for the code needed for the "Work Out Until" function, but I've been unsuccessful. I'm guessing another place to look for it would be within the skill itself?
Senior Moderator
staff: senior moderator
#73 Old 16th Jan 2021 at 5:19 PM
Ohh that's so great! Did you realise what it was that was causing the issues?
I'm not sure exactly about custom moodlets but I think there's a tutorial for that somewhere...
With a random chance, you can just use a random number thing, and then add the moodlet based on that. So there are function for getting an integer:  
RandomUtil.GetInt(1, 10);
Which I think would give you an integer between or including 1 and 10, and then if you want a 10% chance you could say if that integer =1, add the moodlet etc...

The code for adding stages to the interaction is a little complicated I think...
I know there is a ConfigureInteraction() part, which has something to do with it, where you add the stages which are variables declared in the interaction. But it seems like different interactions do it differently, so the WorkOut one is different to FishHere, so I'm not sure which is the right way to go with that. I guess just try one way 

Also @echoweaver had some issues getting the same thing working here  , so maybe using the WorkOut interaction as a reference is a better idea, especially as it's closer to yoga than fishing. Though it does also have tones, so like Don't break a sweat etc, so look for the lines of that reference Stages.
Lab Assistant
Original Poster
#74 Old 17th Jan 2021 at 6:03 AM
Quote: Originally posted by zoe22
Ohh that's so great! Did you realise what it was that was causing the issues?
I'm not sure exactly about custom moodlets but I think there's a tutorial for that somewhere...
With a random chance, you can just use a random number thing, and then add the moodlet based on that. So there are function for getting an integer:  
RandomUtil.GetInt(1, 10);
Which I think would give you an integer between or including 1 and 10, and then if you want a 10% chance you could say if that integer =1, add the moodlet etc...

The code for adding stages to the interaction is a little complicated I think...
I know there is a ConfigureInteraction() part, which has something to do with it, where you add the stages which are variables declared in the interaction. But it seems like different interactions do it differently, so the WorkOut one is different to FishHere, so I'm not sure which is the right way to go with that. I guess just try one way 

Also @echoweaver had some issues getting the same thing working here  , so maybe using the WorkOut interaction as a reference is a better idea, especially as it's closer to yoga than fishing. Though it does also have tones, so like Don't break a sweat etc, so look for the lines of that reference Stages.

Yeah, for base.AnimateSim() I had to use the loop names (Routine1, Routine2, and Routine3) instead of the first state following the Enter state. Minor oversight on my part but it made all the difference.

For the moodlet, I think I’ll just use one that already exists in the game rather than fuss with a custom one. I’m stuck between either the Zen moodlet, which you can only get at a specific lot in Shang Simla, or the Tranquil moodlet which you get from bubble baths.

I’ll look into the WorkOut interaction and see what I can find. If it’s a bit too much, I’ll put that function on the back burner and focus on getting the mod to beta. I also read through the code snippets thread today and saw a snippet that enables objects to be placed in a sims’s inventory. I think I’ll add that in so sims can do yoga wherever they want.
Forum Resident
#75 Old 17th Jan 2021 at 8:56 AM
Quote: Originally posted by Alunn
For the moodlet, I think I’ll just use one that already exists in the game rather than fuss with a custom one. I’m stuck between either the Zen moodlet, which you can only get at a specific lot in Shang Simla, or the Tranquil moodlet which you get from bubble baths.


Just my .2 cents, but I'd say for balance (pun intended ) just go with tranquil. It's already pretty underutilized as it is, so it would be a good alternative to taking bubble baths all the time. I haven't played around in Shang Simla much, but it sounds like the Zen moodlet may be one of those special ones, so it might possibly be weighted slightly more than tranquil, though I'm not entirely sure on that one.

Plus, drawing from real life influence, yoga is a mind and body exercise so tranquil fits better I think. The same way for taking a bubble bath, because you'd be relieving stress on your body and mind. Zen is more or less a state of mind, without getting into semantics, it's more about your mental state and having a clear head, so it really fits better for meditation, where (I think) it's already used by EA.

You have been chosen. They will come soon.
Page 3 of 5
Back to top