/// <summary>Get the date when the crop will next be ready to harvest.</summary> public SDate GetNextHarvest() { // get crop Crop?crop = this.Crop; if (crop == null) { throw new InvalidOperationException("Can't get the harvest date because there's no crop."); } // ready now if (this.CanHarvestNow) { return(SDate.Now()); } // growing: days until next harvest if (!crop.fullyGrown.Value) { int daysUntilLastPhase = this.DaysToFirstHarvest - crop.dayOfCurrentPhase.Value - crop.phaseDays.Take(crop.currentPhase.Value).Sum(); return(SDate.Now().AddDays(daysUntilLastPhase)); } // regrowable crop harvested today if (crop.dayOfCurrentPhase.Value >= crop.regrowAfterHarvest.Value) { return(SDate.Now().AddDays(crop.regrowAfterHarvest.Value)); } // regrowable crop // dayOfCurrentPhase decreases to 0 when fully grown, where <=0 is harvestable return(SDate.Now().AddDays(crop.dayOfCurrentPhase.Value)); }
/********* ** Private methods *********/ /// <summary>Get all crops in a location.</summary> /// <param name="location">The location to scan.</param> private IEnumerable <Crop> GetCropsIn(GameLocation?location) { if (location == null) { yield break; } // planted crops foreach (HoeDirt dirt in location.terrainFeatures.Values.OfType <HoeDirt>()) { if (dirt.crop != null) { yield return(dirt.crop); } } // garden pots foreach (IndoorPot pot in location.objects.Values.OfType <IndoorPot>()) { Crop?crop = pot.hoeDirt.Value?.crop; if (crop != null) { yield return(crop); } } }
/// <summary>Get the output item.</summary> public override ITrackedStack?GetOutput() { // get raw output SObject?output = this.Machine.heldObject.Value; if (output == null) { return(null); } // get flower data int flowerId = -1; string?flowerName = null; int addedPrice = 0; { Crop?flower = Utility.findCloseFlower(this.Location, this.Machine.TileLocation, 5, crop => !crop.forageCrop.Value); if (flower != null) { flowerId = flower.indexOfHarvest.Value; string[] fields = Game1.objectInformation[flowerId].Split('/'); flowerName = fields[0]; addedPrice = Convert.ToInt32(fields[1]) * 2; } } // build object SObject result = new(output.ParentSheetIndex, output.Stack) { name = $"{flowerName ?? "Wild"} Honey", Price = output.Price + addedPrice, preservedParentSheetIndex = { Value = flowerId } }; return(new TrackedItem(result, onEmpty: this.Reset)); }
/// <summary> /// Renders all crops of the worldgrid. /// </summary> /// <param name="spriteBatch">The spritebatch to use for drawing.</param> /// <param name="worldGrid">The worldgrid to get all info from.</param> public void RenderCrops(SpriteBatch spriteBatch, WorldGrid worldGrid) { float maxYTextureIndex = cropTextureAtlas.YSize - 1; for (byte xIndex = 0; xIndex < worldGrid.Size; xIndex++) { for (byte yIndex = 0; yIndex < worldGrid.Size; yIndex++) { if (!worldGrid.HasCrop(yIndex, xIndex)) { continue; } Crop?currentCrop = worldGrid.GetCrop(yIndex, xIndex); int growthIndex = (int)(currentCrop.Value.Growth * maxYTextureIndex); int cropTypeIndex = ((int)currentCrop.Value.Type) - 1; cropTextureAtlas.Draw( cropTypeIndex, growthIndex, 1, 1, spriteBatch, new Rectangle(xIndex * CELL_SIZE * DISPLAY_SCALE, yIndex * CELL_SIZE * DISPLAY_SCALE, CELL_SIZE * DISPLAY_SCALE, CELL_SIZE * DISPLAY_SCALE), Color.White ); } } }
public DownloadPhotoRequest(Uri baseUrl, uint?width = null, uint?height = null, Crop?crop = null, Format?format = null, uint?quality = null, Fit?fit = null, uint?dpi = null) => (BaseUrl, Width, Height, Crop, Format, Quality, Fit, Dpi) =
void HarvestAction(Image contextPanel) { if (isReadyToHarvest && currentCrop != null && spawnedToken == null) { spawnedToken = Instantiate(Resources.Load("wheatToken"), new Vector3(transform.position.x + 1, transform.position.y + 3, transform.position.z + 1), Quaternion.Euler(0, 45, 0)) as GameObject; spawnedToken.GetComponent<wheatContextMenu>().parentFarm = this; currentCrop = null; if(doubleOutput) { spawnedToken2 = Instantiate(Resources.Load("wheatToken"), new Vector3(transform.position.x + 1, transform.position.y + 3, transform.position.z + 3), Quaternion.Euler(0, 45, 0)) as GameObject; spawnedToken2.GetComponent<wheatContextMenu>().parentFarm = this; doubleOutput = false; } } Destroy(contextPanel.gameObject); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="codex">Provides subject entries</param> /// <param name="gameHelper">Provides utility methods for interacting with the game code.</param> /// <param name="progressionMode">Whether to only show content once the player discovers it.</param> /// <param name="highlightUnrevealedGiftTastes">Whether to highlight item gift tastes which haven't been revealed in the NPC profile.</param> /// <param name="showAllGiftTastes">Whether to show all NPC gift tastes.</param> /// <param name="item">The underlying target.</param> /// <param name="context">The context of the object being looked up.</param> /// <param name="knownQuality">Whether the item quality is known. This is <c>true</c> for an inventory item, <c>false</c> for a map object.</param> /// <param name="location">The location containing the item, if applicable.</param> /// <param name="getCropSubject">Get a lookup subject for a crop.</param> /// <param name="fromCrop">The crop associated with the item (if applicable).</param> /// <param name="fromDirt">The dirt containing the crop (if applicable).</param> public ItemSubject(ISubjectRegistry codex, GameHelper gameHelper, bool progressionMode, bool highlightUnrevealedGiftTastes, bool showAllGiftTastes, Item item, ObjectContext context, bool knownQuality, GameLocation?location, Func <Crop, ObjectContext, HoeDirt?, ISubject> getCropSubject, Crop?fromCrop = null, HoeDirt?fromDirt = null) : base(gameHelper) { this.Codex = codex; this.ProgressionMode = progressionMode; this.HighlightUnrevealedGiftTastes = highlightUnrevealedGiftTastes; this.ShowAllGiftTastes = showAllGiftTastes; this.Target = item; this.DisplayItem = this.GetMenuItem(item); this.FromCrop = fromCrop ?? fromDirt?.crop; this.FromDirt = fromDirt; this.Context = context; this.Location = location; this.KnownQuality = knownQuality; this.GetCropSubject = getCropSubject; this.SeedForCrop = item.ParentSheetIndex != 433 || this.FromCrop == null // ignore unplanted coffee beans (to avoid "see also: coffee beans" loop) ? this.TryGetCropForSeed(item) : null; this.Initialize(this.DisplayItem.DisplayName, this.GetDescription(this.DisplayItem), this.GetTypeValue(this.DisplayItem)); }
/// <summary>Get the data to display for this subject.</summary> public override IEnumerable <IDebugField> GetDebugFields() { Item target = this.Target; SObject?obj = target as SObject; Crop? crop = this.FromCrop ?? this.SeedForCrop; // pinned fields yield return(new GenericDebugField("item ID", target.ParentSheetIndex, pinned: true)); yield return(new GenericDebugField("category", $"{target.Category} ({target.getCategoryName()})", pinned: true)); if (obj != null) { yield return(new GenericDebugField("edibility", obj.Edibility, pinned: true)); yield return(new GenericDebugField("item type", obj.Type, pinned: true)); } if (crop != null) { yield return(new GenericDebugField("crop fully grown", this.Stringify(crop.fullyGrown.Value), pinned: true)); yield return(new GenericDebugField("crop phase", $"{crop.currentPhase} (day {crop.dayOfCurrentPhase} in phase)", pinned: true)); } // raw fields foreach (IDebugField field in this.GetDebugFieldsFrom(target)) { yield return(field); } if (crop != null) { foreach (IDebugField field in this.GetDebugFieldsFrom(crop)) { yield return(new GenericDebugField($"crop::{field.Label}", field.Value, field.HasValue, field.IsPinned)); } } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="crop">The crop.</param> /// <param name="isPlanted">Whether the crop is planted.</param> public CropDataParser(Crop?crop, bool isPlanted) { this.Crop = crop; if (crop != null) { // get crop data this.Seasons = crop.seasonsToGrowIn.ToArray(); this.HasMultipleHarvests = crop.regrowAfterHarvest.Value == -1; this.HarvestablePhase = crop.phaseDays.Count - 1; this.CanHarvestNow = (crop.currentPhase.Value >= this.HarvestablePhase) && (!crop.fullyGrown.Value || crop.dayOfCurrentPhase.Value <= 0); this.DaysToFirstHarvest = crop.phaseDays.Take(crop.phaseDays.Count - 1).Sum(); // ignore harvestable phase this.DaysToSubsequentHarvest = crop.regrowAfterHarvest.Value; // adjust for agriculturist profession (10% faster initial growth) if (!isPlanted && Game1.player.professions.Contains(Farmer.agriculturist)) { this.DaysToFirstHarvest = (int)(this.DaysToFirstHarvest * 0.9); } } else { this.Seasons = Array.Empty <string>(); } }
/// <summary>Get the custom fields for a crop.</summary> /// <param name="dirt">The dirt the crop is planted in, if applicable.</param> /// <param name="crop">The crop to represent.</param> /// <param name="isSeed">Whether the crop being displayed is for an unplanted seed.</param> private IEnumerable <ICustomField> GetCropFields(HoeDirt?dirt, Crop?crop, bool isSeed) { if (crop == null) { yield break; } var data = new CropDataParser(crop, isPlanted: !isSeed); bool isForage = crop.whichForageCrop.Value > 0 && crop.fullyGrown.Value; // show crop fields for growing mixed seeds // add next-harvest field if (!isSeed) { // get next harvest SDate nextHarvest = data.GetNextHarvest(); // generate field string summary; if (data.CanHarvestNow) { summary = I18n.Generic_Now(); } else if (!Game1.currentLocation.SeedsIgnoreSeasonsHere() && !data.Seasons.Contains(nextHarvest.Season)) { summary = I18n.Crop_Harvest_TooLate(date: this.Stringify(nextHarvest)); } else { summary = $"{this.Stringify(nextHarvest)} ({this.GetRelativeDateStr(nextHarvest)})"; } yield return(new GenericField(I18n.Crop_Harvest(), summary)); } // crop summary if (!isForage) { List <string> summary = new(); // harvest if (!crop.forageCrop.Value) { summary.Add(data.HasMultipleHarvests ? I18n.Crop_Summary_HarvestOnce(daysToFirstHarvest: data.DaysToFirstHarvest) : I18n.Crop_Summary_HarvestMulti(daysToFirstHarvest: data.DaysToFirstHarvest, daysToNextHarvests: data.DaysToSubsequentHarvest) ); } // seasons summary.Add(I18n.Crop_Summary_Seasons(seasons: string.Join(", ", I18n.GetSeasonNames(data.Seasons)))); // drops if (crop.minHarvest != crop.maxHarvest && crop.chanceForExtraCrops.Value > 0) { summary.Add(I18n.Crop_Summary_DropsXToY(min: crop.minHarvest.Value, max: crop.maxHarvest.Value, percent: (int)Math.Round(crop.chanceForExtraCrops.Value * 100, 2))); } else if (crop.minHarvest.Value > 1) { summary.Add(I18n.Crop_Summary_DropsX(count: crop.minHarvest.Value)); } // crop sale price Item drop = data.GetSampleDrop(); summary.Add(I18n.Crop_Summary_SellsFor(price: GenericField.GetSaleValueString(this.GetSaleValue(drop, false), 1) !)); // generate field yield return(new GenericField(I18n.Crop_Summary(), "-" + string.Join($"{Environment.NewLine}-", summary))); } // dirt water/fertilizer state if (dirt != null && !isForage) { // watered yield return(new GenericField(I18n.Crop_Watered(), this.Stringify(dirt.state.Value == HoeDirt.watered))); // fertilizer string[] appliedFertilizers = this.GetAppliedFertilizers(dirt) .Select(GameI18n.GetObjectName) .Distinct() .DefaultIfEmpty(this.Stringify(false)) .OrderBy(p => p) .ToArray(); yield return(new GenericField(I18n.Crop_Fertilized(), string.Join(", ", appliedFertilizers))); } }
/// <summary>Get the data to display for this subject.</summary> public override IEnumerable <ICustomField> GetData() { // get data Item item = this.Target; SObject?obj = item as SObject; bool isCrop = this.FromCrop != null; bool isSeed = this.SeedForCrop != null; bool isDeadCrop = this.FromCrop?.dead.Value == true; bool canSell = obj?.canBeShipped() == true || this.Metadata.Shops.Any(shop => shop.BuysCategories.Contains(item.Category)); bool isMovieTicket = obj?.ParentSheetIndex == 809 && !obj.bigCraftable.Value; // get overrides bool showInventoryFields = !this.IsSpawnedStoneNode(); { ObjectData?objData = this.Metadata.GetObject(item, this.Context); if (objData != null) { this.Name = objData.NameKey != null?I18n.GetByKey(objData.NameKey) : this.Name; this.Description = objData.DescriptionKey != null?I18n.GetByKey(objData.DescriptionKey) : this.Description; this.Type = objData.TypeKey != null?I18n.GetByKey(objData.TypeKey) : this.Type; showInventoryFields = objData.ShowInventoryFields ?? showInventoryFields; } } // don't show data for dead crop if (isDeadCrop) { yield return(new GenericField(I18n.Crop_Summary(), I18n.Crop_Summary_Dead())); yield break; } // crop fields foreach (ICustomField field in this.GetCropFields(this.FromDirt, this.FromCrop ?? this.SeedForCrop, isSeed)) { yield return(field); } // indoor pot crop if (obj is IndoorPot pot) { Crop?potCrop = pot.hoeDirt.Value.crop; Bush?potBush = pot.bush.Value; if (potCrop != null) { Item drop = this.GameHelper.GetObjectBySpriteIndex(potCrop.indexOfHarvest.Value); yield return(new LinkField(I18n.Item_Contents(), drop.DisplayName, () => this.GetCropSubject(potCrop, ObjectContext.World, pot.hoeDirt.Value))); } if (potBush != null) { ISubject?subject = this.Codex.GetByEntity(potBush, this.Location ?? potBush.currentLocation); if (subject != null) { yield return(new LinkField(I18n.Item_Contents(), subject.Name, () => subject)); } } } // machine output foreach (ICustomField field in this.GetMachineOutputFields(obj)) { yield return(field); } // music blocks if (obj?.Name == "Flute Block") { yield return(new GenericField(I18n.Item_MusicBlock_Pitch(), I18n.Generic_Ratio(value: obj.preservedParentSheetIndex.Value, max: 2300))); } else if (obj?.Name == "Drum Block") { yield return(new GenericField(I18n.Item_MusicBlock_DrumType(), I18n.Generic_Ratio(value: obj.preservedParentSheetIndex.Value, max: 6))); } // item if (showInventoryFields) { // needed for foreach (ICustomField field in this.GetNeededForFields(obj)) { yield return(field); } // sale data if (canSell && !isCrop) { // sale price string?saleValueSummary = GenericField.GetSaleValueString(this.GetSaleValue(item, this.KnownQuality), item.Stack); yield return(new GenericField(I18n.Item_SellsFor(), saleValueSummary)); // sell to List <string> buyers = new(); if (obj?.canBeShipped() == true) { buyers.Add(I18n.Item_SellsTo_ShippingBox()); } buyers.AddRange( from shop in this.Metadata.Shops where shop.BuysCategories.Contains(item.Category) let name = I18n.GetByKey(shop.DisplayKey).ToString() orderby name select name ); yield return(new GenericField(I18n.Item_SellsTo(), string.Join(", ", buyers))); } // clothing if (item is Clothing clothing) { yield return(new GenericField(I18n.Item_CanBeDyed(), this.Stringify(clothing.dyeable.Value))); } // gift tastes if (!isMovieTicket) { IDictionary <GiftTaste, GiftTasteModel[]> giftTastes = this.GetGiftTastes(item); yield return(new ItemGiftTastesField(I18n.Item_LovesThis(), giftTastes, GiftTaste.Love, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(I18n.Item_LikesThis(), giftTastes, GiftTaste.Like, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); if (this.ProgressionMode || this.HighlightUnrevealedGiftTastes || this.ShowAllGiftTastes) { yield return(new ItemGiftTastesField(I18n.Item_NeutralAboutThis(), giftTastes, GiftTaste.Neutral, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(I18n.Item_DislikesThis(), giftTastes, GiftTaste.Dislike, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); yield return(new ItemGiftTastesField(I18n.Item_HatesThis(), giftTastes, GiftTaste.Hate, onlyRevealed: this.ProgressionMode, highlightUnrevealed: this.HighlightUnrevealedGiftTastes)); } } } // recipes if (showInventoryFields) { RecipeModel[] recipes = // recipes that take this item as ingredient this.GameHelper.GetRecipesForIngredient(this.DisplayItem) .Concat(this.GameHelper.GetRecipesForIngredient(item)) // recipes which produce this item .Concat(this.GameHelper.GetRecipesForOutput(this.DisplayItem)) .Concat(this.GameHelper.GetRecipesForOutput(item)) // recipes for a machine .Concat(this.GameHelper.GetRecipesForMachine(this.DisplayItem as SObject)) .Concat(this.GameHelper.GetRecipesForMachine(item as SObject)) .ToArray(); if (recipes.Any()) { yield return(new ItemRecipesField(this.GameHelper, I18n.Item_Recipes(), item, recipes.ToArray())); } } // fish spawn rules if (item.Category == SObject.FishCategory) { yield return(new FishSpawnRulesField(this.GameHelper, I18n.Item_FishSpawnRules(), item.ParentSheetIndex)); } // fish pond data // derived from FishPond::doAction and FishPond::isLegalFishForPonds if (!item.HasContextTag("fish_legendary") && (item.Category == SObject.FishCategory || Utility.IsNormalObjectAtParentSheetIndex(item, 393 /*coral*/) || Utility.IsNormalObjectAtParentSheetIndex(item, 397 /*sea urchin*/))) { foreach (FishPondData fishPondData in Game1.content.Load <List <FishPondData> >("Data\\FishPondData")) { if (!fishPondData.RequiredTags.All(item.HasContextTag)) { continue; } int minChanceOfAnyDrop = (int)Math.Round(Utility.Lerp(0.15f, 0.95f, 1 / 10f) * 100); int maxChanceOfAnyDrop = (int)Math.Round(Utility.Lerp(0.15f, 0.95f, FishPond.MAXIMUM_OCCUPANCY / 10f) * 100); string preface = I18n.Building_FishPond_Drops_Preface(chance: I18n.Generic_Range(min: minChanceOfAnyDrop, max: maxChanceOfAnyDrop)); yield return(new FishPondDropsField(this.GameHelper, I18n.Item_FishPondDrops(), -1, fishPondData, preface)); break; } } // fence if (item is Fence fence) { string healthLabel = I18n.Item_FenceHealth(); // health if (Game1.getFarm().isBuildingConstructed(Constant.BuildingNames.GoldClock)) { yield return(new GenericField(healthLabel, I18n.Item_FenceHealth_GoldClock())); } else { float maxHealth = fence.isGate.Value ? fence.maxHealth.Value * 2 : fence.maxHealth.Value; float health = fence.health.Value / maxHealth; double daysLeft = Math.Round(fence.health.Value * this.Constants.FenceDecayRate / 60 / 24); double percent = Math.Round(health * 100); yield return(new PercentageBarField(healthLabel, (int)fence.health.Value, (int)maxHealth, Color.Green, Color.Red, I18n.Item_FenceHealth_Summary(percent: (int)percent, count: (int)daysLeft))); } } // movie ticket if (isMovieTicket) { MovieData movie = MovieTheater.GetMovieForDate(Game1.Date); if (movie == null) { yield return(new GenericField(I18n.Item_MovieTicket_MovieThisWeek(), I18n.Item_MovieTicket_MovieThisWeek_None())); } else { // movie this week yield return(new GenericField(I18n.Item_MovieTicket_MovieThisWeek(), new IFormattedText[] { new FormattedText(movie.Title, bold: true), new FormattedText(Environment.NewLine), new FormattedText(movie.Description) })); // movie tastes const GiftTaste rejectKey = (GiftTaste)(-1); IDictionary <GiftTaste, string[]> tastes = this.GameHelper.GetMovieTastes() .GroupBy(entry => entry.Value ?? rejectKey) .ToDictionary(group => group.Key, group => group.Select(p => p.Key.Name).OrderBy(p => p).ToArray()); yield return(new MovieTastesField(I18n.Item_MovieTicket_LovesMovie(), tastes, GiftTaste.Love)); yield return(new MovieTastesField(I18n.Item_MovieTicket_LikesMovie(), tastes, GiftTaste.Like)); yield return(new MovieTastesField(I18n.Item_MovieTicket_DislikesMovie(), tastes, GiftTaste.Dislike)); yield return(new MovieTastesField(I18n.Item_MovieTicket_RejectsMovie(), tastes, rejectKey)); } } // dyes if (showInventoryFields) { yield return(new ColorField(I18n.Item_ProducesDye(), item)); } // owned and times cooked/crafted if (showInventoryFields && !isCrop) { // owned yield return(new GenericField(I18n.Item_NumberOwned(), I18n.Item_NumberOwned_Summary(count: this.GameHelper.CountOwnedItems(item)))); // times crafted RecipeModel[] recipes = this.GameHelper .GetRecipes() .Where(recipe => recipe.OutputItemIndex == this.Target.ParentSheetIndex && recipe.OutputItemType == this.Target.GetItemType()) .ToArray(); if (recipes.Any()) { string label = recipes.First().Type == RecipeType.Cooking ? I18n.Item_NumberCooked() : I18n.Item_NumberCrafted(); int timesCrafted = recipes.Sum(recipe => recipe.GetTimesCrafted(Game1.player)); if (timesCrafted >= 0) // negative value means not available for this recipe type { yield return(new GenericField(label, I18n.Item_NumberCrafted_Summary(count: timesCrafted))); } } } // see also crop bool seeAlsoCrop = isSeed && item.ParentSheetIndex != this.SeedForCrop !.indexOfHarvest.Value && // skip seeds which produce themselves (e.g. coffee beans) item.ParentSheetIndex is not(495 or 496 or 497) && // skip random seasonal seeds item.ParentSheetIndex != 770; // skip mixed seeds if (seeAlsoCrop) { Item drop = this.GameHelper.GetObjectBySpriteIndex(this.SeedForCrop !.indexOfHarvest.Value); yield return(new LinkField(I18n.Item_SeeAlso(), drop.DisplayName, () => this.GetCropSubject(this.SeedForCrop, ObjectContext.Inventory, null))); } }
void plantCarrot(Image contextPanel) { currentCrop = Crop.Carrot; Destroy(contextPanel.gameObject); }
void plantBellPepper(Image contextPanel) { currentCrop = Crop.Bell_Pepper; Destroy(contextPanel.gameObject); }
void plantTomato(Image contextPanel) { currentCrop = Crop.Tomato; Destroy(contextPanel.gameObject); }
void plantWheat(Image contextPanel) { currentCrop = Crop.Wheat; Destroy(contextPanel.gameObject); }