/// <summary>Get the data to display for this subject.</summary> public override IEnumerable <ICustomField> GetData() { FarmAnimal animal = this.Target; // calculate maturity bool isFullyGrown = animal.age.Value >= animal.ageWhenMature.Value; int daysUntilGrown = 0; SDate dayOfMaturity = null; if (!isFullyGrown) { daysUntilGrown = animal.ageWhenMature.Value - animal.age.Value; dayOfMaturity = SDate.Now().AddDays(daysUntilGrown); } // yield fields yield return(new CharacterFriendshipField(I18n.Animal_Love(), this.GameHelper.GetFriendshipForAnimal(Game1.player, animal))); yield return(new PercentageBarField(I18n.Animal_Happiness(), animal.happiness.Value, byte.MaxValue, Color.Green, Color.Gray, I18n.Generic_Percent(percent: (int)Math.Round(animal.happiness.Value / (this.Constants.AnimalMaxHappiness * 1f) * 100)))); yield return(new GenericField(I18n.Animal_Mood(), animal.getMoodMessage())); yield return(new GenericField(I18n.Animal_Complaints(), this.GetMoodReason(animal))); yield return(new ItemIconField(this.GameHelper, I18n.Animal_ProduceReady(), animal.currentProduce.Value > 0 ? this.GameHelper.GetObjectBySpriteIndex(animal.currentProduce.Value) : null)); if (!isFullyGrown) { yield return(new GenericField(I18n.Animal_Growth(), $"{I18n.Generic_Days(count: daysUntilGrown)} ({this.Stringify(dayOfMaturity)})")); } yield return(new GenericField(I18n.Animal_SellsFor(), GenericField.GetSaleValueString(animal.getSellPrice(), 1))); }
/// <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) { FarmAnimal animal = this.Target; // calculate maturity bool isFullyGrown = animal.age >= animal.ageWhenMature; int daysUntilGrown = 0; GameDate dayOfMaturity = null; if (!isFullyGrown) { daysUntilGrown = animal.ageWhenMature - animal.age; dayOfMaturity = GameHelper.GetDate(metadata.Constants.DaysInSeason).GetDayOffset(daysUntilGrown); } // yield fields yield return(new CharacterFriendshipField(this.Translate(L10n.Animal.Love), DataParser.GetFriendshipForAnimal(Game1.player, animal, metadata), this.Text)); yield return(new PercentageBarField(this.Translate(L10n.Animal.Happiness), animal.happiness, byte.MaxValue, Color.Green, Color.Gray, this.Translate(L10n.Generic.Percent, new { percent = Math.Round(animal.happiness / (metadata.Constants.AnimalMaxHappiness * 1f) * 100) }))); yield return(new GenericField(this.Translate(L10n.Animal.Mood), animal.getMoodMessage())); yield return(new GenericField(this.Translate(L10n.Animal.Complaints), this.GetMoodReason(animal))); yield return(new ItemIconField(this.Translate(L10n.Animal.ProduceReady), animal.currentProduce > 0 ? GameHelper.GetObjectBySpriteIndex(animal.currentProduce) : null)); if (!isFullyGrown) { yield return(new GenericField(this.Translate(L10n.Animal.Growth), $"{this.Translate(L10n.Generic.Days, new { count = daysUntilGrown })} ({this.Stringify(dayOfMaturity)})")); } yield return(new GenericField(this.Translate(L10n.Animal.SellsFor), GenericField.GetSaleValueString(animal.getSellPrice(), 1, this.Text))); }
/// <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) { FarmAnimal animal = this.Target; // calculate maturity bool isFullyGrown = animal.age >= animal.ageWhenMature; int daysUntilGrown = 0; GameDate dayOfMaturity = null; if (!isFullyGrown) { daysUntilGrown = animal.ageWhenMature - animal.age; dayOfMaturity = GameHelper.GetDate(metadata.Constants.DaysInSeason).GetDayOffset(daysUntilGrown); } // yield fields yield return(new CharacterFriendshipField("Love", DataParser.GetFriendshipForAnimal(Game1.player, animal, metadata))); yield return(new PercentageBarField("Happiness", animal.happiness, byte.MaxValue, Color.Green, Color.Gray, $"{Math.Round(animal.happiness / (metadata.Constants.AnimalMaxHappiness * 1f) * 100)}%")); yield return(new GenericField("Mood today", animal.getMoodMessage())); yield return(new GenericField("Complaints", this.GetMoodReason(animal))); yield return(new ItemIconField("Produce ready", animal.currentProduce > 0 ? GameHelper.GetObjectBySpriteIndex(animal.currentProduce) : null)); if (!isFullyGrown) { yield return(new GenericField("Growth", $"{daysUntilGrown} {TextHelper.Pluralise(daysUntilGrown, "day")} (on {dayOfMaturity})")); } yield return(new GenericField("Sells for", GenericField.GetSaleValueString(animal.getSellPrice(), 1))); }
/// <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 custom fields for a crop.</summary> /// <param name="dirt">The dirt the crop is planted in, if applicable.</param> /// <param name="crop">The crop to represent.</param> /// <param name="isSeed">Whether the crop being displayed is for an unplanted seed.</param> private IEnumerable <ICustomField> GetCropFields(HoeDirt?dirt, Crop?crop, bool isSeed) { if (crop == null) { yield break; } var data = new CropDataParser(crop, isPlanted: !isSeed); bool isForage = crop.whichForageCrop.Value > 0 && crop.fullyGrown.Value; // show crop fields for growing mixed seeds // add next-harvest field if (!isSeed) { // get next harvest SDate nextHarvest = data.GetNextHarvest(); // generate field string summary; if (data.CanHarvestNow) { summary = I18n.Generic_Now(); } else if (!Game1.currentLocation.SeedsIgnoreSeasonsHere() && !data.Seasons.Contains(nextHarvest.Season)) { summary = I18n.Crop_Harvest_TooLate(date: this.Stringify(nextHarvest)); } else { summary = $"{this.Stringify(nextHarvest)} ({this.GetRelativeDateStr(nextHarvest)})"; } yield return(new GenericField(I18n.Crop_Harvest(), summary)); } // crop summary if (!isForage) { List <string> summary = new(); // harvest if (!crop.forageCrop.Value) { summary.Add(data.HasMultipleHarvests ? I18n.Crop_Summary_HarvestOnce(daysToFirstHarvest: data.DaysToFirstHarvest) : I18n.Crop_Summary_HarvestMulti(daysToFirstHarvest: data.DaysToFirstHarvest, daysToNextHarvests: data.DaysToSubsequentHarvest) ); } // seasons summary.Add(I18n.Crop_Summary_Seasons(seasons: string.Join(", ", I18n.GetSeasonNames(data.Seasons)))); // drops if (crop.minHarvest != crop.maxHarvest && crop.chanceForExtraCrops.Value > 0) { summary.Add(I18n.Crop_Summary_DropsXToY(min: crop.minHarvest.Value, max: crop.maxHarvest.Value, percent: (int)Math.Round(crop.chanceForExtraCrops.Value * 100, 2))); } else if (crop.minHarvest.Value > 1) { summary.Add(I18n.Crop_Summary_DropsX(count: crop.minHarvest.Value)); } // crop sale price Item drop = data.GetSampleDrop(); summary.Add(I18n.Crop_Summary_SellsFor(price: GenericField.GetSaleValueString(this.GetSaleValue(drop, false), 1) !)); // generate field yield return(new GenericField(I18n.Crop_Summary(), "-" + string.Join($"{Environment.NewLine}-", summary))); } // dirt water/fertilizer state if (dirt != null && !isForage) { // watered yield return(new GenericField(I18n.Crop_Watered(), this.Stringify(dirt.state.Value == HoeDirt.watered))); // fertilizer string[] appliedFertilizers = this.GetAppliedFertilizers(dirt) .Select(GameI18n.GetObjectName) .Distinct() .DefaultIfEmpty(this.Stringify(false)) .OrderBy(p => p) .ToArray(); yield return(new GenericField(I18n.Crop_Fertilized(), string.Join(", ", appliedFertilizers))); } }
/// <summary>Get the data to display for this subject.</summary> public override IEnumerable <ICustomField> GetData() { // 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 || this.Metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.Category)); bool isMovieTicket = obj?.ParentSheetIndex == 809 && !obj.bigCraftable.Value; // 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; Bush?potBush = pot.bush.Value; 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))); } if (potBush != null) { ISubject?subject = this.Codex.GetByEntity(potBush, this.Location ?? potBush.currentLocation); if (subject != null) { yield return(new LinkField(I18n.Item_Contents(), subject.Name, () => subject)); } } } // 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(); 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 if (!isMovieTicket) { 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 || this.ShowAllGiftTastes) { 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) { RecipeModel[] recipes = // recipes that take this item as ingredient this.GameHelper.GetRecipesForIngredient(this.DisplayItem) .Concat(this.GameHelper.GetRecipesForIngredient(item)) // recipes which produce this item .Concat(this.GameHelper.GetRecipesForOutput(this.DisplayItem)) .Concat(this.GameHelper.GetRecipesForOutput(item)) // recipes for a machine .Concat(this.GameHelper.GetRecipesForMachine(this.DisplayItem as SObject)) .Concat(this.GameHelper.GetRecipesForMachine(item as SObject)) .ToArray(); if (recipes.Any()) { yield return(new ItemRecipesField(this.GameHelper, I18n.Item_Recipes(), item, recipes.ToArray())); } } // fish spawn rules if (item.Category == SObject.FishCategory) { yield return(new FishSpawnRulesField(this.GameHelper, I18n.Item_FishSpawnRules(), item.ParentSheetIndex)); } // fish pond data // derived from FishPond::doAction and FishPond::isLegalFishForPonds if (!item.HasContextTag("fish_legendary") && (item.Category == SObject.FishCategory || Utility.IsNormalObjectAtParentSheetIndex(item, 393 /*coral*/) || Utility.IsNormalObjectAtParentSheetIndex(item, 397 /*sea urchin*/))) { 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 (isMovieTicket) { 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 const GiftTaste rejectKey = (GiftTaste)(-1); IDictionary <GiftTaste, string[]> tastes = this.GameHelper.GetMovieTastes() .GroupBy(entry => entry.Value ?? rejectKey) .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)); yield return(new MovieTastesField(I18n.Item_MovieTicket_RejectsMovie(), tastes, rejectKey)); } } // 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 is not(495 or 496 or 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?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 custom fields for a crop.</summary> /// <param name="crop">The crop to represent.</param> /// <param name="isSeed">Whether the crop being displayed is for an unplanted seed.</param> /// <param name="metadata">Provides metadata that's not available from the game data directly.</param> private IEnumerable <ICustomField> GetCropFields(Crop crop, bool isSeed, Metadata metadata) { if (crop == null) { yield break; } var data = new CropDataParser(crop); // add next-harvest field if (!isSeed) { // get next harvest SDate nextHarvest = data.GetNextHarvest(); int daysToNextHarvest = nextHarvest.DaysSinceStart - SDate.Now().DaysSinceStart; // generate field string summary; if (data.CanHarvestNow) { summary = this.Translate(L10n.Crop.HarvestNow); } else if (!Game1.currentLocation.IsGreenhouse && !data.Seasons.Contains(nextHarvest.Season)) { summary = this.Translate(L10n.Crop.HarvestTooLate, new { date = this.Stringify(nextHarvest) }); } else { summary = $"{this.Stringify(nextHarvest)} ({this.Text.GetPlural(daysToNextHarvest, L10n.Generic.Tomorrow, L10n.Generic.InXDays).Tokens(new { count = daysToNextHarvest })})"; } yield return(new GenericField(this.GameHelper, this.Translate(L10n.Crop.Harvest), summary)); } // crop summary { List <string> summary = new List <string>(); // harvest summary.Add(data.HasMultipleHarvests ? this.Translate(L10n.Crop.SummaryHarvestOnce, new { daysToFirstHarvest = data.DaysToFirstHarvest }) : this.Translate(L10n.Crop.SummaryHarvestMulti, new { daysToFirstHarvest = data.DaysToFirstHarvest, daysToNextHarvests = data.DaysToSubsequentHarvest }) ); // seasons summary.Add(this.Translate(L10n.Crop.SummarySeasons, new { seasons = string.Join(", ", this.Text.GetSeasonNames(data.Seasons)) })); // 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 = data.GetSampleDrop(); 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.GameHelper, this.Translate(L10n.Crop.Summary), "-" + string.Join($"{Environment.NewLine}-", summary))); } }
/// <summary>Get the custom fields for a crop.</summary> /// <param name="dirt">The dirt the crop is planted in, if applicable.</param> /// <param name="crop">The crop to represent.</param> /// <param name="isSeed">Whether the crop being displayed is for an unplanted seed.</param> private IEnumerable <ICustomField> GetCropFields(HoeDirt dirt, Crop crop, bool isSeed) { if (crop == null) { yield break; } var data = new CropDataParser(crop, isPlanted: !isSeed); bool isForage = crop.whichForageCrop.Value > 0 && crop.fullyGrown.Value; // show crop fields for growing mixed seeds // add next-harvest field if (!isSeed) { // get next harvest SDate nextHarvest = data.GetNextHarvest(); // generate field string summary; if (data.CanHarvestNow) { summary = I18n.Generic_Now(); } else if (!Game1.currentLocation.SeedsIgnoreSeasonsHere() && !data.Seasons.Contains(nextHarvest.Season)) { summary = I18n.Crop_Harvest_TooLate(date: this.Stringify(nextHarvest)); } else { summary = $"{this.Stringify(nextHarvest)} ({this.GetRelativeDateStr(nextHarvest)})"; } yield return(new GenericField(I18n.Crop_Harvest(), summary)); } // crop summary if (!isForage) { List <string> summary = new List <string>(); // harvest if (!crop.forageCrop.Value) { summary.Add(data.HasMultipleHarvests ? I18n.Crop_Summary_HarvestOnce(daysToFirstHarvest: data.DaysToFirstHarvest) : I18n.Crop_Summary_HarvestMulti(daysToFirstHarvest: data.DaysToFirstHarvest, daysToNextHarvests: data.DaysToSubsequentHarvest) ); } // seasons summary.Add(I18n.Crop_Summary_Seasons(seasons: string.Join(", ", I18n.GetSeasonNames(data.Seasons)))); // drops if (crop.minHarvest != crop.maxHarvest && crop.chanceForExtraCrops.Value > 0) { summary.Add(I18n.Crop_Summary_DropsXToY(min: crop.minHarvest.Value, max: crop.maxHarvest.Value, percent: (int)Math.Round(crop.chanceForExtraCrops.Value * 100, 2))); } else if (crop.minHarvest.Value > 1) { summary.Add(I18n.Crop_Summary_DropsX(count: crop.minHarvest.Value)); } // crop sale price Item drop = data.GetSampleDrop(); summary.Add(I18n.Crop_Summary_SellsFor(price: GenericField.GetSaleValueString(this.GetSaleValue(drop, false), 1))); // generate field yield return(new GenericField(I18n.Crop_Summary(), "-" + string.Join($"{Environment.NewLine}-", summary))); } // dirt water/fertilizer state if (dirt != null && !isForage) { // watered yield return(new GenericField(I18n.Crop_Watered(), this.Stringify(dirt.state.Value == HoeDirt.watered))); // fertilizer yield return(new GenericField(I18n.Crop_Fertilized(), dirt.fertilizer.Value switch { HoeDirt.noFertilizer => this.Stringify(false), HoeDirt.speedGro => GameI18n.GetObjectName(465), // Speed-Gro HoeDirt.superSpeedGro => GameI18n.GetObjectName(466), // Deluxe Speed-Gro HoeDirt.hyperSpeedGro => GameI18n.GetObjectName(918), // Hyper Speed-Gro HoeDirt.fertilizerLowQuality => GameI18n.GetObjectName(368), // Basic Fertilizer HoeDirt.fertilizerHighQuality => GameI18n.GetObjectName(369), // Quality Fertilizer HoeDirt.fertilizerDeluxeQuality => GameI18n.GetObjectName(919), // Deluxe Fertilizer HoeDirt.waterRetentionSoil => GameI18n.GetObjectName(370), // Basic Retaining Soil HoeDirt.waterRetentionSoilQuality => GameI18n.GetObjectName(371), // Quality Retaining Soil HoeDirt.waterRetentionSoilDeluxe => GameI18n.GetObjectName(920), // Deluxe Retaining Soil _ => I18n.Generic_Unknown() }));
/// <summary>Get the custom fields for a crop.</summary> /// <param name="crop">The crop to represent.</param> /// <param name="isSeed">Whether the crop being displayed is for an unplanted seed.</param> private IEnumerable <ICustomField> GetCropFields(Crop crop, bool isSeed) { if (crop == null) { yield break; } var data = new CropDataParser(crop, isPlanted: !isSeed); // add next-harvest field if (!isSeed) { // get next harvest SDate nextHarvest = data.GetNextHarvest(); // generate field string summary; if (data.CanHarvestNow) { summary = I18n.Generic_Now(); } else if (!Game1.currentLocation.IsGreenhouse && !data.Seasons.Contains(nextHarvest.Season)) { summary = I18n.Crop_Harvest_TooLate(date: this.Stringify(nextHarvest)); } else { summary = $"{this.Stringify(nextHarvest)} ({this.GetRelativeDateStr(nextHarvest)})"; } yield return(new GenericField(I18n.Crop_Harvest(), summary)); } // crop summary if (crop.whichForageCrop.Value <= 0) { List <string> summary = new List <string>(); // harvest if (!crop.forageCrop.Value) { summary.Add(data.HasMultipleHarvests ? I18n.Crop_Summary_HarvestOnce(daysToFirstHarvest: data.DaysToFirstHarvest) : I18n.Crop_Summary_HarvestMulti(daysToFirstHarvest: data.DaysToFirstHarvest, daysToNextHarvests: data.DaysToSubsequentHarvest) ); } // seasons summary.Add(I18n.Crop_Summary_Seasons(seasons: string.Join(", ", I18n.GetSeasonNames(data.Seasons)))); // drops if (crop.minHarvest != crop.maxHarvest && crop.chanceForExtraCrops.Value > 0) { summary.Add(I18n.Crop_Summary_DropsXToY(min: crop.minHarvest.Value, max: crop.maxHarvest.Value, percent: (int)Math.Round(crop.chanceForExtraCrops.Value * 100, 2))); } else if (crop.minHarvest.Value > 1) { summary.Add(I18n.Crop_Summary_DropsX(count: crop.minHarvest.Value)); } // crop sale price Item drop = data.GetSampleDrop(); summary.Add(I18n.Crop_Summary_SellsFor(price: GenericField.GetSaleValueString(this.GetSaleValue(drop, false), 1))); // generate field yield return(new GenericField(I18n.Crop_Summary(), "-" + string.Join($"{Environment.NewLine}-", summary))); } }
/// <summary>Get the data to display for this subject.</summary> public override IEnumerable <ICustomField> GetData() { // 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 || this.Metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.Category)); // get overrides bool showInventoryFields = true; { ObjectData objData = this.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)) { 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, () => this.Codex.GetCrop(potCrop, ObjectContext.World))); } } // machine output foreach (ICustomField field in this.GetMachineOutputFields(obj)) { yield return(field); } // 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, 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 this.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); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.LovesThis(), giftTastes, GiftTaste.Love, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.LikesThis(), giftTastes, GiftTaste.Like, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); if (this.ProgressionMode || this.HighlightUnrevealedGiftTastes) { yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.NeutralAboutThis(), giftTastes, GiftTaste.Neutral, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.DislikesThis(), giftTastes, GiftTaste.Dislike, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(this.GameHelper, L10n.Item.HatesThis(), giftTastes, GiftTaste.Hate, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); } } // recipes switch (item.GetItemType()) { // for ingredient case ItemType.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 ItemType.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; } // fish if (item.Category == SObject.FishCategory) { // spawn rules yield return(new FishSpawnRulesField(this.GameHelper, L10n.Item.FishSpawnRules(), item.ParentSheetIndex, this.Text)); // 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 = L10n.Building.FishPondDropsPreface(chance: L10n.Generic.Range(min: minChanceOfAnyDrop, max: maxChanceOfAnyDrop)); yield return(new FishPondDropsField(this.GameHelper, L10n.Item.FishPondDrops(), -1, fishPondData, preface)); break; } } // 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 * this.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() .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)); } } // dyes yield return(new ColorField(this.GameHelper, L10n.Item.ProducesDye(), item)); // owned and times cooked/crafted if (showInventoryFields && !isCrop) { // 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, () => this.Codex.GetCrop(this.SeedForCrop, ObjectContext.Inventory))); } }
/// <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")); } }