- Site Map >
- Modding and Creation >
- Sims 3 Creation >
- Modding Discussion >
- Workout / Train other Sim costs Money
- Site Map >
- Modding and Creation >
- Sims 3 Creation >
- Modding Discussion >
- Workout / Train other Sim costs Money
Posts: 997
Thanks: 22102 in 95 Posts
Little OT (forgive me chazyra):
Let's say we have two variables, "a" and "b" of type/class Sim. Is it safe to use this check
if (a == b) ...
or it's better to use SimDescriptionID even in this case (with no saved data involved)?
That never gave me any problem in game, but I don't use many features - like traveling
for example, so maybe has not been tested properly.
Back in topic, this solution (cooldown) seems a good choice to me and if you like it you
can even extend the duration of the "subscription" period so you can use gym/dojo
equipments for a full/half day paying only once (maybe instead of using sim minutes
wen can use sim days, I can't check if that's an option right now but I expect it is).
Of course there are at least dozens of other solutions and workarounds (even a
complete overhauling of how the training dummy works) but maybe it's better to
keep it as simple as possible.
Posts: 516
Thanks: 4227 in 14 Posts
Posts: 301
Thanks: 6795 in 13 Posts
That's very useful info. Little OT (forgive me chazyra): Let's say we have two variables, "a" and "b" of type/class Sim. Is it safe to use this check
Code:
if (a == b) ... or it's better to use SimDescriptionID even in this case (with no saved data involved)? That never gave me any problem in game, but I don't use many features - like traveling for example, so maybe has not been tested properly. |
I don't see EA overriding its equality operator, so it compares if A and B are the very same object. That works just fine, cos if one of the two is "null" or invalid you will still have the desired result. Just make sure at least one of them is not "null", cos if they both are then you're going to get a match (and probably something went wrong in the code). The same goes for SimDescription classes. It really depends on the context anyway, because if one of them is out of town (boarding school, free vacation, etc...) or traveling, the Sim class will be destroyed and the SimDescription class compressed and packed, also known as MiniSimDescription. That's where the SimDescriptionID comes useful, because you can do the following :
IMiniSimDescription IMSD = SimDescription.Find(/*put SimDescriptionID value here*/); if (IMSD == null) { MiniSimDescription miniSimDescription = MiniSimDescription.Find(/*put SimDescriptionID value here*/); /*...check for null and work with miniSimDescription here...*/ } else { SimDescription simDescription = IMSD as SimDescription; Sim actor = simDescription.CreatedSim; /*...check for null and work with actor here...*/ }
IMiniSimDescription is an interface implemented both by SimDescription and MiniSimDescription classes, which is why I'm using that one. In the end you will have either a MiniSimDescription or a Sim class. If the former results in "null" then that sim does not exist anymore in your game, if it doesn't then that sim exists but it's in another world. If the latter results in "null" then that sim exists but it's currently out of town (I'm unsure whether ghosts or dead sims will result in "null" too, I never had to verify it, but you can check that eventually on the SimDescription variable), if it doesn't then you're good to go.
But again, you should go through this trouble only in a special context where you need to make sure you have a valid sim to work with, otherwise a simple comparison between the two classes is more than enough
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 997
Thanks: 22102 in 95 Posts
chazyra don't feel overwhelmed by all this information, it's all extra stuff, what you
need to do is relatively simple. You are catching up quickly but if you need help just ask.
Posts: 12
it's really very very much information for me, my programming experience is not sooooo big, and this is my first try to modifying a mod. I thought it would be easier a little bit. I'm proud 'cos the main thing: the payment, is functionally, but it seems like there is still a long way to get this fully working.
I think to check how much time is past since the last payment before trigger a new payment is a very good idea, but i don't know how i can implement this in my code. Can you give me a step by step instruction what i've to do?
btw: english is not my native language, i'm sure you realize that :D
Posts: 301
Thanks: 6795 in 13 Posts
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 12
the payment per se is already functionally in my code (based on ani_'s mod), the problem is that sims have to pay about every second right now
edit:
my code looks like this at the moment -->
Listener
private static void World_OnWorldLoadFinishedEventHandler(object sender, EventArgs e) { EventTracker.AddListener(EventTypeId.kUsedTrainingDummy, new ProcessEventDelegate(WorkOutCostsMoney.TrainingDummy)); }
Action
protected static ListenerAction TrainingDummy(Event e) { Sim sim = e.Actor as Sim; if (sim != null && sim.LotCurrent != null && sim.LotCurrent.CommercialLotSubType == CommercialLotSubType.kEP1_Dojo) { CommonMethods.PayForWorkOut(sim, sim.LotCurrent, CommonMethods.ReturnFee(CommonMethods.WorkOut.TrainingDummy)); } return ListenerAction.Keep; }
for that matter i only need a query in the if-loop how much time is passed since the last pay, right?
Posts: 997
Thanks: 22102 in 95 Posts
btw: english is not my native language |
Same here, don't worry.
i only need a query in the if-loop how much time is passed since the last pay, right? |
Correct. But JunJayMdM was suggesting to use another system: to change directly how the training
dummy behave so it will send a signal only when sims finish training and not every second. It's your call.
Posts: 301
Thanks: 6795 in 13 Posts
1) A PersistableStatic Dictionary<ulong, float> to place in that class, where you store the ID of the Sims and the time the interaction started
2) An alarm that fires every time you want the payment to be issued, which will check if the SIm is still running the interaction, in which case it will get charged
3) A Cleanup function that removes old entries from the Dictionary
This plan has negative aspects though :
1) The Sim might get charged way after the interaction ended, which is confusing
2) If you make them pay in advance to avoid the above problem, then if after the last payment your Sim only trains for 2 minutes, it's not fair
If it's for personal use then it doesn't matter, but if you're planning on making the changes available to everyone then this plan should be dropped. If that's ok with you we can proceed, whatever you decide
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 997
Thanks: 22102 in 95 Posts
so you will have no clue when to stop the Listener |
There's no need to stop it. Checking if there's already a recent entry in the Dictionary will tell
if he has to pay again or not. Sure, it's not very efficient, but those events are fired only if a sim is
using the dummy anyway, so handling the listener code wont be much of a burden for the system.
2) An alarm that fires every time you want the payment to be issued, which will check if the SIm is still running the interaction, in which case it will get charged |
Why are you suggesting to use an Alarm? Sims will pay as soon as the first event regarding them is fired.
What's the matter? The anticipated payment not being realistic?
I agree that overriding/replacing the dummy interactions is probably the best solution though.
Posts: 12
Posts: 301
Thanks: 6795 in 13 Posts
There's no need to stop it. Checking if there's already a recent entry in the Dictionary will tell if he has to pay again or not. Sure, it's not very efficient, but those events are fired only if a sim is using the dummy anyway, so handling the listener code wont be much of a burden for the system. |
What I meant is "stop the Listener from listening on that specific Sim", because we don't know when the interaction ends, unless we check if the interaction is still running. Of course the Listener must be kept or it will work only once XD
Why are you suggesting to use an Alarm? Sims will pay as soon as the first event regarding them is fired. What's the matter? The anticipated payment not being realistic? I agree that overriding/replacing the dummy interactions is probably the best solution though. |
I suggested an Alarm because I thought I read somewhere that the OP wanted a payment to be issued every hour. If it's just a symbolic payment at the start, then we need a List<ulong> and not a Dictionary and the whole thing becomes much simpler and makes more sense.
Is this what we need to be working on?
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 12
Posts: 997
Thanks: 22102 in 95 Posts
and stealing the focus from what chazyra wants. Thank you again, it has been
fun and instructive to talk with you about this stuff. If you are so kind to help her
then I'll go back to work on my mod, but I'll keep on eye here because I'm sure
I have something to learn. EDIT: I must have missed the post where she talked
about paying every hour, my bad then. This thread is almost a chatroom!
Posts: 301
Thanks: 6795 in 13 Posts
I'm puzzled by your first answer, but I feel like I'm only slowing down things and stealing the focus from what chazyra wants. Thank you again, it has been fun and instructive to talk with you about this stuff. If you are so kind to help her then I'll go back to work on my mod, but I'll keep on eye here because I'm sure I have something to learn. EDIT: I must have missed the post where she talked about paying every hour, my bad then. This thread is almost a chatroom! |
Don't worry about it, it seems we're all non-native English speakers here, so it's easy to misunderstand or fail to explain something properly. As long as we get chazyra to succeed
at the beginning i want that sims have to pay when they're stop to use the training dummy, but there is no event like kStoppedTrainingDummy. Because of that i thougt the only way to get paid is let the sims pay per hour, but when it's possible to modify the interaction itself so that it's triggered a "new" event when sims stopping the training, then we can choose this solution. |
That's exactly what replacing the interaction will give us. We can decide when the Sim pays, if we charge per minute or per hour, we may also stop the interaction if the Sim can't afford it anymore and send a notification that states that. We will have much more freedom to do whatever we want in there
In the following post I will post the first step for replacing interactions.
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 301
Thanks: 6795 in 13 Posts
Create a new class file in the project, you can name it PracticeEx.cs. Then :
public sealed class PracticeEx : TrainingDummy.Practice { public sealed class DefinitionEx : TrainingDummy.Practice.Definition { // We need to create a new instance here for PracticeEx so this one will be called instead of the original public override InteractionInstance CreateInstance(ref InteractionInstanceParameters parameters) { InteractionInstance interactionInstance = new PracticeEx(); interactionInstance.Init(ref parameters); return interactionInstance; } } }
Now, save the class and let's forget about it for now and go back to the WorkOutCostsMoney class. Put it back to its original state (i.e. as Ani made it originally, drop all the changes you made) and then proceed with the following additions (they're in bold) :
// Here we will add a delegate that will do the replacement for the interaction static WorkOutCostsMoney() { LoadSaveManager.ObjectGroupsPreLoad += new ObjectGroupsPreLoadHandler(WorkOutCostsMoney.OnPreLoad); World.OnWorldLoadFinishedEventHandler += new EventHandler(WorkOutCostsMoney.World_OnWorldLoadFinishedEventHandler); } private static void OnPreload() { // This is the actual replacement. The EA interaction is now no more! TrainingDummy.Practice.Singleton = new PracticeEx.DefinitionEx(); }
That's it for now, just make these changes and try to compile. If you get errors from VS or whatever you're using, post them here. Now I'm off to bed, we will continue tomorrow
P.S. Do not try the mod yet, this is not complete, the interaction will not work cos now the ITUN is not pointing to it anymore and it will fail.
EDIT : I had a doubt now, is this the right class? If the right one is Practice and not Train, just use that one instead, I'm a bit sleepy now so I'm confused. The changes are the same anyway, except you change the name of the class and the one you're replacing.
EDIT 2 : Ok I fixed the code I posted, in case you didn't catch it, I changed to the right class
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 301
Thanks: 6795 in 13 Posts
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 997
Thanks: 22102 in 95 Posts
Ehm... do you mind explaining "almost every line" of that code?
I'm asking this for chazyra's sake of course, I understand it perfectly...
Jokes aside, can you please explain a bit these 2 things:
- the overriding of the CreationInstance (what exactly is going on there)
- LoadSaveManager.ObjectGroupsPreLoad += new ObjectGroupsPreLoadHandler(WorkOutCostsMoney.OnPreLoad); (total darkness)
Posts: 301
Thanks: 6795 in 13 Posts
Yeah, I'm a dirty liar, I said I would have kept my mouth shut but I just can't. Ehm... do you mind explaining "almost every line" of that code? I'm asking this for chazyra's sake of course, I understand it perfectly... Jokes aside, can you please explain a bit these 2 things: - the overriding of the CreationInstance (what exactly is going on there) - LoadSaveManager.ObjectGroupsPreLoad += new ObjectGroupsPreLoadHandler(WorkOutCostsMoney.OnPreLoad); (total darkness) |
Let's start with the first one. Do you know anything about Generic Types? Basically the outer class (derived from InteractionInstance) is represented in the InteractionDefinition class name as a Generic Type. That means whenever you create a new InteractionDefinition class, you will have to insert the name of the new outer class. Let's go with actual code so it can be better understood :
public abstract class InteractionDefinition<TActor, TTarget, TInteraction> : InteractionDefinition where TActor : class, IActor where TTarget : class, IGameObject where TInteraction : InteractionInstance, new()
The one above is the base InteractionDefinition class, the one where all InteractionDefinition classes are derived from. Those types enclosed in tags with a "T" at the beginning are the Generic Types. Then on the right side, constraints are defined for those types. What does this mean? It means that whenever you create a new class that inherits from InteractionDefinition you will have to define those types and they have to be types derived from or the same of the ones defined in the constraints.
Now, let's focus on what you asked. TInteraction is the outer class, the actualiInteraction. Why is it there? Because the outer class changes everytime and the InteractionDefinition class has a method inside that creates an instance of the outer class. This has been coded to avoid to have to create an instance of the outer class everytime you create an interaction. It's a time saver.
protected virtual InteractionInstance CreateInstance(ref InteractionInstanceParameters parameters) { TInteraction tInteraction = Activator.CreateInstance<TInteraction>(); tInteraction.Init(ref parameters); return tInteraction; }
As you can see, this method creates an instance of TInteraction, whatever it may be, as long as it has a parameterless constructor. At compile time, TInteraction will be replaced in every method to match the new outer class. (even if all the derived classes don't include this method, it is used by all of them, that's how inheritance works). This is where we're getting to why we're doing that in the code. Because look at the original TrainingDummy.Practice definition :
public sealed class Definition : InteractionDefinition<Sim, TrainingDummy, TrainingDummy.Practice>, ICardioInteractionDefinition
As you can see, TInteraction has been replaced to match the outer class but since we need to override that class because we want the game to read ours and not EA's, we have to tell it "NO! Bad code! Create an instance of my class, not the one you have there!". I hope this was clear enough, I tried to explain it the best I could, forgive me if it's not.
About the second question (this one's simpler to explain fortunately XD). Whenever you start a new game or load a saved game and you see the green bar loading, there's a phase where the code preloads all the stuff needed in order to load the World. It only lasts a few seconds then the World loading starts, so it's very quick. During that phase we need to change the static field pointing to the interaction so it points to ours. Since the game will always use that field to load the interaction and never by instantiating the definition, by simply changing that, we'll make sure their interaction will never be used. Said that, to make it do something on the preload phase, we need to add a new delegate to ObjectGroupsPreLoadHandler. It's the same as OnWorldLoadFinishedEventHandler, except it's for the preload. No more no less. Are you familiar with Events? (not the ones with the Listener, those are for the actual Gameplay).
I hope the second one was clear enough too. BTW here's 2 links, one explains Generics, the other one explains Events (or you could search google for different articles) :
MSDN - C# Generics
MSDN - C# Events
MSDN - C# Inheritance
EDIT : Also added a link to inheritance, if needed
EDIT 2 : I enclosed everything in a spoiler so chazyra won't get confused by all this clutter
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 997
Thanks: 22102 in 95 Posts
My mind almost exploded, but I think I get it.
When we instantiate a new interaction we do that using its inner class (the InteractionDefinition), right?
Indeed as a matter of fact when we add an interaction to a game object in the OnStartup method we
use the Singleton (the object representing the InteractionDefinition) of the interaction class.
So we have to override the CreateInstance method of the InteractionDefinition, otherwise, even if we
"hijack" the Singleton (the InteractionDefinition) of Practice, if we create a new instance of that interaction
we'll still create an instance of the old, base, outer class (Practice) instead of our new, derived, outer
class (PracticeEx). Why? Because, according to how polymorphism works, Practice will be the first class
in line of succession that met the type and contraints requirements. In other words, defining an overriding
Run method in PracticeEx would be completely useless because we will not even be instantiating an
interaction of that class in the first place, but an interaction of the base outer class (Practice) that
- inevitably - will only execute its own Run method.
Does that make sense? Please correct me if I'm wrong. And pity my horrible sequence of tenses.
Posts: 301
Thanks: 6795 in 13 Posts
My mind almost exploded, but I think I get it. When we instantiate a new interaction we do that using its inner class (the InteractionDefinition), right? Indeed as a matter of fact when we add an interaction to a game object in the OnStartup method we use the Singleton (the object representing the InteractionDefinition) of the interaction class. So we have to override the CreateInstance method of the InteractionDefinition, otherwise, even if we "hijack" the Singleton (the InteractionDefinition) of Practice, if we create a new instance of that interaction we'll still create an instance of the old, base, outer class (Practice) instead of our new, derived, outer class (PracticeEx). Why? Because, according to how polymorphism works, Practice will be the first class in line of succession that met the type and contraints requirements. In other words, defining an overriding Run method in PracticeEx would be completely useless because we will not even be instantiating an interaction of that class in the first place, but an interaction of the base outer class (Practice) that - inevitably - will only execute its own Run method. Does that make sense? Please correct me if I'm wrong. And pity my horrible sequence of tenses. |
It's not because of a line of succession, it's because a derived class contains all members from previous base classes. You can't see them but they're there. So there's an invisible copy of the last CreateInstance method in our PracticeEx.DefinitionEx class, but it instantiates TInteracion, which translates to TrainingDummy.Practice according to TrainingDummy.Practice.Definition.
Other than that you got it all right and perfectly understood what is going on there
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Posts: 997
Thanks: 22102 in 95 Posts
but who cares, maybe this can help others too so I'll gladly sacrifice myself :D
Does DefinitionEx have to be derived from TrainingDummy.Practice.Definition?
Why? What if we declare it like this instead:
public sealed class DefinitionEx : InteractionDefinition<Sim, TrainingDummy,TrainingDummy.PracticeEx>, ICardioInteractionDefinition { [CUT, we also rewrite the Test method] }
but thanks to this declaration it will be able to "translate" it to our outer class. Or not?
EDIT: TrainingDummy.PracticeEx was a copy/paste mistake.
By the way, in the Practice interaction code I don't see any reference to the interaction name
(there's no getInteractionName method), does the ITUN takes care of that with these tags?
<tradeoff name="Practice"> <Localization autoGenerate="True">
Posts: 301
Thanks: 6795 in 13 Posts
I was tempted to write to you in private cause I'm starting to feel a bit stupid,
but who cares, maybe this can help others too so I'll gladly sacrifice myself :D Does DefinitionEx have to be derived from TrainingDummy.Practice.Definition? Why? What if we declare it like this instead:
Code:
This way the CreationInstance method will keep using the GenericType TInteraction,public sealed class DefinitionEx : InteractionDefinition<Sim, TrainingDummy, TrainingDummy.PracticeEx>, ICardioInteractionDefinition { [CUT, we also rewrite the Test method] } but thanks to this declaration it will be able to "translate" it to our outer class. Or not? By the way, in the Practice interaction code I don't see any reference to the interaction name (there's no getInteractionName method), does the ITUN takes care of that with these tags?
Code:
<tradeoff name="Practice"> <Localization autoGenerate="True"> |
Maybe some moderator can help us moving our posts to another thread, if they find it necessary, meanwhile let's keep "spoiling" XD
First of all, you can't put TrainingDummy.PracticEx in there, cos TrainingDummy doesn't have our class, maybe you meant :
public sealed class DefinitionEx : InteractionDefinition<Sim, TrainingDummy, PracticeEx>, ICardioInteractionDefinition
Second, yes, that would save us from overriding CreateInstance() but then we'd have to rewrite all the methods from the original Definition. In our case it's just Test() but what if you have a Definition with 10 methods and 10 fields and they're all long? Still, even Test() in our case takes more time to rewrite than CreateInstance(). Although, your suggestion is good when we need to rewrite the Definition from scratch. Basically what I'm saying is : go with what will make you save time, is cleaner and less code to rewrite in order to avoid mistakes.
Third (which relates to your observation about the Interaction Name), sometimes EA uses an automated system to generate the names for Interactions, based on the namespace of the Definition class. This is something that I was keeping for Step 2, aka replace the ITUN and verify the Interaction Name. I didn't want to put it in Step 1 cos it's related to the ITUN (that's what <Localization autoGenerate="True"> is about). If we're overriding a class, the namespace will be different and the code won't be able to generate it cos it follows their namespace structure. if you don't know what a namespace is, it's the full name of the type, "Sims3.Gameplay.Objects.HobbiesSkills.TrainingDummy+Practice+Definition" in our case. The ITUN contains this namespace but we need to change it to ours and that will break the autogeneration. Therefore going the way you suggested won't solve this problem either, cos we'll still have a different namespace, no matter what.
How to solve this?
Solution 1 : Override GetInteractionName()
public override string GetInteractionName(Sim actor, TrainingDummy target, InteractionObjectPair iop) { return Localization.LocalizeString("Gameplay/Objects/HobbiesSkills/TrainingDummy/Practice:InteractionName", new object[0]); }
This one is the simplest and fastest solution and it will solve the problem. Where did I get that string from? I know EA namespace structures for interaction names XD. Basically remove "Sims3" at the beginning, replace "." with "/", remove the Definition class and put ":InteractionName" at the end
Solution 2 : Twallan's Singleton trick when overriding GetInteractionName()
//Inside the WorkOutCostsMoney class public static InteractionDefinition sOldSingleton; private static void OnPreload() { WorkOutCostsMoney.sOldSingleton = TrainingDummy.Practice.Singleton; TrainingDummy.Practice.Singleton = new PracticeEx.DefinitionEx(); } //Inside the PracticeEx.DefinitionEx class public override string GetInteractionName(Sim actor, TrainingDummy target, InteractionObjectPair iop) { return base.GetInteractionName(actor, target, new InteractionObjectPair(WorkOutCostsMoney.sOldSingleton, target)); }
This one is pretty much self explanatory. The code will get the namespace from the old Singleton, hence the Interaction Name will be generated correctly.
There is a 3d solution but it involves using a helper class to clone the Tuning in the code and we won't need to override anything. This also has the benefit of not having to import the ITUN in the package, so if you're an Interaction Overrider, this one is recommended, especially if another mod overrides the ITUN of the interaction you're overriding with a script, cos that one will be used instead and there won't be any conflicts (unless your code restricts/allows specific stuff and the ITUN does the opposite, in which case the ITUN wins and the script cries). But for our problem I wouldn't go there, Solution 1 or 2 will be enough
Nothing's real. Nothing's unreal either.
The frontier between true and untrue is a shady fuzzy line.
Destiny, or maybe the long flight's time-span, shall decide the issue.
Who Posted
|