/// <summary>Get the data to display for this subject.</summary> /// <param name="metadata">Provides metadata that's not available from the game data directly.</param> public override IEnumerable <ICustomField> GetData(Metadata metadata) { // get data Item item = this.Target; SObject obj = item as SObject; bool isObject = obj != null; bool isCrop = this.FromCrop != null; bool isSeed = this.SeedForCrop != null; bool isDeadCrop = this.FromCrop?.dead.Value == true; bool canSell = obj?.canBeShipped() == true || metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.Category)); // get overrides bool showInventoryFields = true; { ObjectData objData = metadata.GetObject(item, this.Context); if (objData != null) { this.Name = objData.NameKey != null?this.Translate(objData.NameKey) : this.Name; this.Description = objData.DescriptionKey != null?this.Translate(objData.DescriptionKey) : this.Description; this.Type = objData.TypeKey != null?this.Translate(objData.TypeKey) : this.Type; showInventoryFields = objData.ShowInventoryFields ?? true; } } // don't show data for dead crop if (isDeadCrop) { yield return(new GenericField(this.Translate(L10n.Crop.Summary), this.Translate(L10n.Crop.SummaryDead))); yield break; } // crop fields if (isCrop || isSeed) { // get crop Crop crop = this.FromCrop ?? this.SeedForCrop; // get harvest schedule int harvestablePhase = crop.phaseDays.Count - 1; bool canHarvestNow = (crop.currentPhase.Value >= harvestablePhase) && (!crop.fullyGrown.Value || crop.dayOfCurrentPhase.Value <= 0); int daysToFirstHarvest = crop.phaseDays.Take(crop.phaseDays.Count - 1).Sum(); // ignore harvestable phase // add next-harvest field if (isCrop) { // calculate next harvest int daysToNextHarvest = 0; SDate dayOfNextHarvest = null; if (!canHarvestNow) { // calculate days until next harvest int daysUntilLastPhase = daysToFirstHarvest - crop.dayOfCurrentPhase.Value - crop.phaseDays.Take(crop.currentPhase.Value).Sum(); { // growing: days until next harvest if (!crop.fullyGrown.Value) { daysToNextHarvest = daysUntilLastPhase; } // regrowable crop harvested today else if (crop.dayOfCurrentPhase.Value >= crop.regrowAfterHarvest.Value) { daysToNextHarvest = crop.regrowAfterHarvest.Value; } // regrowable crop else { daysToNextHarvest = crop.dayOfCurrentPhase.Value; // dayOfCurrentPhase decreases to 0 when fully grown, where <=0 is harvestable } } dayOfNextHarvest = SDate.Now().AddDays(daysToNextHarvest); } // generate field string summary; if (canHarvestNow) { summary = this.Translate(L10n.Crop.HarvestNow); } else if (Game1.currentLocation.Name != Constant.LocationNames.Greenhouse && !crop.seasonsToGrowIn.Contains(dayOfNextHarvest.Season)) { summary = this.Translate(L10n.Crop.HarvestTooLate, new { date = this.Stringify(dayOfNextHarvest) }); } else { summary = $"{this.Stringify(dayOfNextHarvest)} ({this.Text.GetPlural(daysToNextHarvest, L10n.Generic.Tomorrow, L10n.Generic.InXDays).Tokens(new { count = daysToNextHarvest })})"; } yield return(new GenericField(this.Translate(L10n.Crop.Harvest), summary)); } // crop summary { List <string> summary = new List <string>(); // harvest summary.Add(crop.regrowAfterHarvest.Value == -1 ? this.Translate(L10n.Crop.SummaryHarvestOnce, new { daysToFirstHarvest = daysToFirstHarvest }) : this.Translate(L10n.Crop.SummaryHarvestMulti, new { daysToFirstHarvest = daysToFirstHarvest, daysToNextHarvests = crop.regrowAfterHarvest }) ); // seasons summary.Add(this.Translate(L10n.Crop.SummarySeasons, new { seasons = string.Join(", ", this.Text.GetSeasonNames(crop.seasonsToGrowIn)) })); // drops if (crop.minHarvest != crop.maxHarvest && crop.chanceForExtraCrops.Value > 0) { summary.Add(this.Translate(L10n.Crop.SummaryDropsXToY, new { min = crop.minHarvest, max = crop.maxHarvest, percent = Math.Round(crop.chanceForExtraCrops.Value * 100, 2) })); } else if (crop.minHarvest.Value > 1) { summary.Add(this.Translate(L10n.Crop.SummaryDropsX, new { count = crop.minHarvest })); } // crop sale price Item drop = GameHelper.GetObjectBySpriteIndex(crop.indexOfHarvest.Value); summary.Add(this.Translate(L10n.Crop.SummarySellsFor, new { price = GenericField.GetSaleValueString(this.GetSaleValue(drop, false, metadata), 1, this.Text) })); // generate field yield return(new GenericField(this.Translate(L10n.Crop.Summary), "-" + string.Join($"{Environment.NewLine}-", summary))); } } // crafting if (obj?.heldObject?.Value != null) { if (obj is Cask cask) { // get cask data SObject agingObj = cask.heldObject.Value; ItemQuality curQuality = (ItemQuality)agingObj.Quality; string curQualityName = this.Translate(L10n.For(curQuality)); // calculate aging schedule float effectiveAge = metadata.Constants.CaskAgeSchedule.Values.Max() - cask.daysToMature.Value; var schedule = ( from entry in metadata.Constants.CaskAgeSchedule let quality = entry.Key let baseDays = entry.Value where baseDays > effectiveAge orderby baseDays ascending let daysLeft = (int)Math.Ceiling((baseDays - effectiveAge) / cask.agingRate.Value) select new { Quality = quality, DaysLeft = daysLeft, HarvestDate = SDate.Now().AddDays(daysLeft) } ) .ToArray(); // display fields yield return(new ItemIconField(this.Translate(L10n.Item.Contents), obj.heldObject.Value)); if (cask.MinutesUntilReady <= 0 || !schedule.Any()) { yield return(new GenericField(this.Translate(L10n.Item.CaskSchedule), this.Translate(L10n.Item.CaskScheduleNow, new { quality = curQualityName }))); } else { string scheduleStr = string.Join(Environment.NewLine, ( from entry in schedule let tokens = new { quality = this.Translate(L10n.For(entry.Quality)), count = entry.DaysLeft, date = entry.HarvestDate } let str = this.Text.GetPlural(entry.DaysLeft, L10n.Item.CaskScheduleTomorrow, L10n.Item.CaskScheduleInXDays).Tokens(tokens) select $"-{str}" )); yield return(new GenericField(this.Translate(L10n.Item.CaskSchedule), this.Translate(L10n.Item.CaskSchedulePartial, new { quality = curQualityName }) + Environment.NewLine + scheduleStr)); } } else if (obj is Furniture) { string summary = this.Translate(L10n.Item.ContentsPlaced, new { name = obj.heldObject.Value.DisplayName }); yield return(new ItemIconField(this.Translate(L10n.Item.Contents), obj.heldObject.Value, summary)); } else { string summary = obj.MinutesUntilReady <= 0 ? this.Translate(L10n.Item.ContentsReady, new { name = obj.heldObject.Value.DisplayName }) : this.Translate(L10n.Item.ContentsPartial, new { name = obj.heldObject.Value.DisplayName, time = this.Stringify(TimeSpan.FromMinutes(obj.MinutesUntilReady)) }); yield return(new ItemIconField(this.Translate(L10n.Item.Contents), obj.heldObject.Value, summary)); } } // item if (showInventoryFields) { // needed for { List <string> neededFor = new List <string>(); // bundles if (isObject) { string[] bundles = (from bundle in this.GetUnfinishedBundles(obj) orderby bundle.Area, bundle.DisplayName select $"{this.GetTranslatedBundleArea(bundle)}: {bundle.DisplayName}").ToArray(); if (bundles.Any()) { neededFor.Add(this.Translate(L10n.Item.NeededForCommunityCenter, new { bundles = string.Join(", ", bundles) })); } } // polyculture achievement if (isObject && metadata.Constants.PolycultureCrops.Contains(obj.ParentSheetIndex)) { int needed = metadata.Constants.PolycultureCount - GameHelper.GetShipped(obj.ParentSheetIndex); if (needed > 0) { neededFor.Add(this.Translate(L10n.Item.NeededForPolyculture, new { count = needed })); } } // full shipment achievement if (isObject && GameHelper.GetFullShipmentAchievementItems().Any(p => p.Key == obj.ParentSheetIndex && !p.Value)) { neededFor.Add(this.Translate(L10n.Item.NeededForFullShipment)); } // a full collection achievement LibraryMuseum museum = Game1.locations.OfType <LibraryMuseum>().FirstOrDefault(); if (museum != null && museum.isItemSuitableForDonation(obj)) { neededFor.Add(this.Translate(L10n.Item.NeededForFullCollection)); } // yield if (neededFor.Any()) { yield return(new GenericField(this.Translate(L10n.Item.NeededFor), string.Join(", ", neededFor))); } } // sale data if (canSell && !isCrop) { // sale price string saleValueSummary = GenericField.GetSaleValueString(this.GetSaleValue(item, this.KnownQuality, metadata), item.Stack, this.Text); yield return(new GenericField(this.Translate(L10n.Item.SellsFor), saleValueSummary)); // sell to List <string> buyers = new List <string>(); if (obj?.canBeShipped() == true) { buyers.Add(this.Translate(L10n.Item.SellsToShippingBox)); } buyers.AddRange( from shop in metadata.Shops where shop.BuysCategories.Contains(item.Category) let name = this.Translate(shop.DisplayKey).ToString() orderby name select name ); yield return(new GenericField(this.Translate(L10n.Item.SellsTo), string.Join(", ", buyers))); } // gift tastes var giftTastes = this.GetGiftTastes(item, metadata); yield return(new ItemGiftTastesField(this.Translate(L10n.Item.LovesThis), giftTastes, GiftTaste.Love)); yield return(new ItemGiftTastesField(this.Translate(L10n.Item.LikesThis), giftTastes, GiftTaste.Like)); } // fence if (item is Fence fence) { string healthLabel = this.Translate(L10n.Item.FenceHealth); // health if (Game1.getFarm().isBuildingConstructed(Constant.BuildingNames.GoldClock)) { yield return(new GenericField(healthLabel, this.Translate(L10n.Item.FenceHealthGoldClock))); } else { float maxHealth = fence.isGate.Value ? fence.maxHealth.Value * 2 : fence.maxHealth.Value; float health = fence.health.Value / maxHealth; double daysLeft = Math.Round(fence.health.Value * metadata.Constants.FenceDecayRate / 60 / 24); double percent = Math.Round(health * 100); yield return(new PercentageBarField(healthLabel, (int)fence.health, (int)maxHealth, Color.Green, Color.Red, this.Translate(L10n.Item.FenceHealthSummary, new { percent = percent, count = daysLeft }))); } } // recipes if (item.GetSpriteType() == ItemSpriteType.Object) { RecipeModel[] recipes = GameHelper.GetRecipesForIngredient(this.DisplayItem).ToArray(); if (recipes.Any()) { yield return(new RecipesForIngredientField(this.Translate(L10n.Item.Recipes), item, recipes, this.Text)); } } // owned if (showInventoryFields && !isCrop && !(item is Tool)) { yield return(new GenericField(this.Translate(L10n.Item.Owned), this.Translate(L10n.Item.OwnedSummary, new { count = GameHelper.CountOwnedItems(item) }))); } // see also crop bool seeAlsoCrop = isSeed && item.ParentSheetIndex != this.SeedForCrop.indexOfHarvest.Value && // skip seeds which produce themselves (e.g. coffee beans) !(item.ParentSheetIndex >= 495 && item.ParentSheetIndex <= 497) && // skip random seasonal seeds item.ParentSheetIndex != 770; // skip mixed seeds if (seeAlsoCrop) { Item drop = GameHelper.GetObjectBySpriteIndex(this.SeedForCrop.indexOfHarvest.Value); yield return(new LinkField(this.Translate(L10n.Item.SeeAlso), drop.DisplayName, () => new ItemSubject(this.Text, drop, ObjectContext.Inventory, false, this.SeedForCrop))); } }
/// <summary>Get the data to display for this subject.</summary> /// <param name="metadata">Provides metadata that's not available from the game data directly.</param> public override IEnumerable <ICustomField> GetData(Metadata metadata) { // get data Item item = this.Target; SObject obj = item as SObject; bool isCrop = this.FromCrop != null; bool isSeed = this.SeedForCrop != null; bool isDeadCrop = this.FromCrop?.dead.Value == true; bool canSell = obj?.canBeShipped() == true || metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.Category)); // get overrides bool showInventoryFields = true; { ObjectData objData = metadata.GetObject(item, this.Context); if (objData != null) { this.Name = objData.NameKey != null?this.Translate(objData.NameKey) : this.Name; this.Description = objData.DescriptionKey != null?this.Translate(objData.DescriptionKey) : this.Description; this.Type = objData.TypeKey != null?this.Translate(objData.TypeKey) : this.Type; showInventoryFields = objData.ShowInventoryFields ?? true; } } // don't show data for dead crop if (isDeadCrop) { yield return(new GenericField(this.GameHelper, this.Translate(L10n.Crop.Summary), this.Translate(L10n.Crop.SummaryDead))); yield break; } // crop fields foreach (ICustomField field in this.GetCropFields(this.FromCrop ?? this.SeedForCrop, isSeed, metadata)) { yield return(field); } // indoor pot crop if (obj is IndoorPot pot) { Crop potCrop = pot.hoeDirt.Value.crop; if (potCrop != null) { Item drop = this.GameHelper.GetObjectBySpriteIndex(potCrop.indexOfHarvest.Value); yield return(new LinkField(this.GameHelper, this.Translate(L10n.Item.Contents), drop.DisplayName, () => new ItemSubject(this.GameHelper, this.Text, this.GameHelper.GetObjectBySpriteIndex(potCrop.indexOfHarvest.Value), ObjectContext.World, knownQuality: false, fromCrop: potCrop))); } } // machine output foreach (ICustomField field in this.GetMachineOutputFields(obj, metadata)) { yield return(field); } // item if (showInventoryFields) { // needed for foreach (ICustomField field in this.GetNeededForFields(obj, metadata)) { yield return(field); } // sale data if (canSell && !isCrop) { // sale price string saleValueSummary = GenericField.GetSaleValueString(this.GetSaleValue(item, this.KnownQuality, metadata), item.Stack, this.Text); yield return(new GenericField(this.GameHelper, this.Translate(L10n.Item.SellsFor), saleValueSummary)); // sell to List <string> buyers = new List <string>(); if (obj?.canBeShipped() == true) { buyers.Add(this.Translate(L10n.Item.SellsToShippingBox)); } buyers.AddRange( from shop in metadata.Shops where shop.BuysCategories.Contains(item.Category) let name = this.Translate(shop.DisplayKey).ToString() orderby name select name ); yield return(new GenericField(this.GameHelper, this.Translate(L10n.Item.SellsTo), string.Join(", ", buyers))); } // gift tastes var giftTastes = this.GetGiftTastes(item, metadata); yield return(new ItemGiftTastesField(this.GameHelper, this.Translate(L10n.Item.LovesThis), giftTastes, GiftTaste.Love)); yield return(new ItemGiftTastesField(this.GameHelper, this.Translate(L10n.Item.LikesThis), giftTastes, GiftTaste.Like)); } // fence if (item is Fence fence) { string healthLabel = this.Translate(L10n.Item.FenceHealth); // health if (Game1.getFarm().isBuildingConstructed(Constant.BuildingNames.GoldClock)) { yield return(new GenericField(this.GameHelper, healthLabel, this.Translate(L10n.Item.FenceHealthGoldClock))); } else { float maxHealth = fence.isGate.Value ? fence.maxHealth.Value * 2 : fence.maxHealth.Value; float health = fence.health.Value / maxHealth; double daysLeft = Math.Round(fence.health.Value * metadata.Constants.FenceDecayRate / 60 / 24); double percent = Math.Round(health * 100); yield return(new PercentageBarField(this.GameHelper, healthLabel, (int)fence.health.Value, (int)maxHealth, Color.Green, Color.Red, this.Translate(L10n.Item.FenceHealthSummary, new { percent = percent, count = daysLeft }))); } } // recipes if (item.GetSpriteType() == ItemSpriteType.Object) { RecipeModel[] recipes = this.GameHelper.GetRecipesForIngredient(this.DisplayItem).ToArray(); if (recipes.Any()) { yield return(new RecipesForIngredientField(this.GameHelper, this.Translate(L10n.Item.Recipes), item, recipes, this.Text)); } } // owned if (showInventoryFields && !isCrop && !(item is Tool)) { yield return(new GenericField(this.GameHelper, this.Translate(L10n.Item.Owned), this.Translate(L10n.Item.OwnedSummary, new { count = this.GameHelper.CountOwnedItems(item) }))); } // see also crop bool seeAlsoCrop = isSeed && item.ParentSheetIndex != this.SeedForCrop.indexOfHarvest.Value && // skip seeds which produce themselves (e.g. coffee beans) !(item.ParentSheetIndex >= 495 && item.ParentSheetIndex <= 497) && // skip random seasonal seeds item.ParentSheetIndex != 770; // skip mixed seeds if (seeAlsoCrop) { Item drop = this.GameHelper.GetObjectBySpriteIndex(this.SeedForCrop.indexOfHarvest.Value); yield return(new LinkField(this.GameHelper, this.Translate(L10n.Item.SeeAlso), drop.DisplayName, () => new ItemSubject(this.GameHelper, this.Text, drop, ObjectContext.Inventory, false, this.SeedForCrop))); } }
/// <summary>Get the data to display for this subject.</summary> public override IEnumerable <ICustomField> GetData() { // get data Item item = this.Target; ItemType itemType = item.GetItemType(); SObject obj = item as SObject; bool isCrop = this.FromCrop != null; bool isSeed = this.SeedForCrop != null; bool isDeadCrop = this.FromCrop?.dead.Value == true; bool canSell = obj?.canBeShipped() == true || this.Metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.Category)); // get overrides bool showInventoryFields = !this.IsSpawnedStoneNode(); { ObjectData objData = this.Metadata.GetObject(item, this.Context); if (objData != null) { this.Name = objData.NameKey != null?I18n.GetByKey(objData.NameKey) : this.Name; this.Description = objData.DescriptionKey != null?I18n.GetByKey(objData.DescriptionKey) : this.Description; this.Type = objData.TypeKey != null?I18n.GetByKey(objData.TypeKey) : this.Type; showInventoryFields = objData.ShowInventoryFields ?? showInventoryFields; } } // don't show data for dead crop if (isDeadCrop) { yield return(new GenericField(I18n.Crop_Summary(), I18n.Crop_Summary_Dead())); yield break; } // crop fields foreach (ICustomField field in this.GetCropFields(this.FromDirt, this.FromCrop ?? this.SeedForCrop, isSeed)) { yield return(field); } // indoor pot crop if (obj is IndoorPot pot) { Crop potCrop = pot.hoeDirt.Value.crop; if (potCrop != null) { Item drop = this.GameHelper.GetObjectBySpriteIndex(potCrop.indexOfHarvest.Value); yield return(new LinkField(I18n.Item_Contents(), drop.DisplayName, () => this.GetCropSubject(potCrop, ObjectContext.World, pot.hoeDirt.Value))); } } // machine output foreach (ICustomField field in this.GetMachineOutputFields(obj)) { yield return(field); } // music blocks if (obj?.Name == "Flute Block") { yield return(new GenericField(I18n.Item_MusicBlock_Pitch(), I18n.Generic_Ratio(value: obj.preservedParentSheetIndex.Value, max: 2300))); } else if (obj?.Name == "Drum Block") { yield return(new GenericField(I18n.Item_MusicBlock_DrumType(), I18n.Generic_Ratio(value: obj.preservedParentSheetIndex.Value, max: 6))); } // item if (showInventoryFields) { // needed for foreach (ICustomField field in this.GetNeededForFields(obj)) { yield return(field); } // sale data if (canSell && !isCrop) { // sale price string saleValueSummary = GenericField.GetSaleValueString(this.GetSaleValue(item, this.KnownQuality), item.Stack); yield return(new GenericField(I18n.Item_SellsFor(), saleValueSummary)); // sell to List <string> buyers = new List <string>(); if (obj?.canBeShipped() == true) { buyers.Add(I18n.Item_SellsTo_ShippingBox()); } buyers.AddRange( from shop in this.Metadata.Shops where shop.BuysCategories.Contains(item.Category) let name = I18n.GetByKey(shop.DisplayKey).ToString() orderby name select name ); yield return(new GenericField(I18n.Item_SellsTo(), string.Join(", ", buyers))); } // clothing if (item is Clothing clothing) { yield return(new GenericField(I18n.Item_CanBeDyed(), this.Stringify(clothing.dyeable.Value))); } // gift tastes IDictionary <GiftTaste, GiftTasteModel[]> giftTastes = this.GetGiftTastes(item); yield return(new ItemGiftTastesField(I18n.Item_LovesThis(), giftTastes, GiftTaste.Love, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(I18n.Item_LikesThis(), giftTastes, GiftTaste.Like, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); if (this.ProgressionMode || this.HighlightUnrevealedGiftTastes) { yield return(new ItemGiftTastesField(I18n.Item_NeutralAboutThis(), giftTastes, GiftTaste.Neutral, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(I18n.Item_DislikesThis(), giftTastes, GiftTaste.Dislike, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(I18n.Item_HatesThis(), giftTastes, GiftTaste.Hate, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); } } // recipes if (showInventoryFields) { switch (itemType) { // for ingredient case ItemType.Object: { RecipeModel[] recipes = this.GameHelper.GetRecipesForIngredient(this.DisplayItem).ToArray(); if (recipes.Any()) { yield return(new RecipesForIngredientField(this.GameHelper, I18n.Item_Recipes(), item, recipes)); } } break; // for machine case ItemType.BigCraftable: { RecipeModel[] recipes = this.GameHelper.GetRecipesForMachine(this.DisplayItem as SObject).ToArray(); if (recipes.Any()) { yield return(new RecipesForMachineField(this.GameHelper, I18n.Item_Recipes(), recipes)); } } break; } } // fish if (item.Category == SObject.FishCategory) { // spawn rules yield return(new FishSpawnRulesField(this.GameHelper, I18n.Item_FishSpawnRules(), item.ParentSheetIndex)); // fish pond data foreach (FishPondData fishPondData in Game1.content.Load <List <FishPondData> >("Data\\FishPondData")) { if (!fishPondData.RequiredTags.All(item.HasContextTag)) { continue; } int minChanceOfAnyDrop = (int)Math.Round(Utility.Lerp(0.15f, 0.95f, 1 / 10f) * 100); int maxChanceOfAnyDrop = (int)Math.Round(Utility.Lerp(0.15f, 0.95f, FishPond.MAXIMUM_OCCUPANCY / 10f) * 100); string preface = I18n.Building_FishPond_Drops_Preface(chance: I18n.Generic_Range(min: minChanceOfAnyDrop, max: maxChanceOfAnyDrop)); yield return(new FishPondDropsField(this.GameHelper, I18n.Item_FishPondDrops(), -1, fishPondData, preface)); break; } } // fence if (item is Fence fence) { string healthLabel = I18n.Item_FenceHealth(); // health if (Game1.getFarm().isBuildingConstructed(Constant.BuildingNames.GoldClock)) { yield return(new GenericField(healthLabel, I18n.Item_FenceHealth_GoldClock())); } else { float maxHealth = fence.isGate.Value ? fence.maxHealth.Value * 2 : fence.maxHealth.Value; float health = fence.health.Value / maxHealth; double daysLeft = Math.Round(fence.health.Value * this.Constants.FenceDecayRate / 60 / 24); double percent = Math.Round(health * 100); yield return(new PercentageBarField(healthLabel, (int)fence.health.Value, (int)maxHealth, Color.Green, Color.Red, I18n.Item_FenceHealth_Summary(percent: (int)percent, count: (int)daysLeft))); } } // movie ticket if (obj?.ParentSheetIndex == 809 && !obj.bigCraftable.Value) { MovieData movie = MovieTheater.GetMovieForDate(Game1.Date); if (movie == null) { yield return(new GenericField(I18n.Item_MovieTicket_MovieThisWeek(), I18n.Item_MovieTicket_MovieThisWeek_None())); } else { // movie this week yield return(new GenericField(I18n.Item_MovieTicket_MovieThisWeek(), new IFormattedText[] { new FormattedText(movie.Title, bold: true), new FormattedText(Environment.NewLine), new FormattedText(movie.Description) })); // movie tastes IDictionary <GiftTaste, string[]> tastes = this.GameHelper.GetMovieTastes() .GroupBy(entry => entry.Value) .ToDictionary(group => group.Key, group => group.Select(p => p.Key.Name).OrderBy(p => p).ToArray()); yield return(new MovieTastesField(I18n.Item_MovieTicket_LovesMovie(), tastes, GiftTaste.Love)); yield return(new MovieTastesField(I18n.Item_MovieTicket_LikesMovie(), tastes, GiftTaste.Like)); yield return(new MovieTastesField(I18n.Item_MovieTicket_DislikesMovie(), tastes, GiftTaste.Dislike)); } } // dyes if (showInventoryFields) { yield return(new ColorField(I18n.Item_ProducesDye(), item)); } // owned and times cooked/crafted if (showInventoryFields && !isCrop) { // owned yield return(new GenericField(I18n.Item_NumberOwned(), I18n.Item_NumberOwned_Summary(count: this.GameHelper.CountOwnedItems(item)))); // times crafted RecipeModel[] recipes = this.GameHelper .GetRecipes() .Where(recipe => recipe.OutputItemIndex == this.Target.ParentSheetIndex && recipe.OutputItemType == this.Target.GetItemType()) .ToArray(); if (recipes.Any()) { string label = recipes.First().Type == RecipeType.Cooking ? I18n.Item_NumberCooked() : I18n.Item_NumberCrafted(); int timesCrafted = recipes.Sum(recipe => recipe.GetTimesCrafted(Game1.player)); if (timesCrafted >= 0) // negative value means not available for this recipe type { yield return(new GenericField(label, I18n.Item_NumberCrafted_Summary(count: timesCrafted))); } } } // see also crop bool seeAlsoCrop = isSeed && item.ParentSheetIndex != this.SeedForCrop.indexOfHarvest.Value && // skip seeds which produce themselves (e.g. coffee beans) !(item.ParentSheetIndex >= 495 && item.ParentSheetIndex <= 497) && // skip random seasonal seeds item.ParentSheetIndex != 770; // skip mixed seeds if (seeAlsoCrop) { Item drop = this.GameHelper.GetObjectBySpriteIndex(this.SeedForCrop.indexOfHarvest.Value); yield return(new LinkField(I18n.Item_SeeAlso(), drop.DisplayName, () => this.GetCropSubject(this.SeedForCrop, ObjectContext.Inventory, null))); } }
/// <summary>Get the data to display for this subject.</summary> /// <param name="metadata">Provides metadata that's not available from the game data directly.</param> public override IEnumerable <ICustomField> GetData(Metadata metadata) { // get data Item item = this.Target; SObject obj = item as SObject; bool isCrop = this.FromCrop != null; bool isSeed = this.SeedForCrop != null; bool isDeadCrop = this.FromCrop?.dead.Value == true; bool canSell = obj?.canBeShipped() == true || metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.Category)); // get overrides bool showInventoryFields = true; { ObjectData objData = metadata.GetObject(item, this.Context); if (objData != null) { this.Name = objData.NameKey != null?L10n.GetRaw(objData.NameKey) : this.Name; this.Description = objData.DescriptionKey != null?L10n.GetRaw(objData.DescriptionKey) : this.Description; this.Type = objData.TypeKey != null?L10n.GetRaw(objData.TypeKey) : this.Type; showInventoryFields = objData.ShowInventoryFields ?? true; } } // don't show data for dead crop if (isDeadCrop) { yield return(new GenericField(this.GameHelper, L10n.Crop.Summary(), L10n.Crop.SummaryDead())); yield break; } // crop fields foreach (ICustomField field in this.GetCropFields(this.FromCrop ?? this.SeedForCrop, isSeed, metadata)) { yield return(field); } // indoor pot crop if (obj is IndoorPot pot) { Crop potCrop = pot.hoeDirt.Value.crop; if (potCrop != null) { Item drop = this.GameHelper.GetObjectBySpriteIndex(potCrop.indexOfHarvest.Value); yield return(new LinkField(this.GameHelper, L10n.Item.Contents(), drop.DisplayName, () => new ItemSubject(this.GameHelper, this.Text, this.ProgressionMode, this.GameHelper.GetObjectBySpriteIndex(potCrop.indexOfHarvest.Value), ObjectContext.World, knownQuality: false, fromCrop: potCrop))); } } // machine output foreach (ICustomField field in this.GetMachineOutputFields(obj, metadata)) { yield return(field); } // item if (showInventoryFields) { // needed for foreach (ICustomField field in this.GetNeededForFields(obj, metadata)) { yield return(field); } // sale data if (canSell && !isCrop) { // sale price string saleValueSummary = GenericField.GetSaleValueString(this.GetSaleValue(item, this.KnownQuality, metadata), item.Stack, this.Text); yield return(new GenericField(this.GameHelper, L10n.Item.SellsFor(), saleValueSummary)); // sell to List <string> buyers = new List <string>(); if (obj?.canBeShipped() == true) { buyers.Add(L10n.Item.SellsToShippingBox()); } buyers.AddRange( from shop in metadata.Shops where shop.BuysCategories.Contains(item.Category) let name = L10n.GetRaw(shop.DisplayKey).ToString() orderby name select name ); yield return(new GenericField(this.GameHelper, L10n.Item.SellsTo(), string.Join(", ", buyers))); } // clothing if (item is Clothing clothing) { yield return(new GenericField(this.GameHelper, L10n.Item.CanBeDyed(), this.Stringify(clothing.dyeable.Value))); } // gift tastes IDictionary <GiftTaste, GiftTasteModel[]> giftTastes = this.GetGiftTastes(item, metadata); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.LovesThis(), item.ParentSheetIndex, giftTastes, GiftTaste.Love, onlyRevealed: this.ProgressionMode)); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.LikesThis(), item.ParentSheetIndex, giftTastes, GiftTaste.Like, onlyRevealed: this.ProgressionMode)); if (this.ProgressionMode) { yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.NeutralAboutThis(), item.ParentSheetIndex, giftTastes, GiftTaste.Neutral, onlyRevealed: this.ProgressionMode)); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.DislikesThis(), item.ParentSheetIndex, giftTastes, GiftTaste.Dislike, onlyRevealed: this.ProgressionMode)); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.HatesThis(), item.ParentSheetIndex, giftTastes, GiftTaste.Hate, onlyRevealed: this.ProgressionMode)); } } // fence if (item is Fence fence) { string healthLabel = L10n.Item.FenceHealth(); // health if (Game1.getFarm().isBuildingConstructed(Constant.BuildingNames.GoldClock)) { yield return(new GenericField(this.GameHelper, healthLabel, L10n.Item.FenceHealthGoldClock())); } else { float maxHealth = fence.isGate.Value ? fence.maxHealth.Value * 2 : fence.maxHealth.Value; float health = fence.health.Value / maxHealth; double daysLeft = Math.Round(fence.health.Value * metadata.Constants.FenceDecayRate / 60 / 24); double percent = Math.Round(health * 100); yield return(new PercentageBarField(this.GameHelper, healthLabel, (int)fence.health.Value, (int)maxHealth, Color.Green, Color.Red, L10n.Item.FenceHealthSummary(percent: (int)percent, count: (int)daysLeft))); } } // movie ticket if (obj?.ParentSheetIndex == 809 && !obj.bigCraftable.Value) { MovieData movie = MovieTheater.GetMovieForDate(Game1.Date); if (movie == null) { yield return(new GenericField(this.GameHelper, L10n.MovieTicket.MovieThisWeek(), L10n.MovieTicket.NoMovieThisWeek())); } else { // movie this week yield return(new GenericField(this.GameHelper, L10n.MovieTicket.MovieThisWeek(), new IFormattedText[] { new FormattedText(movie.Title, bold: true), new FormattedText(Environment.NewLine), new FormattedText(movie.Description) })); // movie tastes IDictionary <GiftTaste, string[]> tastes = this.GameHelper.GetMovieTastes(metadata) .GroupBy(entry => entry.Value) .ToDictionary(group => group.Key, group => group.Select(p => p.Key.Name).OrderBy(p => p).ToArray()); yield return(new MovieTastesField(this.GameHelper, L10n.MovieTicket.LovesMovie(), tastes, GiftTaste.Love)); yield return(new MovieTastesField(this.GameHelper, L10n.MovieTicket.LikesMovie(), tastes, GiftTaste.Like)); yield return(new MovieTastesField(this.GameHelper, L10n.MovieTicket.DislikesMovie(), tastes, GiftTaste.Dislike)); } } // recipes switch (item.GetSpriteType()) { // for ingredient case ItemSpriteType.Object: { RecipeModel[] recipes = this.GameHelper.GetRecipesForIngredient(this.DisplayItem).ToArray(); if (recipes.Any()) { yield return(new RecipesForIngredientField(this.GameHelper, L10n.Item.Recipes(), item, recipes)); } } break; // for machine case ItemSpriteType.BigCraftable: { RecipeModel[] recipes = this.GameHelper.GetRecipesForMachine(this.DisplayItem as SObject).ToArray(); if (recipes.Any()) { yield return(new RecipesForMachineField(this.GameHelper, L10n.Item.Recipes(), recipes)); } } break; } // dyes yield return(new ColorField(this.GameHelper, L10n.Item.ProducesDye(), item)); // owned and times cooked/crafted if (showInventoryFields && !isCrop && !(item is Tool)) { // owned yield return(new GenericField(this.GameHelper, L10n.Item.Owned(), L10n.Item.OwnedSummary(count: this.GameHelper.CountOwnedItems(item)))); // times crafted RecipeModel[] recipes = this.GameHelper .GetRecipes() .Where(recipe => recipe.OutputItemIndex == this.Target.ParentSheetIndex) .ToArray(); if (recipes.Any()) { string label = recipes.First().Type == RecipeType.Cooking ? L10n.Item.Cooked() : L10n.Item.Crafted(); int timesCrafted = recipes.Sum(recipe => recipe.GetTimesCrafted(Game1.player)); if (timesCrafted >= 0) // negative value means not available for this recipe type { yield return(new GenericField(this.GameHelper, label, L10n.Item.CraftedSummary(count: timesCrafted))); } } } // see also crop bool seeAlsoCrop = isSeed && item.ParentSheetIndex != this.SeedForCrop.indexOfHarvest.Value && // skip seeds which produce themselves (e.g. coffee beans) !(item.ParentSheetIndex >= 495 && item.ParentSheetIndex <= 497) && // skip random seasonal seeds item.ParentSheetIndex != 770; // skip mixed seeds if (seeAlsoCrop) { Item drop = this.GameHelper.GetObjectBySpriteIndex(this.SeedForCrop.indexOfHarvest.Value); yield return(new LinkField(this.GameHelper, L10n.Item.SeeAlso(), drop.DisplayName, () => new ItemSubject(this.GameHelper, this.Text, this.ProgressionMode, drop, ObjectContext.Inventory, false, this.SeedForCrop))); } }
/// <summary>Get the data to display for this subject.</summary> /// <param name="metadata">Provides metadata that's not available from the game data directly.</param> public override IEnumerable <ICustomField> GetData(Metadata metadata) { // get data Item item = this.Target; Object obj = item as Object; bool isObject = obj != null; bool isCrop = this.FromCrop != null; bool isSeed = this.SeedForCrop != null; bool isDeadCrop = this.FromCrop?.dead == true; bool canSell = obj?.canBeShipped() == true || metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.category)); // get overrides bool showInventoryFields = true; { ObjectData objData = metadata.GetObject(item, this.Context); if (objData != null) { this.Name = objData.Name ?? this.Name; this.Description = objData.Description ?? this.Description; this.Type = objData.Type ?? this.Type; showInventoryFields = objData.ShowInventoryFields ?? true; } } // don't show data for dead crop if (isDeadCrop) { yield return(new GenericField("Crop", "This crop is dead.")); yield break; } // crop fields if (isCrop || isSeed) { // get crop Crop crop = this.FromCrop ?? this.SeedForCrop; // get harvest schedule int harvestablePhase = crop.phaseDays.Count - 1; bool canHarvestNow = (crop.currentPhase >= harvestablePhase) && (!crop.fullyGrown || crop.dayOfCurrentPhase <= 0); int daysToFirstHarvest = crop.phaseDays.Take(crop.phaseDays.Count - 1).Sum(); // ignore harvestable phase // add next-harvest field if (isCrop) { // calculate next harvest int daysToNextHarvest = 0; GameDate dayOfNextHarvest = null; if (!canHarvestNow) { // calculate days until next harvest int daysUntilLastPhase = daysToFirstHarvest - crop.dayOfCurrentPhase - crop.phaseDays.Take(crop.currentPhase).Sum(); { // growing: days until next harvest if (!crop.fullyGrown) { daysToNextHarvest = daysUntilLastPhase; } // regrowable crop harvested today else if (crop.dayOfCurrentPhase >= crop.regrowAfterHarvest) { daysToNextHarvest = crop.regrowAfterHarvest; } // regrowable crop else { daysToNextHarvest = crop.dayOfCurrentPhase; // dayOfCurrentPhase decreases to 0 when fully grown, where <=0 is harvestable } } dayOfNextHarvest = GameHelper.GetDate(metadata.Constants.DaysInSeason).GetDayOffset(daysToNextHarvest); } // generate field string summary; if (canHarvestNow) { summary = "now"; } else if (Game1.currentLocation.Name != Constant.LocationNames.Greenhouse && !crop.seasonsToGrowIn.Contains(dayOfNextHarvest.Season)) { summary = $"too late in the season for the next harvest (would be on {dayOfNextHarvest})"; } else { summary = $"{dayOfNextHarvest} ({TextHelper.Pluralise(daysToNextHarvest, "tomorrow", $"in {daysToNextHarvest} days")})"; } yield return(new GenericField("Harvest", summary)); } // crop summary { List <string> summary = new List <string>(); // harvest summary.Add($"-harvest after {daysToFirstHarvest} {TextHelper.Pluralise(daysToFirstHarvest, "day")}" + (crop.regrowAfterHarvest != -1 ? $", then every {TextHelper.Pluralise(crop.regrowAfterHarvest, "day", $"{crop.regrowAfterHarvest} days")}" : "")); // seasons summary.Add($"-grows in {string.Join(", ", crop.seasonsToGrowIn)}"); // drops if (crop.minHarvest != crop.maxHarvest && crop.chanceForExtraCrops > 0) { summary.Add($"-drops {crop.minHarvest} to {crop.maxHarvest} ({Math.Round(crop.chanceForExtraCrops * 100, 2)}% chance of extra crops)"); } else if (crop.minHarvest > 1) { summary.Add($"-drops {crop.minHarvest}"); } // crop sale price Item drop = GameHelper.GetObjectBySpriteIndex(crop.indexOfHarvest); summary.Add($"-sells for {GenericField.GetSaleValueString(this.GetSaleValue(drop, false, metadata), 1)}"); // generate field yield return(new GenericField("Crop", string.Join(Environment.NewLine, summary))); } } // crafting if (obj?.heldObject != null) { if (obj is Cask) { // get cask data Cask cask = (Cask)obj; Object agingObj = cask.heldObject; ItemQuality currentQuality = (ItemQuality)agingObj.quality; // calculate aging schedule float effectiveAge = metadata.Constants.CaskAgeSchedule.Values.Max() - cask.daysToMature; var schedule = ( from entry in metadata.Constants.CaskAgeSchedule let quality = entry.Key let baseDays = entry.Value where baseDays > effectiveAge orderby baseDays ascending let daysLeft = (int)Math.Ceiling((baseDays - effectiveAge) / cask.agingRate) select new { Quality = quality, DaysLeft = daysLeft, HarvestDate = GameHelper.GetDate(metadata.Constants.DaysInSeason).GetDayOffset(daysLeft) } ) .ToArray(); // display fields yield return(new ItemIconField("Contents", obj.heldObject)); if (cask.minutesUntilReady <= 0 || !schedule.Any()) { yield return(new GenericField("Aging", $"{currentQuality.GetName()} quality ready")); } else { string scheduleStr = string.Join(Environment.NewLine, (from entry in schedule select $"-{entry.Quality.GetName()} {TextHelper.Pluralise(entry.DaysLeft, "tomorrow", $"in {entry.DaysLeft} days")} ({entry.HarvestDate})")); yield return(new GenericField("Aging", $"-{currentQuality.GetName()} now (use pickaxe to stop aging){Environment.NewLine}" + scheduleStr)); } } else { yield return(new ItemIconField("Contents", obj.heldObject, $"{obj.heldObject.Name} " + (obj.minutesUntilReady > 0 ? "in " + TextHelper.Stringify(TimeSpan.FromMinutes(obj.minutesUntilReady)) : "ready"))); } } // item if (showInventoryFields) { // needed for { List <string> neededFor = new List <string>(); // bundles if (isObject) { string[] bundles = (from bundle in this.GetUnfinishedBundles(obj) orderby bundle.Area, bundle.Name select $"{bundle.Area}: {bundle.Name}").ToArray(); if (bundles.Any()) { neededFor.Add($"community center ({string.Join(", ", bundles)})"); } } // polyculture achievement if (isObject && metadata.Constants.PolycultureCrops.Contains(obj.ParentSheetIndex)) { int needed = metadata.Constants.PolycultureCount - GameHelper.GetShipped(obj.ParentSheetIndex); if (needed > 0) { neededFor.Add($"polyculture achievement (ship {needed} more)"); } } // full shipment achievement if (isObject && GameHelper.GetFullShipmentAchievementItems().Any(p => p.Key == obj.ParentSheetIndex && !p.Value)) { neededFor.Add("full shipment achievement (ship one)"); } // a full collection achievement LibraryMuseum museum = Game1.locations.OfType <LibraryMuseum>().FirstOrDefault(); if (museum != null && museum.isItemSuitableForDonation(obj)) { neededFor.Add("full collection achievement (donate one to museum)"); } // yield if (neededFor.Any()) { yield return(new GenericField("Needed for", string.Join(", ", neededFor))); } } // sale data if (canSell && !isCrop) { // sale price string saleValueSummary = GenericField.GetSaleValueString(this.GetSaleValue(item, this.KnownQuality, metadata), item.Stack); yield return(new GenericField("Sells for", saleValueSummary)); // sell to List <string> buyers = new List <string>(); if (obj?.canBeShipped() == true) { buyers.Add("shipping box"); } buyers.AddRange(from shop in metadata.Shops where shop.BuysCategories.Contains(item.category) orderby shop.DisplayName select shop.DisplayName); yield return(new GenericField("Sells to", TextHelper.OrList(buyers.ToArray()))); } // gift tastes var giftTastes = this.GetGiftTastes(item, metadata); yield return(new ItemGiftTastesField("Loves this", giftTastes, GiftTaste.Love)); yield return(new ItemGiftTastesField("Likes this", giftTastes, GiftTaste.Like)); } // fence if (item is Fence) { Fence fence = (Fence)item; // health if (Game1.getFarm().isBuildingConstructed(Constant.BuildingNames.GoldClock)) { yield return(new GenericField("Health", "no decay with Gold Clock")); } else { float maxHealth = fence.isGate ? fence.maxHealth * 2 : fence.maxHealth; float health = fence.health / maxHealth; float daysLeft = fence.health * metadata.Constants.FenceDecayRate / 60 / 24; yield return(new PercentageBarField("Health", (int)fence.health, (int)maxHealth, Color.Green, Color.Red, $"{Math.Round(health * 100)}% (roughly {Math.Round(daysLeft)} days left)")); } } // recipes if (item.GetSpriteType() == ItemSpriteType.Object) { RecipeModel[] recipes = GameHelper.GetRecipesForIngredient(this.DisplayItem).ToArray(); if (recipes.Any()) { yield return(new RecipesForIngredientField("Recipes", item, recipes)); } } // owned if (showInventoryFields && !isCrop && !(item is Tool)) { yield return(new GenericField("Owned", $"you own {GameHelper.CountOwnedItems(item)} of these")); } }
public bool HighlightItemToShip(Item i) { SObject @object = i as SObject; return(@object != null && @object.canBeShipped()); }