/// <summary> /// Try find a gift reaction dialogue text for gifted object to an NPC /// </summary> /// <param name="npc">NPC had recieved a gift</param> /// <param name="obj">Gifted item recieved by this NPC</param> /// <param name="dialogue">A reaction dialogue text for this gifted item to this NPC</param> /// <returns>True if any gift reaction dialogue was found, otherwise false</returns> public static bool FetchGiftReaction(NPC npc, SObject obj, out string dialogue, string suffix = "") { HashSet <string> possibleKeys = new () { $"GiftReaction_{obj.Name.Replace(' ', '_')}{suffix}", $"GiftReactionCategory_{obj.Category}{suffix}", $"GiftReactionPreserved_{obj.preserve.Value}{suffix}", $"GiftReactionPreserved_{(obj.preserve.Value.HasValue ? "Any" : "")}{suffix}", }; // Add item context tags as possibly dialogue lines for gift reactions possibleKeys.UnionWith(obj.GetContextTags().Select(tag => $"GiftReactionTag_{tag}")); foreach (string dialogueKey in possibleKeys) { if (DialogueHelper.GetRawDialogue(npc.Dialogue, dialogueKey, out KeyValuePair <string, string> reaction)) { dialogue = reaction.Value; return(true); } } dialogue = ""; return(false); }
public IEnumerable <SearchableItem> GetAll() { // // // Be careful about closure variable capture here! // // SearchableItem stores the Func<Item> to create new instances later. Loop variables passed into the // function will be captured, so every func in the loop will use the value from the last iteration. Use the // TryCreate(type, id, entity => item) form to avoid the issue, or create a local variable to pass in. // // IEnumerable <SearchableItem> GetAllRaw() { // get tools for (int q = Tool.stone; q <= Tool.iridium; q++) { int quality = q; yield return(this.TryCreate(ItemType.Tool, ToolFactory.axe, _ => ToolFactory.getToolFromDescription(ToolFactory.axe, quality))); yield return(this.TryCreate(ItemType.Tool, ToolFactory.hoe, _ => ToolFactory.getToolFromDescription(ToolFactory.hoe, quality))); yield return(this.TryCreate(ItemType.Tool, ToolFactory.pickAxe, _ => ToolFactory.getToolFromDescription(ToolFactory.pickAxe, quality))); yield return(this.TryCreate(ItemType.Tool, ToolFactory.wateringCan, _ => ToolFactory.getToolFromDescription(ToolFactory.wateringCan, quality))); if (quality != Tool.iridium) { yield return(this.TryCreate(ItemType.Tool, ToolFactory.fishingRod, _ => ToolFactory.getToolFromDescription(ToolFactory.fishingRod, quality))); } } yield return(this.TryCreate(ItemType.Tool, this.CustomIDOffset, _ => new MilkPail())); // these don't have any sort of ID, so we'll just assign some arbitrary ones yield return(this.TryCreate(ItemType.Tool, this.CustomIDOffset + 1, _ => new Shears())); yield return(this.TryCreate(ItemType.Tool, this.CustomIDOffset + 2, _ => new Pan())); yield return(this.TryCreate(ItemType.Tool, this.CustomIDOffset + 3, _ => new Wand())); // clothing { // items HashSet <int> clothingIds = new HashSet <int>(); foreach (int id in Game1.clothingInformation.Keys) { if (id < 0) { continue; // placeholder data for character customization clothing below } clothingIds.Add(id); yield return(this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID))); } // character customization shirts (some shirts in this range have no data, but game has special logic to handle them) for (int id = 1000; id <= 1111; id++) { if (!clothingIds.Contains(id)) { yield return(this.TryCreate(ItemType.Clothing, id, p => new Clothing(p.ID))); } } } // wallpapers for (int id = 0; id < 112; id++) { yield return(this.TryCreate(ItemType.Wallpaper, id, p => new Wallpaper(p.ID) { Category = SObject.furnitureCategory })); } // flooring for (int id = 0; id < 56; id++) { yield return(this.TryCreate(ItemType.Flooring, id, p => new Wallpaper(p.ID, isFloor: true) { Category = SObject.furnitureCategory })); } // equipment foreach (int id in this.TryLoad <int, string>("Data\\Boots").Keys) { yield return(this.TryCreate(ItemType.Boots, id, p => new Boots(p.ID))); } foreach (int id in this.TryLoad <int, string>("Data\\hats").Keys) { yield return(this.TryCreate(ItemType.Hat, id, p => new Hat(p.ID))); } // weapons foreach (int id in this.TryLoad <int, string>("Data\\weapons").Keys) { yield return(this.TryCreate(ItemType.Weapon, id, p => (p.ID >= 32 && p.ID <= 34) ? (Item) new Slingshot(p.ID) : new MeleeWeapon(p.ID) )); } // furniture foreach (int id in this.TryLoad <int, string>("Data\\Furniture").Keys) { yield return(this.TryCreate(ItemType.Furniture, id, p => Furniture.GetFurnitureInstance(p.ID))); } // craftables foreach (int id in Game1.bigCraftablesInformation.Keys) { yield return(this.TryCreate(ItemType.BigCraftable, id, p => new SObject(Vector2.Zero, p.ID))); } // objects foreach (int id in Game1.objectInformation.Keys) { string[] fields = Game1.objectInformation[id]?.Split('/'); // secret notes if (id == 79) { foreach (int secretNoteId in this.TryLoad <int, string>("Data\\SecretNotes").Keys) { yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset + secretNoteId, _ => { SObject note = new SObject(79, 1); note.name = $"{note.name} #{secretNoteId}"; return note; })); } } // ring else if (id != 801 && fields?.Length >= 4 && fields[3] == "Ring") // 801 = wedding ring, which isn't an equippable ring { yield return(this.TryCreate(ItemType.Ring, id, p => new Ring(p.ID))); } // item else { // spawn main item SObject item = null; yield return(this.TryCreate(ItemType.Object, id, p => { return item = (p.ID == 812 // roe ? new ColoredObject(p.ID, 1, Color.White) : new SObject(p.ID, 1) ); })); if (item == null) { continue; } // flavored items switch (item.Category) { // fruit products case SObject.FruitsCategory: // wine yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + item.ParentSheetIndex, _ => new SObject(348, 1) { Name = $"{item.Name} Wine", Price = item.Price * 3, preserve = { SObject.PreserveType.Wine }, preservedParentSheetIndex = { item.ParentSheetIndex } })); // jelly yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + item.ParentSheetIndex, _ => new SObject(344, 1) { Name = $"{item.Name} Jelly", Price = 50 + item.Price * 2, preserve = { SObject.PreserveType.Jelly }, preservedParentSheetIndex = { item.ParentSheetIndex } })); break; // vegetable products case SObject.VegetableCategory: // juice yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + item.ParentSheetIndex, _ => new SObject(350, 1) { Name = $"{item.Name} Juice", Price = (int)(item.Price * 2.25d), preserve = { SObject.PreserveType.Juice }, preservedParentSheetIndex = { item.ParentSheetIndex } })); // pickled yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => new SObject(342, 1) { Name = $"Pickled {item.Name}", Price = 50 + item.Price * 2, preserve = { SObject.PreserveType.Pickle }, preservedParentSheetIndex = { item.ParentSheetIndex } })); break; // flower honey case SObject.flowersCategory: yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + item.ParentSheetIndex, _ => { SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) { Name = $"{item.Name} Honey", preservedParentSheetIndex = { item.ParentSheetIndex } }; honey.Price += item.Price * 2; return honey; })); break; // roe and aged roe (derived from FishPond.GetFishProduce) case SObject.sellAtFishShopCategory when item.ParentSheetIndex == 812: { this.GetRoeContextTagLookups(out HashSet <string> simpleTags, out List <List <string> > complexTags); foreach (var pair in Game1.objectInformation) { // get input SObject input = this.TryCreate(ItemType.Object, pair.Key, p => new SObject(p.ID, 1))?.Item as SObject; var inputTags = input?.GetContextTags(); if (inputTags?.Any() != true) { continue; } // check if roe-producing fish if (!inputTags.Any(tag => simpleTags.Contains(tag)) && !complexTags.Any(set => set.All(tag => input.HasContextTag(tag)))) { continue; } // yield roe SObject roe = null; Color color = this.GetRoeColor(input); yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => { roe = new ColoredObject(812, 1, color) { name = $"{input.Name} Roe", preserve = { Value = SObject.PreserveType.Roe }, preservedParentSheetIndex = { Value = input.ParentSheetIndex } }; roe.Price += input.Price / 2; return roe; })); // aged roe if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item { yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + item.ParentSheetIndex, _ => new ColoredObject(447, 1, color) { name = $"Aged {input.Name} Roe", Category = -27, preserve = { Value = SObject.PreserveType.AgedRoe }, preservedParentSheetIndex = { Value = input.ParentSheetIndex }, Price = roe.Price * 2 })); } } } break; } } } } return(GetAllRaw().Where(p => p != null)); }
/// <summary> /// Check if an input is excluded by a producer rule or a mass production machine definition. /// Adapted from https://github.com/Digus/StardewValleyMods/blob/master/ProducerFrameworkMod/ProducerRuleController.cs /// </summary> /// <param name="producerRule">The producer rule to check.</param> /// <param name="mpm">The definition of the mass production machine to check.</param> /// <param name="input">The input to check.</param> /// <returns>True if it should be excluded.</returns> public static bool IsInputExcluded(ProducerRule producerRule, MassProductionMachineDefinition mpm, SObject input) { bool isExcludedByRule = producerRule.ExcludeIdentifiers != null && (producerRule.ExcludeIdentifiers.Contains(input.ParentSheetIndex.ToString()) || producerRule.ExcludeIdentifiers.Contains(input.Name) || producerRule.ExcludeIdentifiers.Contains(input.Category.ToString()) || producerRule.ExcludeIdentifiers.Intersect(input.GetContextTags()).Any()); bool isExcludedByMPM = mpm.BlacklistedInputKeys.Contains(input.ParentSheetIndex.ToString()) || mpm.BlacklistedInputKeys.Contains(input.Name) || mpm.BlacklistedInputKeys.Contains(input.Category.ToString()) || mpm.BlacklistedInputKeys.Intersect(input.GetContextTags()).Any(); return(isExcludedByRule || isExcludedByMPM); }
/// <summary> /// Check if an input is excluded by a producer rule. /// </summary> /// <param name="producerRule">The producer rule to check.</param> /// <param name="input">The input to check</param> /// <returns>true if should be excluded</returns> public static bool IsInputExcluded(ProducerRule producerRule, Object input) { return(producerRule.ExcludeIdentifiers != null && (producerRule.ExcludeIdentifiers.Contains(input.ParentSheetIndex.ToString()) || producerRule.ExcludeIdentifiers.Contains(input.Name) || producerRule.ExcludeIdentifiers.Contains(input.Category.ToString()) || producerRule.ExcludeIdentifiers.Intersect(input.GetContextTags()).Any())); }
/// <summary>Get flavored variants of a base item (like Blueberry Wine for Blueberry), if any.</summary> /// <param name="item">A sample of the base item.</param> private IEnumerable <SearchableItem> GetFlavoredObjectVariants(SObject item) { int id = item.ParentSheetIndex; switch (item.Category) { // fruit products case SObject.FruitsCategory: // wine yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 2 + id, _ => new SObject(348, 1) { Name = $"{item.Name} Wine", Price = item.Price * 3, preserve = { SObject.PreserveType.Wine }, preservedParentSheetIndex = { id } })); // jelly yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 3 + id, _ => new SObject(344, 1) { Name = $"{item.Name} Jelly", Price = 50 + item.Price * 2, preserve = { SObject.PreserveType.Jelly }, preservedParentSheetIndex = { id } })); break; // vegetable products case SObject.VegetableCategory: // juice yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 4 + id, _ => new SObject(350, 1) { Name = $"{item.Name} Juice", Price = (int)(item.Price * 2.25d), preserve = { SObject.PreserveType.Juice }, preservedParentSheetIndex = { id } })); // pickled yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, _ => new SObject(342, 1) { Name = $"Pickled {item.Name}", Price = 50 + item.Price * 2, preserve = { SObject.PreserveType.Pickle }, preservedParentSheetIndex = { id } })); break; // flower honey case SObject.flowersCategory: yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, _ => { SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) { Name = $"{item.Name} Honey", preservedParentSheetIndex = { id } }; honey.Price += item.Price * 2; return honey; })); break; // roe and aged roe (derived from FishPond.GetFishProduce) case SObject.sellAtFishShopCategory when id == 812: { this.GetRoeContextTagLookups(out HashSet <string> simpleTags, out List <List <string> > complexTags); foreach (var pair in Game1.objectInformation) { // get input SObject input = this.TryCreate(ItemType.Object, pair.Key, p => new SObject(p.ID, 1))?.Item as SObject; var inputTags = input?.GetContextTags(); if (inputTags?.Any() != true) { continue; } // check if roe-producing fish if (!inputTags.Any(tag => simpleTags.Contains(tag)) && !complexTags.Any(set => set.All(tag => input.HasContextTag(tag)))) { continue; } // yield roe SObject roe = null; Color color = this.GetRoeColor(input); yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, _ => { roe = new ColoredObject(812, 1, color) { name = $"{input.Name} Roe", preserve = { Value = SObject.PreserveType.Roe }, preservedParentSheetIndex = { Value = input.ParentSheetIndex } }; roe.Price += input.Price / 2; return roe; })); // aged roe if (roe != null && pair.Key != 698) // aged sturgeon roe is caviar, which is a separate item { yield return(this.TryCreate(ItemType.Object, this.CustomIDOffset * 7 + id, _ => new ColoredObject(447, 1, color) { name = $"Aged {input.Name} Roe", Category = -27, preserve = { Value = SObject.PreserveType.AgedRoe }, preservedParentSheetIndex = { Value = input.ParentSheetIndex }, Price = roe.Price * 2 })); } } } break; } }