/////////////////////////////////////////////////////////////////////////////// //File: UTLBlock_SalvageCombine.cs // //Description: A UTL file block for storing salvage combination ranges. // This file is shared between the VTClassic Plugin and the VTClassic Editor. // //This file is Copyright (c) 2010 VirindiPlugins // //The original copy of this code can be obtained from http://www.virindi.net/repos/virindi_public // //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.Text; #if VTC_PLUGIN using uTank2.LootPlugins; #endif namespace VTClassic.UTLBlockHandlers { internal class UTLBlock_SalvageCombine : IUTLFileBlockHandler { const int SALVAGEBLOCK_FILE_FORMAT_VERSION = 1; static Random rand = new Random(); public string DefaultCombineString = ""; public Dictionary MaterialCombineStrings = new Dictionary(); public Dictionary MaterialValueModeValues = new Dictionary(); public UTLBlock_SalvageCombine() { //Default rules DefaultCombineString = "1-6, 7-8, 9, 10"; string imbs = "1-10"; //Magic item tinkering MaterialCombineStrings[GameInfo.GetMaterialID("Agate")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Azurite")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Black Opal")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Bloodstone")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Carnelian")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Citrine")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Fire Opal")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Hematite")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Lavender Jade")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Malachite")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Red Jade")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Rose Quartz")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Sunstone")] = imbs; //Weapon tinkering MaterialCombineStrings[GameInfo.GetMaterialID("White Sapphire")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Red Garnet")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Jet")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Imperial Topaz")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Emerald")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Black Garnet")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Aquamarine")] = imbs; //Armor tinkering MaterialCombineStrings[GameInfo.GetMaterialID("Zircon")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Yellow Topaz")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Peridot")] = imbs; //Other MaterialCombineStrings[GameInfo.GetMaterialID("Leather")] = imbs; MaterialCombineStrings[GameInfo.GetMaterialID("Ivory")] = imbs; } #region CombineString Parsing struct sDoublePair { public double a; public double b; } static int GetRangeIndex(List Ranges, double val) { for (int i = 0; i < Ranges.Count; ++i) { //Gaps in ranges go to the previous range if (Ranges[i].a > val) return i - 1; //If we fall into this range, choose it if ((Ranges[i].a <= val) && (Ranges[i].b >= val)) return i; } return Ranges.Count; } static List ParseCombineSting(string pcombinestring) { List Ranges = new List(); //Look through the string and delete all characters we don't understand string combinestring = pcombinestring; for (int i = combinestring.Length - 1; i >= 0; --i) { if (Char.IsDigit(combinestring[i])) continue; if (combinestring[i] == ',') continue; if (combinestring[i] == ';') continue; if (combinestring[i] == '-') continue; if (combinestring[i] == '.') continue; combinestring.Remove(i, 1); } //Split and parse string into ranges string[] toks = combinestring.Split(';', ','); foreach (string tok in toks) { if (tok.Length == 0) continue; string[] numbers = tok.Split('-'); if (numbers.Length == 0) continue; sDoublePair addpair = new sDoublePair(); if (numbers.Length == 1) { addpair.a = double.Parse(numbers[0], System.Globalization.CultureInfo.InvariantCulture); addpair.b = addpair.a; } else { addpair.a = double.Parse(numbers[0], System.Globalization.CultureInfo.InvariantCulture); addpair.b = double.Parse(numbers[1], System.Globalization.CultureInfo.InvariantCulture); } Ranges.Add(addpair); } return Ranges; } /* static bool TestCombineString(double w1, double w2, string pcombinestring) { List Ranges = ParseCombineSting(pcombinestring); //Find out which range we fall into return (GetRangeIndex(Ranges, w1) == GetRangeIndex(Ranges, w2)); } * */ #endregion CombineString Parsing /* public bool CanCombineBags(double bag1workmanship, double bag2workmanship, int material) { if (MaterialCombineStrings.ContainsKey(material)) return TestCombineString(bag1workmanship, bag2workmanship, MaterialCombineStrings[material]); else return TestCombineString(bag1workmanship, bag2workmanship, DefaultCombineString); } */ string GetCombineString(int material) { if (MaterialCombineStrings.ContainsKey(material)) return MaterialCombineStrings[material]; else return DefaultCombineString; } #if VTC_PLUGIN public List TryCombineMultiple(List availablebags) { if (availablebags.Count == 0) return new List(); int material = availablebags[0].GetValueInt(IntValueKey.Material, 0); List ranges = ParseCombineSting(GetCombineString(material)); //Bin the available bags by which part of the combine string they fit in. Dictionary> binnedbags = new Dictionary>(); foreach (GameItemInfo zz in availablebags) { int bin = GetRangeIndex(ranges, zz.GetValueDouble(DoubleValueKey.SalvageWorkmanship, 0d)); if (!binnedbags.ContainsKey(bin)) binnedbags.Add(bin, new List()); binnedbags[bin].Add(zz); } foreach (KeyValuePair> kp in binnedbags) { if (kp.Value.Count < 2) continue; //We now have a list of every piece of salvage that can be combined. What should we combine? //Finding the best possible combination of bags is equivalent to the knapsack problem, an NP-complete computational problem. //That is a bit much for something that is called all the time, so we will just use stupid methods of choosing bags. List ret = new List(); if (MaterialValueModeValues.ContainsKey(material)) { //Salvage for money mode. //First, we see if we can cap out a bag. int valuemodevalue = MaterialValueModeValues[material]; int vsum = 0; int csum = 0; foreach (GameItemInfo ii in kp.Value) { ret.Add(ii.Id); vsum += ii.GetValueInt(IntValueKey.Value, 0); csum += ii.GetValueInt(IntValueKey.UsesRemaining, 0); } //If we are above the value, combine. if (vsum >= valuemodevalue) return ret; //Total bags are below target value. Try combining some without exceeding 100. //In theory this is a hard problem. So to avoid wasting time, we will just randomly try a few possibilities. for (int i = 0; i < 12; ++i) { int ind1 = rand.Next(kp.Value.Count); int ind2 = rand.Next(kp.Value.Count); if (ind1 == ind2) continue; if ((kp.Value[ind1].GetValueInt(IntValueKey.UsesRemaining, 0) + kp.Value[ind2].GetValueInt(IntValueKey.UsesRemaining, 0)) < 100) { //This pair is good. ret.Clear(); ret.Add(kp.Value[ind1].Id); ret.Add(kp.Value[ind2].Id); return ret; } } } else { //Salvage for maximum bags mode. //Just loop and add bags until we are over 100 units. int csum = 0; foreach (GameItemInfo ii in kp.Value) { ret.Add(ii.Id); csum += ii.GetValueInt(IntValueKey.UsesRemaining, 0); if (csum >= 100) break; } return ret; } } return new List(); } #endif public string BlockTypeID { get { return "SalvageCombine"; } } public void Read(System.IO.StreamReader inf, int len) { MaterialValueModeValues.Clear(); string formatversion = inf.ReadLine(); DefaultCombineString = inf.ReadLine(); MaterialCombineStrings.Clear(); int nummatstrings = int.Parse(inf.ReadLine(), System.Globalization.CultureInfo.InvariantCulture); for (int i = 0; i < nummatstrings; ++i) { int mat = int.Parse(inf.ReadLine(), System.Globalization.CultureInfo.InvariantCulture); string cmb = inf.ReadLine(); MaterialCombineStrings[mat] = cmb; } //This is all there is for older blocks. if (inf.EndOfStream) return; //MaterialValueModeValues.Clear(); int nummatvalmodevals = int.Parse(inf.ReadLine(), System.Globalization.CultureInfo.InvariantCulture); for (int i = 0; i < nummatvalmodevals; ++i) { int k = int.Parse(inf.ReadLine(), System.Globalization.CultureInfo.InvariantCulture); int v = int.Parse(inf.ReadLine(), System.Globalization.CultureInfo.InvariantCulture); MaterialValueModeValues[k] = v; } } public void Write(CountedStreamWriter inf) { inf.WriteLine(SALVAGEBLOCK_FILE_FORMAT_VERSION); inf.WriteLine(DefaultCombineString); inf.WriteLine(MaterialCombineStrings.Count); foreach (KeyValuePair kp in MaterialCombineStrings) { inf.WriteLine(kp.Key); inf.WriteLine(kp.Value); } inf.WriteLine(MaterialValueModeValues.Count); foreach (KeyValuePair kp in MaterialValueModeValues) { inf.WriteLine(kp.Key); inf.WriteLine(kp.Value); } } } }