//Adds data to the randomizer dictionaries public static void AddEntry(XmlNode node) { RandomizerEntry entry = new RandomizerEntry(node); foreach (RandomizerVar var in entry.entries) { if (typeof(PlayerData).GetField(var.name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) == null) { return; } } if (!entries.ContainsKey(entry.name)) { entries.Add(entry.name, entry); //Build reverse lookup list for quickly finding pickup name from attributes for (int i = 0; i < entry.entries.Length; i++) { string val = entry.entries[i].value == null ? "" : entry.entries[i].value.ToString(); reverseLookup.Add(entry.entries[i].name + val, entry.name); } for (int i = 0; i < entry.localeNames.Length; i++) { if (i != 1 && i != 2) { reverseLookup.Add(entry.localeNames[i], entry.name); } } } }
//Load entry from XML //TODO: Add error checking for malformatted XML public RandomizerEntry(XmlNode xml) { this.name = xml.SelectSingleNode("name").InnerText; XmlNodeList entriesXml = xml.SelectSingleNode("vars").ChildNodes; XmlNode requiresXml = xml.SelectSingleNode("requirements"); this.type = RandomizerEntry.GetTypeFromString(xml.SelectSingleNode("type").InnerText); XmlNodeList localesXml = xml.SelectNodes("locales/locale"); this.entries = new RandomizerVar[entriesXml.Count]; for (int i = 0; i < entriesXml.Count; i++) { string value = entriesXml[i].Attributes["value"] == null ? "" : entriesXml[i].Attributes["value"].Value; this.entries[i] = new RandomizerVar(entriesXml[i].InnerText, entriesXml[i].Name, value); } this.reqString = requiresXml.InnerText; this.localeNames = new string[localesXml.Count]; for (int i = 0; i < localesXml.Count; i++) { this.localeNames[i] = localesXml[i].InnerText; } GC.Collect(); }
//Randomization algorithm public static void Randomize(System.Random random) { RandomizerMod.instance.Log("----------------------------------------------------------"); RandomizerMod.instance.Log("Beginning randomization with seed " + NewGameSettings.seed); RandomizerMod.instance.Settings.StringValues.Clear(); List <RandomizerEntry> unsorted = new List <RandomizerEntry>(); List <RandomizerEntry> sorted = new List <RandomizerEntry>(); List <RandomizerEntry> reachable = (from entry in entries.Values.AsEnumerable() where (entry.IsReachable(sorted)) select entry).ToList(); List <RandomizerEntry> replaced = new List <RandomizerEntry>(); unsorted.AddRange(entries.Values); foreach (RandomizerEntry entry in reachable) { RandomizerMod.instance.Log("" + entry.name + " is reachable"); } //Loop until we've run out of places to put things at while (reachable.Count > 0) { RandomizerEntry newItem = default(RandomizerEntry); //Need to select an item that isn't a dead end if we're almost out of options if (reachable.Count == 1 && unsorted.Count > 1) { List <RandomizerEntry> candidates = new List <RandomizerEntry>(); List <float> weights = new List <float>(); float totalWeights = 0; foreach (RandomizerEntry entry in unsorted) { int leadCount = entry.LeadsTo(entries.Values.ToList(), sorted, reachable.Union(replaced).ToList()).Count; if (leadCount > 0) { totalWeights += (float)1.0 + Mathf.Log(leadCount); weights.Add(totalWeights); candidates.Add(entry); } } if (candidates.Count > 0) { bool itemAssigned = false; float weight = (float)random.NextDouble() * totalWeights; for (int i = 0; i < weights.Count; i++) { if (weight <= weights.ElementAt(i)) { newItem = candidates.ElementAt(i); itemAssigned = true; break; } } if (!itemAssigned) { RandomizerMod.instance.LogWarn("Weighted randomness has failed, picking full random value"); newItem = candidates.ElementAt(random.Next(candidates.Count)); } RandomizerMod.instance.Log("Running out of options, " + newItem.name + " should prevent hard lock"); } else { newItem = unsorted.ElementAt(random.Next(unsorted.Count)); RandomizerMod.instance.LogWarn("Running out of options, " + newItem.name + " will probably not help anything"); } } else { if (RandomizerMod.instance.Settings.hardMode) { List <RandomizerEntry> candidates = new List <RandomizerEntry>(); foreach (RandomizerEntry entry in unsorted) { if (random.Next(100) < 35 || entry.LeadsTo(entries.Values.ToList(), sorted, reachable.Union(replaced).ToList()).Count == 0) { candidates.Add(entry); } } if (candidates.Count > 0) { newItem = candidates.ElementAt(random.Next(candidates.Count)); } else { newItem = unsorted.ElementAt(random.Next(unsorted.Count)); } } else { newItem = unsorted.ElementAt(random.Next(unsorted.Count)); } } //Update list of items that need to be placed still sorted.Add(newItem); unsorted.Remove(newItem); //Randomly place the chosen item among the reachable elements //No need to check requirements, we can assume the new item isn't required for the replace location since it's already reachable RandomizerEntry replaceAt = reachable.ElementAt(random.Next(reachable.Count)); replaced.Add(replaceAt); reachable.Remove(replaceAt); RandomizerMod.instance.Log("Adding permutation: " + replaceAt.name + " = " + newItem.name); RandomizerMod.instance.Settings.StringValues.Add(replaceAt.name, newItem.name); reachable.AddRange(GetNewReachableItems(reachable, replaced, sorted)); } //Restart if the algorithm fails //Hopefully it never does if (unsorted.Count != 0) { RandomizerMod.instance.LogWarn("Randomization has somehow failed"); foreach (RandomizerEntry entry in entries.Values.ToList().FindAll(item => !replaced.Contains(item))) { RandomizerMod.instance.Log(entry.name + " is unreachable"); } Randomize(new System.Random(random.Next())); } }
//Override for PlayerData.GetBool public static bool GetPlayerDataBool(string name) { PlayerData pd = PlayerData.instance; //Don't run randomizer code in non-randomizer saves if (!RandomizerMod.instance.Settings.randomizer) { return(pd.GetBoolInternal(name)); } if (GameManager.instance.GetSceneNameString() != "RestingGrounds_07" && GameManager.instance.GetSceneNameString() != "RestingGrounds_04") { if (name == "hasDreamGate" || name == "dreamNailUpgraded" || name == "hasDreamNail") { return(pd.GetBoolInternal(name)); } } if (string.IsNullOrEmpty(name)) { return(false); } if (name == "_true") { return(true); } else if (name == "_false") { return(false); } else if (name == "hasAcidArmour") { return(GameManager.instance.GetSceneNameString() == "Waterways_13" ? false : PlayerData.instance.hasAcidArmour); } //Check stack trace to see if player is in a menu string stack = new StackTrace().ToString(); //Split into multiple ifs because this looks horrible otherwise //TODO: Cleaner way of checking than stack trace if (!stack.Contains("at ShopMenuStock.BuildItemList()")) { if (stack.Contains("at HutongGames.PlayMaker.Fsm.Start()")) { return(pd.GetBoolInternal(name)); } if (name.Contains("gotCharm_") && (stack.Contains("at HutongGames.PlayMaker.Fsm.DoTransition(HutongGames.PlayMaker.FsmTransition transition, Boolean isGlobal)") || InInventory())) { return(pd.GetBoolInternal(name)); } } string key; string key2; //Don't run randomizer if bool is not in the loaded data if (!reverseLookup.TryGetValue(name, out key) || !RandomizerMod.instance.Settings.StringValues.TryGetValue(key, out key2)) { return(pd.GetBoolInternal(name)); } else { int index = entries[key].GetIndex(name); RandomizerEntry randomizerEntry = entries[key2]; RandomizerVar var; //Return the matching bool or the first one if there is no matching index if (randomizerEntry.entries.Length > index) { var = randomizerEntry.entries[index]; } else { var = randomizerEntry.entries[0]; } if (var.type == typeof(bool)) { return(pd.GetBoolInternal(var.name)); } else { if (key2 == "Vengeful Spirit") { return(RandomizerMod.instance.Settings.fireball1); } else if (key2 == "Shade Soul") { return(RandomizerMod.instance.Settings.fireball2); } else if (key2 == "Desolate Dive") { return(RandomizerMod.instance.Settings.quake1); } else if (key2 == "Descending Dark") { return(RandomizerMod.instance.Settings.quake2); } else if (key2 == "Howling Wraiths") { return(RandomizerMod.instance.Settings.scream1); } else if (key2 == "Abyss Shriek") { return(RandomizerMod.instance.Settings.scream2); } return(pd.GetIntInternal(var.name) >= (int)var.value); } } }
public static int GetPlayerDataInt(string name) { PlayerData pd = PlayerData.instance; //Don't run randomizer code in non-randomizer saves if (!RandomizerMod.instance.Settings.randomizer) { if (name.StartsWith("_")) { name = name.Substring(1); } return(pd.GetIntInternal(name)); } if (string.IsNullOrEmpty(name)) { return(0); } if (name == "_true") { return(2); } else if (name == "_fireballLevel") { return(RandomizerMod.instance.Settings.FireballLevel()); } else if (name == "_quakeLevel") { return(RandomizerMod.instance.Settings.QuakeLevel()); } else if (name == "_screamLevel") { return(RandomizerMod.instance.Settings.ScreamLevel()); } string key; string key2; //Don't run randomizer if int is not in the loaded data if (!reverseLookup.TryGetValue(name, out key) || !RandomizerMod.instance.Settings.StringValues.TryGetValue(key, out key2)) { return(pd.GetIntInternal(name)); } else { int index = entries[key].GetIndex(name); RandomizerEntry randomizerEntry = entries[key2]; RandomizerVar var; //Return the matching var or the first one if there is no matching index if (randomizerEntry.entries.Length > index) { var = randomizerEntry.entries[index]; } else { var = randomizerEntry.entries[0]; } if (var.type == typeof(bool)) { return(pd.GetBoolInternal(var.name) ? 2 : 0); } else { if (key2 == "Vengeful Spirit") { return(Convert.ToInt32(RandomizerMod.instance.Settings.fireball1) * 2); } else if (key2 == "Shade Soul") { return(Convert.ToInt32(RandomizerMod.instance.Settings.fireball2) * 2); } else if (key2 == "Desolate Dive") { return(Convert.ToInt32(RandomizerMod.instance.Settings.quake1) * 2); } else if (key2 == "Descending Dark") { return(Convert.ToInt32(RandomizerMod.instance.Settings.quake2) * 2); } else if (key2 == "Howling Wraiths") { return(Convert.ToInt32(RandomizerMod.instance.Settings.scream1) * 2); } else if (key2 == "Abyss Shriek") { return(Convert.ToInt32(RandomizerMod.instance.Settings.scream2) * 2); } return(pd.GetIntInternal(var.name) >= (int)var.value ? 2 : 0); } } }