using DesertPaintCodex.Services; using System; using System.Collections.Generic; using System.Diagnostics; namespace DesertPaintCodex.Models { public class PaintRecipe { public const uint PaintRecipeMinConcentration = 10; public const uint RibbonRecipeMinConcentration = 50; private struct RGB { public int R; public int G; public int B; }; public class ReagentQuantity { public readonly string Name; public uint Quantity; public ReagentQuantity(string name, uint quantity) { Name = name; Quantity = quantity; } public ReagentQuantity(ReagentQuantity other) { Name = other.Name; Quantity = other.Quantity; } public override string ToString() { return $"{Name} {Quantity}"; } }; private readonly List _recipe = new(); private readonly List _reagents = new(); private readonly PaintColor _reactedColor = new(); private readonly PaintColor _baseColor = new(); private bool _recalculate; public PaintRecipe() { } public PaintRecipe(PaintRecipe other) { _recalculate = true; foreach (string reagentName in other._reagents) { _reagents.Add(reagentName); } foreach (ReagentQuantity copyReagent in other._recipe) { ReagentQuantity ingredient = new(copyReagent); _recipe.Add(ingredient); } } public void CopyFrom(PaintRecipe other) { _recalculate = true; _reagents.Clear(); foreach (string reagentName in other._reagents) { _reagents.Add(reagentName); } _recipe.Clear(); foreach (ReagentQuantity otherIngredient in other._recipe) { ReagentQuantity ingredient = new(otherIngredient); _recipe.Add(ingredient); } } public override string ToString() { string result = PaletteService.FindNearest(ReactedColor); result += " |"; foreach (ReagentQuantity ri in _recipe) { result += " " + ri; } return result; } public List Reagents => _recipe; public PaintColor ReactedColor { get { if (!_recalculate) return _reactedColor; ComputeBaseColor(); ComputeReactedColor(); _recalculate = false; return _reactedColor; } } public PaintColor BaseColor { get { if (!_recalculate) return _baseColor; ComputeBaseColor(); ComputeReactedColor(); _recalculate = false; return _baseColor; } } public void AddReagent(string reagentName, uint quantity = 1) { if (quantity == 0) { return; } Reagent reagent = ReagentService.GetReagent(reagentName); ReagentQuantity? reagentQty = _recipe.Find(x => x.Name.Equals(reagentName)); if (reagentQty != null) { if (!reagent.IsCatalyst) { reagentQty.Quantity += quantity; } } else { ReagentQuantity newReagentQty = new(reagentName, reagent.IsCatalyst ? 1 : quantity); _recipe.Add(newReagentQty); } _reagents.Add(reagentName); _recalculate = true; } public void Clear() { _reagents.Clear(); _recipe.Clear(); _recalculate = true; } private static byte CalculateColor(int baseSum, uint pigmentCount, int reactSum) { // Changed to Math.Floor from Math.Round, since Round appears to be incorrect. return (byte)Math.Max(Math.Min(Math.Floor((((float)baseSum / pigmentCount) + reactSum)), 255), 0); } // Compute the color including reactions based on the player's profile private void ComputeReactedColor() { RGB baseRGB; baseRGB.R = 0; baseRGB.G = 0; baseRGB.B = 0; RGB reactionColor; reactionColor.R = 0; reactionColor.G = 0; reactionColor.B = 0; uint pigmentCount = 0; ReactionSet? reactions = ProfileManager.CurrentProfile?.Reactions; Debug.Assert(reactions != null); // track visited reagents so the reaction is only applied once HashSet reagentSet = new(); List prevReagents = new(); foreach (ReagentQuantity ingredient in _recipe) { string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) { continue; } Reagent reagent = ReagentService.GetReagent(reagentName); if (!reagent.IsCatalyst) { Debug.Assert(reagent.Color != null); baseRGB.R += (reagent.Color.Red * (int)ingredient.Quantity); baseRGB.G += (reagent.Color.Green * (int)ingredient.Quantity); baseRGB.B += (reagent.Color.Blue * (int)ingredient.Quantity); pigmentCount += ingredient.Quantity; } if (!reagentSet.Contains(reagentName) && reagentSet.Count <= 4) { reagentSet.Add(reagentName); // Run reactions. foreach (Reagent otherReagent in prevReagents) { Reaction? reaction = reactions.Find(otherReagent, reagent); if (reaction != null) { reactionColor.R += reaction.Red; reactionColor.G += reaction.Green; reactionColor.B += reaction.Blue; } } prevReagents.Add(reagent); } } _reactedColor.Red = CalculateColor(baseRGB.R, pigmentCount, reactionColor.R); _reactedColor.Green = CalculateColor(baseRGB.G, pigmentCount, reactionColor.G); _reactedColor.Blue = CalculateColor(baseRGB.B, pigmentCount, reactionColor.B); } // Compute the base color without any reactions private void ComputeBaseColor() { RGB baseRGB; baseRGB.R = 0; baseRGB.G = 0; baseRGB.B = 0; uint pigmentCount = 0; foreach (ReagentQuantity ingredient in _recipe) { string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) { continue; } Reagent reagent = ReagentService.GetReagent(reagentName); if (reagent.IsCatalyst) continue; Debug.Assert(reagent.Color != null); baseRGB.R += (reagent.Color.Red * (int)ingredient.Quantity); baseRGB.G += (reagent.Color.Green * (int)ingredient.Quantity); baseRGB.B += (reagent.Color.Blue * (int)ingredient.Quantity); pigmentCount += ingredient.Quantity; } _baseColor.Red = CalculateColor(baseRGB.R, pigmentCount, 0); _baseColor.Green = CalculateColor(baseRGB.G, pigmentCount, 0); _baseColor.Blue = CalculateColor(baseRGB.B, pigmentCount, 0); } // Compute the base color without any reactions public uint Cost { get { uint cost = 0; foreach (ReagentQuantity ingredient in _recipe) { string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) { continue; } Reagent reagent = ReagentService.GetReagent(reagentName); cost += (reagent.Cost * ingredient.Quantity); } return cost; } } public uint Concentration { get { uint concentration = 0; foreach (ReagentQuantity ingredient in _recipe) { string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) { continue; } Reagent reagent = ReagentService.GetReagent(reagentName); if (reagent.IsCatalyst) continue; concentration += ingredient.Quantity; } return concentration; } } public uint GetBulkCost(uint quantity) { uint cost = 0; foreach (ReagentQuantity ingredient in _recipe) { string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) { continue; } Reagent reagent = ReagentService.GetReagent(reagentName); cost += (reagent.Cost * ingredient.Quantity); } uint batchCount = (uint)Math.Ceiling((double)quantity / cost); // number of batches require to make quantity return batchCount * cost; } public bool IsValidForConcentration(uint concentration) { uint weight = 0; foreach (ReagentQuantity ingredient in _recipe) { string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) { continue; } Reagent reagent = ReagentService.GetReagent(reagentName); if (!reagent.IsCatalyst) { weight += ingredient.Quantity; } } return (weight >= concentration); } public bool HasMissingReactions() { ReactionSet? reactions = ProfileManager.CurrentProfile?.Reactions; Debug.Assert(reactions != null); HashSet reagentSet = new(); List prevReagents = new(); foreach (ReagentQuantity ingredient in _recipe) { if (reagentSet.Count > 4) return false; string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) continue; if (reagentSet.Contains(reagentName)) continue; reagentSet.Add(reagentName); Reagent reagent = ReagentService.GetReagent(reagentName); // Find reactions. foreach (Reagent otherReagent in prevReagents) { Reaction? reaction = reactions.Find(otherReagent, reagent); if (reaction == null) return true; } prevReagents.Add(reagent); } return false; } public List<(string, string)> MissingReactionPairs() { ReactionSet? reactions = ProfileManager.CurrentProfile?.Reactions; Debug.Assert(reactions != null); HashSet reagentSet = new(); List prevReagents = new(); List<(string, string)> missingReactionPairs = new List<(string, string)>(16); foreach (ReagentQuantity ingredient in _recipe) { if (reagentSet.Count > 4) return missingReactionPairs; // no more reactions string reagentName = ingredient.Name; if (string.IsNullOrEmpty(reagentName)) continue; if (reagentSet.Contains(reagentName)) continue; reagentSet.Add(reagentName); Reagent reagent = ReagentService.GetReagent(reagentName); // Find reactions. foreach (Reagent prevReagent in prevReagents) { Reaction? reaction = reactions.Find(prevReagent, reagent); if (reaction == null) { missingReactionPairs.Add((prevReagent.Name, reagentName)); } } prevReagents.Add(reagent); } return missingReactionPairs; } } }