# HG changeset patch # User Jason Maltzen # Date 2015-12-23 21:25:07 # Node ID 2040107278aad7261ee949a18d68ddc8bd8321b8 # Parent f28757bb21cb14f05bedaf2462a4142e23d57003 Fixes #8 #9 #6 #3 : Order of ingredients should now match the paint bench. Basic brute-force recipe generation implementation. Fixed rounding error on the simulator. Give feedback when using the simulator if reactions are missing. Also: adds an interface for modifying ingredient settings for recipe generation, adds a help option to view missing recorded reactions diff --git a/DesertPaintLab.csproj b/DesertPaintLab.csproj --- a/DesertPaintLab.csproj +++ b/DesertPaintLab.csproj @@ -75,6 +75,12 @@ + + + + + + diff --git a/MainWindow.cs b/MainWindow.cs --- a/MainWindow.cs +++ b/MainWindow.cs @@ -29,15 +29,6 @@ using DesertPaintLab; public partial class MainWindow : Gtk.Window { - const int colorTolerance = 2; - - int swatchHeight = 24; - int colorBarWidth = 306; - int redBarSpacing = 32; - int greenBarSpacing = 42; - int blueBarSpacing = 52; - - bool unsavedData = false; bool shouldShutDown = false; string appDataPath; @@ -94,6 +85,9 @@ public partial class MainWindow : Gtk.Wi string colorsPath = FileUtils.FindApplicationResourceFile("colors.txt"); Palette.Load(colorsPath); + + string ingredientsPath = FileUtils.FindApplicationResourceFile("ingredients.txt"); + ReagentManager.Load(ingredientsPath); Build(); @@ -134,11 +128,7 @@ public partial class MainWindow : Gtk.Wi screenBuffer = new Gdk.Pixbuf(Gdk.Colorspace.Rgb, false, 8, screenWidth, screenHeight); - swatchHeight *= pixelMultiplier; - colorBarWidth *= pixelMultiplier; - redBarSpacing *= pixelMultiplier; - greenBarSpacing *= pixelMultiplier; - blueBarSpacing *= pixelMultiplier; + ReactionRecorder.SetPixelMultiplier(pixelMultiplier); if (!OpenProfile()) { @@ -345,6 +335,7 @@ public partial class MainWindow : Gtk.Wi { profile.Load(); PopulateDropDowns(); + recipe.Reactions = profile.Reactions; } return profileSelected; @@ -493,13 +484,13 @@ public partial class MainWindow : Gtk.Wi } } } - recipe.ComputeBaseColor(ref expectedColor); + expectedColor.Set(recipe.BaseColor); unmodifiedSwatch.Color = expectedColor; //SetExpectedColor(recipeColor.Red, recipeColor.Green, recipeColor.Blue); if (reactionKnown) { - recipe.ComputeReactedColor(profile, ref reactedColor); + reactedColor.Set(recipe.ReactedColor); reactionSwatch.Color = reactedColor; } else @@ -538,8 +529,9 @@ public partial class MainWindow : Gtk.Wi // rootWindow.Colormap, 0, 0, 0, 0, screenWidth, screenHeight); int stride = screenBuffer.Rowstride; byte* pixBytes = (byte*)screenBuffer.Pixels; + int redPixelStart = -1; - bool wasCaptured = ReactionRecorder.CaptureReaction(pixBytes, screenWidth, screenHeight, stride, ref reactedColor); + bool wasCaptured = ReactionRecorder.CaptureReaction(pixBytes, screenWidth, screenHeight, stride, ref reactedColor, ref redPixelStart); if (!wasCaptured && enableDebugMenu) { // write out the screenshot @@ -553,9 +545,29 @@ public partial class MainWindow : Gtk.Wi } while (System.IO.File.Exists(filename)); screenBuffer.Save(filename, "png"); } + else + { + // convert to pixel offset instead of byte + int redPixelStartX = (redPixelStart % stride) / 3; + int redPixelStartY = (redPixelStart / stride); + // write out the screenshot + string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + string filename; + int i = 0; + do + { + ++i; + filename = System.IO.Path.Combine(screenshotDir, String.Format("DesertPaintLab_Colormatch{0}.png", i)); + } while (System.IO.File.Exists(filename)); + int captureAreaWidth = Math.Min(64, screenWidth - redPixelStartX + 64); + int captureAreaHeight = Math.Min(64, screenWidth - redPixelStartY + 64); + Gdk.Pixbuf outPixBuf = new Gdk.Pixbuf(screenBuffer, Math.Max(0, redPixelStartX - 16), Math.Max(0, redPixelStartY - 16), captureAreaWidth, captureAreaHeight); + //screenBuffer.Save(filename, "png"); + outPixBuf.Save(filename, "png"); + } //screenBuffer.Save("screenshot.png", "png"); - return !wasCaptured; + return wasCaptured; } protected virtual void OnDebugScreenshot(object sender, System.EventArgs e) @@ -628,6 +640,7 @@ public partial class MainWindow : Gtk.Wi MessageType.Error, ButtonsType.Ok, "Pigment Lab dialog box NOT FOUND. Please ensure " + "that there is an unobstructed view of the dialog " + + "and that your interface size is set to 'small' " + "when you press the Capture button."); md.Run(); @@ -681,6 +694,7 @@ public partial class MainWindow : Gtk.Wi { profile.Load(); PopulateDropDowns(); + recipe.Reactions = profile.Reactions; } } @@ -753,7 +767,16 @@ public partial class MainWindow : Gtk.Wi protected void OnOpenRecipeGenerator(object sender, EventArgs e) { - throw new NotImplementedException(); + RecipeGeneratorWindow win = new RecipeGeneratorWindow(profile); + win.Show(); + //RecipeGenerator gen = new RecipeGenerator(); + //gen.BeginRecipeGeneration(profile.Reactions, 15, 7); + //MessageDialog md = new MessageDialog(this, + // DialogFlags.DestroyWithParent, + // MessageType.Info, ButtonsType.Close, + // "Coming Soon!"); + //md.Run(); + //md.Destroy(); } protected void OnShowReactionStatus(object sender, EventArgs e) @@ -761,5 +784,11 @@ public partial class MainWindow : Gtk.Wi ReactionStatusWindow win = new ReactionStatusWindow(profile); win.Show(); } + + protected void OnShowIngredients(object sender, EventArgs e) + { + ReagentWindow win = new ReagentWindow(profile); + win.Show(); + } } diff --git a/PaintColor.cs b/PaintColor.cs --- a/PaintColor.cs +++ b/PaintColor.cs @@ -83,6 +83,14 @@ namespace DesertPaintLab this.green = green; this.blue = blue; } + + public PaintColor(PaintColor other) + { + name = other.name; + red = other.red; + green = other.green; + blue = other.blue; + } public int GetDistanceSquared(PaintColor otherColor) { @@ -97,6 +105,14 @@ namespace DesertPaintLab green = 0; blue = 0; } + + public void Set(PaintColor other) + { + this.red = other.red; + this.green = other.green; + this.blue = other.blue; + this.name = other.name; + } public override string ToString() { diff --git a/PaintRecipe.cs b/PaintRecipe.cs --- a/PaintRecipe.cs +++ b/PaintRecipe.cs @@ -1,4 +1,26 @@ -using System; +/* + * Copyright (c) 2015, Jason Maltzen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +using System; using System.Collections.Generic; namespace DesertPaintLab @@ -12,48 +34,147 @@ namespace DesertPaintLab public int b; }; + public class RecipeIngredient + { + public string name; + public uint quantity; + + public RecipeIngredient(string name, uint quantity) + { + this.name = name; + this.quantity = quantity; + } + }; + + private List recipe = new List(); private List reagents = new List(); + private bool dirty = false; + private PaintColor reactedColor = new PaintColor(); + private PaintColor baseColor = new PaintColor(); + private ReactionSet reactions; + public PaintRecipe() { } + public List Ingredients + { + get { + return recipe; + } + } + + public ReactionSet Reactions + { + get + { + return reactions; + } + set + { + dirty = true; + reactions = value; + } + } + + public PaintColor ReactedColor + { + get + { + if (dirty) + { + ComputeBaseColor(); + ComputeReactedColor(); + dirty = false; + } + return reactedColor; + } + } + + public PaintColor BaseColor + { + get + { + if (dirty) + { + ComputeBaseColor(); + ComputeReactedColor(); + dirty = false; + } + return baseColor; + } + } + public void AddReagent(String reagentName) { + AddReagent(reagentName, 1); + } + + public void AddReagent(String reagentName, uint quantity) + { + if (quantity == 0) + { + return; + } + Reagent reagent = ReagentManager.GetReagent(reagentName); + if (reagent == null) + { + Console.WriteLine("ERROR: invalid reagent {0}", reagentName); + return; + } + + RecipeIngredient ingredient = recipe.Find(x => x.name.Equals(reagentName)); + if (ingredient != null) + { + if (!reagent.IsCatalyst) + { + ingredient.quantity += quantity; + dirty = true; + } + } + else + { + RecipeIngredient newIngredient = new RecipeIngredient(reagentName, reagent.IsCatalyst ? 1 : quantity); + recipe.Add(newIngredient); + dirty = true; + } reagents.Add(reagentName); } public void Clear() { reagents.Clear(); + recipe.Clear(); } - byte CalculateColor(int baseSum, int pigmentCount, int reactSum) + byte CalculateColor(int baseSum, uint pigmentCount, int reactSum) { - return (byte)Math.Max(Math.Min(Math.Round((((float)baseSum / (float)pigmentCount) + (float)reactSum)), 255), 0); + // Changed to Math.Floor from Math.Round, since Round appears to be incorrect. + return (byte)Math.Max(Math.Min(Math.Floor((((float)baseSum / (float)pigmentCount) + (float)reactSum)), 255), 0); } // Compute the color including reactions based on the player's profile - public void ComputeReactedColor(PlayerProfile profile, ref PaintColor paintColor) + private void ComputeReactedColor() { - RGB baseColor; - baseColor.r = 0; - baseColor.g = 0; - baseColor.b = 0; + RGB baseRGB; + baseRGB.r = 0; + baseRGB.g = 0; + baseRGB.b = 0; RGB reactionColor; reactionColor.r = 0; reactionColor.g = 0; reactionColor.b = 0; - int pigmentCount = 0; - string prevReagent = null; + uint pigmentCount = 0; // track visited reagents so the reaction is only applied once SortedDictionary reagentSet = new SortedDictionary(); List prevReagents = new List(); - foreach (string reagentName in reagents) + foreach (RecipeIngredient ingredient in recipe) { + string reagentName = ingredient.name; if (reagentName == null) { continue; @@ -62,47 +183,45 @@ namespace DesertPaintLab Reagent reagent = ReagentManager.GetReagent(reagentName); if (!reagent.IsCatalyst) { - baseColor.r += reagent.Color.Red; - baseColor.g += reagent.Color.Green; - baseColor.b += reagent.Color.Blue; - pigmentCount += 1; + 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 (prevReagent == null || !prevReagent.Equals(reagentName)) + + if (!reagentSet.ContainsKey(reagentName) && reagentSet.Count <= 4) { - if (!reagentSet.ContainsKey(reagentName) && reagentSet.Count <= 4) + reagentSet[reagentName] = true; + // Run reactions. + foreach (Reagent otherReagent in prevReagents) { - reagentSet[reagentName] = true; - // Run reactions. - foreach (Reagent otherReagent in prevReagents) + Reaction reaction = reactions.Find(otherReagent, reagent); + if (reaction != null) { - Reaction reaction = profile.FindReaction(otherReagent, reagent); - if (reaction != null) - { - reactionColor.r += reaction.Red; - reactionColor.g += reaction.Green; - reactionColor.b += reaction.Blue; - } + reactionColor.r += reaction.Red; + reactionColor.g += reaction.Green; + reactionColor.b += reaction.Blue; } - prevReagents.Add(reagent); } + prevReagents.Add(reagent); } - prevReagent = reagentName; } - paintColor.Red = CalculateColor(baseColor.r, pigmentCount, reactionColor.r); - paintColor.Green = CalculateColor(baseColor.g, pigmentCount, reactionColor.g); - paintColor.Blue = CalculateColor(baseColor.b, pigmentCount, reactionColor.b); + 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 - public void ComputeBaseColor(ref PaintColor color) + private void ComputeBaseColor() { - RGB baseColor; - baseColor.r = 0; - baseColor.g = 0; - baseColor.b = 0; - int pigmentCount = 0; - foreach (string reagentName in reagents) + RGB baseRGB; + baseRGB.r = 0; + baseRGB.g = 0; + baseRGB.b = 0; + uint pigmentCount = 0; + foreach (RecipeIngredient ingredient in recipe) { + string reagentName = ingredient.name; if (reagentName == null) { continue; @@ -111,15 +230,110 @@ namespace DesertPaintLab Reagent reagent = ReagentManager.GetReagent(reagentName); if (!reagent.IsCatalyst) { - baseColor.r += reagent.Color.Red; - baseColor.g += reagent.Color.Green; - baseColor.b += reagent.Color.Blue; - pigmentCount += 1; + 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; } } - color.Red = CalculateColor(baseColor.r, pigmentCount, 0); - color.Green = CalculateColor(baseColor.g, pigmentCount, 0); - color.Blue = CalculateColor(baseColor.b, pigmentCount, 0); + 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 (RecipeIngredient ingredient in recipe) + { + string reagentName = ingredient.name; + if (reagentName == null) + { + continue; + } + + Reagent reagent = ReagentManager.GetReagent(reagentName); + cost += (reagent.Cost * ingredient.quantity); + } + return cost; + } + } + + public uint GetBulkCost(uint quantity) + { + uint cost = 0; + foreach (RecipeIngredient ingredient in recipe) + { + string reagentName = ingredient.name; + if (reagentName == null) + { + continue; + } + + Reagent reagent = ReagentManager.GetReagent(reagentName); + cost += (reagent.Cost * ingredient.quantity); + } + uint batchCount = (uint)Math.Ceiling((double)quantity / (double)cost); // number of batches require to make quantity + return batchCount * cost; + } + + public bool IsValid + { + get + { + uint weight = 0; + foreach (RecipeIngredient ingredient in recipe) + { + string reagentName = ingredient.name; + if (reagentName == null) + { + continue; + } + + Reagent reagent = ReagentManager.GetReagent(reagentName); + if (!reagent.IsCatalyst) + { + weight += ingredient.quantity; + } + } + return (weight >= 10); + } + } + + public bool CheckMissingReactions(ref List> missing) + { + missing.Clear(); + + SortedDictionary reagentSet = new SortedDictionary(); + List prevReagents = new List(); + + foreach (RecipeIngredient ingredient in recipe) + { + string reagentName = ingredient.name; + if (reagentName == null) + { + continue; + } + + Reagent reagent = ReagentManager.GetReagent(reagentName); + if (!reagentSet.ContainsKey(reagentName) && reagentSet.Count <= 4) + { + reagentSet[reagentName] = true; + // Run reactions. + foreach (Reagent otherReagent in prevReagents) + { + Reaction reaction = reactions.Find(otherReagent, reagent); + if (reaction == null) + { + missing.Add(new KeyValuePair(otherReagent.Name, reagent.Name)); + } + } + prevReagents.Add(reagent); + } + } + return (missing.Count > 0); } } } diff --git a/Palette.cs b/Palette.cs --- a/Palette.cs +++ b/Palette.cs @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2010, Tess Snider + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + using System; using System.IO; using System.Collections.Generic; @@ -10,6 +32,12 @@ namespace DesertPaintLab static List colors = new List(); static Regex colorEntry = new Regex(@"\#(?\w\w)(?\w\w)(?\w\w)\s*(?\w+)"); + public static List Colors + { + get { + return colors; + } + } public Palette () { @@ -35,6 +63,14 @@ namespace DesertPaintLab } } } + + public static int Count + { + get + { + return colors.Count; + } + } public static string FindNearest(PaintColor color) { diff --git a/PlayerProfile.cs b/PlayerProfile.cs --- a/PlayerProfile.cs +++ b/PlayerProfile.cs @@ -23,6 +23,7 @@ using System; using System.IO; using System.Collections.Generic; +using System.Text.RegularExpressions; namespace DesertPaintLab { @@ -31,16 +32,61 @@ namespace DesertPaintLab string name; string directory; string reactFile; + string reagentFile; - SortedDictionary> reactions = - new SortedDictionary>(); + static Regex recipeHeaderRegex = new Regex(@"^--- Recipe: (?(\w*\s)*\w+)\s*"); + static Regex recipeIngredientRegex = new Regex(@"(?(\w+\s)?\w+)\s*\|\s*(?\d+)\s*"); + + ReactionSet reactions = new ReactionSet(); + // ingredient -> [ingredient, reaction] + //SortedDictionary> reactions = + // new SortedDictionary>(); + SortedDictionary recipes; + + static PlayerProfile current = null; + + static PlayerProfile Current + { + get { + return current; + } + } public PlayerProfile(string name, string directory) { this.name = name; this.directory = directory; this.reactFile = System.IO.Path.Combine(directory, "dp_reactions.txt"); + this.reagentFile = System.IO.Path.Combine(directory, "ingredients.txt"); } + + public string Directory + { + get { + return this.directory; + } + } + + public ReactionSet Reactions + { + get { + return this.reactions; + } + } + + public string ReagentFile + { + get { + return this.reagentFile; + } + } + + public SortedDictionary Recipes + { + get { + return this.recipes; + } + } public void Initialize() { @@ -120,25 +166,26 @@ namespace DesertPaintLab public bool SaveToPP(string ppFile) { Reaction reaction1, reaction2; - SortedDictionary secondReagentDict; using (StreamWriter writer = new StreamWriter(ppFile)) { - foreach (KeyValuePair> firstPair in reactions) - { - foreach (KeyValuePair secondPair in firstPair.Value) - { - reaction1 = secondPair.Value; - if ((reaction1 != null) && !reaction1.Exported) - { - reaction2 = null; - reactions.TryGetValue(secondPair.Key, out secondReagentDict); - if (secondReagentDict != null) - { - secondReagentDict.TryGetValue(firstPair.Key, out reaction2); - } - if (reaction2 != null) - { - writer.Write(firstPair.Key + " | " + secondPair.Key + " | "); + foreach (string reagentName1 in ReagentManager.Names) + { + // TODO: could be more efficient by only iterating over the names after reagent1 + foreach (string reagentName2 in ReagentManager.Names) + { + if (reagentName1.Equals(reagentName2)) + { + continue; + } + Reagent reagent1 = ReagentManager.GetReagent(reagentName1); + Reagent reagent2 = ReagentManager.GetReagent(reagentName2); + reaction1 = reactions.Find(reagent1, reagent2); + if (reaction1 != null && !reaction1.Exported) + { + reaction2 = reactions.Find(reagent2, reagent1); + if (reaction2 != null) + { + writer.Write(reagent1.PracticalPaintName + " | " + reagent2.PracticalPaintName + " | "); if ((Math.Abs(reaction1.Red) > Math.Abs(reaction1.Green)) || (Math.Abs(reaction2.Red) > Math.Abs(reaction2.Green))) { @@ -167,14 +214,27 @@ namespace DesertPaintLab } // Clear Exported flags. - foreach (KeyValuePair> firstPair in reactions) - { - foreach (KeyValuePair secondPair in firstPair.Value) - { - if (secondPair.Value != null) - { - secondPair.Value.Exported = false; - } + foreach (string reagentName1 in ReagentManager.Names) + { + // TODO: could be more efficient by only iterating over the names after reagent1 + foreach (string reagentName2 in ReagentManager.Names) + { + if (reagentName1.Equals(reagentName2)) + { + continue; + } + Reagent reagent1 = ReagentManager.GetReagent(reagentName1); + Reagent reagent2 = ReagentManager.GetReagent(reagentName2); + reaction1 = reactions.Find(reagent1, reagent2); + if (reaction1 != null) + { + reaction1.Exported = false; + } + reaction2 = reactions.Find(reagent2, reagent1); + if (reaction2 != null) + { + reaction2.Exported = false; + } } } return true; @@ -208,17 +268,17 @@ namespace DesertPaintLab public void Load() { string line; - SortedDictionary dict; reactions.Clear(); - ReagentManager.Load(System.IO.Path.Combine(directory, "ingredients.txt")); + ReagentManager.LoadProfileReagents(reagentFile); ReagentManager.InitializeReactions(ref reactions); using (StreamReader reader = new StreamReader(reactFile)) { while ((line = reader.ReadLine()) != null) { string[] tokens = line.Split(null); - reactions.TryGetValue(tokens[0], out dict); - dict[tokens[1]] = new Reaction(int.Parse(tokens[2]), int.Parse(tokens[3]), int.Parse(tokens[4])); + Reagent reagent1 = ReagentManager.GetReagent(tokens[0]); + Reagent reagent2 = ReagentManager.GetReagent(tokens[1]); + reactions.Set(reagent1, reagent2, new Reaction(int.Parse(tokens[2]), int.Parse(tokens[3]), int.Parse(tokens[4]))); } } } @@ -228,43 +288,144 @@ namespace DesertPaintLab Reaction reaction; using (StreamWriter writer = new StreamWriter(reactFile, false)) { - foreach (KeyValuePair> firstPair in reactions) - { - foreach (KeyValuePair secondPair in firstPair.Value) - { - reaction = secondPair.Value; - - if (reaction != null) - { - writer.WriteLine(firstPair.Key + " " + secondPair.Key + " " + - reaction.Red + " " + reaction.Green + " " + reaction.Blue); - } - } - } - } + foreach (string reagentName1 in ReagentManager.Names) + { + // TODO: could be more efficient by only iterating over the names after reagent1 + foreach (string reagentName2 in ReagentManager.Names) + { + if (reagentName1.Equals(reagentName2)) + { + continue; + } + Reagent reagent1 = ReagentManager.GetReagent(reagentName1); + Reagent reagent2 = ReagentManager.GetReagent(reagentName2); + reaction = reactions.Find(reagent1, reagent2); + if (reaction != null) + { + writer.WriteLine(reagent1.PracticalPaintName + " " + reagent2.PracticalPaintName + " " + + reaction.Red + " " + reaction.Green + " " + reaction.Blue); + } + } + } + } } + + public void LoadRecipes() + { + this.recipes = new SortedDictionary(); + string recipeFile = System.IO.Path.Combine(directory, "dp_recipes.txt"); + string line; + Match match; + bool inRecipe = false; + PaintRecipe recipe = null; + string currentRecipeColor = null; + if (File.Exists(recipeFile)) + { + using (StreamReader reader = new StreamReader(recipeFile)) + { + while ((line = reader.ReadLine()) != null) + { + match = recipeHeaderRegex.Match(line); + if (match.Success) + { + if (recipe != null && currentRecipeColor != null) + { + recipes.Add(currentRecipeColor, recipe); + } + recipe = new PaintRecipe(); + recipe.Reactions = reactions; + currentRecipeColor = match.Groups["colorname"].Value; + inRecipe = true; + } + else if (inRecipe) + { + match = recipeIngredientRegex.Match(line); + if (match.Success) + { + string ingredient = match.Groups["ingredient"].Value; + uint quantity = uint.Parse(match.Groups["quantity"].Value); + recipe.AddReagent(ingredient, quantity); + } + } + } + if (inRecipe) + { + recipes.Add(currentRecipeColor, recipe); + } + } + } + } + + public void SaveRecipes() + { + if (recipes != null) + { + string recipeFile = System.IO.Path.Combine(directory, "dp_recipes.txt"); + using (StreamWriter writer = new StreamWriter(recipeFile, false)) + { + foreach (KeyValuePair pair in recipes) + { + writer.WriteLine("--- Recipe: {0}", pair.Key); + foreach (PaintRecipe.RecipeIngredient ingredient in pair.Value.Ingredients) + { + writer.WriteLine("{0,-14} | {1}", ingredient.name, ingredient.quantity); + } + } + } + } + } + + public void ExportWikiRecipes(string file) + { + PaintRecipe recipe; + using (StreamWriter writer = new StreamWriter(file)) + { + writer.WriteLine("{| class='wikitable sortable' border=\"1\" style=\"background-color:#DEB887;\""); + writer.WriteLine("! Color !! Recipe !! Verified"); + foreach (PaintColor color in Palette.Colors) + { + writer.WriteLine("|-"); + string colorLine = "| "; + colorLine += "style=\"font-weight: bold; background-color: #" + color.Red.ToString("X2") + color.Green.ToString("X2") + color.Blue.ToString("X2") + ";"; + if (color.Red < 128 && color.Green < 128 && color.Blue < 128) + { + // dark color gets light text + colorLine += " color: #FFFFFF;"; + } + colorLine += "\" | " + color.Name + " || "; + if (recipes.TryGetValue(color.Name, out recipe)) + { + foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients) + { + colorLine += " " + ingredient.name + " " + ingredient.quantity.ToString(); + } + } + else + { + // no recipe + } + colorLine += " || "; + writer.WriteLine(colorLine); + } + writer.WriteLine("|}"); + } + } public Reaction FindReaction(Reagent reagent1, Reagent reagent2) - { - SortedDictionary secondReagentDict = null; - Reaction reaction = null; - reactions.TryGetValue(reagent1.Name, out secondReagentDict); - if (secondReagentDict != null) - { - secondReagentDict.TryGetValue(reagent2.Name, out reaction); - } - return reaction; + { + return reactions.Find(reagent1, reagent2); } public void SetReaction(Reagent reagent1, Reagent reagent2, Reaction reaction) { - SortedDictionary secondReagentDict = null; - reactions.TryGetValue(reagent1.Name, out secondReagentDict); - if (secondReagentDict != null) - { - secondReagentDict[reagent2.Name] = reaction; - } + reactions.Set(reagent1, reagent2, reaction); } + + public void SetRecipe(PaintRecipe recipe) + { + string colorName = Palette.FindNearest(recipe.ReactedColor); + recipes[colorName] = recipe; + } } } diff --git a/Reaction.cs b/Reaction.cs --- a/Reaction.cs +++ b/Reaction.cs @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2015, Tess Snider + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + using System; namespace DesertPaintLab { diff --git a/ReactionRecorder.cs b/ReactionRecorder.cs --- a/ReactionRecorder.cs +++ b/ReactionRecorder.cs @@ -1,4 +1,26 @@ -using System; +/* + * Copyright (c) 2015, Jason Maltzen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +using System; namespace DesertPaintLab { @@ -7,11 +29,17 @@ namespace DesertPaintLab { const int colorTolerance = 2; - const int swatchHeight = 24; - const int colorBarWidth = 306; - const int redBarSpacing = 32; - const int greenBarSpacing = 42; - const int blueBarSpacing = 52; + const int DEFAULT_SWATCH_HEIGHT = 24; + const int DEFAULT_COLOR_BAR_WIDTH = 306; + const int DEFAULT_RED_BAR_SPACING = 32; + const int DEFAULT_GREEN_BAR_SPACING = 42; + const int DEFAULT_BLUE_BAR_SPACING = 52; + + static int swatchHeight = DEFAULT_SWATCH_HEIGHT; + static int colorBarWidth = DEFAULT_COLOR_BAR_WIDTH; + static int redBarSpacing = DEFAULT_RED_BAR_SPACING; + static int greenBarSpacing = DEFAULT_GREEN_BAR_SPACING; + static int blueBarSpacing = DEFAULT_BLUE_BAR_SPACING; private static bool IsPapyTexture(byte r, byte g, byte b) { @@ -19,9 +47,18 @@ namespace DesertPaintLab ((r < 0xF4) && (g < 0xE0) && (b < 0xC4)); } - unsafe public static bool CaptureReaction(byte* pixBytes, int screenshotWidth, int screenshotHeight, int stride, ref PaintColor reactedColor) + public static void SetPixelMultiplier(int pixelMultiplier) { - byte r, g, b; + swatchHeight = DEFAULT_SWATCH_HEIGHT * pixelMultiplier; + colorBarWidth = DEFAULT_COLOR_BAR_WIDTH * pixelMultiplier; + redBarSpacing = DEFAULT_RED_BAR_SPACING * pixelMultiplier; + greenBarSpacing = DEFAULT_GREEN_BAR_SPACING * pixelMultiplier; + blueBarSpacing = DEFAULT_BLUE_BAR_SPACING * pixelMultiplier; + } + + unsafe public static bool CaptureReaction(byte* pixBytes, int screenshotWidth, int screenshotHeight, int stride, ref PaintColor reactedColor, ref int redPixelStart) + { + byte pixel_r, pixel_g, pixel_b; int pixelStart, otherPixelStart; bool colorMatch; for (int x = 0; x < screenshotWidth - colorBarWidth; ++x) @@ -30,12 +67,12 @@ namespace DesertPaintLab { // Look for the color swatch. pixelStart = (y * stride) + (x * 3); - r = pixBytes[pixelStart]; - g = pixBytes[pixelStart + 1]; - b = pixBytes[pixelStart + 2]; + pixel_r = pixBytes[pixelStart]; + pixel_g = pixBytes[pixelStart + 1]; + pixel_b = pixBytes[pixelStart + 2]; // 1.) Check if this is a dark pixel. - if ((r < 0x46) && (g < 0x46) && (b < 0x46)) + if ((pixel_r < 0x46) && (pixel_g < 0x46) && (pixel_b < 0x46)) { // 2.) Check the pixel above it, // to see if it's from the papy texture. @@ -62,9 +99,9 @@ namespace DesertPaintLab for (int i = 1; i < swatchHeight - 2; ++i) { otherPixelStart = pixelStart + (stride * i); - if ((Math.Abs(r - pixBytes[otherPixelStart++]) > colorTolerance) || - (Math.Abs(g - pixBytes[otherPixelStart++]) > colorTolerance) || - (Math.Abs(b - pixBytes[otherPixelStart]) > colorTolerance)) + if ((Math.Abs(pixel_r - pixBytes[otherPixelStart++]) > colorTolerance) || + (Math.Abs(pixel_g - pixBytes[otherPixelStart++]) > colorTolerance) || + (Math.Abs(pixel_b - pixBytes[otherPixelStart]) > colorTolerance)) { colorMatch = false; break; @@ -75,7 +112,7 @@ namespace DesertPaintLab { // WE FOUND THE SWATCH! // Now we know where the color bars are. - int redPixelStart = pixelStart + (redBarSpacing * stride); + redPixelStart = pixelStart + (redBarSpacing * stride); int redPixelCount = 0; while ((pixBytes[redPixelStart] > 0x9F) && (pixBytes[redPixelStart + 1] < 0x62) && diff --git a/ReactionSet.cs b/ReactionSet.cs new file mode 100644 --- /dev/null +++ b/ReactionSet.cs @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015, Jason Maltzen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; + +namespace DesertPaintLab +{ + public class ReactionSet + { + // ingredient -> [ingredient, reaction] + SortedDictionary> reactions = + new SortedDictionary>(); + + public ReactionSet() + { + } + + public Reaction Find(Reagent reagent1, Reagent reagent2) + { + SortedDictionary secondReagentDict = null; + Reaction reaction = null; + reactions.TryGetValue(reagent1.PracticalPaintName, out secondReagentDict); + if (secondReagentDict != null) + { + secondReagentDict.TryGetValue(reagent2.PracticalPaintName, out reaction); + } + return reaction; + } + + public void Set(Reagent reagent1, Reagent reagent2, Reaction reaction) + { + SortedDictionary secondReagentDict = null; + reactions.TryGetValue(reagent1.PracticalPaintName, out secondReagentDict); + if (secondReagentDict == null) + { + secondReagentDict = new SortedDictionary(); + reactions.Add(reagent1.PracticalPaintName, secondReagentDict); + } + secondReagentDict[reagent2.PracticalPaintName] = reaction; + } + + public void Clear() + { + reactions.Clear(); + } + } +} + diff --git a/Reagent.cs b/Reagent.cs --- a/Reagent.cs +++ b/Reagent.cs @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2015, Jason Maltzen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + using System; namespace DesertPaintLab @@ -5,8 +27,11 @@ namespace DesertPaintLab public class Reagent { string name; + string ppName; bool isCatalyst = false; - int cost = 0; + uint cost = 0; + bool enabled = true; + uint recipeMax = 10; PaintColor color; public bool IsCatalyst @@ -32,21 +57,76 @@ namespace DesertPaintLab return name; } } - - public Reagent(string name, int cost) - { - this.name = name; - this.cost = cost; - isCatalyst = true; - } - - public Reagent(string name, byte red, byte green, byte blue, int cost) - { - color = new PaintColor(red, green, blue); - this.name = name; - this.cost = cost; - } - + + public string PracticalPaintName + { + get + { + return ppName; + } + } + + public bool Enabled + { + get + { + return enabled; + } + set + { + enabled = value; + } + } + + public uint Cost + { + get + { + return cost; + } + set + { + cost = Math.Max(1, value); + } + } + + public uint RecipeMax + { + get + { + return recipeMax; + } + set + { + if (!isCatalyst) + { + recipeMax = Math.Max(0, value); + } + } + } + + // catalyst + public Reagent(string name, string ppName) + { + this.name = name; + this.ppName = ppName; + this.cost = 2; + this.enabled = true; + this.recipeMax = 1; + this.isCatalyst = true; + } + + public Reagent(string name, string ppName, byte red, byte green, byte blue) + { + this.color = new PaintColor(red, green, blue); + this.name = name; + this.ppName = ppName; + this.cost = 1; + this.recipeMax = 10; + this.enabled = true; + this.isCatalyst = false; + } + public override string ToString() { if (isCatalyst) diff --git a/ReagentManager.cs b/ReagentManager.cs --- a/ReagentManager.cs +++ b/ReagentManager.cs @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2010, Tess Snider + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ using System; using System.IO; using System.Collections.Generic; @@ -7,11 +28,15 @@ namespace DesertPaintLab { public class ReagentManager { - static Regex reagentRegex = new Regex(@"(?\w+)\s*\|\s*(?\d+),\s*(?\d+),\s*(?\d+)\s*\|\s*(?\d+)\s*\|.*"); - static Regex catalystRegex = new Regex(@"(?\w+)\s*\|\s*catalyst\s*\|\s*(?\d+)\s*\|.*"); + // PP format + static Regex reagentRegex = new Regex(@"(?\w+)\s*\|\s*(?\d+),\s*(?\d+),\s*(?\d+)\s*\|\s*(?\d+)\s*\|\s*(?[YN])\s*\|\s*(?(bulk|normal))\s*\|\s*(?\d+).*"); + static Regex catalystRegex = new Regex(@"(?\w+)\s*\|\s*catalyst\s*\|\s*(?\d+)\s*\|\s*(?[YN])\s*\|\s*(?(bulk|normal)).*"); + static Regex internalReagentRegex = new Regex(@"(?(\w*\s)*\w+)\s*\|\s*(?\w+)\s*\|\s*(?\d+),\s*(?\d+),\s*(?\d+).*"); + static Regex internalCatalystRegex = new Regex(@"(?(\w+\s)*\w+)\s*\|\s*(?\w+)\s*\|\s*catalyst.*"); static SortedDictionary reagents = new SortedDictionary(); static List names = new List(); + static Dictionary nameLookup = new Dictionary(); // pp name to our name static Gtk.ListStore nameStore = new Gtk.ListStore(typeof(string)); @@ -35,12 +60,52 @@ namespace DesertPaintLab { } + + // Loads reagent name/colors + public static void Load(string file) + { + Match match; + string line; + reagents.Clear(); + using (StreamReader reader = new StreamReader(file)) + { + while ((line = reader.ReadLine()) != null) + { + match = internalReagentRegex.Match(line); + if (match.Success) + { + string name = match.Groups["name"].Value; + string ppname = match.Groups["ppname"].Value; + reagents.Add(name, + new Reagent(name, ppname, + byte.Parse(match.Groups["red"].Value), + byte.Parse(match.Groups["green"].Value), + byte.Parse(match.Groups["blue"].Value))); + nameStore.AppendValues(name); + names.Add(name); + nameLookup.Add(ppname, name); + } + else + { + match = internalCatalystRegex.Match(line); + if (match.Success) + { + string name = match.Groups["name"].Value; + string ppname = match.Groups["ppname"].Value; + reagents.Add(name, new Reagent(ppname, ppname)); + nameStore.AppendValues(name); + names.Add(name); + nameLookup.Add(ppname, name); + } + } + } + } + } - public static void Load(string file) + public static void LoadProfileReagents(string file) { Match match; string line; - reagents.Clear(); using (StreamReader reader = new StreamReader(file)) { while ((line = reader.ReadLine()) != null) @@ -48,45 +113,88 @@ namespace DesertPaintLab match = reagentRegex.Match(line); if (match.Success) { - string name = match.Groups["name"].Value; - reagents.Add(name, - new Reagent(name, - byte.Parse(match.Groups["red"].Value), - byte.Parse(match.Groups["green"].Value), - byte.Parse(match.Groups["blue"].Value), - int.Parse(match.Groups["cost"].Value))); - nameStore.AppendValues(name); - names.Add(name); + string ppname = match.Groups["name"].Value; + string name = null; + nameLookup.TryGetValue(ppname, out name); + Reagent reagent = GetReagent(name); + if (reagent != null && !reagent.IsCatalyst) + { + reagent.Enabled = match.Groups["enabled"].Value.Equals("Y"); + reagent.Cost = uint.Parse(match.Groups["cost"].Value); + reagent.RecipeMax = uint.Parse(match.Groups["max"].Value); + } } else { match = catalystRegex.Match(line); if (match.Success) { - string name = match.Groups["name"].Value; - int cost = int.Parse(match.Groups["cost"].Value); - reagents.Add(name, new Reagent(name, cost)); - nameStore.AppendValues(name); - names.Add(name); + string ppname = match.Groups["name"].Value; + string name = null; + nameLookup.TryGetValue(ppname, out name); + Reagent reagent = GetReagent(name); + if (reagent != null && reagent.IsCatalyst) + { + reagent.Enabled = match.Groups["enabled"].Value.Equals("Y"); + reagent.Cost = uint.Parse(match.Groups["cost"].Value); + } } } } } } + + public static void SaveProfileReagents(string file) + { + using (StreamWriter writer = new StreamWriter(file)) + { + writer.WriteLine("// Ingredients are in the form:"); + writer.WriteLine("// Name | RGB values | cost | enabled (Y/N) | bulk/normal | max items per paint (1-20)"); + writer.WriteLine("//"); + writer.WriteLine("// It is recommended to only change the cost value"); + writer.WriteLine("// It is not recommended to set many of the ingredients above 10 per paint"); + + List sortedReagents = new List(reagents.Count); + foreach (KeyValuePair pair in reagents) + { + sortedReagents.Add(pair.Value); + } + sortedReagents.Sort( (x,y) => ((x.IsCatalyst && !y.IsCatalyst) ? 1 : ((y.IsCatalyst && !x.IsCatalyst) ? -1 : x.PracticalPaintName.CompareTo(y.PracticalPaintName))) ); + foreach (Reagent reagent in sortedReagents) + { + if (!reagent.IsCatalyst) + { + writer.WriteLine("{0,-10} | {1,3}, {2,3}, {3,3} | {4,7} | {5} | {6} | {7}", + reagent.PracticalPaintName, + reagent.Color.Red, reagent.Color.Blue, reagent.Color.Green, + reagent.Cost, + reagent.Enabled ? "Y" : "N", + reagent.RecipeMax >= 10 ? " bulk" : "normal", + reagent.RecipeMax); + } + else + { + writer.WriteLine("{0,-10} | catalyst | {1,7} | {2} | normal | 1", + reagent.PracticalPaintName, + reagent.Cost, + reagent.Enabled ? "Y" : "N"); + } + } + } + } + - public static void InitializeReactions(ref SortedDictionary> reactions) + public static void InitializeReactions(ref ReactionSet reactions) { foreach (KeyValuePair pair1 in reagents) { - SortedDictionary dict = new SortedDictionary(); foreach (KeyValuePair pair2 in reagents) { if (pair1.Key != pair2.Key) { - dict.Add(pair2.Key, null); + reactions.Set(pair1.Value, pair2.Value, null); } } - reactions.Add(pair1.Key, dict); } } @@ -133,6 +241,16 @@ namespace DesertPaintLab { Reagent returnVal; reagents.TryGetValue(reagentName, out returnVal); + if (returnVal == null) + { + // convert pp name to our internal name + string otherName = null; + nameLookup.TryGetValue(reagentName, out otherName); + if (otherName != null) + { + reagents.TryGetValue(otherName, out returnVal); + } + } return returnVal; } } diff --git a/ReagentWindow.cs b/ReagentWindow.cs new file mode 100644 --- /dev/null +++ b/ReagentWindow.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; + +namespace DesertPaintLab +{ + public partial class ReagentWindow : Gtk.Window + { + private class ReagentCheckButton : Gtk.CheckButton + { + private Reagent reagent; + public Reagent Reagent + { + get { + return reagent; + } + set { + reagent = value; + } + } + } + private class ReagentEntry : Gtk.Entry + { + private Reagent reagent; + public Reagent Reagent + { + get { + return reagent; + } + set { + reagent = value; + } + } + + // public ReagentEntry(int size) : base(size) + //{ + //} + + override protected void OnTextInserted(string text, ref int position) + { + uint val; + if (uint.TryParse(text, out val)) + { + base.OnTextInserted(text, ref position); + } + } + } + + bool dirty = false; + PlayerProfile profile; + + SortedDictionary ingredientCheckButtons = new SortedDictionary(); + SortedDictionary ingredientCostEntries = new SortedDictionary(); + SortedDictionary ingredientQuantityEntries = new SortedDictionary(); + int numReagents = 14; + public ReagentWindow(PlayerProfile profile) + : base(Gtk.WindowType.Toplevel) + { + this.profile = profile; + this.Build(); + + numReagents = ReagentManager.Names.Count; + ingredientTable.NRows = (uint)numReagents; + ingredientTable.NColumns = 4; + uint row = 0; + foreach (string reagentName in ReagentManager.Names) + { + Reagent reagent = ReagentManager.GetReagent(reagentName); + ReagentCheckButton checkButton = new ReagentCheckButton(); + checkButton.Active = reagent.Enabled; + checkButton.Reagent = reagent; + checkButton.Toggled += OnEnableToggled; + ingredientCheckButtons.Add(reagentName, checkButton); + ingredientTable.Attach(checkButton, 0, 1, row, row+1); // checkbox for enabled + ingredientTable.Attach(new Gtk.Label(reagentName), 1, 2, row, row+1); // name label + ReagentEntry costEntry = new ReagentEntry(); + costEntry.MaxLength = 6; + costEntry.WidthChars = 6; + costEntry.Text = reagent.Cost.ToString(); + // TODO: set up validator + // TODO: set up event handler for changed + costEntry.Reagent = reagent; + costEntry.TextDeleted += OnCostChanged; + costEntry.TextInserted += OnCostChanged; + ingredientCostEntries.Add(reagentName, costEntry); + ingredientTable.Attach(costEntry, 2, 3, row, row+1); // cost input + ReagentEntry maxQuantityEntry = new ReagentEntry(); + maxQuantityEntry.Text = reagent.RecipeMax.ToString(); + maxQuantityEntry.MaxLength = 4; + maxQuantityEntry.WidthChars = 4; + // TODO: set up validator + // TODO: set up event handler for changed + maxQuantityEntry.Reagent = reagent; + if (reagent.IsCatalyst) + { + maxQuantityEntry.Sensitive = false; + } + else + { + maxQuantityEntry.TextDeleted += OnQuantityChanged; + maxQuantityEntry.TextInserted += OnQuantityChanged; + } + ingredientQuantityEntries.Add(reagentName, maxQuantityEntry); + ingredientTable.Attach(maxQuantityEntry, 3, 4, row, row+1); // maximum quantity input + ++row; + } + + okButton.Clicked += OnOK; + cancelButton.Clicked += OnCancel; + ShowAll(); + } + + private void OnCostChanged(object o, EventArgs args) + { + ReagentEntry costEntry = (ReagentEntry)o; + uint newCost; + if (uint.TryParse(costEntry.Text, out newCost)) + { + if (costEntry.Reagent.Cost != newCost) + { + dirty = true; + } + } + } + + private void OnQuantityChanged(object o, EventArgs args) + { + ReagentEntry qtyEntry = (ReagentEntry)o; + uint newCost; + if (uint.TryParse(qtyEntry.Text, out newCost)) + { + if (qtyEntry.Reagent.Cost != newCost) + { + dirty = true; + } + } + } + + private void OnEnableToggled(object o, EventArgs args) + { + ReagentCheckButton btn = (ReagentCheckButton)o; + if (btn.Active != btn.Reagent.Enabled) + { + dirty = true; + } + } + + private void OnOK(object obj, EventArgs args) + { + if (dirty) + { + // save out state + foreach (string reagentName in ReagentManager.Names) + { + ReagentCheckButton checkButton = ingredientCheckButtons[reagentName]; + ReagentEntry costEntry = ingredientCostEntries[reagentName]; + ReagentEntry qtyEntry = ingredientQuantityEntries[reagentName]; + checkButton.Reagent.Enabled = checkButton.Active; + uint val; + if (uint.TryParse(costEntry.Text, out val)) + { + costEntry.Reagent.Cost = val; + } + if (uint.TryParse(qtyEntry.Text, out val)) + { + qtyEntry.Reagent.RecipeMax = val; + } + } + + ReagentManager.SaveProfileReagents(profile.ReagentFile); + } + this.Destroy(); + } + + private void OnCancel(object obj, EventArgs args) + { + this.Destroy(); + } + } +} + diff --git a/RecipeGenerator.cs b/RecipeGenerator.cs new file mode 100644 --- /dev/null +++ b/RecipeGenerator.cs @@ -0,0 +1,722 @@ +/* + * Copyright (c) 2015, Jason Maltzen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Threading; + +namespace DesertPaintLab +{ + public class NewRecipeEventArgs : EventArgs + { + string color; + PaintRecipe recipe; + + public NewRecipeEventArgs(string color, PaintRecipe recipe) + { + this.color = color; + this.recipe = recipe; + } + + public string Color + { + get { + return color; + } + } + + public PaintRecipe Recipe + { + get { + return recipe; + } + } + } + + public class RecipeGenerator + { + private class SearchNode + { + //int initialReagentCount; + List reagents; + HashSet reagentInUse = new HashSet(); + List costSortedReagents; + PaintRecipe testRecipe = null; + public PaintRecipe TestRecipe + { + get + { + return testRecipe; + } + set + { + testRecipe = value; + } + } + public uint CurrentTargetQuantity { get; set; } + public uint MaxQuantity { get; set; } + uint maxReagents; + public uint MaxReagents + { + get + { + return maxReagents; + } + set + { + maxReagents = value; + currentWeights = new uint[maxReagents]; + } + } + public uint UsedQuantity { get; private set; } + + public uint CatalystCount { get; set; } + + uint[] currentWeights; + public uint[] CurrentWeights + { + get + { + return currentWeights; + } + } + + public SearchNode(List costSortedReagents, List reagents) + { + this.costSortedReagents = new List(costSortedReagents); + this.reagents = new List(reagents); + foreach (uint reagentIdx in reagents) + { + reagentInUse.Add(reagentIdx); + } + InitialCount = this.reagents.Count; + MaxReagents = (uint)this.reagents.Count; // better set this later! + UsedQuantity = 0; + } + + // top-level search + public SearchNode(List costSortedReagents, uint startReagent) + { + this.costSortedReagents = new List(costSortedReagents); + this.reagents = new List(); + this.reagents.Add(NextFreeReagent(startReagent)); + InitialCount = 1; // don't iterate up beyond the start reagent + MaxReagents = 1; + UsedQuantity = 0; + } + + public int InitialCount { get; private set; } + public List Reagents + { + get + { + return reagents; + } + } + + public Reagent Reagent(int idx) + { + return costSortedReagents[(int)reagents[idx]]; + } + + public uint LastReagent + { + get + { + return reagents[reagents.Count - 1]; + } + } + + public void RemoveLastReagent() + { + uint reagentIdx = reagents[reagents.Count-1]; + ReleaseReagent(reagentIdx); + if (costSortedReagents[(int)reagentIdx].IsCatalyst) + { + --CatalystCount; + } + reagents.RemoveAt(reagents.Count-1); + } + + public void ReplaceLastReagent(uint reagentIdx) + { + uint oldReagentIdx = reagents[reagents.Count-1]; + ReleaseReagent(oldReagentIdx); + reagents[reagents.Count-1] = reagentIdx; + if (costSortedReagents[(int)oldReagentIdx].IsCatalyst) + { + --CatalystCount; + } + if (costSortedReagents[(int)reagentIdx].IsCatalyst) + { + ++CatalystCount; + } + } + + public uint NextFreeReagent(uint startIdx) + { + uint idx = startIdx; + for (; idx < costSortedReagents.Count; ++idx) + { + bool inUse = reagentInUse.Contains(idx); + if (inUse == false) + { + //Console.WriteLine("Found free reagent idx {0}", idx); + reagentInUse.Add(idx); + return idx; + } + } + //Console.WriteLine("Failed to find free reagent."); + return (uint)costSortedReagents.Count; + } + + private void ReleaseReagent(uint reagentIdx) + { + reagentInUse.Remove(reagentIdx); + } + + public bool AddNextReagent() + { + bool ok = (reagents.Count < MaxReagents); + if (ok) + { + uint nextReagent = NextFreeReagent(0); + reagents.Add(nextReagent); + if (costSortedReagents[(int)nextReagent].IsCatalyst) + { + ++CatalystCount; + } + InitForQuantity(CurrentTargetQuantity); + } + return ok; + } + + public void InitForQuantity(uint quantity) + { + //System.Console.WriteLine("Target quantity: {0}, reagent count: {1}", quantity, reagents.Count); + CurrentTargetQuantity = quantity; + if (CurrentTargetQuantity < (10 + CatalystCount)) + { + return; + } + UsedQuantity = 0; + uint remainingReagents = ((uint)reagents.Count - CatalystCount); + uint remainingWeight = CurrentTargetQuantity - CatalystCount; + for (int i = 0; i < reagents.Count; ++i) + { + Reagent reagent = Reagent(i); + + if (reagent.IsCatalyst) + { + currentWeights[i] = 1; + ++UsedQuantity; + } + else + { + uint weight = (uint)Math.Min(remainingWeight - (remainingReagents-1), reagent.RecipeMax); + remainingWeight -= weight; + currentWeights[i] = weight; + UsedQuantity += weight; + } + --remainingReagents; + } + } + + public void SetWeight(int idx, uint quantity) + { + UsedQuantity -= currentWeights[idx]; + currentWeights[idx] = quantity; + UsedQuantity += quantity; + } + } + + const uint DEFAULT_MAX_QUANTITY = 14; // minimum recipe: 10 base + 4 catalysts + const uint DEFAULT_MAX_REAGENTS = 5; + + //uint maxQuantity; // maximum number of total ingredients + uint maxReagents; // maximum number of reagents to use in the recipe + uint fullQuantityDepth; // at or equal this number of reagents, ignore ingredient settings for max quantity + uint fullQuantity; // The max number of a reagent to use at full quantity + + ReactionSet reactions; + bool running = false; + + SortedDictionary recipeCosts = new SortedDictionary(); + SortedDictionary recipes = new SortedDictionary(); + + uint totalReagents; + + List costSortedReagents = new List(); + + ConcurrentQueue searchQueue = new ConcurrentQueue(); + + int recipeCount = 0; + + List generatorThreads = new List(); + Object workerLock = new Object(); + + bool requestCancel = false; + + // events + public event EventHandler Finished; + public event EventHandler Progress; + public event EventHandler NewRecipe; + + public RecipeGenerator() + { + } + + public SortedDictionary Recipes + { + get + { + return recipes; + } + } + + public int RecipeCount + { + get + { + return recipeCount; + } + } + + private class ReagentCostSort : IComparer + { + public int Compare(Reagent reagent1, Reagent reagent2) + { + return (int)reagent1.Cost - (int)reagent2.Cost; + } + } + + public void InitRecipes(SortedDictionary recipes, ReactionSet reactions) + { + if (running) + { + return; + } + this.reactions = reactions; + foreach (PaintRecipe recipe in recipes.Values) + { + // TODO: copy? + AddCheapestRecipe(recipe); + } + } + + public void BeginRecipeGeneration(ReactionSet reactions, uint maxQuantity, uint maxReagents, uint fullQuantityDepth, uint fullQuantity) + { + if (running) + { + // Already running - don't start again + return; + } + this.running = true; + + this.reactions = reactions; + //this.maxQuantity = maxQuantity; + this.maxReagents = maxReagents; + this.fullQuantity = fullQuantity; + this.fullQuantityDepth = fullQuantityDepth; + + // first, sort reagents by cost. + costSortedReagents.Clear(); + foreach (string name in ReagentManager.Names) + { + Reagent reagent = ReagentManager.GetReagent(name); + if (reagent.Enabled) + { + costSortedReagents.Add(reagent); + } + } + costSortedReagents.Sort(new ReagentCostSort()); + this.maxReagents = (uint)Math.Min(costSortedReagents.Count, this.maxReagents); + + totalReagents = (uint)costSortedReagents.Count; + + // Pre-populate recipes list with: + // 1) 1-ingredient recipes @ 10db for all enabled ingredients with a count >= 10 + // 2) any previously-generated recipes + foreach (Reagent reagent in costSortedReagents) + { + if (!reagent.IsCatalyst && reagent.RecipeMax >= 10) + { + PaintRecipe recipe = new PaintRecipe(); + recipe.Reactions = reactions; + recipe.AddReagent(reagent.Name, 10); + AddCheapestRecipe(recipe); + } + } + + for (uint reagentIdx = 0; reagentIdx < costSortedReagents.Count; ++reagentIdx) + { + SearchNode initialNode = new SearchNode(costSortedReagents, reagentIdx); + initialNode.MaxQuantity = maxQuantity; + initialNode.MaxReagents = maxReagents; + searchQueue.Enqueue(initialNode); + } + + // start worker threads to do the actual work + + requestCancel = false; + running = true; + // Start the workers thread + for (int i = 0; i < costSortedReagents.Count; ++i) + { + Thread thr = new Thread(new ThreadStart(this.Generate)); + generatorThreads.Add(thr); + } + foreach (Thread thr in generatorThreads) + { + thr.Start(); + } + + } + + public void ResumeRecipeGeneration() + { + if (running) + { + // Already running - don't start again + return; + } + this.running = true; + requestCancel = false; + + for (int i = 0; i < costSortedReagents.Count; ++i) + { + Thread thr = new Thread(new ThreadStart(this.Generate)); + generatorThreads.Add(thr); + } + foreach (Thread thr in generatorThreads) + { + thr.Start(); + } + } + + private void Generate() + { + SearchNode node; + + bool ok = true; + do + { + lock (workerLock) + { + ok = searchQueue.TryDequeue(out node); + } + if (ok) + { + uint targetQuantity = node.MaxQuantity + 1; + do { + --targetQuantity; + node.InitForQuantity(targetQuantity); + } while (targetQuantity > 10 && (node.CurrentTargetQuantity != node.UsedQuantity)); + + while (ok = Iterate(node) && !requestCancel) + { + if (Progress != null) + { + Progress(this, null); + } + } + if (ok) + { + // stopped because cancel was requested - requeue the node in its current state for resume + searchQueue.Enqueue(node); + } + } + } while (!requestCancel && ok); + + bool done = false; + lock(workerLock) + { + generatorThreads.Remove(Thread.CurrentThread); + + done = (generatorThreads.Count == 0); + } + if (done) + { + if (Finished != null) + { + Finished(this, null); + } + running = false; + } + } + + // Add the cheapest recipe to the recipe list + // returns the discarded recipe from the pair (or null if no original recipe to replace) + private PaintRecipe AddCheapestRecipe(PaintRecipe recipe) + { + PaintRecipe discarded = recipe; + if (recipe.IsValid) + { + recipe.Reactions = reactions; + + string colorName = Palette.FindNearest(recipe.ReactedColor); + //System.Console.WriteLine("Recipe: {0} {1}:", colorName, recipe.Cost); + //foreach (PaintRecipe.RecipeIngredient ingr in recipe.Ingredients) + //{ + // System.Console.WriteLine(" -> {0} {1}", ingr.quantity, ingr.name); + //} + uint cost; + lock (workerLock) + { + if (recipeCosts.TryGetValue(colorName, out cost)) + { + if (cost > recipe.Cost) + { + discarded = recipes[colorName]; + recipeCosts[colorName] = recipe.Cost; + recipes[colorName] = recipe; + if (NewRecipe != null) + { + NewRecipeEventArgs args = new NewRecipeEventArgs(colorName, recipe); + NewRecipe(this, args); + } + } + } + else + { + discarded = null; + recipeCosts.Add(colorName, recipe.Cost); + recipes.Add(colorName, recipe); + if (NewRecipe != null) + { + NewRecipeEventArgs args = new NewRecipeEventArgs(colorName, recipe); + NewRecipe(this, args); + } + } + } + } + else + { + string msg = String.Format("Recipe is invalid ({0} ingredients)\n", recipe.Ingredients.Count); + foreach (PaintRecipe.RecipeIngredient ingr in recipe.Ingredients) + { + msg += String.Format(" -> {0} {1}", ingr.quantity, ingr.name); + } + lock (workerLock) { + Console.WriteLine(msg); + } + } + + return discarded; + } + + private bool Iterate(SearchNode node) + { + // pick recipe quantities at current recipe ingredients/size + if (NextRecipe(node)) + { + lock(workerLock) + { + ++recipeCount; + } + //System.Console.WriteLine("Found next recipe at size {0} qty {1}", node.Reagents.Count, node.CurrentTargetQuantity); + return true; + } + + if (NextRecipeSize(node)) + { + //System.Console.WriteLine("Found next recipee size {0}", node.CurrentTargetQuantity); + return true; + } + + // Search for next ingredient combo - all quantity combos for previous were searched + //System.Console.WriteLine("Finding next ingredient combo"); + do + { + if (!node.AddNextReagent()) + { + while ((node.Reagents.Count > node.InitialCount) && (node.LastReagent == (totalReagents-1))) + { + node.RemoveLastReagent(); + } + if (node.Reagents.Count == node.InitialCount) + { + // done + return false; + } + uint nextReagent = node.NextFreeReagent(node.LastReagent); + while ((node.Reagents.Count > node.InitialCount) && (nextReagent >= totalReagents)) + { + // No more reagents to try at this level + node.RemoveLastReagent(); + if (node.Reagents.Count > node.InitialCount) + { + nextReagent = node.NextFreeReagent(node.LastReagent); + } + } + if (node.Reagents.Count == node.InitialCount) + { + // done + return false; + } + node.ReplaceLastReagent(nextReagent); + } + } while (node.MaxQuantity < (10 + node.CatalystCount)); + node.InitForQuantity(node.MaxQuantity); + + //string outStr = "{0} : {1} : "; + //for (int i = 0; i < currentReagents.Count; ++i) + //{ + // Reagent reagent = costSortedReagents[(int)currentReagents[i]]; + // if (i > 0) + // { + // outStr += ", "; + // } + // outStr += reagent.Name + " (" + reagent.Cost + ")"; + //} + //Console.WriteLine(outStr, currentReagents.Count, recipeCount); + return true; + } + + private bool NextRecipe(SearchNode node) + { + // First, run the current recipe + if (node.TestRecipe == null) + { + node.TestRecipe = new PaintRecipe(); + node.TestRecipe.Reactions = reactions; + } + node.TestRecipe.Clear(); + for (int i = 0; i < node.Reagents.Count; ++i) + { + node.TestRecipe.AddReagent(node.Reagent(i).Name, node.CurrentWeights[i]); + } + PaintRecipe replacement = AddCheapestRecipe(node.TestRecipe); + if (replacement == null) + { + node.TestRecipe = new PaintRecipe(); + node.TestRecipe.Reactions = reactions; + } + else + { + node.TestRecipe = replacement; + } + + // check for the next recipe + uint remainingWeight = node.CurrentTargetQuantity - node.CatalystCount; + if (remainingWeight < 10) + { + // not possible to make a valid recipe + return false; + } + //uint remainingReagents = (uint)node.Reagents.Count - node.CatalystCount; + + uint depth = (uint)node.Reagents.Count; + uint weightToConsume = 0; + uint spaceBelow = 0; + int reagentsBelow = 0; + for (int i = (int)depth-1 ; i >= 0; --i) + { + uint currentWeight = node.CurrentWeights[i]; + + if ((spaceBelow >= (weightToConsume+1)) && (currentWeight > 1)) + { + // reduce this node by 1, allocate remaining weight to reagents below it + node.SetWeight(i, currentWeight-1); + weightToConsume += 1; + for (int j = i+1; j < depth; ++j) + { + --reagentsBelow; + Reagent reagent = node.Reagent(j); + uint allocated = (uint)Math.Min(reagent.IsCatalyst ? 1 : (depth <= fullQuantityDepth ? fullQuantity : reagent.RecipeMax), weightToConsume - reagentsBelow); + if (allocated > 100) + { + Console.WriteLine("ACK: allocated = {0}", allocated); + } + node.SetWeight(j, allocated); + weightToConsume -= allocated; + } + break; + } + else + { + Reagent reagent = node.Reagent(i); + spaceBelow += (reagent.IsCatalyst ? 1 : (depth <= fullQuantityDepth ? fullQuantity : reagent.RecipeMax)); + weightToConsume += currentWeight; + ++reagentsBelow; + } + } + + //int recipeWeight = 0; + //foreach (int weight in node.CurrentWeights) + //{ + // recipeWeight += weight; + //} + //if ((weightToConsume != 0) || (recipeWeight != node.CurrentTargetQuantity)) + //{ + // Console.WriteLine("Failed recipe with leftover weight {0} ({1}/{2}):", weightToConsume, recipeWeight, node.CurrentTargetQuantity); + // for (int i = 0; i < node.Reagents.Count; ++i) + // { + // Console.WriteLine(" > {0} {1}", node.Reagent(i).Name, node.CurrentWeights[i]); + // } + //} + + return (weightToConsume == 0); + } + + private bool NextRecipeSize(SearchNode node) + { + uint newQuantity = node.CurrentTargetQuantity - 1; + if (newQuantity < (10 + node.CatalystCount)) + { + return false; + } + + node.InitForQuantity(newQuantity); + if (node.CurrentTargetQuantity > node.UsedQuantity) + { + return false; + } + + return true; + } + + public void Wait() + { + if (running) + { + foreach (Thread thr in generatorThreads) + { + thr.Join(); + } + } + } + + public void Stop() + { + this.requestCancel = true; + } + + public void Reset() + { + recipes.Clear(); + recipeCosts.Clear(); + } + } +} + diff --git a/RecipeGeneratorWindow.cs b/RecipeGeneratorWindow.cs new file mode 100644 --- /dev/null +++ b/RecipeGeneratorWindow.cs @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2015, Jason Maltzen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +using System; +using System.Collections.Generic; + +namespace DesertPaintLab +{ + public partial class RecipeGeneratorWindow : Gtk.Window + { + RecipeGenerator generator; + PlayerProfile profile; + bool canceling = false; + bool running = false; + + static Gtk.ListStore colorStore = new Gtk.ListStore(typeof(string)); + + List> missingReactions = new List>(); + + long lastProgressUpdate; + long lastStatusUpdate; + + static public Gtk.ListStore RecipeModel + { + get + { + return colorStore; + } + } + + public RecipeGeneratorWindow(PlayerProfile profile) : base(Gtk.WindowType.Toplevel) + { + this.profile = profile; + this.Build(); + maxIngredientsSpinButton.Value = 5; // TODO: read/save profile info + maxRecipeSpinButton.Value = 20; // TODO: read/save profile info + fullQuantitySpinButton.Value = 20; // TODO: read/save profile info + fullQuantityDepthSpinButton.Value = 4; // TODO: read/save profile info + + fullQuantityDepthSpinButton.SetRange(0, ReagentManager.Names.Count); + maxIngredientsSpinButton.SetRange(0, ReagentManager.Names.Count); + + Gtk.TreeViewColumn recipeColorColumn = new Gtk.TreeViewColumn(); + Gtk.CellRendererText recipeColumnCell = new Gtk.CellRendererText(); + recipeColorColumn.PackStart(recipeColumnCell, true); + recipeColorColumn.Title = "Color"; + + recipeList.AppendColumn(recipeColorColumn); + recipeColorColumn.AddAttribute(recipeColumnCell, "text", 0); + + colorStore.SetSortColumnId(0, Gtk.SortType.Ascending); + recipeList.Model = RecipeModel; + + recipeList.Selection.Changed += OnColorSelected; + + profile.LoadRecipes(); + + // init UI + foreach (string key in profile.Recipes.Keys) + { + colorStore.AppendValues(key); + } + + countLabel.Text = String.Format("{0} / {1}", profile.Recipes.Count, Palette.Count); + canceling = false; + running = false; + } + + protected void OnMaxIngredientsChanged(object sender, EventArgs e) + { + // TODO: save profile setting + // TODO: no longer permit resume + } + + protected void OnMaxRecipeChanged(object sender, EventArgs e) + { + // TODO: save profile setting + // TODO: no longer permit resume + } + + protected void OnFullQuantityDepthChanged(object sender, EventArgs e) + { + // TODO: save profile setting + // TODO: no longer permit resume + } + + protected void OnFullQuantityChanged(object sender, EventArgs e) + { + // TODO: save profile setting + // TODO: no longer permit resume + } + + protected void OnBegin(object sender, EventArgs e) + { + maxIngredientsSpinButton.Sensitive = false; + ExportAction.Sensitive = false; + SettingsAction.Sensitive = false; + maxRecipeSpinButton.Sensitive = false; + beginButton.Sensitive = false; // TODO: change to "pause"? + stopResumeButton.Sensitive = true; + fullQuantitySpinButton.Sensitive = false; + fullQuantityDepthSpinButton.Sensitive = false; + + generator = new RecipeGenerator(); + + generator.InitRecipes(profile.Recipes, profile.Reactions); + countLabel.Text = String.Format("{0} / {1}", generator.Recipes.Count, Palette.Count); + + generator.Progress += OnProgress; + generator.Finished += OnFinished; + generator.NewRecipe += OnNewRecipe; + // TODO: hook up event notifications + // - progress + // - complete + // - new recipe / recipe update + + // Total recipe search count + //int current = ReagentManager.Names.Count; + //long recipePermutations = 1; + //for (int i = 0; i < maxIngredientsSpinButton.ValueAsInt; ++i) + //{ + // recipePermutations *= current; + // --current; + //} + //System.Console.WriteLine("Will search {0} reagent permutations.", recipePermutations); + + lastProgressUpdate = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + lastStatusUpdate = lastProgressUpdate; + + running = true; + stopResumeButton.Label = "Stop"; + + generator.BeginRecipeGeneration(profile.Reactions, (uint)maxRecipeSpinButton.ValueAsInt, (uint)maxIngredientsSpinButton.ValueAsInt, (uint)fullQuantityDepthSpinButton.ValueAsInt, (uint)fullQuantitySpinButton.ValueAsInt); + } + + protected void OnStopResume(object sender, EventArgs e) + { + if (generator != null) + { + if (running) + { + canceling = true; + generator.Stop(); + } + else + { + // this must be a resume + lastProgressUpdate = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + lastStatusUpdate = lastProgressUpdate; + + canceling = false; + running = true; + + stopResumeButton.Label = "Stop"; + generator.BeginRecipeGeneration(profile.Reactions, (uint)maxRecipeSpinButton.ValueAsInt, (uint)maxIngredientsSpinButton.ValueAsInt, (uint)fullQuantityDepthSpinButton.ValueAsInt, (uint)fullQuantitySpinButton.ValueAsInt); + } + } + } + + protected void OnFinished(object sender, EventArgs args) + { + Gtk.Application.Invoke(delegate { + running = false; + beginButton.Sensitive = true; + ExportAction.Sensitive = true; + SettingsAction.Sensitive = true; + stopResumeButton.Sensitive = false; + maxIngredientsSpinButton.Sensitive = true; + maxRecipeSpinButton.Sensitive = true; + fullQuantitySpinButton.Sensitive = true; + fullQuantityDepthSpinButton.Sensitive = true; + //generator = null; // don't. Hang on to generator for resume. + profile.SaveRecipes(); + if (canceling) + { + stopResumeButton.Label = "Resume"; + stopResumeButton.Sensitive = true; + } + }); + } + + protected void OnNewRecipe(object sender, NewRecipeEventArgs args) + { + Gtk.Application.Invoke(delegate + { + progressBar.Pulse(); + // TODO: Add item to recipe list only if not already listed + bool exists = false; + Gtk.TreeIter iter; + if (colorStore.GetIterFirst(out iter)) + { + do + { + string color = (string)colorStore.GetValue(iter, 0); + if (color.Equals(args.Color)) + { + exists = true; + break; + } + } while (colorStore.IterNext(ref iter)); + } + if (!exists) + { + Console.WriteLine("Add new recipe for {0}", args.Color); + // bool isMissingReactions = args.Recipe.CheckMissingReactions(ref missingReactions); + // string missingReactionLabel = isMissingReactions ? "X" : ""; + colorStore.AppendValues(args.Color); // , missingReactionLabel); + countLabel.Text = String.Format("{0} / {1}", generator.Recipes.Count, Palette.Count); + } + profile.SetRecipe(args.Recipe); + }); + } + + protected void OnProgress(object sender, EventArgs args) + { + Gtk.Application.Invoke(delegate + { + // TODO: based on time rather than count + long progressTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + long delta = progressTime - lastProgressUpdate; + if (delta > 30) + { + progressBar.Pulse(); + lastProgressUpdate = progressTime; + } + delta = progressTime - lastStatusUpdate; + if (delta > 500) + { + // Update recipe count label as well + statusLabel.Text = String.Format("Recipes searched: {0:N00}", generator.RecipeCount); + lastStatusUpdate = progressTime; + } + //progressBar.Fraction = (double)((generator.RecipeCount / 10000) % 100) / 100.0; + }); + } + + protected void OnColorSelected(object o, EventArgs args) + { + Gtk.TreeModel model; + Gtk.TreeIter iter; + Gtk.TreeSelection selection = recipeList.Selection; + if ((selection != null) && selection.GetSelected(out model, out iter)) + { + string colorName = (string)colorStore.GetValue(iter, 0); + PaintRecipe recipe; + if (profile.Recipes.TryGetValue(colorName, out recipe)) + { + foreach (Gtk.Widget child in recipeListBox.AllChildren) + { + recipeListBox.Remove(child); + } + if (recipe.CheckMissingReactions(ref missingReactions)) + { + statusLabel.Text = "WARNING: This recipe includes reactions that have not yet been recorded."; + } + foreach (PaintRecipe.RecipeIngredient ingredient in recipe.Ingredients) + { + Gtk.Label label = new Gtk.Label(ingredient.quantity.ToString() + " " + ingredient.name); + recipeListBox.PackStart(label); + label.Show(); + } + } + paintSwatch.Color = recipe.ReactedColor; + } + } + + protected void OnExportToWiki(object sender, EventArgs e) + { + Gtk.FileChooserDialog fileDialog = + new Gtk.FileChooserDialog("Select destination file.", + this, Gtk.FileChooserAction.Save, + Gtk.Stock.Cancel, Gtk.ResponseType.Cancel, + Gtk.Stock.Save, Gtk.ResponseType.Accept); + Gtk.ResponseType resp = (Gtk.ResponseType)fileDialog.Run(); + if (resp == Gtk.ResponseType.Accept) + { + string fileName = fileDialog.Filename; + string directory = fileDialog.CurrentFolder; + profile.ExportWikiRecipes(System.IO.Path.Combine(directory, fileName)); + } + fileDialog.Destroy(); + } + + protected void OnShowIngredients(object sender, EventArgs e) + { + ReagentWindow win = new ReagentWindow(profile); + win.Show(); + } + } +} + diff --git a/SimulatorWindow.cs b/SimulatorWindow.cs --- a/SimulatorWindow.cs +++ b/SimulatorWindow.cs @@ -27,14 +27,30 @@ namespace DesertPaintLab { public partial class SimulatorWindow : Gtk.Window { - PlayerProfile profile; Gtk.ListStore recipeData = new Gtk.ListStore(typeof(string), typeof(int)); - PaintRecipe paintRecipe = new PaintRecipe(); + PaintRecipe paintRecipe; + + List missingWarned = new List(); + List> newMissing = new List>(); + + private class IngredientPair + { + public string first; + public string second; + + public IngredientPair(string first, string second) + { + this.first = first; + this.second = second; + } + } public SimulatorWindow(PlayerProfile profile) : base(Gtk.WindowType.Toplevel) { - this.profile = profile; this.Build (); + + paintRecipe = new PaintRecipe(); + paintRecipe.Reactions = profile.Reactions; Gtk.TreeViewColumn reagentColumn = new Gtk.TreeViewColumn(); Gtk.CellRendererText reagentColumnCell = new Gtk.CellRendererText(); @@ -145,9 +161,35 @@ namespace DesertPaintLab } while (recipeData.IterNext(ref iter)); - PaintColor resultColor = new PaintColor(); - paintRecipe.ComputeReactedColor(profile, ref resultColor); - paintSwatch.Color = resultColor; + PaintColor resultColor = new PaintColor(paintRecipe.ReactedColor); + paintSwatch.Color = resultColor; + if (paintRecipe.CheckMissingReactions(ref newMissing)) + { + string warningMsg = ""; + + foreach (KeyValuePair newEntry in newMissing) + { + IngredientPair match = missingWarned.Find(x => (x.first.Equals(newEntry.Key) && x.second.Equals(newEntry.Value))); + if (match == null) + { + match = new IngredientPair(newEntry.Key, newEntry.Value); + missingWarned.Add(match); + warningMsg += newEntry.Key + " + " + newEntry.Value + "\n"; + } + } + if (warningMsg.Length > 0) + { + Gtk.MessageDialog md = new Gtk.MessageDialog(this, + Gtk.DialogFlags.DestroyWithParent, + Gtk.MessageType.Warning, + Gtk.ButtonsType.Ok, + "These combinations have not yet had reactions recorded:\n\n" + + warningMsg); + md.Run(); + md.Destroy(); + } + } + } protected virtual void OnIncrementReagent (object sender, System.EventArgs e) diff --git a/bin/Debug/DesertPaintLab/template/ingredients.txt b/bin/Debug/DesertPaintLab/template/ingredients.txt --- a/bin/Debug/DesertPaintLab/template/ingredients.txt +++ b/bin/Debug/DesertPaintLab/template/ingredients.txt @@ -5,17 +5,17 @@ // It is not recommended to set many of the ingredients above 10 per paint Cabbage | 128, 64, 144 | 8 | Y | bulk | 10 -Clay | 128, 96, 32 | 4 | Y | bulk | 20 -Carrot | 224, 112, 32 | 10 | Y | bulk | 10 -Copper | 64, 192, 192 | 30 | Y | normal | 8 -Iron | 96, 48, 32 | 30 | Y | normal | 8 +Carrot | 224, 112, 32 | 8 | Y | bulk | 10 +Clay | 128, 96, 32 | 10 | Y | bulk | 20 +DeadTongue | 112, 64, 64 | 500 | Y | normal | 4 +ToadSkin | 48, 96, 48 | 500 | Y | normal | 4 +EarthLight | 128, 240, 224 | 10000 | Y | normal | 4 +RedSand | 144, 16, 24 | 4 | Y | bulk | 20 Lead | 80, 80, 96 | 50 | Y | normal | 6 -RedSand | 144, 16, 24 | 10 | Y | bulk | 20 -Silver | 16, 16, 32 | 50 | N | normal | 6 -ToadSkin | 48, 96, 48 | 500 | N | normal | 4 -DeadTongue | 112, 64, 64 | 500 | N | normal | 4 -EarthLight | 128, 240, 224 | 10000 | N | normal | 4 +Silver | 16, 16, 32 | 50 | Y | normal | 6 +Iron | 96, 48, 32 | 30 | Y | normal | 8 +Copper | 64, 192, 192 | 30 | Y | normal | 8 +Sulfur | catalyst | 15 | Y | normal | 1 +Potash | catalyst | 50 | Y | normal | 1 Lime | catalyst | 20 | Y | normal | 1 -Sulfur | catalyst | 10 | Y | normal | 1 -Potash | catalyst | 50 | Y | normal | 1 -Saltpeter | catalyst | 10 | Y | normal | 1 \ No newline at end of file +Saltpeter | catalyst | 10 | Y | normal | 1 diff --git a/bin/Debug/ingredients.txt b/bin/Debug/ingredients.txt new file mode 100644 --- /dev/null +++ b/bin/Debug/ingredients.txt @@ -0,0 +1,19 @@ +// Desert Paint Lab Ingredients +// Name | Practical Paint Name | RGB values +// These should be kept in the order they show up on the paint bench + +Cabbage Juice | Cabbage | 128, 64, 144 +Carrot | Carrot | 224, 112, 32 +Clay | Clay | 128, 96, 32 +Dead Tongue | DeadTongue | 112, 64, 64 +Toad Skin | ToadSkin | 48, 96, 48 +Earth Light | EarthLight | 128, 240, 224 +Red Sand | RedSand | 144, 16, 24 +Lead | Lead | 80, 80, 96 +Silver Powder | Silver | 16, 16, 32 +Iron | Iron | 96, 48, 32 +Copper | Copper | 64, 192, 192 +Sulfur | Sulfur | catalyst +Potash | Potash | catalyst +Lime | Lime | catalyst +Saltpeter | Saltpeter | catalyst diff --git a/bin/Debug/template/ingredients.txt b/bin/Debug/template/ingredients.txt --- a/bin/Debug/template/ingredients.txt +++ b/bin/Debug/template/ingredients.txt @@ -5,17 +5,17 @@ // It is not recommended to set many of the ingredients above 10 per paint Cabbage | 128, 64, 144 | 8 | Y | bulk | 10 -Clay | 128, 96, 32 | 4 | Y | bulk | 20 -Carrot | 224, 112, 32 | 10 | Y | bulk | 10 -Copper | 64, 192, 192 | 30 | Y | normal | 8 -Iron | 96, 48, 32 | 30 | Y | normal | 8 +Carrot | 224, 112, 32 | 8 | Y | bulk | 10 +Clay | 128, 96, 32 | 10 | Y | bulk | 20 +DeadTongue | 112, 64, 64 | 500 | Y | normal | 4 +ToadSkin | 48, 96, 48 | 500 | Y | normal | 4 +EarthLight | 128, 240, 224 | 10000 | Y | normal | 4 +RedSand | 144, 16, 24 | 4 | Y | bulk | 20 Lead | 80, 80, 96 | 50 | Y | normal | 6 -RedSand | 144, 16, 24 | 10 | Y | bulk | 20 -Silver | 16, 16, 32 | 50 | N | normal | 6 -ToadSkin | 48, 96, 48 | 500 | N | normal | 4 -DeadTongue | 112, 64, 64 | 500 | N | normal | 4 -EarthLight | 128, 240, 224 | 10000 | N | normal | 4 +Silver | 16, 16, 32 | 50 | Y | normal | 6 +Iron | 96, 48, 32 | 30 | Y | normal | 8 +Copper | 64, 192, 192 | 30 | Y | normal | 8 +Sulfur | catalyst | 15 | Y | normal | 1 +Potash | catalyst | 50 | Y | normal | 1 Lime | catalyst | 20 | Y | normal | 1 -Sulfur | catalyst | 10 | Y | normal | 1 -Potash | catalyst | 50 | Y | normal | 1 -Saltpeter | catalyst | 10 | Y | normal | 1 \ No newline at end of file +Saltpeter | catalyst | 10 | Y | normal | 1 diff --git a/bin/Release/ingredients.txt b/bin/Release/ingredients.txt new file mode 100644 --- /dev/null +++ b/bin/Release/ingredients.txt @@ -0,0 +1,19 @@ +// Desert Paint Lab Ingredients +// Name | Practical Paint Name | RGB values +// These should be kept in the order they show up on the paint bench + +Cabbage Juice | Cabbage | 128, 64, 144 +Carrot | Carrot | 224, 112, 32 +Clay | Clay | 128, 96, 32 +Dead Tongue | DeadTongue | 112, 64, 64 +Toad Skin | ToadSkin | 48, 96, 48 +Earth Light | EarthLight | 128, 240, 224 +Red Sand | RedSand | 144, 16, 24 +Lead | Lead | 80, 80, 96 +Silver Powder | Silver | 16, 16, 32 +Iron | Iron | 96, 48, 32 +Copper | Copper | 64, 192, 192 +Sulfur | Sulfur | catalyst +Potash | Potash | catalyst +Lime | Lime | catalyst +Saltpeter | Saltpeter | catalyst diff --git a/bin/Release/template/ingredients.txt b/bin/Release/template/ingredients.txt --- a/bin/Release/template/ingredients.txt +++ b/bin/Release/template/ingredients.txt @@ -5,17 +5,17 @@ // It is not recommended to set many of the ingredients above 10 per paint Cabbage | 128, 64, 144 | 8 | Y | bulk | 10 -Clay | 128, 96, 32 | 4 | Y | bulk | 20 -Carrot | 224, 112, 32 | 10 | Y | bulk | 10 -Copper | 64, 192, 192 | 30 | Y | normal | 8 -Iron | 96, 48, 32 | 30 | Y | normal | 8 +Carrot | 224, 112, 32 | 8 | Y | bulk | 10 +Clay | 128, 96, 32 | 10 | Y | bulk | 20 +DeadTongue | 112, 64, 64 | 500 | Y | normal | 4 +ToadSkin | 48, 96, 48 | 500 | Y | normal | 4 +EarthLight | 128, 240, 224 | 10000 | Y | normal | 4 +RedSand | 144, 16, 24 | 4 | Y | bulk | 20 Lead | 80, 80, 96 | 50 | Y | normal | 6 -RedSand | 144, 16, 24 | 10 | Y | bulk | 20 -Silver | 16, 16, 32 | 50 | N | normal | 6 -ToadSkin | 48, 96, 48 | 500 | N | normal | 4 -DeadTongue | 112, 64, 64 | 500 | N | normal | 4 -EarthLight | 128, 240, 224 | 10000 | N | normal | 4 +Silver | 16, 16, 32 | 50 | Y | normal | 6 +Iron | 96, 48, 32 | 30 | Y | normal | 8 +Copper | 64, 192, 192 | 30 | Y | normal | 8 +Sulfur | catalyst | 15 | Y | normal | 1 +Potash | catalyst | 50 | Y | normal | 1 Lime | catalyst | 20 | Y | normal | 1 -Sulfur | catalyst | 10 | Y | normal | 1 -Potash | catalyst | 50 | Y | normal | 1 -Saltpeter | catalyst | 10 | Y | normal | 1 \ No newline at end of file +Saltpeter | catalyst | 10 | Y | normal | 1 diff --git a/gtk-gui/DesertPaintLab.ReagentWindow.cs b/gtk-gui/DesertPaintLab.ReagentWindow.cs new file mode 100644 --- /dev/null +++ b/gtk-gui/DesertPaintLab.ReagentWindow.cs @@ -0,0 +1,101 @@ + +// This file has been generated by the GUI designer. Do not modify. +namespace DesertPaintLab +{ + public partial class ReagentWindow + { + private global::Gtk.VBox vbox2; + + private global::Gtk.Frame frame3; + + private global::Gtk.Alignment GtkAlignment; + + private global::Gtk.Table ingredientTable; + + private global::Gtk.Label GtkLabel; + + private global::Gtk.HButtonBox hbuttonbox3; + + private global::Gtk.Button okButton; + + private global::Gtk.Button cancelButton; + + protected virtual void Build () + { + global::Stetic.Gui.Initialize (this); + // Widget DesertPaintLab.ReagentWindow + this.Name = "DesertPaintLab.ReagentWindow"; + this.Title = "ReagentWindow"; + this.WindowPosition = ((global::Gtk.WindowPosition)(4)); + // Container child DesertPaintLab.ReagentWindow.Gtk.Container+ContainerChild + this.vbox2 = new global::Gtk.VBox (); + this.vbox2.Name = "vbox2"; + this.vbox2.Spacing = 6; + this.vbox2.BorderWidth = ((uint)(8)); + // Container child vbox2.Gtk.Box+BoxChild + this.frame3 = new global::Gtk.Frame (); + this.frame3.Name = "frame3"; + this.frame3.ShadowType = ((global::Gtk.ShadowType)(0)); + // Container child frame3.Gtk.Container+ContainerChild + this.GtkAlignment = new global::Gtk.Alignment (0F, 0F, 1F, 1F); + this.GtkAlignment.Name = "GtkAlignment"; + this.GtkAlignment.LeftPadding = ((uint)(12)); + // Container child GtkAlignment.Gtk.Container+ContainerChild + this.ingredientTable = new global::Gtk.Table (((uint)(3)), ((uint)(3)), false); + this.ingredientTable.Name = "ingredientTable"; + this.ingredientTable.RowSpacing = ((uint)(6)); + this.ingredientTable.ColumnSpacing = ((uint)(6)); + this.GtkAlignment.Add (this.ingredientTable); + this.frame3.Add (this.GtkAlignment); + this.GtkLabel = new global::Gtk.Label (); + this.GtkLabel.Name = "GtkLabel"; + this.GtkLabel.LabelProp = "Ingredients"; + this.GtkLabel.UseMarkup = true; + this.frame3.LabelWidget = this.GtkLabel; + this.vbox2.Add (this.frame3); + global::Gtk.Box.BoxChild w3 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.frame3])); + w3.Position = 0; + // Container child vbox2.Gtk.Box+BoxChild + this.hbuttonbox3 = new global::Gtk.HButtonBox (); + this.hbuttonbox3.Name = "hbuttonbox3"; + // Container child hbuttonbox3.Gtk.ButtonBox+ButtonBoxChild + this.okButton = new global::Gtk.Button (); + this.okButton.WidthRequest = 80; + this.okButton.CanFocus = true; + this.okButton.Name = "okButton"; + this.okButton.UseUnderline = true; + this.okButton.Label = "Ok"; + this.hbuttonbox3.Add (this.okButton); + global::Gtk.ButtonBox.ButtonBoxChild w4 = ((global::Gtk.ButtonBox.ButtonBoxChild)(this.hbuttonbox3 [this.okButton])); + w4.Expand = false; + w4.Fill = false; + // Container child hbuttonbox3.Gtk.ButtonBox+ButtonBoxChild + this.cancelButton = new global::Gtk.Button (); + this.cancelButton.WidthRequest = 80; + this.cancelButton.CanFocus = true; + this.cancelButton.Name = "cancelButton"; + this.cancelButton.UseUnderline = true; + this.cancelButton.Label = "Cancel"; + this.hbuttonbox3.Add (this.cancelButton); + global::Gtk.ButtonBox.ButtonBoxChild w5 = ((global::Gtk.ButtonBox.ButtonBoxChild)(this.hbuttonbox3 [this.cancelButton])); + w5.Position = 1; + w5.Expand = false; + w5.Fill = false; + this.vbox2.Add (this.hbuttonbox3); + global::Gtk.Box.BoxChild w6 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbuttonbox3])); + w6.PackType = ((global::Gtk.PackType)(1)); + w6.Position = 1; + w6.Expand = false; + w6.Fill = false; + this.Add (this.vbox2); + if ((this.Child != null)) { + this.Child.ShowAll (); + } + this.DefaultWidth = 400; + this.DefaultHeight = 357; + this.Show (); + this.okButton.Clicked += new global::System.EventHandler (this.OnOK); + this.cancelButton.Clicked += new global::System.EventHandler (this.OnCancel); + } + } +} diff --git a/gtk-gui/DesertPaintLab.RecipeGeneratorWindow.cs b/gtk-gui/DesertPaintLab.RecipeGeneratorWindow.cs new file mode 100644 --- /dev/null +++ b/gtk-gui/DesertPaintLab.RecipeGeneratorWindow.cs @@ -0,0 +1,392 @@ + +// This file has been generated by the GUI designer. Do not modify. +namespace DesertPaintLab +{ + public partial class RecipeGeneratorWindow + { + private global::Gtk.UIManager UIManager; + + private global::Gtk.Action ExportAction; + + private global::Gtk.Action ExportToWikiAction; + + private global::Gtk.Action SettingsAction; + + private global::Gtk.Action IngredientsAction; + + private global::Gtk.VBox vbox2; + + private global::Gtk.MenuBar menubar1; + + private global::Gtk.HBox hbox1; + + private global::Gtk.VBox vbox8; + + private global::Gtk.Label label3; + + private global::Gtk.SpinButton maxIngredientsSpinButton; + + private global::Gtk.HSeparator hseparator3; + + private global::Gtk.Label label4; + + private global::Gtk.SpinButton maxRecipeSpinButton; + + private global::Gtk.HSeparator hseparator4; + + private global::Gtk.Label label8; + + private global::Gtk.SpinButton fullQuantityDepthSpinButton; + + private global::Gtk.Label label9; + + private global::Gtk.SpinButton fullQuantitySpinButton; + + private global::Gtk.ScrolledWindow GtkScrolledWindow1; + + private global::Gtk.TreeView recipeList; + + private global::Gtk.VBox vbox3; + + private global::Gtk.Frame frame2; + + private global::Gtk.Alignment GtkAlignment; + + private global::Gtk.ScrolledWindow scrolledwindow1; + + private global::Gtk.VBox recipeListBox; + + private global::Gtk.Label recipeLabel; + + private global::DesertPaintLab.PaintSwatch paintSwatch; + + private global::Gtk.HBox hbox3; + + private global::Gtk.HSeparator hseparator2; + + private global::Gtk.Button stopResumeButton; + + private global::Gtk.Label countLabel; + + private global::Gtk.Button beginButton; + + private global::Gtk.HSeparator hseparator1; + + private global::Gtk.Label statusLabel; + + private global::Gtk.ProgressBar progressBar; + + protected virtual void Build () + { + global::Stetic.Gui.Initialize (this); + // Widget DesertPaintLab.RecipeGeneratorWindow + this.UIManager = new global::Gtk.UIManager (); + global::Gtk.ActionGroup w1 = new global::Gtk.ActionGroup ("Default"); + this.ExportAction = new global::Gtk.Action ("ExportAction", "Export", null, null); + this.ExportAction.ShortLabel = "Export"; + w1.Add (this.ExportAction, null); + this.ExportToWikiAction = new global::Gtk.Action ("ExportToWikiAction", "Export to Wiki", null, null); + this.ExportToWikiAction.ShortLabel = "Export to Wiki"; + w1.Add (this.ExportToWikiAction, null); + this.SettingsAction = new global::Gtk.Action ("SettingsAction", "Settings", null, null); + this.SettingsAction.ShortLabel = "Tools"; + w1.Add (this.SettingsAction, null); + this.IngredientsAction = new global::Gtk.Action ("IngredientsAction", "Ingredients", null, null); + this.IngredientsAction.ShortLabel = "Ingredients"; + w1.Add (this.IngredientsAction, null); + this.UIManager.InsertActionGroup (w1, 0); + this.AddAccelGroup (this.UIManager.AccelGroup); + this.Name = "DesertPaintLab.RecipeGeneratorWindow"; + this.Title = "Recipe Generator"; + this.WindowPosition = ((global::Gtk.WindowPosition)(4)); + // Container child DesertPaintLab.RecipeGeneratorWindow.Gtk.Container+ContainerChild + this.vbox2 = new global::Gtk.VBox (); + this.vbox2.Name = "vbox2"; + this.vbox2.Spacing = 6; + this.vbox2.BorderWidth = ((uint)(8)); + // Container child vbox2.Gtk.Box+BoxChild + this.UIManager.AddUiFromString (""); + this.menubar1 = ((global::Gtk.MenuBar)(this.UIManager.GetWidget ("/menubar1"))); + this.menubar1.Name = "menubar1"; + this.vbox2.Add (this.menubar1); + global::Gtk.Box.BoxChild w2 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.menubar1])); + w2.Position = 0; + w2.Expand = false; + w2.Fill = false; + // Container child vbox2.Gtk.Box+BoxChild + this.hbox1 = new global::Gtk.HBox (); + this.hbox1.Name = "hbox1"; + this.hbox1.Spacing = 6; + // Container child hbox1.Gtk.Box+BoxChild + this.vbox8 = new global::Gtk.VBox (); + this.vbox8.Name = "vbox8"; + this.vbox8.Spacing = 6; + // Container child vbox8.Gtk.Box+BoxChild + this.label3 = new global::Gtk.Label (); + this.label3.Name = "label3"; + this.label3.LabelProp = "Maximum Ingredients"; + this.vbox8.Add (this.label3); + global::Gtk.Box.BoxChild w3 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label3])); + w3.Position = 0; + w3.Expand = false; + w3.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.maxIngredientsSpinButton = new global::Gtk.SpinButton (0, 14, 1); + this.maxIngredientsSpinButton.CanFocus = true; + this.maxIngredientsSpinButton.Name = "maxIngredientsSpinButton"; + this.maxIngredientsSpinButton.Adjustment.PageIncrement = 10; + this.maxIngredientsSpinButton.ClimbRate = 1; + this.maxIngredientsSpinButton.Numeric = true; + this.vbox8.Add (this.maxIngredientsSpinButton); + global::Gtk.Box.BoxChild w4 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.maxIngredientsSpinButton])); + w4.Position = 1; + w4.Expand = false; + w4.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.hseparator3 = new global::Gtk.HSeparator (); + this.hseparator3.Name = "hseparator3"; + this.vbox8.Add (this.hseparator3); + global::Gtk.Box.BoxChild w5 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator3])); + w5.Position = 2; + w5.Expand = false; + w5.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.label4 = new global::Gtk.Label (); + this.label4.Name = "label4"; + this.label4.LabelProp = "Max Total Quantity"; + this.label4.UseMarkup = true; + this.label4.Wrap = true; + this.vbox8.Add (this.label4); + global::Gtk.Box.BoxChild w6 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label4])); + w6.Position = 3; + w6.Expand = false; + w6.Fill = false; + w6.Padding = ((uint)(8)); + // Container child vbox8.Gtk.Box+BoxChild + this.maxRecipeSpinButton = new global::Gtk.SpinButton (0, 100, 1); + this.maxRecipeSpinButton.CanFocus = true; + this.maxRecipeSpinButton.Name = "maxRecipeSpinButton"; + this.maxRecipeSpinButton.Adjustment.PageIncrement = 10; + this.maxRecipeSpinButton.ClimbRate = 1; + this.maxRecipeSpinButton.Numeric = true; + this.vbox8.Add (this.maxRecipeSpinButton); + global::Gtk.Box.BoxChild w7 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.maxRecipeSpinButton])); + w7.Position = 4; + w7.Expand = false; + w7.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.hseparator4 = new global::Gtk.HSeparator (); + this.hseparator4.Name = "hseparator4"; + this.vbox8.Add (this.hseparator4); + global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.hseparator4])); + w8.Position = 5; + w8.Expand = false; + w8.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.label8 = new global::Gtk.Label (); + this.label8.Name = "label8"; + this.label8.LabelProp = "Full Quantity Depth"; + this.vbox8.Add (this.label8); + global::Gtk.Box.BoxChild w9 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label8])); + w9.Position = 6; + w9.Expand = false; + w9.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.fullQuantityDepthSpinButton = new global::Gtk.SpinButton (0, 15, 1); + this.fullQuantityDepthSpinButton.CanFocus = true; + this.fullQuantityDepthSpinButton.Name = "fullQuantityDepthSpinButton"; + this.fullQuantityDepthSpinButton.Adjustment.PageIncrement = 10; + this.fullQuantityDepthSpinButton.ClimbRate = 1; + this.fullQuantityDepthSpinButton.Numeric = true; + this.vbox8.Add (this.fullQuantityDepthSpinButton); + global::Gtk.Box.BoxChild w10 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.fullQuantityDepthSpinButton])); + w10.Position = 7; + w10.Expand = false; + w10.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.label9 = new global::Gtk.Label (); + this.label9.Name = "label9"; + this.label9.LabelProp = "FullQuantity"; + this.vbox8.Add (this.label9); + global::Gtk.Box.BoxChild w11 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.label9])); + w11.Position = 8; + w11.Expand = false; + w11.Fill = false; + // Container child vbox8.Gtk.Box+BoxChild + this.fullQuantitySpinButton = new global::Gtk.SpinButton (0, 30, 1); + this.fullQuantitySpinButton.CanFocus = true; + this.fullQuantitySpinButton.Name = "fullQuantitySpinButton"; + this.fullQuantitySpinButton.Adjustment.PageIncrement = 10; + this.fullQuantitySpinButton.ClimbRate = 1; + this.fullQuantitySpinButton.Numeric = true; + this.vbox8.Add (this.fullQuantitySpinButton); + global::Gtk.Box.BoxChild w12 = ((global::Gtk.Box.BoxChild)(this.vbox8 [this.fullQuantitySpinButton])); + w12.Position = 9; + w12.Expand = false; + w12.Fill = false; + this.hbox1.Add (this.vbox8); + global::Gtk.Box.BoxChild w13 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.vbox8])); + w13.Position = 0; + w13.Expand = false; + w13.Fill = false; + // Container child hbox1.Gtk.Box+BoxChild + this.GtkScrolledWindow1 = new global::Gtk.ScrolledWindow (); + this.GtkScrolledWindow1.Name = "GtkScrolledWindow1"; + this.GtkScrolledWindow1.ShadowType = ((global::Gtk.ShadowType)(1)); + // Container child GtkScrolledWindow1.Gtk.Container+ContainerChild + this.recipeList = new global::Gtk.TreeView (); + this.recipeList.WidthRequest = 300; + this.recipeList.CanFocus = true; + this.recipeList.Name = "recipeList"; + this.GtkScrolledWindow1.Add (this.recipeList); + this.hbox1.Add (this.GtkScrolledWindow1); + global::Gtk.Box.BoxChild w15 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.GtkScrolledWindow1])); + w15.Position = 1; + // Container child hbox1.Gtk.Box+BoxChild + this.vbox3 = new global::Gtk.VBox (); + this.vbox3.Name = "vbox3"; + this.vbox3.Spacing = 6; + // Container child vbox3.Gtk.Box+BoxChild + this.frame2 = new global::Gtk.Frame (); + this.frame2.WidthRequest = 200; + this.frame2.HeightRequest = 200; + this.frame2.Name = "frame2"; + this.frame2.ShadowType = ((global::Gtk.ShadowType)(0)); + // Container child frame2.Gtk.Container+ContainerChild + this.GtkAlignment = new global::Gtk.Alignment (0F, 0F, 1F, 1F); + this.GtkAlignment.Name = "GtkAlignment"; + this.GtkAlignment.LeftPadding = ((uint)(12)); + // Container child GtkAlignment.Gtk.Container+ContainerChild + this.scrolledwindow1 = new global::Gtk.ScrolledWindow (); + this.scrolledwindow1.CanFocus = true; + this.scrolledwindow1.Name = "scrolledwindow1"; + this.scrolledwindow1.ShadowType = ((global::Gtk.ShadowType)(1)); + // Container child scrolledwindow1.Gtk.Container+ContainerChild + global::Gtk.Viewport w16 = new global::Gtk.Viewport (); + w16.ShadowType = ((global::Gtk.ShadowType)(0)); + // Container child GtkViewport.Gtk.Container+ContainerChild + this.recipeListBox = new global::Gtk.VBox (); + this.recipeListBox.Name = "recipeListBox"; + this.recipeListBox.Homogeneous = true; + this.recipeListBox.Spacing = 6; + this.recipeListBox.BorderWidth = ((uint)(1)); + w16.Add (this.recipeListBox); + this.scrolledwindow1.Add (w16); + this.GtkAlignment.Add (this.scrolledwindow1); + this.frame2.Add (this.GtkAlignment); + this.recipeLabel = new global::Gtk.Label (); + this.recipeLabel.Name = "recipeLabel"; + this.recipeLabel.LabelProp = "Recipe"; + this.recipeLabel.UseMarkup = true; + this.frame2.LabelWidget = this.recipeLabel; + this.vbox3.Add (this.frame2); + global::Gtk.Box.BoxChild w21 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.frame2])); + w21.Position = 0; + // Container child vbox3.Gtk.Box+BoxChild + this.paintSwatch = new global::DesertPaintLab.PaintSwatch (); + this.paintSwatch.HeightRequest = 200; + this.paintSwatch.Events = ((global::Gdk.EventMask)(256)); + this.paintSwatch.Name = "paintSwatch"; + this.vbox3.Add (this.paintSwatch); + global::Gtk.Box.BoxChild w22 = ((global::Gtk.Box.BoxChild)(this.vbox3 [this.paintSwatch])); + w22.Position = 1; + this.hbox1.Add (this.vbox3); + global::Gtk.Box.BoxChild w23 = ((global::Gtk.Box.BoxChild)(this.hbox1 [this.vbox3])); + w23.Position = 2; + w23.Expand = false; + w23.Fill = false; + this.vbox2.Add (this.hbox1); + global::Gtk.Box.BoxChild w24 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox1])); + w24.Position = 1; + // Container child vbox2.Gtk.Box+BoxChild + this.hbox3 = new global::Gtk.HBox (); + this.hbox3.Name = "hbox3"; + this.hbox3.Spacing = 6; + // Container child hbox3.Gtk.Box+BoxChild + this.hseparator2 = new global::Gtk.HSeparator (); + this.hseparator2.Name = "hseparator2"; + this.hbox3.Add (this.hseparator2); + global::Gtk.Box.BoxChild w25 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.hseparator2])); + w25.Position = 0; + // Container child hbox3.Gtk.Box+BoxChild + this.stopResumeButton = new global::Gtk.Button (); + this.stopResumeButton.Sensitive = false; + this.stopResumeButton.CanFocus = true; + this.stopResumeButton.Name = "stopResumeButton"; + this.stopResumeButton.UseUnderline = true; + this.stopResumeButton.Label = "Stop"; + this.hbox3.Add (this.stopResumeButton); + global::Gtk.Box.BoxChild w26 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.stopResumeButton])); + w26.Position = 1; + w26.Expand = false; + w26.Fill = false; + w26.Padding = ((uint)(20)); + // Container child hbox3.Gtk.Box+BoxChild + this.countLabel = new global::Gtk.Label (); + this.countLabel.Name = "countLabel"; + this.countLabel.LabelProp = "0/192"; + this.hbox3.Add (this.countLabel); + global::Gtk.Box.BoxChild w27 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.countLabel])); + w27.Position = 2; + w27.Expand = false; + w27.Fill = false; + w27.Padding = ((uint)(40)); + // Container child hbox3.Gtk.Box+BoxChild + this.beginButton = new global::Gtk.Button (); + this.beginButton.CanFocus = true; + this.beginButton.Name = "beginButton"; + this.beginButton.UseUnderline = true; + this.beginButton.Label = "Begin"; + this.hbox3.Add (this.beginButton); + global::Gtk.Box.BoxChild w28 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.beginButton])); + w28.Position = 3; + w28.Expand = false; + w28.Fill = false; + w28.Padding = ((uint)(20)); + // Container child hbox3.Gtk.Box+BoxChild + this.hseparator1 = new global::Gtk.HSeparator (); + this.hseparator1.Name = "hseparator1"; + this.hbox3.Add (this.hseparator1); + global::Gtk.Box.BoxChild w29 = ((global::Gtk.Box.BoxChild)(this.hbox3 [this.hseparator1])); + w29.Position = 4; + this.vbox2.Add (this.hbox3); + global::Gtk.Box.BoxChild w30 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.hbox3])); + w30.Position = 2; + w30.Expand = false; + w30.Fill = false; + // Container child vbox2.Gtk.Box+BoxChild + this.statusLabel = new global::Gtk.Label (); + this.statusLabel.Name = "statusLabel"; + this.vbox2.Add (this.statusLabel); + global::Gtk.Box.BoxChild w31 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.statusLabel])); + w31.PackType = ((global::Gtk.PackType)(1)); + w31.Position = 3; + w31.Expand = false; + w31.Fill = false; + // Container child vbox2.Gtk.Box+BoxChild + this.progressBar = new global::Gtk.ProgressBar (); + this.progressBar.Name = "progressBar"; + this.vbox2.Add (this.progressBar); + global::Gtk.Box.BoxChild w32 = ((global::Gtk.Box.BoxChild)(this.vbox2 [this.progressBar])); + w32.PackType = ((global::Gtk.PackType)(1)); + w32.Position = 4; + w32.Expand = false; + w32.Fill = false; + this.Add (this.vbox2); + if ((this.Child != null)) { + this.Child.ShowAll (); + } + this.DefaultWidth = 887; + this.DefaultHeight = 570; + this.Show (); + this.ExportToWikiAction.Activated += new global::System.EventHandler (this.OnExportToWiki); + this.IngredientsAction.Activated += new global::System.EventHandler (this.OnShowIngredients); + this.maxIngredientsSpinButton.ValueChanged += new global::System.EventHandler (this.OnMaxIngredientsChanged); + this.maxRecipeSpinButton.ValueChanged += new global::System.EventHandler (this.OnMaxRecipeChanged); + this.fullQuantityDepthSpinButton.ValueChanged += new global::System.EventHandler (this.OnFullQuantityDepthChanged); + this.fullQuantitySpinButton.Input += new global::Gtk.InputHandler (this.OnFullQuantityChanged); + this.stopResumeButton.Clicked += new global::System.EventHandler (this.OnStopResume); + this.beginButton.Clicked += new global::System.EventHandler (this.OnBegin); + } + } +} diff --git a/gtk-gui/MainWindow.cs b/gtk-gui/MainWindow.cs --- a/gtk-gui/MainWindow.cs +++ b/gtk-gui/MainWindow.cs @@ -27,10 +27,12 @@ public partial class MainWindow private global::Gtk.Action ScreenshotAction; - private global::Gtk.Action RecipeGeneratorAction; + private global::Gtk.Action RecipesAction; private global::Gtk.Action ReactionStatusAction; + private global::Gtk.Action IngredientsAction; + private global::Gtk.VBox vbox1; private global::Gtk.MenuBar menubar1; @@ -128,12 +130,15 @@ public partial class MainWindow this.ScreenshotAction = new global::Gtk.Action ("ScreenshotAction", "Screenshot", null, null); this.ScreenshotAction.ShortLabel = "Screenshot"; w1.Add (this.ScreenshotAction, null); - this.RecipeGeneratorAction = new global::Gtk.Action ("RecipeGeneratorAction", "Recipe Generator", null, null); - this.RecipeGeneratorAction.ShortLabel = "Recipe Generator"; - w1.Add (this.RecipeGeneratorAction, null); + this.RecipesAction = new global::Gtk.Action ("RecipesAction", "Recipes", null, null); + this.RecipesAction.ShortLabel = "Recipe Generator"; + w1.Add (this.RecipesAction, null); this.ReactionStatusAction = new global::Gtk.Action ("ReactionStatusAction", "Reaction Status", null, null); this.ReactionStatusAction.ShortLabel = "Reaction Status"; w1.Add (this.ReactionStatusAction, null); + this.IngredientsAction = new global::Gtk.Action ("IngredientsAction", "Ingredients", null, null); + this.IngredientsAction.ShortLabel = "Ingredients"; + w1.Add (this.IngredientsAction, null); this.UIManager.InsertActionGroup (w1, 0); this.AddAccelGroup (this.UIManager.AccelGroup); this.Name = "MainWindow"; @@ -143,7 +148,7 @@ public partial class MainWindow this.vbox1 = new global::Gtk.VBox (); this.vbox1.Name = "vbox1"; // Container child vbox1.Gtk.Box+BoxChild - this.UIManager.AddUiFromString (""); + this.UIManager.AddUiFromString (""); this.menubar1 = ((global::Gtk.MenuBar)(this.UIManager.GetWidget ("/menubar1"))); this.menubar1.Name = "menubar1"; this.vbox1.Add (this.menubar1); @@ -374,8 +379,9 @@ public partial class MainWindow this.ExportForPracticalPaintAction.Activated += new global::System.EventHandler (this.OnExport); this.RunSimulatorAction.Activated += new global::System.EventHandler (this.RunSimulator); this.ScreenshotAction.Activated += new global::System.EventHandler (this.OnDebugScreenshot); - this.RecipeGeneratorAction.Activated += new global::System.EventHandler (this.OnOpenRecipeGenerator); + this.RecipesAction.Activated += new global::System.EventHandler (this.OnOpenRecipeGenerator); this.ReactionStatusAction.Activated += new global::System.EventHandler (this.OnShowReactionStatus); + this.IngredientsAction.Activated += new global::System.EventHandler (this.OnShowIngredients); this.ingredient1ComboBox.Changed += new global::System.EventHandler (this.OnChangedIngredient1); this.ingredient2ComboBox.Changed += new global::System.EventHandler (this.OnChangedIngredient2); this.ingredient3ComboBox.Changed += new global::System.EventHandler (this.OnChangedIngredient3); diff --git a/gtk-gui/gui.stetic b/gtk-gui/gui.stetic --- a/gtk-gui/gui.stetic +++ b/gtk-gui/gui.stetic @@ -78,9 +78,9 @@ Screenshot - + Action - Recipe Generator + Recipes Recipe Generator @@ -90,6 +90,12 @@ Reaction Status + + Action + Ingredients + Ingredients + + Desert Paint Lab @@ -111,7 +117,8 @@ - + + @@ -1133,4 +1140,549 @@ You can either import an existing Practi + + + + Action + Export + Export + + + Action + Export to Wiki + Export to Wiki + + + + Action + Settings + Tools + + + Action + Ingredients + Ingredients + + + + + Recipe Generator + CenterOnParent + + + + 6 + 8 + + + + + + + + + + + + + + 0 + True + False + False + + + + + + 6 + + + + 6 + + + + Maximum Ingredients + + + 0 + True + False + False + + + + + + True + 14 + 10 + 1 + 1 + True + + + + 1 + True + False + False + + + + + + + + 2 + True + False + False + + + + + + Max Total Quantity + True + True + + + 3 + True + False + False + 8 + + + + + + True + 100 + 10 + 1 + 1 + True + + + + 4 + True + False + False + + + + + + + + 5 + True + False + False + + + + + + Full Quantity Depth + + + 6 + True + False + False + + + + + + True + 15 + 10 + 1 + 1 + True + + + + 7 + True + False + False + + + + + + FullQuantity + + + 8 + True + False + False + + + + + + True + 30 + 10 + 1 + 1 + True + + + + 9 + True + False + False + + + + + 0 + True + False + False + + + + + + In + + + + 300 + True + True + + + + + 1 + True + + + + + + 6 + + + + 200 + 200 + None + + + + 0 + 0 + 12 + + + + True + In + + + + None + + + + True + 6 + 1 + + + + + + + + + + + + + + + + + + + + + <b>Recipe</b> + True + + + label_item + + + + + 0 + True + + + + + + 200 + ButtonPressMask + + + 1 + True + + + + + 2 + True + False + False + + + + + 1 + False + + + + + + 6 + + + + + + 0 + True + + + + + + False + True + TextOnly + Stop + True + + + + 1 + True + False + False + 20 + + + + + + 0/192 + + + 2 + True + False + False + 40 + + + + + + True + TextOnly + Begin + True + + + + 3 + True + False + False + 20 + + + + + + + + 4 + True + + + + + 2 + True + False + False + + + + + + + + End + 3 + True + False + False + + + + + + + + End + 4 + True + False + False + + + + + + + + ReagentWindow + CenterOnParent + + + + 6 + 8 + + + + None + + + + 0 + 0 + 12 + + + + 3 + 3 + 6 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <b>Ingredients</b> + True + + + label_item + + + + + 0 + True + + + + + + 2 + + + + 80 + True + TextOnly + Ok + True + + + + False + False + + + + + + 80 + True + TextOnly + Cancel + True + + + + 1 + False + False + + + + + End + 1 + True + False + False + + + + + \ No newline at end of file