/// <summary>Construct an instance.</summary> /// <param name="blueprint">The building blueprint.</param> /// <param name="building">A sample building constructed by the blueprint.</param> public RecipeModel(BluePrint blueprint, Building building) : this( key: blueprint.name, type: RecipeType.BuildingBlueprint, displayType : I18n.Building_Construction(), ingredients : blueprint.itemsRequired .Select(ingredient => new RecipeIngredientModel(ingredient.Key, ingredient.Value)) .ToArray(), item : _ => null, isKnown : () => true, machineParentSheetIndex : null, isForMachine : _ => false ) { this.SpecialOutput = new RecipeItemEntry( new SpriteInfo(building.texture.Value, building.getSourceRectForMenu()), building.buildingType.Value ); }
/// <inheritdoc /> public override Vector2?DrawValue(SpriteBatch spriteBatch, SpriteFont font, Vector2 position, float wrapWidth) { // get margins const int groupVerticalMargin = 6; const int groupLeftMargin = 0; const int firstRecipeTopMargin = 5; const int firstRecipeLeftMargin = 14; const int otherRecipeTopMargin = 2; float inputDividerWidth = font.MeasureString("+").X; float itemSpacer = inputDividerWidth; // current drawing position Vector2 curPos = position; float absoluteWrapWidth = position.X + wrapWidth; // icon size and line height float lineHeight = this.LineHeight; var iconSize = new Vector2(this.IconSize); float joinerWidth = inputDividerWidth + (itemSpacer * 2); // draw recipes curPos.Y += groupVerticalMargin; foreach (RecipeByTypeGroup group in this.Recipes) { // check if we can align columns bool alignColumns = wrapWidth >= (group.TotalColumnWidth + itemSpacer + ((group.ColumnWidths.Length - 1) * joinerWidth)); // columns + space between output/input + space between each input // draw group label curPos.X = position.X + groupLeftMargin; curPos += this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, $"{group.Type}:", Color.Black); // draw recipe lines foreach (RecipeEntry entry in group.Recipes) { // fade recipes which aren't known Color iconColor = entry.IsKnown ? Color.White : Color.White * .5f; Color textColor = entry.IsKnown ? Color.Black : Color.Gray; // reset position for recipe output curPos = new Vector2( position.X + firstRecipeLeftMargin, curPos.Y + firstRecipeTopMargin ); // draw output item (icon + name + count + chance) float inputLeft; { var outputSize = this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, entry.Output.DisplayText, textColor, entry.Output.Sprite, iconSize, iconColor); float outputWidth = alignColumns ? group.ColumnWidths[0] : outputSize.X; inputLeft = curPos.X + outputWidth + itemSpacer; curPos.X = inputLeft; } // draw input items for (int i = 0, last = entry.Inputs.Length - 1; i <= last; i++) { RecipeItemEntry input = entry.Inputs[i]; // move the draw position down to a new line if the next item would be drawn off the right edge Vector2 inputSize = this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, input.DisplayText, textColor, input.Sprite, iconSize, iconColor, probe: true); if (alignColumns) { inputSize.X = group.ColumnWidths[i + 1]; } if (curPos.X + inputSize.X > absoluteWrapWidth) { curPos = new Vector2( x: inputLeft, y: curPos.Y + lineHeight + otherRecipeTopMargin ); } // draw input item (icon + name + count) this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, input.DisplayText, textColor, input.Sprite, iconSize, iconColor); curPos = new Vector2( x: curPos.X + inputSize.X, y: curPos.Y ); // draw input item joiner if (i != last) { // move draw position to next line if needed if (curPos.X + joinerWidth > absoluteWrapWidth) { curPos = new Vector2( x: inputLeft, y: curPos.Y + lineHeight + otherRecipeTopMargin ); } else { curPos.X += itemSpacer; } // draw the input item joiner var joinerSize = this.DrawIconText(spriteBatch, font, curPos, absoluteWrapWidth, "+", textColor); curPos.X += joinerSize.X + itemSpacer; } } curPos.Y += lineHeight; } curPos.Y += lineHeight; // blank line between groups } // vertical spacer at the bottom of the recipes curPos.Y += groupVerticalMargin; // get drawn dimensions return(new Vector2(wrapWidth, curPos.Y - position.Y - lineHeight)); }
/********* ** Private methods *********/ /// <summary>Build an optimized representation of the recipes to display.</summary> /// <param name="ingredient">The ingredient item.</param> /// <param name="rawRecipes">The raw recipes to list.</param> private IEnumerable <RecipeByTypeGroup> BuildRecipeGroups(Item ingredient, RecipeModel[] rawRecipes) { /**** ** build models for matching recipes ****/ Dictionary <string, RecipeEntry[]> rawGroups = rawRecipes // split into specific recipes that match the item // (e.g. a recipe with several possible inputs => several recipes with one possible input) .SelectMany(recipe => { Item outputItem = recipe.CreateItem(ingredient); RecipeItemEntry output = this.CreateItemEntry( name: recipe.SpecialOutput?.DisplayText ?? outputItem?.DisplayName, item: outputItem, sprite: recipe.SpecialOutput?.Sprite, minCount: recipe.MinOutput, maxCount: recipe.MaxOutput, chance: recipe.OutputChance, isOutput: true ); return(this.GetCartesianInputs(recipe) .Select(inputIds => { // get ingredient models IEnumerable <RecipeItemEntry> inputs = inputIds .Select((inputId, index) => this.TryCreateItemEntry(inputId, recipe.Ingredients[index])) .Where(p => p != null); if (recipe.Type != RecipeType.TailorInput) // tailoring is always two ingredients with cloth first { inputs = inputs.OrderBy(entry => entry.DisplayText); } // build recipe return new RecipeEntry( name: recipe.Key, type: recipe.DisplayType, isKnown: recipe.IsKnown(), inputs: inputs.ToArray(), output: output ); })); }) // filter to unique recipe // (e.g. two recipe matches => one recipe) .GroupBy(recipe => recipe.UniqueKey) .Select(item => item.First()) // sort .OrderBy(recipe => recipe.Type) .ThenBy(recipe => recipe.Output.DisplayText) // group by type .GroupBy(p => p.Type) .ToDictionary(p => p.Key, p => p.ToArray()); /**** ** build recipe groups with column widths ****/ foreach (var rawGroup in rawGroups) { // build column width list var columnWidths = new List <float>(); void TrackWidth(int index, string text, SpriteInfo icon) { while (columnWidths.Count < index + 1) { columnWidths.Add(0); } float width = Game1.smallFont.MeasureString(text).X; if (icon != null) { width += this.IconSize + this.IconMargin; } columnWidths[index] = Math.Max(columnWidths[index], width); } // get max width of each column in the group foreach (var recipe in rawGroup.Value) { TrackWidth(0, $"{recipe.Output.DisplayText}:", recipe.Output.Sprite); for (int i = 0; i < recipe.Inputs.Length; i++) { TrackWidth(i + 1, recipe.Inputs[i].DisplayText, recipe.Inputs[i].Sprite); } } // save widths yield return(new RecipeByTypeGroup( type: rawGroup.Key, recipes: rawGroup.Value, columnWidths: columnWidths )); } }