public void Randomize(RandomizerOptions options, Action <string> notify = null, string outPath = null, bool sekiro = false, Preset preset = null, bool encrypted = true) { // sekiro = false; string distDir = sekiro ? "dists" : "dist"; if (!Directory.Exists(distDir)) { // From Release/Debug dirs distDir = $@"..\..\..\{distDir}"; } if (!Directory.Exists(distDir)) { throw new Exception("Missing data directory"); } if (outPath == null) { outPath = Directory.GetCurrentDirectory(); } Console.WriteLine($"Options and seed: {options}"); Console.WriteLine(); int seed = (int)options.Seed; notify?.Invoke("Loading game data"); string modDir = null; if (options["mergemods"]) { string modPath = sekiro ? "mods" : "mod"; DirectoryInfo modDirInfo = new DirectoryInfo($@"{outPath}\..\{modPath}"); if (!modDirInfo.Exists) { throw new Exception($"Can't merge mods: {modDirInfo.FullName} not found"); } modDir = modDirInfo.FullName; if (new DirectoryInfo(outPath).FullName == modDir) { throw new Exception($"Can't merge mods: already running from 'mods' directory"); } } GameData game = new GameData(distDir, sekiro); game.Load(modDir); // game.SearchParamInt(15200090); return; if (modDir != null) { Console.WriteLine(); } // Prologue if (options["enemy"]) { Console.WriteLine("Ctrl+F 'Boss placements' or 'Miniboss placements' or 'Basic placements' to see enemy placements."); } if (options["item"] || !sekiro) { Console.WriteLine("Ctrl+F 'Hints' to see item placement hints, or Ctrl+F for a specific item name."); } Console.WriteLine(); #if !DEBUG for (int i = 0; i < 50; i++) { Console.WriteLine(); } #endif // Slightly different high-level algorithm for each game. As always, can try to merge more in the future. if (sekiro) { Events events = new Events(@"dists\Base\sekiro-common.emedf.json"); EventConfig eventConfig; using (var reader = File.OpenText("dists/Base/events.txt")) { IDeserializer deserializer = new DeserializerBuilder().Build(); eventConfig = deserializer.Deserialize <EventConfig>(reader); } EnemyLocations locations = null; if (options["enemy"]) { notify?.Invoke("Randomizing enemies"); locations = new EnemyRandomizer(game, events, eventConfig).Run(options, preset); if (!options["enemytoitem"]) { locations = null; } } if (options["item"]) { notify?.Invoke("Randomizing items"); SekiroLocationDataScraper scraper = new SekiroLocationDataScraper(); LocationData data = scraper.FindItems(game); AnnotationData anns = new AnnotationData(game, data); anns.Load(options); anns.AddEnemyLocations(locations); SkillSplitter.Assignment split = null; if (!options["norandom_skills"] && options["splitskills"]) { split = new SkillSplitter(game, data, anns, events).SplitAll(); } Permutation perm = new Permutation(game, data, anns, explain: false); perm.Logic(new Random(seed), options, preset); notify?.Invoke("Editing game files"); PermutationWriter write = new PermutationWriter(game, data, anns, events, eventConfig); write.Write(new Random(seed + 1), perm, options); if (!options["norandom_skills"]) { SkillWriter skills = new SkillWriter(game, data, anns); skills.RandomizeTrees(new Random(seed + 2), perm, split); } if (options["edittext"]) { HintWriter hints = new HintWriter(game, data, anns); hints.Write(options, perm); } } MiscSetup.SekiroCommonPass(game, events, options); notify?.Invoke("Writing game files"); if (!options["dryrun"]) { game.SaveSekiro(outPath); } return; } else { Events events = new Events(@"dist\Base\ds3-common.emedf.json"); LocationDataScraper scraper = new LocationDataScraper(logUnused: false); LocationData data = scraper.FindItems(game); AnnotationData ann = new AnnotationData(game, data); ann.Load(options); ann.AddSpecialItems(); notify?.Invoke("Randomizing"); Random random = new Random(seed); Permutation permutation = new Permutation(game, data, ann, explain: false); permutation.Logic(random, options, null); notify?.Invoke("Editing game files"); random = new Random(seed + 1); PermutationWriter writer = new PermutationWriter(game, data, ann, events, null); writer.Write(random, permutation, options); random = new Random(seed + 2); CharacterWriter characters = new CharacterWriter(game, data); characters.Write(random, options); notify?.Invoke("Writing game files"); if (!options["dryrun"]) { game.SaveDS3(outPath, encrypted); } } }
public void RandomizeTrees(Random random, Permutation permutation, SkillSplitter.Assignment split) { // >= 700: prosthetics // < 400: skills before mushin GameEditor editor = game.Editor; PARAM param = game.Params["SkillParam"]; // Orderings for skills which completely supersede each other. (For prosthetics, just use their natural id ordering) Dictionary <int, int> skillOrderings = new Dictionary <int, int> { [110] = 111, // Nightjar slash [210] = 211, // Ichimonji [310] = 311, // Praying Strikes }; Dictionary <int, ItemKey> texts = new Dictionary <int, ItemKey> { [0] = game.ItemForName("Shinobi Esoteric Text"), [1] = game.ItemForName("Prosthetic Esoteric Text"), [2] = game.ItemForName("Ashina Esoteric Text"), [3] = game.ItemForName("Senpou Esoteric Text"), // [4] = game.ItemForName("Mushin Esoteric Text"), }; SortedDictionary <ItemKey, string> names = game.Names(); string descName(int desc) { return(names[new ItemKey(ItemType.WEAPON, desc)]); } Dictionary <int, SkillData> allData = new Dictionary <int, SkillData>(); Dictionary <int, SkillSlot> allSlots = new Dictionary <int, SkillSlot>(); Dictionary <ItemKey, SkillData> skillItems = new Dictionary <ItemKey, SkillData>(); List <SkillData> skills = new List <SkillData>(); List <SkillSlot> skillSlots = new List <SkillSlot>(); List <SkillData> prosthetics = new List <SkillData>(); List <SkillSlot> prostheticSlots = new List <SkillSlot>(); bool explain = false; foreach (PARAM.Row r in param.Rows) { SkillData data = new SkillData { ID = (int)r.ID, Item = (int)r["SkilLDescriptionId"].Value, Equip = (int)r["Unk1"].Value, Flag = (int)r["EventFlagId"].Value, Placeholder = (int)r["Unk5"].Value, SpEffects = new[] { (int)r["Unk2"].Value, (int)r["Unk3"].Value }, EmblemChange = (byte)r["Unk10"].Value != 0, }; data.Key = new ItemKey(ItemType.WEAPON, data.Item); skillItems[data.Key] = data; SkillSlot slot = new SkillSlot { ID = (int)r.ID, Col = (short)r["MenuDisplayPositionIndexXZ"].Value, Row = (short)r["MenuDisplayPositionIndexY"].Value, Text = data.ID < 400 && texts.TryGetValue((byte)r["Unk7"].Value, out ItemKey text) ? text : null, }; if (explain) { Console.WriteLine($"{r.ID}: {data.Item}, {data.Equip}, {descName(data.Item)}"); } if (data.ID < 400) { skills.Add(data); skillSlots.Add(slot); } else if (data.ID >= 700) { prosthetics.Add(data); prostheticSlots.Add(slot); } allData[data.ID] = data; allSlots[slot.ID] = slot; } void applyData(PARAM.Row r, SkillData data) { r["SkilLDescriptionId"].Value = data.Item; r["EventFlagId"].Value = data.Flag; r["Unk1"].Value = data.Equip; r["Unk2"].Value = data.SpEffects[0]; r["Unk3"].Value = data.SpEffects[0]; r["Unk5"].Value = data.Placeholder; r["Unk10"].Value = (byte)(data.EmblemChange ? 1 : 0); } Shuffle(random, skills); Shuffle(random, skillSlots); Shuffle(random, prosthetics); Shuffle(random, prostheticSlots); // Skills rando if (split == null) { Dictionary <ItemKey, string> textWeight = new Dictionary <ItemKey, string>(); Dictionary <ItemKey, string> textLocations = texts.Values.ToDictionary(t => t, t => { SlotKey target = permutation.GetFiniteTargetKey(t); textWeight[t] = permutation.GetLogOrder(target); SlotAnnotation sn = ann.Slot(data.Location(target).LocScope); if (explain) { Console.WriteLine($"{game.Name(t)} in {sn.Area} - {sn.Text}. Lateness {(permutation.ItemLateness.TryGetValue(t, out double val) ? val : -1)}"); } return(sn.Area); });