/// <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)));
        }
Пример #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)
        {
            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)));
        }
Пример #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)
        {
            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)));
        }
Пример #4
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)));
            }
        }
Пример #5
0
        /// <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)));
            }
        }
Пример #6
0
        /// <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)));
            }
        }
Пример #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>
        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)));
            }
        }
Пример #8
0
        /// <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)));
            }
        }
Пример #9
0
        /// <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()
                }));
Пример #10
0
        /// <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)));
            }
        }
Пример #11
0
        /// <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)));
            }
        }
Пример #12
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;
            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"));
            }
        }