Example #1
0
        /// <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)));
            }
        }
Example #2
0
        /// <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)));
            }
        }
Example #3
0
        /// <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)));
            }
        }
Example #5
0
        /// <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));
                }
            }
        }
Example #6
0
        /// <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) })));
        }
Example #7
0
        /// <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)));
            }
        }
Example #8
0
        /// <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));
        }