- Site Map >
- Modding and Creation >
- Sims 3 Creation >
- Modding Discussion >
- [update: new progress] Making a functioning library
- Site Map >
- Modding and Creation >
- Sims 3 Creation >
- Modding Discussion >
- [update: new progress] Making a functioning library
Posts: 562
Thanks: 3567 in 11 Posts
Another update: I removed the code for taking the book of the list, and as expected if you remove the book, it is still there for you to remove again. So removing the book from the list is working fine, it is just not wanting to destroy the book itself...There are still no script errors. Do you, @gamefreak130, or anyone else have any ideas for getting this to work? If this isn't working, perhaps i should try overhauling it a bit and having the actual book in library be moved to the sim's inventory and back, as you said.
Another question I have is about changing the game speed, I used
Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates)
for pausing the game before the menu shows up to pick a book to take out/return which works fine, and
Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates)
for after something has been chosen, however this doesn't seem to work, the game just stays paused after it has been selected.
Any help is greatly appreciated.
Posts: 437
Thanks: 5285 in 22 Posts
It's me again ) Another update: I removed the code for taking the book of the list, and as expected if you remove the book, it is still there for you to remove again. So removing the book from the list is working fine, it is just not wanting to destroy the book itself...There are still no script errors. Do you, @gamefreak130, or anyone else have any ideas for getting this to work? If this isn't working, perhaps i should try overhauling it a bit and having the actual book in library be moved to the sim's inventory and back, as you said. |
I've never worked with cloning objects, so I'm not sure what the problem could be. Maybe try using Actor.Inventory.TryToRemove() method to remove the book from the Sim's inventory before destroying it. It's possible that the book still may not be properly destroyed, but hopefully ErrorTrap will eventually find it and clean it up.
Another question I have is about changing the game speed, I used Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates) for pausing the game before the menu shows up to pick a book to take out/return which works fine, and Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates) for after something has been chosen, however this doesn't seem to work, the game just stays paused after it has been selected. |
Again, I'm not sure what the problem could be there, but I don't see why pausing and resuming the game speed manually is necessary -- doesn't the ObjectPickerDialog already do that?
If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Posts: 562
Thanks: 3567 in 11 Posts
I'll also try the inventory thing too. Thanks
Posts: 562
Thanks: 3567 in 11 Posts
I figured out why the book was not being destroyed - it turns out I was just being a bit of an idiot and was adding the original book to the list rather than the cloned one
I added in some pure scripting, so when an object is sold, it checks if it is a book, and if the book is from a library, and if so will remove the book from the list of books taken out, and deduct the price of the book as well as 500 simoleons from the household funds.
I also managed to get an alarm working, so after 48 sim hrs if the book hasn't been returned, a fine of 500 is deducted from the household funds, and this repeats every 48 hrs until the book is returned. I tested it with 3 sim hours so hopefully it is okay with the 48, but i also plan on making this value tunable, as well as the amount deducted as a fine. That is on the list of things to do, with making string tables for all the text etc, not to mention testing travelling with library books
A smaller thing I did was overwrite the name of the book merchant to be called Librarian.
My current problem is how the Library Registers work themselves. I thought using a destructor would work for if a register was sold, and the plan was to destroy all the books in the register's list, basically automatically returning the books, and to show a notification.
The code for that:
List<string> bookNames = new List<string>(); foreach (KeyValuePair<Book, ulong> entry in bookOwner) { bookNames.Add(entry.Key.Title); Book book = entry.Key; bookOwner.Remove(book); book.Destroy(); } Sim.ActiveActor.ShowTNSIfSelectable("Due to the closure of a library, the following books have been automatically returned: " + bookNames, StyledNotification.NotificationStyle.kSystemMessage);
This didn't work, so I added it to the pure scripting part, by checking if the object sold was a library register object and this also didn't work! (even the notification did not show up, so i suppose the whole section of code is not being called.)
However I noticed that when the register is sold (or deleted with testing cheats + shift click and delete), it doesn't actually affect the list of books taken out. When another register is added, you are still able to return the books previously taken out, and in fact, when I added a second register to the lot (and a third to a different lot) , the same list of books taken out is shown that you can return. This surprised me, as I thought the list (it is actually a dictionary with the book as the key, the sim who took it out as the value) was within the object itself, and the object being each individual register. But it seems like what books have been taken out is global to all library registers in the world. This on its own isn't really a problem for me (though I would like to understand why this is happening in this way!) but I need a way to automatically return the books if the registers are sold/deleted and there is no way for the books to be returned.
Another problem I am having is charging the sim a fine when they don't have enough money. I tried setting it so that if the sim has less money than what is needed to be fined, the money is set to 0 simoleons:
//when a library book is sold if (Sim.ActiveActor.FamilyFunds < (500 + bookSold.BookValue)) Sim.ActiveActor.ModifyFunds(0 - Sim.ActiveActor.FamilyFunds); Sim.ActiveActor.ModifyFunds(0 - (500+bookSold.BookValue));
I honestly don't know what is happening, but sometimes the funds end up as the price of the book, sometimes some amount a bit less than the book is added, and sometimes a small amount is taken away eg 15 simoleons. I'm guessing it is to do with when the Sim.Active.FamilyFunds value is taken - ie. before or after the book is sold - but really it doesn't seem to behave the same way each time. Maybe this isn't the right way to implement it, and i should use the household bills, though I haven't looked into using those properly yet.
Thanks in advance for any help
Posts: 437
Thanks: 5285 in 22 Posts
When another register is added, you are still able to return the books previously taken out, and in fact, when I added a second register to the lot (and a third to a different lot) , the same list of books taken out is shown that you can return. This surprised me, as I thought the list (it is actually a dictionary with the book as the key, the sim who took it out as the value) was within the object itself, and the object being each individual register. But it seems like what books have been taken out is global to all library registers in the world. This on its own isn't really a problem for me (though I would like to understand why this is happening in this way!) but I need a way to automatically return the books if the registers are sold/deleted and there is no way for the books to be returned. |
Is your book dictionary static or located somewhere other than the LibraryRegister class (i.e. within an interaction class)? Those are the only two things I could think of that would explain a global book list.
Another problem I am having is charging the sim a fine when they don't have enough money. I tried setting it so that if the sim has less money than what is needed to be fined, the money is set to 0 simoleons:
Code:
//when a library book is sold if (Sim.ActiveActor.FamilyFunds < (500 + bookSold.BookValue)) Sim.ActiveActor.ModifyFunds(0 - Sim.ActiveActor.FamilyFunds); Sim.ActiveActor.ModifyFunds(0 - (500+bookSold.BookValue)); I honestly don't know what is happening, but sometimes the funds end up as the price of the book, sometimes some amount a bit less than the book is added, and sometimes a small amount is taken away eg 15 simoleons. I'm guessing it is to do with when the Sim.Active.FamilyFunds value is taken - ie. before or after the book is sold - but really it doesn't seem to behave the same way each time. Maybe this isn't the right way to implement it, and i should use the household bills, though I haven't looked into using those properly yet. |
Honestly, I have no idea what the problem could be here. My initial thought is that you have no "else" between your insufficient funds check and the full price of the fine, so if the sim has insufficient funds, they will essentially be fined twice; however, that shouldn't be causing problems like the ones you describe. I would try adding to household bills to see if a similar issue occurs. You can use the following line to add to the household bills:
Sim.ActiveActor.Household.UnpaidBills += num;
Where "num" is the amount you want to add as a positive integer.
If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Posts: 3,856
Thanks: 8483 in 67 Posts
if (Sim.ActiveActor.FamilyFunds < (500 + bookSold.BookValue)) { Sim.ActiveActor.ModifyFunds(0 - Sim.ActiveActor.FamilyFunds); } else { Sim.ActiveActor.ModifyFunds(0 - (500+bookSold.BookValue)); }
So, the if statement actually can be different according to your if statement. If we narrow it down, you're basically saying (with examples of course):
- If the Family funds is less than 500 + 72 (this can be a random book price, making this 572) then do this.
- If the family funds is less than 500 + 150 (this being also a random book price example, making this 650) then do this.
Sorry for barging in here Just figured this could be helpful
Actually, relooking at it, I always like to make sure that the statement can either be "Less" or "Equal to" by doing:
if (Sim.ActiveActor.FamilyFunds <= (500 + bookSold.BookValue))
that way you always know that if the price is exactly the same as the family funds it will still charge them
Posts: 562
Thanks: 3567 in 11 Posts
And gamefreak, yes the dictionary is static so i guess that explains why it is global. It is within the LibraryRegister class.
However, I don't know how to use the non-static dictionary from the AskForBook and ReturnBooks classes - it says an object reference is required. It works in the CheckForBook() method, which is called when an book is sold, and this is just inside the LibraryRegister class. So it's only a problem for when I'm trying to access the dictionary in the Interaction classes inside the LibraryRegister class, I think at least
Here is my entire code, though it might be a bit all over the place
namespace Sims3.Gameplay.Objects.Register.Zoeoemod { public class Instantiator { [Tunable] protected static bool kInstantiator = false; static Instantiator() { World.OnWorldLoadFinishedEventHandler += new EventHandler(OnWorldLoadFinished); } private static void OnWorldLoadFinished(object sender, EventArgs e) { EventTracker.AddListener(EventTypeId.kSoldObject, new ProcessEventDelegate(OnObjectSold)); } private static ListenerAction OnObjectSold(Event e) { try { bool found = false; IGameObject objectSold = e.TargetObject as IGameObject; if (objectSold is Book) { Book bookSold = objectSold as Book; foreach (LibraryRegister register in Queries.GetObjects<LibraryRegister>()) { found = register.CheckForBook(objectSold as Book); if (found == true) { break; } } if (found == true) { if (Sim.ActiveActor.FamilyFunds < (500 + bookSold.BookValue)) Sim.ActiveActor.ModifyFunds(0 - Sim.ActiveActor.FamilyFunds); else Sim.ActiveActor.ModifyFunds(0 - (500+bookSold.BookValue)); Sim.ActiveActor.ShowTNSIfSelectable("You have been fined 500 Simoleons for selling " + bookSold.Title.ToString()+". Return your books next time!", StyledNotification.NotificationStyle.kGameMessageNegative); } } else if (objectSold is LibraryRegister) { LibraryRegister register = objectSold as LibraryRegister; register.OnSoldLibraryRegister(); } } catch(Exception) { } return ListenerAction.Keep; } } class LibraryRegister : BookStoreRegister { public void OnSoldLibraryRegister() { List<string> bookNames = new List<string>(); foreach (KeyValuePair<Book, ulong> entry in bookOwner) { bookNames.Add(entry.Key.Title); Book book = entry.Key; bookOwner.Remove(book); book.Destroy(); } Sim.ActiveActor.ShowTNSIfSelectable("Due to the closure of a library, the following books have been automatically returned: " + bookNames, StyledNotification.NotificationStyle.kSystemMessage); } public override string RegisterRoleName(bool isFemale) { return "Librarian"; } private IDictionary<Book, ulong> bookOwner = new Dictionary<Book, ulong>(); public bool CheckForBook(Book bookToFind) { foreach (KeyValuePair<Book, ulong> entry in bookOwner) { if (bookToFind == entry.Key) { bookOwner.Remove(bookToFind); return true; } } return false; } protected sealed class TakeOutBook : Interaction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); protected override bool Run() { Sim simInRole = Target.CurrentRole.SimInRole; InteractionInstance instance = AskForBook.AskForBookSingleton.CreateInstance(simInRole, Actor, Actor.InheritedPriority(), base.Autonomous, base.CancellableByPlayer); Actor.InteractionQueue.PushAsContinuation(instance, mustRun: true); return true; } [DoesntRequireTuning] private sealed class Definition : InteractionDefinition<Sim, LibraryRegister, TakeOutBook> { protected override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair interaction) { return "Take Out Book"; } protected override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { List<Book> booksOnMyLot = GetBooksOnLot(target, justFirst: true, noDuplicates: false); if (booksOnMyLot.Count <= 0 || isAutonomous) { return false; } return true; } } } public class AskForBook : BuyWithRegister { public class AskForBookDefinition : DefinitionBase<AskForBook> { } public static readonly AskForBookDefinition AskForBookSingleton = new AskForBookDefinition(); public override void PostAnimation() { ChooseBook(); Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); } protected void ChooseBook() { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); GreyedOutTooltipCallback greyedOutTooltipCallback = null; List<Book> booksOnMyLot = GetBooksOnLot(Target, justFirst: false, noDuplicates: true); foreach (Book item3 in booksOnMyLot) { if (!(item3 is BookAlchemyRecipe) && !(item3 is BookFish) && !(item3 is BookRecipe) && !(item3 is SheetMusic) && item3.TestReadBook(this.Actor as Sim, this.Autonomous, ref greyedOutTooltipCallback)) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(item3.Data.GeometryState), ResourceUtils.HashString32(item3.Data.MaterialState)); MedicalJournalData medicalJournalData = item3.Data as MedicalJournalData; if (medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((item3 as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, item3.Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(item3, list2); list.Add(item); } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Choose Book:"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows); if (selectedObjects != null && selectedObjects.Count != 0 && (selectedObjects[0].Item is Book book)) { try { Actor.ShowTNSIfSelectable("You've taken out this book: " + book.Title.ToString(), StyledNotification.NotificationStyle.kSimTalking); Book bookToAdd = GetBook(Actor, book); bookOwner.Add(bookToAdd, Actor.SimDescription.SimDescriptionId); handle = bookToAdd.AddAlarmRepeating(48f, TimeUnit.Hours, new AlarmTimerCallback(FineSim), "TimeUntilFine", AlarmType.AlwaysPersisted); } catch { } } } } static AlarmHandle handle; private static void FineSim() { if(Sim.ActiveActor.FamilyFunds<500) Sim.ActiveActor.ModifyFunds(0 - Sim.ActiveActor.FamilyFunds); else Sim.ActiveActor.ModifyFunds(0 - 500); Sim.ActiveActor.ShowTNSIfSelectable("You have been fined 500 Simoleons for not returning a book in time. Return it in the next 48 hours or you will be fined again.", StyledNotification.NotificationStyle.kGameMessageNegative); } protected sealed class ReturnBooks : ImmediateInteraction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); protected override bool Run() { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); foreach (KeyValuePair <Book , ulong > entry in bookOwner) { if (entry.Value == Actor.SimDescription.SimDescriptionId) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(entry.Key.Data.GeometryState), ResourceUtils.HashString32(entry.Key.Data.MaterialState)); MedicalJournalData medicalJournalData = entry.Key.Data as MedicalJournalData; if (medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((entry.Key as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, entry.Key.Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(entry.Key, list2); list.Add(item); } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Return Book"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows) ; Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); if (selectedObjects.Count != 0 && (selectedObjects[0].Item is Book book)&&selectedObjects!=null) { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); bookOwner.Remove(book); book.RemoveAlarm(handle); book.Destroy(); book.Dispose(); return true; } else return false; } [DoesntRequireTuning] private sealed class Definition : ImmediateInteractionDefinition<Sim, LibraryRegister, ReturnBooks> { protected override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair interaction) { return "Return Books"; } protected override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { return !isAutonomous; } } } private static Book GetBook(Sim actor, Book book) { Book newBook = null; newBook = book.Clone() as Book; if (!actor.Inventory.TryToAdd(newBook)) { newBook.Destroy(); return null; } return newBook; } public static List<Book> GetBooksOnLot( GameObject target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if (target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if (b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if (justFirst) { return list; } } } return list; } public static List<Book> GetBooksOnLot(Sim target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if (target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if (b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if (justFirst) { return list; } } } return list; } private void AddReturnInteraction() { } public override void OnStartup() { base.OnStartup(); AddInteraction(TakeOutBook.Singleton); AddInteraction(ReturnBooks.Singleton); RemoveInteractionByType(Buy.Singleton); RemoveInteractionByType(Browse.Singleton); } } }
Posts: 433
Thanks: 782 in 6 Posts
if (condition) { Action } else { OtherAction }
using System; using System.Collections.Generic; using Sims3.Gameplay.Abstracts; using Sims3.Gameplay.Actors; using Sims3.Gameplay.Autonomy; using Sims3.Gameplay.Core; using Sims3.Gameplay.EventSystem; using Sims3.Gameplay.Interactions; using Sims3.Gameplay.Interfaces; using Sims3.Gameplay.Utilities; using Sims3.SimIFace; using Sims3.UI; namespace Sims3.Gameplay.Objects.Register.Zoeoemod { public class Instantiator { [Tunable] protected static bool kInstantiator = false; static Instantiator() { World.OnWorldLoadFinishedEventHandler += new EventHandler(OnWorldLoadFinished); } private static void OnWorldLoadFinished(object sender, EventArgs e) { EventTracker.AddListener(EventTypeId.kSoldObject, new ProcessEventDelegate(OnObjectSold)); } private static ListenerAction OnObjectSold(Event e) { try { bool found = false; IGameObject objectSold = e.TargetObject as IGameObject; if(objectSold is Book) { Book bookSold = objectSold as Book; foreach (LibraryRegister register in Queries.GetObjects<LibraryRegister>()) { found = register.CheckForBook(objectSold as Book); if(found) { break; } } if(found) { if(Sim.ActiveActor.FamilyFunds < (500 + bookSold.BookValue)) // USE the { and } !!! { Sim.ActiveActor.ModifyFunds(-Sim.ActiveActor.FamilyFunds);// 0-x equals -x so instead of 0-500 use -500 } else { Sim.ActiveActor.ModifyFunds(-(500 + bookSold.BookValue)); // 0-x equals -x so instead of 0-500 use -500 } Sim.ActiveActor.ShowTNSIfSelectable("You have been fined 500 Simoleons for selling " + bookSold.Title.ToString() + ". Return your books next time!", StyledNotification.NotificationStyle.kGameMessageNegative); } } else { LibraryRegister libraryRegister = objectSold as LibraryRegister; if(libraryRegister != null) // use cast as null check { LibraryRegister register = libraryRegister; register.OnSoldLibraryRegister(); } } } catch(Exception) { } return ListenerAction.Keep; } } public class LibraryRegister : BookStoreRegister { public Dictionary<Book, ulong> bookOwner = new Dictionary<Book, ulong>(); //Use Dictionary instead of idictionary for type safety (ok this is a bit nitpicky but still) public void OnSoldLibraryRegister() { List<string> bookNames = new List<string>(); foreach (KeyValuePair<Book, ulong> entry in bookOwner) { bookNames.Add(entry.Key.Title); Book book = entry.Key; bookOwner.Remove(book); book.Destroy(); } Sim.ActiveActor.ShowTNSIfSelectable("Due to the closure of a library, the following books have been automatically returned: " + bookNames, StyledNotification.NotificationStyle.kSystemMessage); } public override string RegisterRoleName(bool isFemale) { return "Librarian"; } public bool CheckForBook(Book bookToFind) { foreach (KeyValuePair<Book, ulong> entry in bookOwner) { if(bookToFind == entry.Key) { bookOwner.Remove(bookToFind); return true; } } return false; } protected sealed class TakeOutBook : Interaction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); public override bool Run() { Sim simInRole = Target.CurrentRole.SimInRole; InteractionInstance instance = AskForBook.AskForBookSingleton.CreateInstance(simInRole, Actor, Actor.InheritedPriority(), base.Autonomous, base.CancellableByPlayer); Actor.InteractionQueue.PushAsContinuation(instance, mustRun: true); return true; } [DoesntRequireTuning] private sealed class Definition : InteractionDefinition<Sim, LibraryRegister, TakeOutBook> { public override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair interaction) { return "Take Out Book"; } public override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { List<Book> booksOnMyLot = GetBooksOnLot(target, justFirst: true, noDuplicates: false); if(booksOnMyLot.Count <= 0 || isAutonomous) { return false; } return true; } } } public class AskForBook : BuyWithRegister { public class AskForBookDefinition : DefinitionBase<AskForBook> { } public static readonly AskForBookDefinition AskForBookSingleton = new AskForBookDefinition(); public override void PostAnimation() { ChooseBook(); Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); } public void ChooseBook() { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); GreyedOutTooltipCallback greyedOutTooltipCallback = null; List<Book> booksOnMyLot = GetBooksOnLot(Target, justFirst: false, noDuplicates: true); foreach (Book item3 in booksOnMyLot) { if(!(item3 is BookAlchemyRecipe) && !(item3 is BookFish) && !(item3 is BookRecipe) && !(item3 is SheetMusic) && item3.TestReadBook(this.Actor as Sim, this.Autonomous, ref greyedOutTooltipCallback)) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(item3.Data.GeometryState), ResourceUtils.HashString32(item3.Data.MaterialState)); MedicalJournalData medicalJournalData = item3.Data as MedicalJournalData; if(medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((item3 as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, item3.Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(item3, list2); list.Add(item); } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Choose Book:"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows); if(selectedObjects != null && selectedObjects.Count != 0 && (selectedObjects[0].Item is Book)) { Book book = (Book)selectedObjects[0].Item; try { Actor.ShowTNSIfSelectable("You've taken out this book: " + book.Title.ToString(), StyledNotification.NotificationStyle.kSimTalking); Book bookToAdd = GetBook(Actor, book); LibraryRegister RegisterPointer = ((LibraryRegister)this.mRegister); //Get The register from the baseclass if( RegisterPointer== null) { return; } RegisterPointer.bookOwner.Add(bookToAdd, Actor.SimDescription.SimDescriptionId); handle = bookToAdd.AddAlarmRepeating(48f, TimeUnit.Hours, new AlarmTimerCallback(FineSim), "TimeUntilFine", AlarmType.AlwaysPersisted); } catch { } } } } static AlarmHandle handle; private static void FineSim() { if(Sim.ActiveActor.FamilyFunds<500) { Sim.ActiveActor.ModifyFunds(-Sim.ActiveActor.FamilyFunds); // 0-x equals -x so instead of 0-value use -value } else { Sim.ActiveActor.ModifyFunds(-500); // 0-x equals -x so instead of 0-500 use -500 } Sim.ActiveActor.ShowTNSIfSelectable("You have been fined 500 Simoleons for not returning a book in time. Return it in the next 48 hours or you will be fined again.", StyledNotification.NotificationStyle.kGameMessageNegative); } protected sealed class ReturnBooks : ImmediateInteraction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); public override bool Run() { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); LibraryRegister RegisterPointer = (LibraryRegister)this.Target; //fetch the Pointer to the Register if(RegisterPointer == null) { return true; } foreach (KeyValuePair <Book , ulong > entry in RegisterPointer.bookOwner) // use the Register reference { if(entry.Value == Actor.SimDescription.SimDescriptionId) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(entry.Key.Data.GeometryState), ResourceUtils.HashString32(entry.Key.Data.MaterialState)); MedicalJournalData medicalJournalData = entry.Key.Data as MedicalJournalData; if(medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((entry.Key as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, entry.Key.Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(entry.Key, list2); list.Add(item); } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Return Book"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows) ; Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); if(selectedObjects.Count != 0 && (selectedObjects[0].Item is Book)&&selectedObjects!=null) { Book book = (Book)selectedObjects[0].Item; Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); RegisterPointer.bookOwner.Remove(book);//use the Register reference book.RemoveAlarm(handle); book.Destroy(); book.Dispose(); return true; } else { return false; } } [DoesntRequireTuning] private sealed class Definition : ImmediateInteractionDefinition<Sim, LibraryRegister, ReturnBooks> { public override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair interaction) { return "Return Books"; } public override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { return !isAutonomous; } } } private static Book GetBook(Sim actor, Book book) { Book newBook = null; newBook = book.Clone() as Book; if(!actor.Inventory.TryToAdd(newBook)) { newBook.Destroy(); return null; } return newBook; } public static List<Book> GetBooksOnLot(GameObject target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if(target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if(b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if(justFirst) { return list; } } } return list; } public static List<Book> GetBooksOnLot(Sim target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if(target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if(b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if(justFirst) { return list; } } } return list; } private void AddReturnInteraction() { } public override void OnStartup() { base.OnStartup(); AddInteraction(TakeOutBook.Singleton); AddInteraction(ReturnBooks.Singleton); RemoveInteractionByType(Buy.Singleton); RemoveInteractionByType(Browse.Singleton); } } }
Posts: 562
Thanks: 3567 in 11 Posts
Also thanks for the tip about using {}, I didn't realise it can cause errors eek
Posts: 562
Thanks: 3567 in 11 Posts
There are still problems with fining the sim when the have less money than can be fined, though I think I can see what is happening - it looks like the funds are being made to be 0 simoleons, before the money from selling the book is added. So say my sim has $12, sells a library book worth $65, the +$53 pops up and they end up with the $65 in total. Then if they sell another library book worth $135, the +$70 pops up and the end up with the $135. Then if they sell a $130 library book, the -$5 pops up and they end up with $130. I feel like that's an overly complicated explanation but I am pretty sure this is what happening, as it explains the seemingly random amounts added and taken away before i fixed my If and Else statements.
Would it be a better idea to use the household bills instead?
On the problem of getting rid the library register, when i shift click delete it, the book is still in the inventory, but has no consequences from being sold. When I sell the register, only one sim's books disappeared, and another still had theirs again with no fine for selling, and no notification shows up.
The code that is called when a register is sold:
public void OnSoldLibraryRegister() { List<string> bookNames = new List<string>(); foreach (KeyValuePair<Book, ulong> entry in bookOwner) { bookNames.Add(entry.Key.Title); Book book = entry.Key; bookOwner.Remove(book); book.Destroy(); } Sim.ActiveActor.ShowTNSIfSelectable("Due to the closure of a library, the following books have been automatically returned: " + bookNames, StyledNotification.NotificationStyle.kSystemMessage); }
What should I do to reliably return the books when a register is destroyed or sold?
Posts: 437
Thanks: 5285 in 22 Posts
Hi again, There are still problems with fining the sim when the have less money than can be fined, though I think I can see what is happening - it looks like the funds are being made to be 0 simoleons, before the money from selling the book is added. So say my sim has $12, sells a library book worth $65, the +$53 pops up and they end up with the $65 in total. Then if they sell another library book worth $135, the +$70 pops up and the end up with the $135. Then if they sell a $130 library book, the -$5 pops up and they end up with $130. I feel like that's an overly complicated explanation but I am pretty sure this is what happening, as it explains the seemingly random amounts added and taken away before i fixed my If and Else statements. Would it be a better idea to use the household bills instead? |
It sounds like you're on the right track; the way you describe it, it sounds like the fine is taken before the book is actually sold and the sim receives the money. Thus, the sim's funds are reduced to 0 from the fine, but they are then credited the value of the book; that might just be the order in which the events are handled. I would try using the household bills, because that way you can add the full fine to the bills, rather than having to reduce the penalty -- gameplay-wise, that makes more sense to me. If you insist on an immediate, variable fine, then you can try using an alarm or a persistable stopwatch with a short delay; you can take a look at my Job Overhaul mod for an example of global alarms.
On the problem of getting rid the library register, when i shift click delete it, the book is still in the inventory, but has no consequences from being sold. When I sell the register, only one sim's books disappeared, and another still had theirs again with no fine for selling, and no notification shows up. What should I do to reliably return the books when a register is destroyed or sold? |
I'm still kind of stumped on this one. The fact that the notification does not appear suggests that an unhandled exception occurred somewhere in your foreach loop; perhaps it would work if you used a null check on entry.Key before you add the title to bookNames and destroy the book.
If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Posts: 433
Thanks: 782 in 6 Posts
~LibraryRegister() { Sims3.UI.StyledNotification.Show(new Sims3.UI.StyledNotification.Format("Object Deleted", Sims3.UI.StyledNotification.NotificationStyle.kGameMessagePositive)); //just for debbuging purpose OnSoldLibraryRegister(); }
Posts: 562
Thanks: 3567 in 11 Posts
I did actually originally have a destructor but I must have deleted it! So I'll add that back in.
A memory leak? Sounds bad...
It's true I could remove the books myself, but I thought if I'm going to upload the mod for download, I'd like to consider as many situations as possible so things don't go wrong for people.
But thank you for your response, I will do some more testing
And thanks as well, gamefreak. I think you're right about using the household bills - it makes more sense, it's just harder to test hehe.
Posts: 437
Thanks: 5285 in 22 Posts
Hey zoe22, since you shift click deleting the register your OnSoldLibraryRegister will not be called (since it gets destroyed and not sold) |
Ah, thanks Battery, I hadn't even realized that! An alternative to a destructor is to change your event handler type from "kSoldObject" to "kEventObjectDisposed." This way, your code should work with minimal changes; even the book fines should work then, since the actual disposal of the book is guaranteed to take place after the household funds have been credited for selling the book!
EDIT: I realize as I type this that traveling with books in the inventory may be a major issue, since your fine alarms will not carry over between worlds, and selling loaned books will not fine Sims because technically the register it came from no longer exists in the current world. You may be able to use a PersistableStatic Tuple to store the book, the sim, and the register it came from across multiple worlds, but let's make sure it all works in one world first before we start undertaking large changes like that
If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Posts: 562
Thanks: 3567 in 11 Posts
And definitely I'll cross the bridge about travelling when I get to it!
Posts: 562
Thanks: 3567 in 11 Posts
Here is the package file for my mod as Battery suggested it would be helpful to see how it works
It is a clone of the consignment register that came with Ambitions.
zoeoe_registerlibraryMod.7z (26.3 KB, 4 downloads) - View custom content | ||||||||||
Date Time Attr Size Compressed Name ------------------- ----- ------------ ------------ ------------------------ 2019-08-30 10:58:07 ....A 41185 26734 zoeoe_registerlibraryMod.package ------------------- ----- ------------ ------------ ------------------------ 41185 26734 1 files, 0 folders |
Posts: 433
Thanks: 782 in 6 Posts
So, I tried kEventObjectDisposed, and it works great for the books being sold, the fine works perfectly! However it still doesn't work for when the register is sold or deleted. The books are still in the sims' inventories as regular books - can be sold with no fine. I could try a destructor again but I am pretty sure it didn't work last time. Here is the package file for my mod as Battery suggested it would be helpful to see how it works It is a clone of the consignment register that came with Ambitions. |
Ok i got the reasons for your problem ill be back when ive coded the solution in a bit
From the Region where kEventObjectDisposed is fired
if (!fromDtor) { if (this.InInventory && this.ItemComp == null && this.mOwnerLot != null && this.mOwnerLot.Household != null && this.mOwnerLot.Household.SharedFamilyInventory != null && this.mOwnerLot.Household.SharedFamilyInventory.Inventory != null) { this.mOwnerLot.Household.SharedFamilyInventory.Inventory.RemoveByForce(this, true); } if (this.mObjComponents != null) { for (int i = 0; i < this.mObjComponents.Count; i++) { this.mObjComponents[i].Dispose(); } this.mObjComponents = null; } GC.SuppressFinalize(this); } if (this.ObjectDisposed != null) { this.ObjectDisposed(this); } EventTracker.SendEvent(EventTypeId.kEventObjectDisposed, null, this);
As you can see the Event is fired AFTER the object is disposed so it is null at that point, ergo you cant do any actions on it.
override the dispose method and tie it to the OnObjectSold method
E: Do you want the Bookvalue+Fine to be substracted like when selling the book when the register gets sold and or deleted ?
E1: I have attached a soluion to your problem let me know if i should explain what was changed
E2: Books only got removed from one actor because you used Sim.ActiveActor for your fining
E3: Removed irritating outdated download
Posts: 562
Thanks: 3567 in 11 Posts
I just want to give a quick update:
Thank you for the code, Battery, I have used it to modify mine as I wanted it to work slightly differently.
The OnBookLostOrSold function, I just needed to be OnBookSold, as if the register is deleted and the book is removed, then there doesn't need to be a fine, only when the book is sold.
public void OnBookSold(ulong simdesc, Book bookSold) { try { SimDescription tempdesc = SimDescription.Find(simdesc); if (tempdesc == null) { StyledNotification.Show(new StyledNotification.Format("No owner", StyledNotification.NotificationStyle.kGameMessagePositive)); return; } if (tempdesc.FamilyFunds < (500 + bookSold.BookValue)) { tempdesc.ModifyFunds(-tempdesc.FamilyFunds); } else { tempdesc.ModifyFunds(-(500 + bookSold.BookValue)); } if (tempdesc.Household != Sim.ActiveActor.Household) { return; } Sim.ActiveActor.ShowTNSIfSelectable(tempdesc.FirstName + " has been fined 500 Simoleons for selling a library book: " + bookSold.Title.ToString() + ". Return your books next time!", StyledNotification.NotificationStyle.kGameMessageNegative); } catch { } }
I added the overrided Dispose method from your code too, to implement the code where books are destroyed and the player notified:
public override void Dispose() { string bookNames = ""; foreach (KeyValuePair<Book, ulong> entry in bookOwner) { Book book = entry.Key; bookNames += book.Title + " value: " + book.Value + "\n"; bookOwner.Remove(book); book.Destroy(); } Sim.ActiveActor.ShowTNSIfSelectable("Due to the closure of a library, the following books have been automatically returned: " + bookNames, StyledNotification.NotificationStyle.kSystemMessage); base.Dispose(); }
I did test it quickly, however, and it still does not seem to work properly. I think again only the books from one sim are being destroyed. This has nothing to do with the FineSim method, as that is called when the alarm for book due date is finished, not when a book (or a register) is sold. The code for getting rid of the books when a register is disposed has nothing to do with an individual sim, so I'm not sure about that.
The other thing I realised was using the kObjectDisposed rather than kObjectSold, was that if a book is disposed and not sold (perhaps when a register is disposed), the sim would be fined, when they should only be fined when they sell a book. Perhaps I will use a short delay like gamefreak suggested.
Anyway, I haven't messed around with it much yet so I will spend a bit more time to see if I can work anything out, but i just wanted to let you know that I didn't ignore Battery's code
To be honest, it's giving me a bit of a headache at this point, so I think I might come back to it in a few days or so with a clearer mind.
Thanks everyone for your help so far
I'll also leave my whole code again, just in case I've done something stupid when figuring out Battery's code
using System; using System.Collections.Generic; using System.Text; using Sims3.Gameplay.Interactions; using Sims3.Gameplay.Actors; using Sims3.Gameplay.Autonomy; using Sims3.SimIFace; using Sims3.UI; using Sims3.Gameplay.Objects.Miscellaneous; using Sims3.Gameplay.Abstracts; using Sims3.Gameplay.Objects.RabbitHoles; using Sims3.Gameplay.Objects.Register; using Sims3.Gameplay.Interfaces; using Sims3.Gameplay.Skills; using Sims3.Gameplay.ActorSystems; using Sims3.Gameplay.TuningValues; using Sims3.Gameplay.Core; using Sims3.Gameplay.Utilities; using Sims3.Gameplay.Objects.Alchemy; using Sims3.Gameplay.Roles; using Sims3.Gameplay.EventSystem; using Sims3.Gameplay.CAS; //NOTE //When a sim moves out with library books //When a sim goes abroad namespace Sims3.Gameplay.Objects.Register.Zoeoemod { public class Instantiator { [Tunable] protected static bool kInstantiator = false; static Instantiator() { World.OnWorldLoadFinishedEventHandler += new EventHandler(OnWorldLoadFinished); } private static void OnWorldLoadFinished(object sender, EventArgs e) { EventTracker.AddListener(EventTypeId.kEventObjectDisposed, new ProcessEventDelegate(OnObjectSold)); } private static ListenerAction OnObjectSold(Event e) { try { ulong found = 0; IGameObject objectSold = e.TargetObject as IGameObject; if (objectSold is Book) { Book bookSold = objectSold as Book; if (bookSold != null) { foreach (LibraryRegister register in Queries.GetObjects<LibraryRegister>()) { found = register.CheckForBook(objectSold as Book); if (found!=0) { register.OnBookSold(found, bookSold); break; } } } } } catch(Exception) { } return ListenerAction.Keep; } } class LibraryRegister : BookStoreRegister { public void OnBookSold(ulong simdesc, Book bookSold) { try { SimDescription tempdesc = SimDescription.Find(simdesc); if (tempdesc == null) { StyledNotification.Show(new StyledNotification.Format("No owner", StyledNotification.NotificationStyle.kGameMessagePositive)); return; } if (tempdesc.FamilyFunds < (500 + bookSold.BookValue)) { tempdesc.ModifyFunds(-tempdesc.FamilyFunds); } else { tempdesc.ModifyFunds(-(500 + bookSold.BookValue)); } if (tempdesc.Household != Sim.ActiveActor.Household) { return; } Sim.ActiveActor.ShowTNSIfSelectable(tempdesc.FirstName + " has been fined 500 Simoleons for selling a library book: " + bookSold.Title.ToString() + ". Return your books next time!", StyledNotification.NotificationStyle.kGameMessageNegative); } catch { } } public override void Dispose() { string bookNames = ""; foreach (KeyValuePair<Book, ulong> entry in bookOwner) { Book book = entry.Key; bookNames += book.Title + " value: " + book.Value + "\n"; bookOwner.Remove(book); book.Destroy(); } Sim.ActiveActor.ShowTNSIfSelectable("Due to the closure of a library, the following books have been automatically returned: " + bookNames, StyledNotification.NotificationStyle.kSystemMessage); base.Dispose(); } public override string RegisterRoleName(bool isFemale) { return "Librarian"; } private Dictionary<Book, ulong> bookOwner = new Dictionary<Book, ulong>(); public ulong CheckForBook(Book bookToFind) { foreach (KeyValuePair<Book, ulong> entry in bookOwner) { if (bookToFind == entry.Key) { bookOwner.Remove(bookToFind); return entry.Value; } } return 0; } protected sealed class TakeOutBook : Interaction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); protected override bool Run() { Sim simInRole = Target.CurrentRole.SimInRole; InteractionInstance instance = AskForBook.AskForBookSingleton.CreateInstance(simInRole, Actor, Actor.InheritedPriority(), base.Autonomous, base.CancellableByPlayer); Actor.InteractionQueue.PushAsContinuation(instance, mustRun: true); return true; } [DoesntRequireTuning] private sealed class Definition : InteractionDefinition<Sim, LibraryRegister, TakeOutBook> { protected override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair interaction) { return "Take Out Book"; } protected override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { List<Book> booksOnMyLot = GetBooksOnLot(target, justFirst: true, noDuplicates: false); if (booksOnMyLot.Count <= 0 || isAutonomous) { return false; } return true; } } } public class AskForBook : BuyWithRegister { public class AskForBookDefinition : DefinitionBase<AskForBook> { } public static readonly AskForBookDefinition AskForBookSingleton = new AskForBookDefinition(); public override void PostAnimation() { ChooseBook(); Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); } protected void ChooseBook() { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); GreyedOutTooltipCallback greyedOutTooltipCallback = null; List<Book> booksOnMyLot = GetBooksOnLot(Target, justFirst: false, noDuplicates: true); foreach (Book item3 in booksOnMyLot) { if (!(item3 is BookAlchemyRecipe) && !(item3 is BookFish) && !(item3 is BookRecipe) && !(item3 is SheetMusic) && item3.TestReadBook(this.Actor as Sim, this.Autonomous, ref greyedOutTooltipCallback)) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(item3.Data.GeometryState), ResourceUtils.HashString32(item3.Data.MaterialState)); MedicalJournalData medicalJournalData = item3.Data as MedicalJournalData; if (medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((item3 as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, item3.Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(item3, list2); list.Add(item); } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Choose Book:"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows); if (selectedObjects != null && selectedObjects.Count != 0 && (selectedObjects[0].Item is Book book)) { //Book book = (Book)selectedObjects[0].Item; try { Actor.ShowTNSIfSelectable("You've taken out this book: " + book.Title.ToString(), StyledNotification.NotificationStyle.kSimTalking); Book bookToAdd = GetBook(Actor, book); LibraryRegister RegisterPointer = ((LibraryRegister)this.mRegister); //Get The register from the baseclass if (RegisterPointer == null) { return; } RegisterPointer.bookOwner.Add(bookToAdd, Actor.SimDescription.SimDescriptionId); handle = bookToAdd.AddAlarmRepeating(48f, TimeUnit.Hours, new AlarmTimerCallback(FineSim), "TimeUntilFine", AlarmType.AlwaysPersisted); } catch { } } } } static AlarmHandle handle; private static void FineSim() { if (Sim.ActiveActor.FamilyFunds < 500) { Sim.ActiveActor.ModifyFunds(0 - Sim.ActiveActor.FamilyFunds); } else { Sim.ActiveActor.ModifyFunds(0 - 500); } Sim.ActiveActor.ShowTNSIfSelectable("You have been fined 500 Simoleons for not returning a book in time. Return it in the next 48 hours or you will be fined again.", StyledNotification.NotificationStyle.kGameMessageNegative); } protected sealed class ReturnBooks : ImmediateInteraction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); protected override bool Run() { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); LibraryRegister RegisterPointer = Target; //fetch the Pointer to the Register if (RegisterPointer == null) { return true; } foreach (KeyValuePair <Book , ulong > entry in RegisterPointer.bookOwner) { if (entry.Value == Actor.SimDescription.SimDescriptionId) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(entry.Key.Data.GeometryState), ResourceUtils.HashString32(entry.Key.Data.MaterialState)); MedicalJournalData medicalJournalData = entry.Key.Data as MedicalJournalData; if (medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((entry.Key as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, entry.Key.Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(entry.Key, list2); list.Add(item); } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Return Book"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows) ; Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); if (selectedObjects.Count != 0 && (selectedObjects[0].Item is Book book)&&selectedObjects!=null) { //Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); RegisterPointer.bookOwner.Remove(book); book.RemoveAlarm(handle); book.Destroy(); book.Dispose(); return true; } else return false; } [DoesntRequireTuning] private sealed class Definition : ImmediateInteractionDefinition<Sim, LibraryRegister, ReturnBooks> { protected override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair interaction) { return "Return Books"; } protected override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { return !isAutonomous; } } } private static Book GetBook(Sim actor, Book book) { Book newBook = null; newBook = book.Clone() as Book; if (!actor.Inventory.TryToAdd(newBook)) { newBook.Destroy(); return null; } return newBook; } public static List<Book> GetBooksOnLot( GameObject target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if (target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if (b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if (justFirst) { return list; } } } return list; } public static List<Book> GetBooksOnLot(Sim target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if (target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if (b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if (justFirst) { return list; } } } return list; } private void AddReturnInteraction() { } public override void OnStartup() { base.OnStartup(); AddInteraction(TakeOutBook.Singleton); AddInteraction(ReturnBooks.Singleton); RemoveInteractionByType(Buy.Singleton); RemoveInteractionByType(Browse.Singleton); } } }
Posts: 433
Thanks: 782 in 6 Posts
The "New" version is the one from discord it might be what you wanted. youll still be fined but you can rip that out of the code and you should be good to go -the mentioned fine system issues
using System; using System.Collections.Generic; using Sims3.Gameplay.Abstracts; using Sims3.Gameplay.Actors; using Sims3.Gameplay.Autonomy; using Sims3.Gameplay.CAS; using Sims3.Gameplay.Core; using Sims3.Gameplay.EventSystem; using Sims3.Gameplay.Interactions; using Sims3.Gameplay.Interfaces; using Sims3.Gameplay.Utilities; using Sims3.SimIFace; using Sims3.UI; namespace Sims3.Gameplay.Objects.Register.Zoeoemod { public class Instantiator { [Tunable] protected static bool kInstantiator; static Instantiator() { World.OnWorldLoadFinishedEventHandler += OnWorldLoadFinished; } static void OnWorldLoadFinished(object sender, EventArgs e) { EventTracker.AddListener(EventTypeId.kSoldObject, new ProcessEventDelegate(OnObjectSold)); } static ListenerAction OnObjectSold(Event e) { try { ulong found = 0; IGameObject objectSold = e.TargetObject as IGameObject; Book book = objectSold as Book; if(book != null) { Book bookSold = book; foreach (LibraryRegister register in Queries.GetObjects<LibraryRegister>()) { found = register.CheckForBook(book); if(found!=0) { register.OnBookLostorSold(found,book, true); break; } } } }finally { } return ListenerAction.Keep; } } public class LibraryRegister : BookStoreRegister { public void OnBookLostorSold(ulong simdesc,Book bookSold, bool wasSold) { try { SimDescription tempdesc = SimDescription.Find(simdesc); if(tempdesc == null) { return; } if(tempdesc.FamilyFunds < (500 + bookSold.BookValue)) { tempdesc.ModifyFunds(-tempdesc.FamilyFunds); } else { tempdesc.ModifyFunds(-(500 + bookSold.BookValue)); } this.totalfine += (500 + bookSold.BookValue); /*if(tempdesc.Household != Sim.ActiveActor.Household) { return; }*/ if(wasSold) { Sim.ActiveActor.ShowTNSIfSelectable(tempdesc.FirstName + " has been fined 500 Simoleons for selling \n" + bookSold.Title.ToString() + ".\n Return your books next time!", StyledNotification.NotificationStyle.kGameMessageNegative); return; } Sim.ActiveActor.ShowTNSIfSelectable(tempdesc.FirstName +" has been fined 500 Simoleons for not returning your Books\n" + bookSold.Title.ToString() + ".\n Return your books next time!", StyledNotification.NotificationStyle.kGameMessageNegative); }catch { } } public int totalfine = 0; public override void Dispose() { string bookNames = ""; InventoryStack stack; Sim InventoryOwner; try { foreach (KeyValuePair<ulong, List<Book>> entry in bookOwner) { InventoryOwner = SimDescription.Find(entry.Key).CreatedSim; List<Book> templist = entry.Value; for (int i = 0; i < templist.Count; i++) { bookNames += templist[i].Title+" value: " + templist[i].Value + "\n"; OnBookLostorSold(entry.Key,templist[i],false); if(!InventoryOwner.Inventory.TryToRemoveStack(InventoryOwner.Inventory.FindItemStack(templist[i],out stack))) { InventoryOwner.Inventory.RemoveByForce(templist[i]); } templist[i].Destroy(); } } bookOwner.Clear(); Sim.ActiveActor.ShowTNSIfSelectable("Due to the closure of a library, the following books have been automatically returned: \n\n" + bookNames, StyledNotification.NotificationStyle.kSystemMessage); }catch { } base.Dispose(); } public Dictionary<ulong, List<Book>> bookOwner = new Dictionary<ulong, List<Book>>(); public override string RegisterRoleName(bool isFemale) { return "Librarian"; } public ulong CheckForBook(Book bookToFind) { foreach (KeyValuePair<ulong, List<Book>> entry in bookOwner) { if (entry.Value.Contains(bookToFind)) { return entry.Key; } } return 0; } protected sealed class TakeOutBook : Interaction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); public override bool Run() { Sim simInRole = Target.CurrentRole.SimInRole; InteractionInstance instance = AskForBook.AskForBookSingleton.CreateInstance(simInRole, Actor, Actor.InheritedPriority(), base.Autonomous, base.CancellableByPlayer); Actor.InteractionQueue.PushAsContinuation(instance, mustRun: true); return true; } [DoesntRequireTuning] sealed class Definition : InteractionDefinition<Sim, LibraryRegister, TakeOutBook> { public override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair iop) { return "Take Out Book"; } public override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { List<Book> booksOnMyLot = GetBooksOnLot(target, justFirst: true, noDuplicates: false); if(booksOnMyLot.Count <= 0 || isAutonomous) { return false; } return true; } } } public class AskForBook : BuyWithRegister { public class AskForBookDefinition : DefinitionBase<AskForBook> { } public static readonly AskForBookDefinition AskForBookSingleton = new AskForBookDefinition(); public override void PostAnimation() { ChooseBook(); //Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); } public void ChooseBook() { Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); GreyedOutTooltipCallback greyedOutTooltipCallback = null; List<Book> booksOnMyLot = GetBooksOnLot(Target, justFirst: false, noDuplicates: true); foreach (Book item3 in booksOnMyLot) { if(!(item3 is BookAlchemyRecipe) && !(item3 is BookFish) && !(item3 is BookRecipe) && !(item3 is SheetMusic) && item3.TestReadBook(this.Actor as Sim, this.Autonomous, ref greyedOutTooltipCallback)) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(item3.Data.GeometryState), ResourceUtils.HashString32(item3.Data.MaterialState)); MedicalJournalData medicalJournalData = item3.Data as MedicalJournalData; if(medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((item3 as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, item3.Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(item3, list2); list.Add(item); } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Choose Book:"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows); if(selectedObjects != null && selectedObjects.Count != 0 && (selectedObjects[0].Item is Book)) { Book book = (Book)selectedObjects[0].Item; try { Actor.ShowTNSIfSelectable("You've taken out this book: " + book.Title.ToString(), StyledNotification.NotificationStyle.kSimTalking); Book bookToAdd = GetBook(Actor, book); LibraryRegister RegisterPointer = ((LibraryRegister)this.mRegister); if( RegisterPointer== null) { return; } if(RegisterPointer.bookOwner.ContainsKey(Actor.SimDescription.SimDescriptionId)) { RegisterPointer.bookOwner[Actor.SimDescription.SimDescriptionId].Add(bookToAdd); } else { List<Book> blist = new List<Book>(); blist.Add(book); RegisterPointer.bookOwner.Add(Actor.SimDescription.SimDescriptionId,blist); } handle = bookToAdd.AddAlarmRepeating(48f, TimeUnit.Hours, new AlarmTimerCallback(FineSim), "TimeUntilFine", AlarmType.AlwaysPersisted); } catch{} } } } static AlarmHandle handle; static void FineSim() { if(Sim.ActiveActor.FamilyFunds<500) { Sim.ActiveActor.ModifyFunds(-Sim.ActiveActor.FamilyFunds); // 0-x equals -x so instead of 0-value use -value } else { Sim.ActiveActor.ModifyFunds(-500); // 0-x equals -x so instead of 0-500 use -500 } Sim.ActiveActor.ShowTNSIfSelectable("You have been fined 500 Simoleons for not returning a book in time. Return it in the next 48 hours or you will be fined again.", StyledNotification.NotificationStyle.kGameMessageNegative); } protected sealed class ReturnBooks : ImmediateInteraction<Sim, LibraryRegister> { public static readonly InteractionDefinition Singleton = new Definition(); public override bool Run() { //Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Pause, Gameflow.SetGameSpeedContext.GameStates); int numSelectableRows = 1; List<ObjectPicker.HeaderInfo> headers = new List<ObjectPicker.HeaderInfo>(); List<ObjectPicker.TabInfo> listObjs = new List<ObjectPicker.TabInfo>(); headers.Add(new ObjectPicker.HeaderInfo("Ui/Caption/ObjectPicker:Title", "Ui/Tooltip/ObjectPicker:Name", 250)); List<ObjectPicker.RowInfo> list = new List<ObjectPicker.RowInfo>(); LibraryRegister RegisterPointer = (LibraryRegister)this.Target; //fetch the Pointer to the Register if(RegisterPointer == null) { return true; } foreach (KeyValuePair <ulong , List<Book>> entry in RegisterPointer.bookOwner) // use the Register reference { if(entry.Key == Actor.SimDescription.SimDescriptionId) { for (int i = 0; i < entry.Value.Count; i++) { List<ObjectPicker.ColumnInfo> list2 = new List<ObjectPicker.ColumnInfo>(); ResourceKey objectDescKey = new ResourceKey(ResourceUtils.XorFoldHashString32("book_standard"), 23466547u, 1u); ThumbnailKey thumbnail = new ThumbnailKey(objectDescKey, ThumbnailSize.Medium, ResourceUtils.HashString32(entry.Value[i].Data.GeometryState), ResourceUtils.HashString32(entry.Value[i].Data.MaterialState)); MedicalJournalData medicalJournalData = entry.Value[i].Data as MedicalJournalData; if(medicalJournalData != null) { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, medicalJournalData.GetTitle((entry.Value[i] as MedicalJournal).mOwner, medicalJournalData.CurrentEdition))); } else { list2.Add(new ObjectPicker.ThumbAndTextColumn(thumbnail, entry.Value[i].Data.Title)); } ObjectPicker.RowInfo item = new ObjectPicker.RowInfo(entry.Key, list2); list.Add(item); } } } ObjectPicker.TabInfo item2 = new ObjectPicker.TabInfo("Coupon", Localization.LocalizeString("Ui/Caption/ObjectPicker:Books"), list); listObjs.Add(item2); string title = "Return Book"; List<ObjectPicker.RowInfo> selectedObjects = ObjectPickerDialog.Show(title, Localization.LocalizeString("Ui/Caption/Global:Accept"), Localization.LocalizeString("Ui/Caption/ObjectPicker:Cancel"), listObjs, headers, numSelectableRows) ; //Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); if(selectedObjects.Count != 0 && (selectedObjects[0].Item is Book)&&selectedObjects!=null) { Book book = (Book)selectedObjects[0].Item; //Gameflow.SetGameSpeed(SimIFace.Gameflow.GameSpeed.Normal, Gameflow.SetGameSpeedContext.GameStates); RegisterPointer.bookOwner[Actor.SimDescription.SimDescriptionId].Remove(book); book.RemoveAlarm(handle); book.Destroy(); book.Dispose(); return true; } else { return false; } } [DoesntRequireTuning] sealed class Definition : ImmediateInteractionDefinition<Sim, LibraryRegister, ReturnBooks> { public override string GetInteractionName(Sim actor, LibraryRegister target, InteractionObjectPair iop) { return "Return Books"; } public override bool Test(Sim actor, LibraryRegister target, bool isAutonomous, ref GreyedOutTooltipCallback greyedOutTooltipCallback) { return !isAutonomous; } } } static Book GetBook(Sim actor, Book book) { Book newBook = null; try { newBook = book.Clone() as Book; if(!actor.Inventory.TryToAdd(newBook)) { newBook.Destroy(); return null; } return newBook; }catch { } return newBook; } public static List<Book> GetBooksOnLot(GameObject target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if(target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if(b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if(justFirst) { return list; } } } return list; } public static List<Book> GetBooksOnLot(Sim target, bool justFirst, bool noDuplicates) { List<Book> list = new List<Book>(); if(target == null) { return list; } Lot lot = target.LotCurrent; Book[] objects = Queries.GetObjects<Book>(); Book b; for (int i = 0; i < objects.Length; i++) { b = objects[i]; if(b.GetOwnerLot() == lot && (justFirst || !noDuplicates || !list.Exists((Book toTest) => toTest.BookId == b.BookId))) { list.Add(b); if(justFirst) { return list; } } } return list; } private void AddReturnInteraction() { } public override void OnStartup() { base.OnStartup(); AddInteraction(TakeOutBook.Singleton); AddInteraction(ReturnBooks.Singleton); RemoveInteractionByType(Buy.Singleton); RemoveInteractionByType(Browse.Singleton); } } }
Posts: 562
Thanks: 3567 in 11 Posts
My next steps will be to make sure no problems occur when sims move out, the household is changed, and sims travel abroad.
Thinking about this, I realise what you meant (Battery) about changing the FineSim() method, as it would fine the household even if the book is not part of the household. Would there be a way to tie the alarm to the sim who owns the book, or to the book taken out, or would a better option be to just return any books that don't belong to the active household? I'm not sure how to do this though, would it be using the event listeners? I see there is a kMoveOutOfTown and kMovedHousehold and a few similar ones - not sure if these are appropriate?
Posts: 433
Thanks: 782 in 6 Posts
I've finally got round to trying out the new version of your code, Battery, and everything is working great so far! Bless you! The fining works when there are insufficient funds, and the books are properly destroyed when a register is destroyed or sold. My next steps will be to make sure no problems occur when sims move out, the household is changed, and sims travel abroad. Thinking about this, I realise what you meant (Battery) about changing the FineSim() method, as it would fine the household even if the book is not part of the household. Would there be a way to tie the alarm to the sim who owns the book, or to the book taken out, or would a better option be to just return any books that don't belong to the active household? I'm not sure how to do this though, would it be using the event listeners? I see there is a kMoveOutOfTown and kMovedHousehold and a few similar ones - not sure if these are appropriate? |
Hey Zoe22,
the probably easiest way would be to loop through the BookOwner Dictionary each time an alarm fires, if the dictionary contains an SimdescriptionID of any sim from the current household fine the household.
Of course this depends on how excactly the system should work eg should all households with borrowed books be fined or just the active one ?
And last but certainly not least the suggestion above can be optimized but how depends on your needs
Posts: 562
Thanks: 3567 in 11 Posts
It's been while but recently looking at this project has made my brain start to implode so
But I am back and hopefully will make some more progress.
At the moment I am wanting to sort out the fining business. At the moment I feel it is easiest for only active sims to be fined for overdue books, and so I have been trying to work out how to do this.
In the Library Register class I have:
private AlarmHandle handle;
And in the AskForBook class I have:
RegisterPointer.handle = bookToAdd.AddAlarmRepeating(1f, TimeUnit.Hours, new AlarmTimerCallback(RegisterPointer.FineSim), "TimeUntilFine", AlarmType.AlwaysPersisted); //1 hour for testing purposes
RegisterPointer is the LibraryRegister variable made by Battery to get the register from the base class, in order to have the book chosen to be taken out added to the register's dictionary which stores the book and sim description.
I then have the FineSim method just in the LibraryRegister class which currently just fines the active household and works okay, but I need it to first check that the sim who took the book is in the active household.
My problem is getting the information I need in the FineSim Class. I tried changing it to take the parameter of a Book, and then calling:
RegisterPointer.handle = bookToAdd.AddAlarmRepeating(1f, TimeUnit.Hours, new AlarmTimerCallback(RegisterPointer.FineSim(bookToAdd)), "TimeUntilFine", AlarmType.AlwaysPersisted);
but it doesn't seem to like the parameter as it is no longer recognised as a method. It just says "Method name expected". I guess it is just to do with the way the alarm callbacks are called, as it doesn't use brackets when calling FineSim, but I can't find how to pass the variable to the FineSim method. If I can do this, I believe I could then find the simdesc in the bookOwner dictionary for that book in order to check if they are in the active household? So if you can help me out that would be great
Thanks
Posts: 437
Thanks: 5285 in 22 Posts
My problem is getting the information I need in the FineSim Class. I tried changing it to take the parameter of a Book, and then calling:
Code:
RegisterPointer.handle = bookToAdd.AddAlarmRepeating(1f, TimeUnit.Hours, new AlarmTimerCallback(RegisterPointer.FineSim(bookToAdd)), "TimeUntilFine", AlarmType.AlwaysPersisted); but it doesn't seem to like the parameter as it is no longer recognised as a method. It just says "Method name expected". I guess it is just to do with the way the alarm callbacks are called, as it doesn't use brackets when calling FineSim, but I can't find how to pass the variable to the FineSim method. If I can do this, I believe I could then find the simdesc in the bookOwner dictionary for that book in order to check if they are in the active household? So if you can help me out that would be great Thanks |
When creating a new AlarmTimerCallback, you only put in the name of the method without parentheses or arguments. The assumption the game has is that the method you point to takes no arguments and does not return anything, which does not appear to be the case here.
If you really need the local variable bookToAdd in order to do something, you can use what's called an anonymous delegate to directly insert whatever code you want run into the AlarmTimerCallback and work around the limitation. For example:
RegisterPointer.handle = bookToAdd.AddAlarmRepeating(1f, TimeUnit.Hours, new AlarmTimerCallback(delegate() { //Just an example of what you could do using anonymous delegates; place more relevant code here if (bookToAdd is MedicalJournal) { Sims3.UI.StyledNotification.Show(new Sims3.UI.StyledNotification.Format("This is a medical journal!", Sims3.UI.StyledNotification.NotificationStyle.kDebugAlert)); } }), "TimeUntilFine", AlarmType.AlwaysPersisted);
You could also use what's called a lambda expression to achieve the same result:
RegisterPointer.handle = bookToAdd.AddAlarmRepeating(1f, TimeUnit.Hours, new AlarmTimerCallback(() => { //Again, just an example if (bookToAdd is MedicalJournal) { Sims3.UI.StyledNotification.Show(new Sims3.UI.StyledNotification.Format("This is a medical journal!", Sims3.UI.StyledNotification.NotificationStyle.kDebugAlert)); } }), "TimeUntilFine", AlarmType.AlwaysPersisted);
In this instance, the lambda expression will compile into a delegate and is essentially syntactic sugar.
If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Posts: 562
Thanks: 3567 in 11 Posts
I'm going to do some proper testing to double check that everything works properly so far. What I have done now for fining, is check if the sim who has taken out the book is in the active household, and if not it will just return the book. So hopefully that will take care of sims that move out of the household.
Then I think all that is mainly left to do is to sort out situations like when sims travel. You suggested a PersistableStatic Tuple? (ahem no idea what that is). Would it be easier to automatically return books before sims travel etc or better to carry the information through. Just thought of some more scenarios - when sims go to boarding school, or parents go on those vacation opportunities that come with generations. Do you know anything about those and how they work with alarms?
Thanks for all your help
Posts: 437
Thanks: 5285 in 22 Posts
That's great, gamefreak! I'm going to do some proper testing to double check that everything works properly so far. What I have done now for fining, is check if the sim who has taken out the book is in the active household, and if not it will just return the book. So hopefully that will take care of sims that move out of the household. Then I think all that is mainly left to do is to sort out situations like when sims travel. You suggested a PersistableStatic Tuple? (ahem no idea what that is). Would it be easier to automatically return books before sims travel etc or better to carry the information through. Just thought of some more scenarios - when sims go to boarding school, or parents go on those vacation opportunities that come with generations. Do you know anything about those and how they work with alarms? Thanks for all your help |
Good job for planning for all those edge cases; that's what programming is all about
Whenever Sims are sent to boarding school or free vacations, what happens -- or what's supposed to happen, as the system is rather unstable -- is the Sim object itself is destroyed, but the SimDescription as well as all their inventory objects still exist and are used to recreate the Sim upon their "return." Since you're targeting SimDescriptions, there should be no real problems with free vacations. However, you may just want to return any books a Sim has in their inventory before they're sent off to boarding school, which can be done through a kSentSimToBoardingSchool EventListener; then, in order to retrieve the student, you would have to use e.Target, rather than e.Actor.
Regarding traveling, it would be much less work to just return the books before traveling occurs; the problem is, unless you're traveling to the future, it doesn't look like there's an easy way to listen for when Sims initially leave for vacation. You might try using kVacationTelemetryInfo, but I'm not sure if that'd work reliably.
If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Who Posted
|