/// <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> /// <remarks>Tree growth algorithm reverse engineered from <see cref="StardewValley.TerrainFeatures.Tree.dayUpdate"/>.</remarks> public override IEnumerable <ICustomField> GetData(Metadata metadata) { Tree tree = this.Target; // get growth stage WildTreeGrowthStage stage = (WildTreeGrowthStage)Math.Min(tree.growthStage.Value, (int)WildTreeGrowthStage.Tree); bool isFullyGrown = stage == WildTreeGrowthStage.Tree; yield return(new GenericField(this.GameHelper, L10n.Tree.Stage(), isFullyGrown ? L10n.Tree.StageDone() : L10n.Tree.StagePartial(stageName: L10n.For(stage), step: (int)stage, max: (int)WildTreeGrowthStage.Tree) )); // get growth schedule if (!isFullyGrown) { string label = L10n.Tree.NextGrowth(); if (Game1.IsWinter && !Game1.currentLocation.IsGreenhouse) { yield return(new GenericField(this.GameHelper, label, L10n.Tree.NextGrowthWinter())); } else if (stage == WildTreeGrowthStage.SmallTree && this.HasAdjacentTrees(this.Tile)) { yield return(new GenericField(this.GameHelper, label, L10n.Tree.NextGrowthAdjacentTrees())); } else { yield return(new GenericField(this.GameHelper, label, L10n.Tree.NextGrowthChance(stage: L10n.For(stage + 1), chance: tree.fertilized.Value ? 100 : 20))); } } // get fertilizer if (!isFullyGrown) { yield return(new GenericField(this.GameHelper, L10n.Tree.IsFertilized(), this.Stringify(tree.fertilized.Value) + (tree.fertilized.Value ? $" ({L10n.Tree.IsFertilizedEffects()})" : ""))); } // get seed if (isFullyGrown) { yield return(new GenericField(this.GameHelper, L10n.Tree.HasSeed(), this.Stringify(tree.hasSeed.Value))); } }
/// <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) { NPC npc = this.Target; switch (this.TargetType) { case TargetType.Villager: // special NPCs like Gunther if (metadata.Constants.AsocialVillagers.Contains(npc.name)) { // no data } // children else if (npc is Child child) { // birthday SDate birthday = SDate.Now().AddDays(-child.daysOld); yield return(new GenericField(this.Text.Get(L10n.Npc.Birthday), this.Text.Stringify(birthday, withYear: true))); // age { ChildAge stage = (ChildAge)child.age; int daysOld = child.daysOld; int daysToNext = this.GetDaysToNextChildGrowth(stage, daysOld); bool isGrown = daysToNext == -1; int daysAtNext = daysOld + (isGrown ? 0 : daysToNext); string ageLabel = this.Translate(L10n.NpcChild.Age); string ageName = this.Translate(L10n.For(stage)); string ageDesc = isGrown ? this.Translate(L10n.NpcChild.AgeDescriptionGrown, new { label = ageName }) : this.Translate(L10n.NpcChild.AgeDescriptionPartial, new { label = ageName, count = daysToNext, nextLabel = this.Text.Get(L10n.For(stage + 1)) }); yield return(new PercentageBarField(ageLabel, child.daysOld, daysAtNext, Color.Green, Color.Gray, ageDesc)); } // friendship if (Game1.player.friendships.ContainsKey(child.name)) { FriendshipModel friendship = DataParser.GetFriendshipForVillager(Game1.player, child, metadata); yield return(new CharacterFriendshipField(this.Translate(L10n.Npc.Friendship), friendship, this.Text)); yield return(new GenericField(this.Translate(L10n.Npc.TalkedToday), this.Stringify(Game1.player.friendships[child.name][2] == 1))); } } // villagers else { // birthday if (npc.birthday_Season != null) { SDate birthday = new SDate(npc.birthday_Day, npc.birthday_Season); yield return(new GenericField(this.Text.Get(L10n.Npc.Birthday), this.Text.Stringify(birthday))); } // friendship if (Game1.player.friendships.ContainsKey(npc.name)) { FriendshipModel friendship = DataParser.GetFriendshipForVillager(Game1.player, npc, metadata); yield return(new GenericField(this.Translate(L10n.Npc.CanRomance), friendship.IsSpouse ? this.Translate(L10n.Npc.CanRomanceMarried) : this.Stringify(npc.datable))); yield return(new CharacterFriendshipField(this.Translate(L10n.Npc.Friendship), friendship, this.Text)); yield return(new GenericField(this.Translate(L10n.Npc.TalkedToday), this.Stringify(Game1.player.friendships[npc.name][2] == 1))); yield return(new GenericField(this.Translate(L10n.Npc.GiftedToday), this.Stringify(Game1.player.friendships[npc.name][3] > 0))); if (!friendship.IsSpouse) { yield return(new GenericField(this.Translate(L10n.Npc.GiftedThisWeek), this.Translate(L10n.Generic.Ratio, new { value = Game1.player.friendships[npc.name][1], max = NPC.maxGiftsPerWeek }))); } } else { yield return(new GenericField(this.Translate(L10n.Npc.Friendship), this.Translate(L10n.Npc.FriendshipNotMet))); } // gift tastes var giftTastes = this.GetGiftTastes(npc, metadata); yield return(new CharacterGiftTastesField(this.Translate(L10n.Npc.LovesGifts), giftTastes, GiftTaste.Love)); yield return(new CharacterGiftTastesField(this.Translate(L10n.Npc.LikesGifts), giftTastes, GiftTaste.Like)); } break; case TargetType.Pet: Pet pet = (Pet)npc; yield return(new CharacterFriendshipField(this.Translate(L10n.Pet.Love), DataParser.GetFriendshipForPet(Game1.player, pet), this.Text)); yield return(new GenericField(this.Translate(L10n.Pet.PettedToday), this.Stringify(this.Reflection.GetField <bool>(pet, "wasPetToday").GetValue()))); break; case TargetType.Monster: // basic info Monster monster = (Monster)npc; yield return(new GenericField(this.Translate(L10n.Monster.Invincible), this.Translate(L10n.Generic.Seconds, new { count = this.Reflection.GetField <int>(monster, "invincibleCountdown").GetValue() }), hasValue: monster.isInvincible())); yield return(new PercentageBarField(this.Translate(L10n.Monster.Health), monster.health, monster.maxHealth, Color.Green, Color.Gray, this.Translate(L10n.Generic.PercentRatio, new { percent = Math.Round((monster.health / (monster.maxHealth * 1f) * 100)), value = monster.health, max = monster.maxHealth }))); yield return(new ItemDropListField(this.Translate(L10n.Monster.Drops), this.GetMonsterDrops(monster), this.Text, defaultText: this.Translate(L10n.Monster.DropsNothing))); yield return(new GenericField(this.Translate(L10n.Monster.Experience), this.Stringify(monster.experienceGained))); yield return(new GenericField(this.Translate(L10n.Monster.Defence), this.Stringify(monster.resilience))); yield return(new GenericField(this.Translate(L10n.Monster.Attack), this.Stringify(monster.damageToFarmer))); // Adventure Guild quest AdventureGuildQuestData adventureGuildQuest = metadata.GetAdventurerGuildQuest(monster.name); if (adventureGuildQuest != null) { int kills = adventureGuildQuest.Targets.Select(p => Game1.stats.getMonstersKilled(p)).Sum(); yield return(new GenericField(this.Translate(L10n.Monster.AdventureGuild), $"{this.Translate(kills >= adventureGuildQuest.RequiredKills ? L10n.Monster.AdventureGuildComplete : L10n.Monster.AdventureGuildIncomplete)} ({this.Translate(L10n.Monster.AdventureGuildProgress, new { count = kills, requiredCount = adventureGuildQuest.RequiredKills })})")); } break; } }
/// <summary>Draw the value (or return <c>null</c> to render the <see cref="GenericField.Value"/> using the default format).</summary> /// <param name="spriteBatch">The sprite batch being drawn.</param> /// <param name="font">The recommended font.</param> /// <param name="position">The position at which to draw.</param> /// <param name="wrapWidth">The maximum width before which content should be wrapped.</param> /// <returns>Returns the drawn dimensions, or <c>null</c> to draw the <see cref="GenericField.Value"/> using the default format.</returns> public override Vector2?DrawValue(SpriteBatch spriteBatch, SpriteFont font, Vector2 position, float wrapWidth) { FriendshipModel friendship = this.Friendship; // draw status float leftOffset = 0; { string statusText = this.Translations.Get(L10n.For(friendship.Status)); Vector2 textSize = spriteBatch.DrawTextBlock(font, statusText, new Vector2(position.X + leftOffset, position.Y), wrapWidth - leftOffset); leftOffset += textSize.X + DrawHelper.GetSpaceWidth(font); } // draw hearts for (int i = 0; i < friendship.TotalHearts; i++) { // get icon Color color; Rectangle icon; if (friendship.LockedHearts >= friendship.TotalHearts - i) { icon = Sprites.Icons.FilledHeart; color = Color.Black * 0.35f; } else if (i >= friendship.FilledHearts) { icon = Sprites.Icons.EmptyHeart; color = Color.White; } else { icon = Sprites.Icons.FilledHeart; color = Color.White; } // draw spriteBatch.DrawSprite(Sprites.Icons.Sheet, icon, position.X + leftOffset, position.Y, color, Game1.pixelZoom); leftOffset += Sprites.Icons.FilledHeart.Width * Game1.pixelZoom; } // draw stardrop (if applicable) if (friendship.HasStardrop) { leftOffset += 1; float zoom = (Sprites.Icons.EmptyHeart.Height / (Sprites.Icons.Stardrop.Height * 1f)) * Game1.pixelZoom; spriteBatch.DrawSprite(Sprites.Icons.Sheet, Sprites.Icons.Stardrop, position.X + leftOffset, position.Y, Color.White * 0.25f, zoom); leftOffset += Sprites.Icons.Stardrop.Width * zoom; } // get caption text string caption = null; if (this.Friendship.EmptyHearts == 0 && this.Friendship.LockedHearts > 0) { caption = $"({this.Translations.Get(L10n.Npc.FriendshipNeedBouquet)})"; } else { int pointsToNext = this.Friendship.GetPointsToNext(); if (pointsToNext > 0) { caption = $"({this.Translations.Get(L10n.Npc.FriendshipNeedPoints, new { count = pointsToNext })})"; } } // draw caption { float spaceSize = DrawHelper.GetSpaceWidth(font); Vector2 textSize = Vector2.Zero; if (caption != null) { textSize = spriteBatch.DrawTextBlock(font, caption, new Vector2(position.X + leftOffset + spaceSize, position.Y), wrapWidth - leftOffset); } return(new Vector2(Sprites.Icons.FilledHeart.Width * Game1.pixelZoom * this.Friendship.TotalHearts + textSize.X + spaceSize, Math.Max(Sprites.Icons.FilledHeart.Height * Game1.pixelZoom, textSize.Y))); } }
/// <summary>Get the custom fields for machine output.</summary> /// <param name="machine">The machine whose output to represent.</param> /// <param name="metadata">Provides metadata that's not available from the game data directly.</param> private IEnumerable <ICustomField> GetMachineOutputFields(SObject machine, Metadata metadata) { if (machine == null) { yield break; } SObject heldObj = machine.heldObject.Value; int minutesLeft = machine.MinutesUntilReady; // cask if (machine is Cask cask) { // output item if (heldObj != null) { ItemQuality curQuality = (ItemQuality)heldObj.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.GameHelper, this.Translate(L10n.Item.Contents), heldObj)); if (minutesLeft <= 0 || !schedule.Any()) { yield return(new GenericField(this.GameHelper, 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.GameHelper, this.Translate(L10n.Item.CaskSchedule), this.Translate(L10n.Item.CaskSchedulePartial, new { quality = curQualityName }) + Environment.NewLine + scheduleStr)); } } } // crab pot else if (machine is CrabPot pot) { // bait if (heldObj == null) { if (pot.bait.Value != null) { yield return(new ItemIconField(this.GameHelper, this.Translate(L10n.Item.CrabpotBait), pot.bait.Value)); } else if (Game1.player.professions.Contains(11)) // no bait needed if luremaster { yield return(new GenericField(this.GameHelper, this.Translate(L10n.Item.CrabpotBait), this.Translate(L10n.Item.CrabpotBaitNotNeeded))); } else { yield return(new GenericField(this.GameHelper, this.Translate(L10n.Item.CrabpotBait), this.Translate(L10n.Item.CrabpotBaitNeeded))); } } // output item if (heldObj != null) { string summary = this.Translate(L10n.Item.ContentsReady, new { name = heldObj.DisplayName }); yield return(new ItemIconField(this.GameHelper, this.Translate(L10n.Item.Contents), heldObj, summary)); } } // furniture else if (machine is Furniture) { // displayed item if (heldObj != null) { string summary = this.Translate(L10n.Item.ContentsPlaced, new { name = heldObj.DisplayName }); yield return(new ItemIconField(this.GameHelper, this.Translate(L10n.Item.Contents), heldObj, summary)); } } // auto-grabber else if (machine.ParentSheetIndex == Constant.ObjectIndexes.AutoGrabber) { string readyText = this.Text.Stringify(heldObj is Chest output && output.items.Any()); yield return(new GenericField(this.GameHelper, this.Translate(L10n.Item.Contents), readyText)); } // generic machine else { // output item if (heldObj != null) { string summary = minutesLeft <= 0 ? this.Translate(L10n.Item.ContentsReady, new { name = heldObj.DisplayName }) : this.Translate(L10n.Item.ContentsPartial, new { name = heldObj.DisplayName, time = this.Stringify(TimeSpan.FromMinutes(minutesLeft)) }); yield return(new ItemIconField(this.GameHelper, this.Translate(L10n.Item.Contents), heldObj, summary)); } } }
/// <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> /// <remarks>Tree growth algorithm reverse engineered from <see cref="FruitTree.dayUpdate"/>.</remarks> public override IEnumerable <ICustomField> GetData(Metadata metadata) { FruitTree tree = this.Target; // get basic info bool isMature = tree.daysUntilMature.Value <= 0; bool isDead = tree.stump; bool isStruckByLightning = tree.struckByLightningCountdown.Value > 0; // show next fruit if (isMature && !isDead) { string label = this.Translate(L10n.FruitTree.NextFruit); if (isStruckByLightning) { yield return(new GenericField(label, this.Translate(L10n.FruitTree.NextFruitStruckByLightning, new { count = tree.struckByLightningCountdown }))); } else if (Game1.currentSeason != tree.fruitSeason.Value && !tree.GreenHouseTree) { yield return(new GenericField(label, this.Translate(L10n.FruitTree.NextFruitOutOfSeason))); } else if (tree.fruitsOnTree.Value == FruitTree.maxFruitsOnTrees) { yield return(new GenericField(label, this.Translate(L10n.FruitTree.NextFruitMaxFruit))); } else { yield return(new GenericField(label, this.Translate(L10n.Generic.Tomorrow))); } } // show growth data if (!isMature) { SDate dayOfMaturity = SDate.Now().AddDays(tree.daysUntilMature); string grownOnDateText = this.Translate(L10n.FruitTree.GrowthSummary, new { date = this.Stringify(dayOfMaturity) }); string daysUntilGrownText = this.Text.GetPlural(tree.daysUntilMature, L10n.Generic.Tomorrow, L10n.Generic.InXDays).Tokens(new { count = tree.daysUntilMature }); string growthText = $"{grownOnDateText} ({daysUntilGrownText})"; yield return(new GenericField(this.Translate(L10n.FruitTree.NextFruit), this.Translate(L10n.FruitTree.NextFruitTooYoung))); yield return(new GenericField(this.Translate(L10n.FruitTree.Growth), growthText)); if (this.HasAdjacentObjects(this.Tile)) { yield return(new GenericField(this.Translate(L10n.FruitTree.Complaints), this.Translate(L10n.FruitTree.ComplaintsAdjacentObjects))); } } else { // get quality schedule ItemQuality currentQuality = this.GetCurrentQuality(tree, metadata.Constants.FruitTreeQualityGrowthTime); if (currentQuality == ItemQuality.Iridium) { yield return(new GenericField(this.Translate(L10n.FruitTree.Quality), this.Translate(L10n.FruitTree.QualityNow, new { quality = this.Translate(L10n.For(currentQuality)) }))); } else { string[] summary = this .GetQualitySchedule(tree, currentQuality, metadata.Constants.FruitTreeQualityGrowthTime) .Select(entry => { // read schedule ItemQuality quality = entry.Key; int daysLeft = entry.Value; SDate date = SDate.Now().AddDays(daysLeft); int yearOffset = date.Year - Game1.year; // generate summary line string qualityName = this.Translate(L10n.For(quality)); if (daysLeft <= 0) { return("-" + this.Translate(L10n.FruitTree.QualityNow, new { quality = qualityName })); } string line; if (yearOffset == 0) { line = $"-{this.Translate(L10n.FruitTree.QualityOnDate, new { quality = qualityName, date = this.Stringify(date) })}"; } else if (yearOffset == 1) { line = $"-{this.Translate(L10n.FruitTree.QualityOnDateNextYear, new { quality = qualityName, date = this.Stringify(date) })}"; } else { line = $"-{this.Translate(L10n.FruitTree.QualityOnDate, new { quality = qualityName, date = this.Text.Stringify(date, withYear: true), year = date.Year })}"; } line += $" ({this.Text.GetPlural(daysLeft, L10n.Generic.Tomorrow, L10n.Generic.InXDays).Tokens(new { count = daysLeft })})"; return(line); }) .ToArray(); yield return(new GenericField(this.Translate(L10n.FruitTree.Quality), string.Join(Environment.NewLine, summary))); } } // show season yield return(new GenericField(this.Translate(L10n.FruitTree.Season), this.Translate(L10n.FruitTree.SeasonSummary, new { season = this.Text.GetSeasonName(tree.fruitSeason) }))); }
/// <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> /// <remarks>Tree growth algorithm reverse engineered from <see cref="StardewValley.TerrainFeatures.Tree.dayUpdate"/>.</remarks> public override IEnumerable <ICustomField> GetData(Metadata metadata) { Tree tree = this.Target; // get growth stage WildTreeGrowthStage stage = (WildTreeGrowthStage)Math.Min(tree.growthStage, (int)WildTreeGrowthStage.Tree); bool isFullyGrown = stage == WildTreeGrowthStage.Tree; yield return(new GenericField(this.Translate(L10n.Tree.Stage), isFullyGrown ? this.Translate(L10n.Tree.StageDone) : this.Translate(L10n.Tree.StagePartial, new { stageName = this.Translate(L10n.For(stage)), step = (int)stage, max = (int)WildTreeGrowthStage.Tree }) )); // get growth scheduler if (!isFullyGrown) { string label = this.Translate(L10n.Tree.NextGrowth); if (Game1.IsWinter && Game1.currentLocation.Name != Constant.LocationNames.Greenhouse) { yield return(new GenericField(label, this.Translate(L10n.Tree.NextGrowthWinter))); } else if (stage == WildTreeGrowthStage.SmallTree && this.HasAdjacentTrees(this.Tile)) { yield return(new GenericField(label, this.Translate(L10n.Tree.NextGrowthAdjacentTrees))); } else { yield return(new GenericField(label, this.Translate(L10n.Tree.NextGrowthRandom, new { stage = this.Translate(L10n.For(stage + 1)) }))); } } // get seed if (isFullyGrown) { yield return(new GenericField(this.Translate(L10n.Tree.HasSeed), this.Stringify(tree.hasSeed))); } }
/// <summary>Get the display value for sale price data.</summary> /// <param name="saleValues">The sale price data.</param> /// <param name="stackSize">The number of items in the stack.</param> /// <param name="translations">Provides methods for fetching translations and generating text.</param> public static string GetSaleValueString(IDictionary <ItemQuality, int> saleValues, int stackSize, ITranslationHelper translations) { // can't be sold if (saleValues == null || !saleValues.Any() || saleValues.Values.All(p => p == 0)) { return(null); } // one quality if (saleValues.Count == 1) { string result = translations.Get(L10n.Generic.Price, new { price = saleValues.First().Value }); if (stackSize > 1 && stackSize <= Constant.MaxStackSizeForPricing) { result += $" ({translations.Get(L10n.Generic.PriceForStack, new { price = saleValues.First().Value * stackSize, count = stackSize })})"; } return(result); } // prices by quality List <string> priceStrings = new List <string>(); for (ItemQuality quality = ItemQuality.Normal; ; quality = quality.GetNext()) { if (saleValues.ContainsKey(quality)) { priceStrings.Add(quality == ItemQuality.Normal ? translations.Get(L10n.Generic.Price, new { price = saleValues[quality] }) : translations.Get(L10n.Generic.PriceForQuality, new { price = saleValues[quality], quality = translations.Get(L10n.For(quality)) }) ); } if (quality.GetNext() == quality) { break; } } return(string.Join(", ", priceStrings)); }