Replies: 52 (Who?), Viewed: 8296 times.
Page 1 of 3
Senior Moderator
staff: senior moderator
Original Poster
#1 Old 13th Jul 2019 at 12:37 PM Last edited by zoe22 : 19th Aug 2019 at 8:38 PM.
Default [update: new progress] Making a functioning library
Hi, so I have been working on making a mod that allows your sims to actually use a library for borrowing a book and take it home, rather than having to stay on the library lot to read the book. My plan for it, is to have a register like situation (like the consignment store, or the bookstore/general store from WA), where your sim will "Take out Book", the ask to buy animation will play, and then a menu with the books on the lot will show up for you to select a book, which will be copied into your sim's inventory. Then using a timer, after say 48 sim hours, the book is either destroyed, or must be returned with a fine as a consequence for a late return, depending on how in depth it should be.

What I have managed to get working so far is by using the BookStoreRegister as a base, added a "Take Out Book" interaction, the list of books shows up, and the selected book gets copied into the sim's inventory.
This is the code:



My next step (which I've been struggling with) is getting the "Buy" animation/ social interaction to take place before the book can be chosen. I used the ShoppingRegister class as a guide, and worked out that it has the interaction that you click on (Buy), which creates (instantiates?) another interaction in the Run function:
Code:
protected override bool Run()
		{
			Sim simInRole = Target.CurrentRole.SimInRole;
			InteractionInstance instance = BuyItemsWithRegister.BuyItemsSingleton.CreateInstance(simInRole, Actor, Actor.InheritedPriority(), base.Autonomous, base.CancellableByPlayer);
			Actor.InteractionQueue.PushAsContinuation(instance, mustRun: true);
			return true;
		}

And this BuyItemsWithRegister interaction is derived from the BuyWithRegister social interaction, which I think is what I need.
The BuyItemsWithRegister has the "mRegister.ShowShoppingDialog(Actor);" in the PostAnimation Function, which is the actual shopping menu, for buying and selling, which I don't want. So my thought was that
I could create an AskForBook interaction using the same format as the BuyItemsWithRegister, and instead of the ShoppingDialogue thing, I would then put my code for the list of books menu, and creating the selected book. The AskForBook interaction is called/created/instantiated (sorry, don't know the technical terms) from the Run function in the TakeOutBook interaction which is in the code above, and is the one you click on, which is a reflection of the BuyItemsWithRegister interaction being initiated from the Buy interaction (hope this makes sense).
However, when I tried this it did not work at all. Nothing happened when I clicked on "Take Out Book".
This is the code for that:



I also tried adding base.AddInteraction(AskForBook.AskForBookSingleton);
but that actually meant the pie menu did not even show up anything when the register was clicked on!

So in conclusion, please help!
But seriously, if anyone could help, give some advice, I'd really appreciate it. I hope I made sense with explaining it, and if it would help for me to upload the package with one of the working/not working versions, or screenshots, let me know.
Advertisement
Field Researcher
#2 Old 14th Jul 2019 at 6:27 AM Last edited by gamefreak130 : 14th Jul 2019 at 2:14 PM.
You've certainly come a long way from the time you asked me for some C# tutorials several months ago

The reason pushing the interaction instance doesn't work is because it's relying on GetSelectedObjects() where there are no objects to select. The PopulatePieMenuPicker method only serves to populate an object picker dialog that is run when the interaction is selected from the pie menu; since you are no longer selecting that interaction from the pie menu, that dialog is no longer called and no object is selected.

Instead, you will need to call the object picker dialog directly from within the Run() method. To do this, copy in your existing code from PopulatePieMenuPicker, declaring listObjs, headers, and numSelectableRows as local variables within the Run() method rather than as parameters. Then, replace this:

Code:
Book book = null;
book = (GetSelectedObject() as Book);
if (book == null)
{
    return false;
}


With this:

Code:
List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show([String title for your dialog box -- preferably localized!], Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows);
if (selectedObjects.Count == 0 || !(selectedObjects[0].Item is Book book))
{
    return false;
}


This should work as intended. Let me know if you run into any more problems; I'll keep an eye on this post

"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
Senior Moderator
staff: senior moderator
Original Poster
#3 Old 14th Jul 2019 at 4:10 PM
Thank you! And yes, I'm finding it a lot easier to understand the game code this time round

I did what you said, however in game when I click Take Out Book, my sim just resets and I get an NRaas ErrorTrap script error for my sim
Here is my new full code:



PS, I am planning on localizing the strings once I get the base of my mod done, but that's another journey for another day
Field Researcher
#4 Old 14th Jul 2019 at 9:28 PM
Quote:
Originally Posted by zoe22
Thank you! And yes, I'm finding it a lot easier to understand the game code this time round

I did what you said, however in game when I click Take Out Book, my sim just resets and I get an NRaas ErrorTrap script error for my sim


Can you upload the error?

"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
Senior Moderator
staff: senior moderator
Original Poster
#5 Old 14th Jul 2019 at 10:52 PM
Not sure how to upload the file so I copied all of it here:
Field Researcher
#6 Old 14th Jul 2019 at 11:38 PM
Quote:
Originally Posted by zoe22
Not sure how to upload the file so I copied all of it here


Ah, I see. Take out your local variable "parameters" in the Run() method, then change all references to that variable to instead reference the properties of the AskForBook interaction instance.

For example, this:

Code:
List<Book> booksOnMyLot = GetBooksOnLot(parameters.Target as LibraryRegister, justFirst: false, noDuplicates: true);


Should become this:

Code:
List<Book> booksOnMyLot = GetBooksOnLot(this.Target as LibraryRegister, justFirst: false, noDuplicates: true);


Hopefully, then it will at least run without throwing a script error

"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
Senior Moderator
staff: senior moderator
Original Poster
#7 Old 15th Jul 2019 at 12:00 AM
Ah thank you, I'm going to bed now but I'll have a go tomorrow. Could you explain why it has to be like that?
Field Researcher
#8 Old 15th Jul 2019 at 1:30 AM
Quote:
Originally Posted by zoe22
Ah thank you, I'm going to bed now but I'll have a go tomorrow. Could you explain why it has to be like that?

I assume you know how inheritance works in C#? Well, in this case, AskForBook "is-a" BuyWithRegister, which "is-a" SocialInteraction, which "is-a" Interaction, which "is-a" InteractionInstance. Any type that "is-a" InteractionInstance, including AskForBook, contains a number of properties defining the actor of the interaction, the target, and some additional tuning information, all of which are assigned when the instance is instantiated (usually, instantiation happens when the option is clicked in the pie menu, but AskForBook is instantiated when TakeOutBook is run). For example, if you were to say within AskForBook:

Code:
public void Method()
{
    Sim sim = this.Actor;
}


What you are saying is, "regardless of how many AskForBook instances are in existence, when this method runs, I want to access the assigned 'Actor' property of whichever instance of AskForBook called the method, and assign that value to this new variable of type Sim that I'm creating called 'sim.'"

Now, that's all well and good, but how are these InteractionInstance properties assigned a value in the first place? Well, the game creates an InteractionInstanceParameters object with all the necessary values that is passed to an interaction definition, which contains code to test if the interaction is valid, among other housekeeping items. If everything checks out, an "empty" instance of the interaction associated with that definition is created, and the game copies the values within the InteractionInstanceParameters object into the interaction instance proper.

All of this is a long-winded way of saying that the InteractionInstanceParameters object you created within the AskForBook interaction instance is unnecessary, and because you didn't put values into it like the game does, the game got confused trying to access a "Target" property that contained no value -- that's what the script meant when it said "a null value was found where an object instance was required." Instead, you can just refer to the instance's own properties using the "this" keyword.

"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
Senior Moderator
staff: senior moderator
Original Poster
#9 Old 15th Jul 2019 at 11:43 AM Last edited by zoe22 : 15th Jul 2019 at 12:07 PM.
Ooh okay, that mostly makes sense to me :P
So take 3:
Whether my sim is on the same lot as the register or not, I can click Take Out Book, and my sim has the AskToTakeBook interaction going (the interaction in the queue in top right corner switches to the bookstore clerk sim), but doesn't actually carry out the social interaction. The Choose Book menu shows up, and if I pick a book, the right book is put into my sim's inventory.
However if I click cancel, and if my sim is on a different lot to the register, the game crashes. The mini dump file says "The thread tried to read from or write to a virtual address for which it does not have the appropriate access." And the text doc:


If my sim is on the same lot and I click cancel at the object picker menu, I get a script error. I actually have two at different points, one says:


and the other is the same as last time I think but I'll copy it here just in case:



Another thing which might be the problem, was that when I changed "parameters.Target as LibraryRegister" to "this.Target as LibraryRegister", there was an error "Cannot convert type Sims3.Gameplay.Actors.Sim to Sims3.Gameplay.Objects.Register.ZoeoeMod.LibraryRegister" so I removed the "as LibraryRegister". I'm guessing it is something to do with the fact the social interaction has to have a target as a sim, not an object?

I think I will try changing the GetBooksOnLot to use the Sim as the target too, rather than the register, and I'm thinking it should work because the sim should be on the same lot as the register? But perhaps there's a more robust way, as I feel like there would be problems if the sim isn't on lot or there hasn't been a sim assigned to the role yet...
[edit: did not work, the same thing happens, except it also crashes when object picker menu is cancelled and sim is on the same lot]

Thanks again for helping
Field Researcher
#10 Old 15th Jul 2019 at 2:17 PM
The crashing is interesting...I’ve never seen anything like that before. Regarding the animation not playing, try renaming the Run() method and removing its override keyword. You may even be able to change the return type from “bool” to “void.”

"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
Senior Moderator
staff: senior moderator
Original Poster
#11 Old 15th Jul 2019 at 4:38 PM Last edited by zoe22 : 15th Jul 2019 at 5:06 PM.
I will try. Do you know what I should do about the different targets? If I use the target as Sim then I had to create a second GetBooksOnLot function with the target type as Sim rather than GameObject for the parameters, as in the test function in the Takeout Book interaction calls it with target as the Game object. As I said in my last post this still caused it to crash. So in the AskForBook class, should the target be the register or the sim? Is it even possible for it to be the register if it's derived from a social interaction?

Update! It works! (Almost...) Renaming the Run Method seemed to do the trick. The only problem, however, is that if I click cancel at the object picker menu, I get a script error my sim , who resets and the error file says "A null value was found where an object instance was required"

Would it be worth doing using try {} catch {} or better to use the code that deals with cancelling?
Field Researcher
#12 Old 15th Jul 2019 at 5:13 PM
Quote:
Originally Posted by zoe22
I will try. Do you know what I should do about the different targets? If I use the target as Sim then I had to create a second GetBooksOnLot function with the target type as Sim rather than GameObject for the parameters, as in the test function in the Takeout Book interaction calls it with target as the Game object. As I said in my last post this still caused it to crash. So in the AskForBook class, should the target be the register or the sim? Is it even possible for it to be the register if it's derived from a social interaction?

Yes, the target of TakeOutBook is still the register object, but the target of AskForBook is the Sim operating the register. At the beginning of your Test() method in AskForBookDefinition, add this:

Code:
if (!ShoppingRegister.TestIfSocialValid(actor, target))
{
    return false;
}


And in the beginning of the Test() method of your TakeOutBookDefinition, add this:

Code:
if (!target.TestInteractionAvailability())
{
    return false;
}


These are test methods in the game code that will check whether the sim managing the register is valid and whether the two are on the same lot; if everything checks out, then the definition will do your checks to see if there are any books on that lot.

"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
Senior Moderator
staff: senior moderator
Original Poster
#13 Old 15th Jul 2019 at 9:53 PM
Yes thanks, the only problem now is when I cancel the object picker dialogue it crashes as there's no book chosen. Not sure how to error handle this, checking it's not null doesn't work.
Field Researcher
DELETED POST
16th Jul 2019 at 12:31 AM
This message has been deleted by gamefreak130.
Field Researcher
#14 Old 16th Jul 2019 at 12:35 AM
Quote:
Originally Posted by zoe22
Yes thanks, the only problem now is when I cancel the object picker dialogue it crashes as there's no book chosen. Not sure how to error handle this, checking it's not null doesn't work.


Crashes? As in, crash-to-desktop? I don't know how that could be happening.

The ObjectPickerDialog returns null if the cancel button is clicked. Are you checking that by using something like this?

Code:
if (selectedObjects == null || selectedObjects.Count == 0 || !(selectedObjects[0].Item is Book book))
{
    return false;
}

"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
Senior Moderator
staff: senior moderator
Original Poster
#15 Old 16th Jul 2019 at 10:57 AM Last edited by zoe22 : 16th Jul 2019 at 12:17 PM.
Woops I meant a script error, not crashes, don't know why I said that... I do have a check for it being null and it is still giving a script error for my sim when I click cancel

Code:
if (selectedObjects.Count == 0 || !(selectedObjects[0].Item is Book book))

                {

                    Actor.ShowTNSIfSelectable("There are no books!", StyledNotification.NotificationStyle.kGameMessageNegative);

                }
                else
                {
                    if (!(selectedObjects == null))
                    {
                        Actor.ShowTNSIfSelectable("Working! - " + book.Title.ToString(), StyledNotification.NotificationStyle.kSimTalking);
                        GetBook(Actor, book);
                    }
                }
Field Researcher
#16 Old 16th Jul 2019 at 1:12 PM
Quote:
Originally Posted by zoe22
Woops I meant a script error, not crashes, don't know why I said that... I do have a check for it being null and it is still giving a script error for my sim when I click cancel

I think there's another constructor for the ObjectPickerDialog that can be used to disable the cancel button. Try this instead:

Code:
List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(true, ModalDialog.PauseMode.PauseSimulator, title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows, new Vector2(-1f, -1f) , false, null, true, true);


Be sure to test what happens if the okay button is clicked and nothing is selected, as well.

"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
Senior Moderator
staff: senior moderator
Original Poster
#17 Old 16th Jul 2019 at 1:43 PM
Yep that works for blanking out the cancel button, and the okay button is blanked out until a book is selected anyway. I know it's a small thing, but do you think it is possible to allow for cancelling? If not, it doesn't really matter.
For the next part, I'd really like to have a return book interaction. Is there a way to sort of tag the books taken out as taken from a register, so it is only possible to return the books taken from a register?
Thanks
Field Researcher
#18 Old 16th Jul 2019 at 4:15 PM
Quote:
Originally Posted by zoe22
Yep that works for blanking out the cancel button, and the okay button is blanked out until a book is selected anyway. I know it's a small thing, but do you think it is possible to allow for cancelling? If not, it doesn't really matter.
For the next part, I'd really like to have a return book interaction. Is there a way to sort of tag the books taken out as taken from a register, so it is only possible to return the books taken from a register?
Thanks

I think the best way to do what you’re thinking of doing is by creating a new List<Book> inside your LibraryRegister class, outside of your interaction classes. That way, your AskForBook interaction will be able to add checked-out books to that list. When you make your “Return Book” interaction, you can have a test in your definition such that only Sims with a book in their inventory that is contained in the List<Book> will be allowed to return that book. When the interaction is run, the book is removed from the list and from the Sim’s inventory. This list will be on a per-register basis, so a Sim cannot return a book from one library at another’s register.

There are three things you’ll need to watch out for in such a system. What happens when a book is sold from a Sim’s inventory? Will the list be persistable (i.e. will the data survive saving/reloading)? Finally, what happens when a Sim travels with the library books still in their inventory? Will they still be able to return them upon returning home?

"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
Senior Moderator
staff: senior moderator
Original Poster
#19 Old 16th Jul 2019 at 5:40 PM
Ahhhh there's always so much more to consider than anticipated. Regarding the books being sold, is it possible to force the books to be unsellable and perhaps even impossible to remove from the inventory, apart from when your sim is directly reading the book? Kind of like the pager thing that sims get in the law enforcement career and such?
Field Researcher
#20 Old 17th Jul 2019 at 2:57 AM
Quote:
Originally Posted by zoe22
Ahhhh there's always so much more to consider than anticipated. Regarding the books being sold, is it possible to force the books to be unsellable and perhaps even impossible to remove from the inventory, apart from when your sim is directly reading the book? Kind of like the pager thing that sims get in the law enforcement career and such?

I'm honestly not sure if such a thing is possible without creating a custom book class, which I do not recommend doing. I suppose you could add an event listener to the kSoldObject, then, if the object sold is a book in any LibraryRegister's list, a fine would be assessed to the Sim's household. However, this would be diving into the realm of pure scripting, with all the instantiators and OnWorldLoadFinished delegates that implies.

That's the only real issue that I can think of. The register lists should be persistable, and travel shouldn't affect anything, but you never can know until you test

"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
Senior Moderator
staff: senior moderator
Original Poster
#21 Old 17th Jul 2019 at 9:55 AM
Ah, I was actually thinking of making a custom book class... I'm not really sure what I should do then, I will have think about it.
Thanks for all your help though
It's great I got this far
Senior Moderator
staff: senior moderator
Original Poster
#22 Old 17th Jul 2019 at 2:09 PM
So I have been adding a return books immediate interaction, using the List of the books taken out, as well as a dictionary with the book as the key and the sim who took it out as the value, but when I destroy the book selected, it's still there...
Here is the code in the Run Function:


I also used a try catch which solved the cancelling problem
Field Researcher
#23 Old 18th Jul 2019 at 2:45 AM
Quote:
Originally Posted by zoe22
So I have been adding a return books immediate interaction, using the List of the books taken out, as well as a dictionary with the book as the key and the sim who took it out as the value, but when I destroy the book selected, it's still there...
Here is the code in the Run Function:


I also used a try catch which solved the cancelling problem

Is the book removed from the register's list of checked-out books? If not, then you're running into a script error that's been silenced by your try-catch block -- one reason why I usually don't use try-catches in my mods.

You could try running book.Dispose() after book.Destroy() just to make sure that it's been fully nuked from the game: see here for more info. Also, I'd remove the book from the list before removing it from the game, just to be safe.

Regarding some code refactoring you could do, you have a variable "booksCanReturn" that goes completely unused and can be removed. Additionally, so far as I can tell, there is no reason to have both a list and a dictionary managing checked-out books; you can use one or the other, but having both seems redundant and unnecessary.

I've found that storing Sim objects in persistable data structures is very unreliable. This is because of the way Sim objects inherently function -- they are essentially "blank slates" with scripting data slapped onto them. Whenever a Sim travels, or is hard-reset by mods like NRaas MasterController, the game literally destroys the Sim object, creates an entirely new Sim object, and copies the critical data over; as far as the code is concerned, that Sim is no longer the "same" Sim it originally was, so any tests you would do with that Sim would return false.

Instead, if you go the dictionary route, make it a Dictionary<Book, ulong>, and then use the Sim.SimDescription.SimDescriptionId property as the value. That way, the book will be tied to the Sim's permanent data, rather than the Sim object itself.

Another thing to consider: the way this works, there will be two copies of any given book: one that is checked-out by a Sim and another that remains in the library. I don't know if there's a way to facilitate moving books from the library into a Sim's inventory and back again -- it may be more trouble than it's worth. Still, something to think about

"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
Senior Moderator
staff: senior moderator
Original Poster
#24 Old 18th Jul 2019 at 12:06 PM
The book was removed from the list of books taken out, just not in the inventory. I'll try the dispose method.
Woops I didn't notice how I used the lists, and I remember thinking I could just use the dictionary but didn't think it through. I wanted to have the dictionary so only the sim who takes out the book can return it, not sure if i could do that with the list.
Thanks for the advice about the Sim thing, I'll change that.

Originally I was thinking of having the books be moved in that way, and I can't remember what I did at first but one way I implemented it ended having any book that was taken out was removed from the list of books you can take out, but it was still in the bookshelf. So I was happier with however I made it now. But that also makes me wonder about how the books are saved, like do they also have the problems that sims do, where a copy might be made to replace it, or not? I thought that could be the reason the books were removed from the list but not from the sim's inventory but I didn't test it out.
Senior Moderator
staff: senior moderator
Original Poster
#25 Old 22nd Jul 2019 at 12:41 PM Last edited by zoe22 : 26th Jul 2019 at 5:58 PM.
Hi, so made some of the changes, added Dispose() and removed the try catch in case it was hiding any errors from destroying the books. However, the same thing happens when returning the books - they are removed from the list but are still in the inventory.
Page 1 of 3
Back to top