Files @ a1bd6e3474d6
Branch filter:

Location: ATITD-Tools/Desert-Paint-Codex/Models/PaintRecipe.cs

Malkyne
Should now extraplate white reactions if any of the channels didn't clip.
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<ReagentQuantity> _recipe   = new();
        private readonly List<string>          _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<ReagentQuantity> 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<string> reagentSet   = new();
            List<Reagent>                  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 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<string> reagentSet = new();
            List<Reagent> 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;
        }
    }
}