public static void ReloadFMGs() { if (AssetLocator.Type == GameType.Undefined) { return; } if (AssetLocator.Type == GameType.DarkSoulsIISOTFS) { ReloadFMGsDS2(); IsLoaded = true; return; } IBinder fmgBinder; var desc = AssetLocator.GetEnglishItemMsgbnd(); if (AssetLocator.Type == GameType.DemonsSouls || AssetLocator.Type == GameType.DarkSoulsPTDE || AssetLocator.Type == GameType.DarkSoulsRemastered) { fmgBinder = BND3.Read(desc.AssetPath); } else { fmgBinder = BND4.Read(desc.AssetPath); } _fmgs = new Dictionary <ItemFMGTypes, FMG>(); foreach (var file in fmgBinder.Files) { _fmgs.Add((ItemFMGTypes)file.ID, FMG.Read(file.Bytes)); } IsLoaded = true; }
public static void Unpack(this FMG fmg, string sourceFile) { XmlWriterSettings xws = new XmlWriterSettings(); // You need Indent for it to write newlines xws.Indent = true; // But don't actually indent so there's more room for the text xws.IndentChars = ""; XmlWriter xw = XmlWriter.Create($"{sourceFile}.xml", xws); xw.WriteStartElement("fmg"); xw.WriteElementString("compression", fmg.Compression.ToString()); xw.WriteElementString("version", fmg.Version.ToString()); xw.WriteElementString("bigendian", fmg.BigEndian.ToString()); xw.WriteStartElement("entries"); // I think they're sorted already, but whatever fmg.Entries.Sort((e1, e2) => e1.ID.CompareTo(e2.ID)); foreach (FMG.Entry entry in fmg.Entries) { xw.WriteStartElement("text"); xw.WriteAttributeString("id", entry.ID.ToString()); xw.WriteString(entry.Text ?? "%null%"); xw.WriteEndElement(); } xw.WriteEndElement(); xw.WriteEndElement(); xw.Close(); }
public CloneFmgsAction(FMG fmg, string fstring, List <FMG.Entry> entries, bool setsel) { Fmg = fmg; Clonables.AddRange(entries); FmgString = fstring; SetSelection = setsel; }
public static void Repack(string sourceFile) { FMG fmg = new FMG(); XmlDocument xml = new XmlDocument(); xml.Load(sourceFile); Enum.TryParse(xml.SelectSingleNode("fmg/compression")?.InnerText ?? "None", out DCX.Type compression); fmg.Compression = compression; fmg.Version = (FMG.FMGVersion)Enum.Parse(typeof(FMG.FMGVersion), xml.SelectSingleNode("fmg/version").InnerText); fmg.BigEndian = bool.Parse(xml.SelectSingleNode("fmg/bigendian").InnerText); foreach (XmlNode textNode in xml.SelectNodes("fmg/entries/text")) { int id = int.Parse(textNode.Attributes["id"].InnerText); // \r\n is drawn as two newlines ingame string text = textNode.InnerText.Replace("\r", ""); if (text == "%null%") { text = null; } fmg.Entries.Add(new FMG.Entry(id, text)); } string outPath = sourceFile.Replace(".fmg.xml", ".fmg"); YBUtil.Backup(outPath); fmg.Write(outPath); }
private static void RewriteFMG(FMG destFMG, Dictionary <int, string> destDict) { destFMG.Entries.Clear(); foreach (var item in destDict) { destFMG.Entries.Add(new FMG.Entry(item.Key, item.Value)); } }
public ActionResult DeleteConfirmed(int id) { FMG fMG = db.fmg.Find(id); db.fmg.Remove(fMG); db.SaveChanges(); return(RedirectToAction("Index")); }
public ActionResult Edit([Bind(Include = "fmgID,fmgAvgHoldTime,fmgAvgSpeedAnswer,fmgMaxHoldTime,fmgAbandoned,fmgNumberOfAcceptedCalls,fmgPercentAbandoned,fmgAvgAbandon,fmgDateTime")] FMG fMG) { if (ModelState.IsValid) { db.Entry(fMG).State = EntityState.Modified; db.SaveChanges(); return(RedirectToAction("Index")); } return(View(fMG)); }
public static Position Create(FMG.DomainObjects.Position domainObject, bool fullResource = true) { var resource = new Position { Name = domainObject.Name, Shortname = domainObject.ShortName, Line = domainObject.Line.Name }; return resource; }
public ActionResult Create([Bind(Include = "fmgID,fmgAvgHoldTime,fmgAvgSpeedAnswer,fmgMaxHoldTime,fmgAbandoned,fmgNumberOfAcceptedCalls,fmgPercentAbandoned,fmgAvgAbandon,fmgDateTime")] FMG fMG) { if (ModelState.IsValid) { db.fmg.Add(fMG); db.SaveChanges(); return(RedirectToAction("Confirm")); } return(View(fMG)); }
public bool ScrapeMsgs(Universe u) { if (spec.MsgDir == null) { return(false); } foreach (KeyValuePair <string, FMG> entry in editor.LoadBnds(spec.MsgDir, (data, name) => FMG.Read(data)).SelectMany(e => e.Value) .Concat(editor.Load(spec.MsgDir, name => FMG.Read(name), "*.fmg")).OrderBy(e => e.Key)) { if (MsgTypes.ContainsKey(entry.Key)) { Namespace type = MsgTypes[entry.Key]; foreach (FMG.Entry name in entry.Value.Entries) { u.Names[Obj.Of(type, name.ID)] = name.Text; } } } if (Directory.Exists($@"{spec.GameDir}\{spec.MsgDir}\talk")) { foreach (KeyValuePair <string, FMG> entry in editor.Load(spec.MsgDir + @"\talk", name => FMG.Read(name), "*.fmg")) { foreach (FMG.Entry name in entry.Value.Entries) { u.Names[Obj.Talk(name.ID)] = name.Text; } } } if (spec.ParamFile == null || !talkParamMsgId.ContainsKey(spec.Game)) { return(false); } LoadParams(); if (!Params.ContainsKey("TalkParam")) { return(false); } string msgId = talkParamMsgId[spec.Game]; foreach (PARAM.Row row in Params["TalkParam"].Rows) { int dialogue = (int)row[msgId].Value; if (dialogue > 0) { Obj dialogueObj = Obj.Of(Namespace.DIALOGUE, dialogue); if (u.Names.ContainsKey(dialogueObj)) { u.Names[Obj.Talk((int)row.ID)] = u.Names[dialogueObj]; } } } return(true); }
public FMGHandler(TextHandler th, BND3 bnd, string name) { IEnumerable <BinderFile> list = bnd.Files.Where(f => Path.GetFileNameWithoutExtension(f.Name) == name); FileProper = list.ElementAt(0); Proper = FMG.Read(FileProper.Bytes); if (list.Count() > 1) { FilePatch = list.ElementAtOrDefault(1); Patch = FMG.Read(FilePatch.Bytes); } }
private string GetFromFile(FMG fmg, long id) { if (fmg == null) { return(null); } FMG.Entry entry = fmg.Entries.FirstOrDefault(f => f.ID == id); if (entry != null && !string.IsNullOrWhiteSpace(entry.Text)) { return(entry.Text); } return(null); }
private static Dictionary <int, string> MakeRef(IBinder refBND, int iRef) { FMG refFMG = null; Dictionary <int, string> refDict = null; if (refBND != null) { refFMG = FMG.Read((refBND.Files[iRef]).Bytes); refDict = refFMG.Entries.ToDictionary(t => t.ID, t => t.Text); } return(refDict); }
// GET: FMGs/Edit/5 public ActionResult Edit(int?id) { if (id == null) { return(new HttpStatusCodeResult(HttpStatusCode.BadRequest)); } FMG fMG = db.fmg.Find(id); if (fMG == null) { return(HttpNotFound()); } return(View(fMG)); }
public static LeagueTablePosition Create(FMG.DomainObjects.LeagueTablePosition domainObject, bool fullResource = true) { return new LeagueTablePosition { Position = domainObject.Position, Matches = domainObject.Matches, Points = domainObject.Points, Wins = domainObject.Wins, Draws = domainObject.Draws, Losses = domainObject.Losses, GoalsScored = domainObject.GoalsScored, GoalsConceded = domainObject.GoalsConceded, GoalDifference = domainObject.GoalDifference, }; }
public static void ReloadFmgs() { try { BND4 fmgBnd = BND4.Read(FMGBndPath); ItemFMG = FMG.Read(fmgBnd.Files.Find(x => Path.GetFileName(x.Name) == "アイテム名.fmg").Bytes); } catch (Exception e) { if (!Failwarn) { Debug.Log("Failed to load item fmg file. Item names will not be shown."); Failwarn = true; } } }
private void SetInFile(FMG fmg, long id, string s) { if (fmg == null) { return; } FMG.Entry entry = fmg.Entries.FirstOrDefault(f => f.ID == id); if (entry != null) { entry.Text = s; } else { FMG.Entry newEntry = new FMG.Entry((int)id, s); fmg.Entries.Add(newEntry); } }
public static void LoadMessages(SQLiteConnection con, string messageDir) { List <string> messageFilepaths = new List <string>(); messageFilepaths.AddRange(Directory.GetFiles(messageDir, "*.msgbnd.dcx")); foreach (string messageFilepath in messageFilepaths) { var msgbnd = BND3.Read(messageFilepath); foreach (BinderFile file in msgbnd.Files) { string name = Path.GetFileNameWithoutExtension(file.Name); // Yes, .msgbnd file is FMG FMG msg = FMG.Read(file.Bytes); ReadMessagesIntoDatabase(con, name, msg); } } }
private static int NullOverwrite(int entriesOverwritten, FMG sourceFMG, FMG destFMG) { int i = 0; foreach (var item in sourceFMG.Entries) //Each entry in the current FMG file { //ConsoleLog(item.ID); //Debug //Batch up dest entries if there are extras if ((item.ID > destFMG.Entries[i].ID)) //Catch the count up if the entries in the destination langauge is out of place (Extra entries that shouldn't be there) { while ((item.ID > destFMG.Entries[i].ID) && (i < destFMG.Entries.Count - 1)) { if (i < destFMG.Entries.Count - 1) { i++; } } } //If item IDs match, check if the destination is null, then overwrite if (item.ID == destFMG.Entries[i].ID) //Overwrite all Null whitespace or empty entries in destination { //ConsoleLog(item.ID + " = true"); //Debug if (string.IsNullOrWhiteSpace(destFMG.Entries[i].Text)) { destFMG.Entries[i].Text = item.Text; if (!string.IsNullOrWhiteSpace(destFMG.Entries[i].Text)) //Keep track of entries that actually changed { Log.Add($"{ destFMG.Entries[i].ID }: { destFMG.Entries[i].Text }"); entriesOverwritten++; } //Debug.WriteLine("writing " + destFMG.Entries[i].Text + " to " + destFMG.Entries[i].ID + " in " + Path.GetFileName(sourceBND.Files[iFile].Name)); } if (i < destFMG.Entries.Count - 1) { i++; } } } return(entriesOverwritten); }
public static void ReloadFMGsDS2() { var desc = AssetLocator.GetEnglishItemMsgbnd(true); var files = Directory.GetFileSystemEntries($@"{AssetLocator.GameRootDirectory}\{desc.AssetPath}", @"*.fmg").ToList(); _ds2fmgs = new Dictionary <string, FMG>(); foreach (var file in files) { var modfile = $@"{AssetLocator.GameModDirectory}\{desc.AssetPath}\{Path.GetFileName(file)}"; if (AssetLocator.GameModDirectory != null && File.Exists(modfile)) { var fmg = FMG.Read(modfile); _ds2fmgs.Add(Path.GetFileNameWithoutExtension(modfile), fmg); } else { var fmg = FMG.Read(file); _ds2fmgs.Add(Path.GetFileNameWithoutExtension(file), fmg); } } }
public void DumpMessages(string dir) { foreach (string path in Directory.GetFiles(dir, "*.msgbnd*")) { string name = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(path)); try { IBinder bnd = BND3.Read(path); foreach (BinderFile file in bnd.Files) { string fileName = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file.Name)); string uname = System.Text.RegularExpressions.Regex.Replace(fileName, @"[^\x00-\x7F]", c => string.Format(@"u{0:x4}", (int)c.Value[0])); string fname = $"{name}_{uname}.txt"; // Console.WriteLine(fname); // string fileName = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file.Name)); FMG fmg = FMG.Read(file.Bytes); if (fmg.Entries != null) { File.WriteAllLines($@"{dir}\{fname}", fmg.Entries.Select(e => $"{e.ID} {(e.Text == null ? "" : e.Text.Replace("\r", "").Replace("\n", "\\n"))}")); } } } catch (Exception ex) { throw new Exception($"Failed to load file: {name}: {path}\r\n\r\n{ex}"); } } foreach (string path in Directory.GetFiles(dir, "*.fmg")) { FMG fmg = FMG.Read(path); string fname = Path.GetFileNameWithoutExtension(path); if (fmg.Entries != null) { File.WriteAllLines($@"{dir}\{fname}.txt", fmg.Entries.Select(e => $"{e.ID} {(e.Text == null ? "" : e.Text.Replace("\r", "").Replace("\n", "\\n"))}")); } } }
public static void LoadAllFMG(bool forceReload) { if (!forceReload && GameTypeCurrentFmgsAreLoadedFrom == GameDataManager.GameType) { return; } List <FMG> weaponNameFMGs = new List <FMG>(); List <FMG> armorNameFMGs = new List <FMG>(); /* * [ptde] * weapon 1 * armor 2 * * [ds1r] * weapon 1, 30 * armor 2, 32 * * [ds3] * weapon 1 * armor 2 * weapon_dlc1 18 * armor_dlc1 19 * weapon_dlc2 33 * armor_dlc2 34 * * [bb] * weapon 1 * armor 2 * * [sdt] * weapon 1 * armor 2 */ void TryToLoadFromMSGBND(string language, string msgbndName, int weaponNamesIdx, int armorNamesIdx) { var msgbndRelativePath = $@"msg\{language}\{msgbndName}"; var fullMsgbndPath = GameDataManager.GetInterrootPath(msgbndRelativePath); IBinder msgbnd = null; if (File.Exists(fullMsgbndPath)) { if (BND3.Is(fullMsgbndPath)) { msgbnd = BND3.Read(fullMsgbndPath); } else if (BND4.Is(fullMsgbndPath)) { msgbnd = BND4.Read(fullMsgbndPath); } weaponNameFMGs.Add(FMG.Read(msgbnd.Files.First(x => x.ID == weaponNamesIdx).Bytes)); armorNameFMGs.Add(FMG.Read(msgbnd.Files.First(x => x.ID == armorNamesIdx).Bytes)); } if (msgbnd == null) { System.Windows.Forms.MessageBox.Show( $"Unable to find text file '{msgbndRelativePath}'. Some player equipment may not show names.", "Unable to find asset", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Warning); return; } } if (GameDataManager.GameType == GameDataManager.GameTypes.DS1) { TryToLoadFromMSGBND("ENGLISH", "item.msgbnd", 11, 12); TryToLoadFromMSGBND("ENGLISH", "menu.msgbnd", 115, 117); //Patch } else if (GameDataManager.GameType == GameDataManager.GameTypes.DS1R) { TryToLoadFromMSGBND("ENGLISH", "item.msgbnd.dcx", 11, 12); TryToLoadFromMSGBND("ENGLISH", "item.msgbnd.dcx", 115, 117); //Patch } else if (GameDataManager.GameType == GameDataManager.GameTypes.DS3) { TryToLoadFromMSGBND("engus", "item_dlc1.msgbnd.dcx", 11, 12); TryToLoadFromMSGBND("engus", "item_dlc1.msgbnd.dcx", 211, 212); //DLC1 TryToLoadFromMSGBND("engus", "item_dlc2.msgbnd.dcx", 11, 12); TryToLoadFromMSGBND("engus", "item_dlc2.msgbnd.dcx", 211, 212); //DLC1 TryToLoadFromMSGBND("engus", "item_dlc2.msgbnd.dcx", 251, 252); //DLC2 } else if (GameDataManager.GameType == GameDataManager.GameTypes.SDT) { TryToLoadFromMSGBND("engus", "item.msgbnd.dcx", 11, 12); } else if (GameDataManager.GameType == GameDataManager.GameTypes.BB) { TryToLoadFromMSGBND("engus", "item.msgbnd.dcx", 11, 12); } WeaponNames.Clear(); void DoWeaponEntry(FMG.Entry entry) { if (string.IsNullOrWhiteSpace(entry.Text) || !ParamManager.EquipParamWeapon.ContainsKey(entry.ID)) { return; } //if (GameDataManager.GameType == GameDataManager.GameTypes.DS3 && (entry.ID % 10000) != 0) // return; //else if ((entry.ID % 1000) != 0) // return; string val = entry.Text + $" <{entry.ID}>"; if (WeaponNames.ContainsKey(entry.ID)) { WeaponNames[entry.ID] = val; } else { WeaponNames.Add(entry.ID, val); } } foreach (var wpnNameFmg in weaponNameFMGs) { foreach (var entry in wpnNameFmg.Entries) { DoWeaponEntry(entry); } } ProtectorNames_HD.Clear(); ProtectorNames_BD.Clear(); ProtectorNames_AM.Clear(); ProtectorNames_LG.Clear(); void DoProtectorParamEntry(FMG.Entry entry) { if (string.IsNullOrWhiteSpace(entry.Text)) { return; } //if (entry.ID < 1000000 && GameDataManager.GameType == GameDataManager.GameTypes.DS3) // return; if (ParamManager.EquipParamProtector.ContainsKey(entry.ID)) { string val = entry.Text + $" <{entry.ID}>"; var protectorParam = ParamManager.EquipParamProtector[entry.ID]; if (protectorParam.HeadEquip) { if (ProtectorNames_HD.ContainsKey(entry.ID)) { ProtectorNames_HD[entry.ID] = val; } else { ProtectorNames_HD.Add(entry.ID, val); } } else if (protectorParam.BodyEquip) { if (ProtectorNames_BD.ContainsKey(entry.ID)) { ProtectorNames_BD[entry.ID] = val; } else { ProtectorNames_BD.Add(entry.ID, entry.Text + $" <{entry.ID}>"); } } else if (protectorParam.ArmEquip) { if (ProtectorNames_AM.ContainsKey(entry.ID)) { ProtectorNames_AM[entry.ID] = val; } else { ProtectorNames_AM.Add(entry.ID, entry.Text + $" <{entry.ID}>"); } } else if (protectorParam.LegEquip) { if (ProtectorNames_LG.ContainsKey(entry.ID)) { ProtectorNames_LG[entry.ID] = val; } else { ProtectorNames_LG.Add(entry.ID, entry.Text + $" <{entry.ID}>"); } } } } foreach (var armorNameFmg in armorNameFMGs) { foreach (var entry in armorNameFmg.Entries) { DoProtectorParamEntry(entry); } } WeaponNames = WeaponNames.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_HD = ProtectorNames_HD.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_BD = ProtectorNames_BD.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_AM = ProtectorNames_AM.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_LG = ProtectorNames_LG.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); foreach (var protector in ParamManager.EquipParamProtector) { if (protector.Value.HeadEquip && !ProtectorNames_HD.ContainsKey((int)protector.Key)) { ProtectorNames_HD.Add((int)protector.Key, $"<{protector.Key}>"); } else if (protector.Value.BodyEquip && !ProtectorNames_BD.ContainsKey((int)protector.Key)) { ProtectorNames_BD.Add((int)protector.Key, $"<{protector.Key}>"); } else if (protector.Value.ArmEquip && !ProtectorNames_AM.ContainsKey((int)protector.Key)) { ProtectorNames_AM.Add((int)protector.Key, $"<{protector.Key}>"); } else if (protector.Value.LegEquip && !ProtectorNames_LG.ContainsKey((int)protector.Key)) { ProtectorNames_LG.Add((int)protector.Key, $"<{protector.Key}>"); } } foreach (var weapon in ParamManager.EquipParamWeapon) { if (!WeaponNames.ContainsKey((int)weapon.Key)) { WeaponNames.Add((int)weapon.Key, $"<{weapon.Key}>"); } } ProtectorNames_HD = ProtectorNames_HD.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_BD = ProtectorNames_BD.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_AM = ProtectorNames_AM.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_LG = ProtectorNames_LG.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); WeaponNames = WeaponNames.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); GameTypeCurrentFmgsAreLoadedFrom = GameDataManager.GameType; }
public static void ReloadFmgs() { BND4 fmgBnd = BND4.Read(FMGBndPath); ItemFMG = FMG.Read(fmgBnd.Files.Find(x => Path.GetFileName(x.Name) == "アイテム名.fmg").Bytes); }
public void WriteList(GameData game, Dictionary <int, EnemyInfo> fullInfos) { // Generate things HashSet <int> allBonfires = new HashSet <int> { 1001950, // Dragonspring - Hirata Estate 1001951, // Estate Path 1001952, // Bamboo Thicket Slope 1001953, // Hirata Estate - Main Hall 1001955, // Hirata Audience Chamber 1001954, // Hirata Estate - Hidden Temple 1101950, // Dilapidated Temple 1101956, // Ashina Outskirts 1101951, // Outskirts Wall - Gate Path 1101952, // Outskirts Wall - Stairway 1101953, // Underbridge Valley 1101954, // Ashina Castle Gate Fortress 1101955, // Ashina Castle Gate 1101957, // Flames of Hatred 1111950, // Ashina Castle 1111951, // Upper Tower - Antechamber 1111957, // Upper Tower - Ashina Dojo 1111952, // Castle Tower Lookout 1111953, // Upper Tower - Kuro's Room 1111956, // Old Grave 1111954, // Great Serpent Shrine 1111955, // Abandoned Dungeon Entrance 1121951, // Ashina Reservoir 1121950, // Near Secret Passage 1301950, // Underground Waterway 1301951, // Bottomless Hole 1701955, // Ashina Depths 1701954, // Poison Pool 1701956, // Guardian Ape's Burrow 1501950, // Hidden Forest 1501951, // Mibu Village 1501952, // Water Mill 1501953, // Wedding Cave Door 1701957, // Under-Shrine Valley 1701950, // Sunken Valley 1701951, // Gun Fort 1701952, // Riven Cave 1701958, // Bodhisattva Valley 1701953, // Guardian Ape's Watering Hole 2001950, // Senpou Temple, Mt. Kongo 2001951, // Shugendo 2001952, // Temple Grounds 2001953, // Main Hall 2001954, // Inner Sanctum 2001955, // Sunken Valley Cavern 2001956, // Bell Demon's Temple 2501950, // Fountainhead Palace 2501951, // Vermilion Bridge 2501956, // Mibu Manor 2501952, // Flower Viewing Stage 2501953, // Great Sakura 2501954, // Palace Grounds 2501957, // Feeding Grounds 2501958, // Near Pot Noble 2501955, // Sanctuary }; // Probably shouldn't use tuples, but too late now List <(int, List <string>, List <int>, List <int>)> paths = new List <(int, List <string>, List <int>, List <int>)> { // Tutorial (1, new List <string> { "ashinareservoir", "ashinacastle" }, new List <int> { 8306, 0 }, new List <int> { 1121951, // Ashina Reservoir 1121950, // Near Secret Passage }), // First stretch of Ashina Outskirts (1, new List <string> { "ashinaoutskirts" }, new List <int> { 8302, 1, 8302, -1, 1100330, 1 }, new List <int> { 1101956, // Ashina Outskirts 1101951, // Outskirts Wall - Gate Path 1101952, // Outskirts Wall - Stairway }), // Ashina Outskirts up to Blazing Bull (1, new List <string> { "ashinaoutskirts", "ashinacastle" }, new List <int> { 8302, 1, 8302, -1, 8301, 1, 8301, -1, 1100330, 1 }, new List <int> { 1101952, // Outskirts Wall - Stairway 1101953, // Underbridge Valley 1101954, // Ashina Castle Gate Fortress 1101955, // Ashina Castle Gate // 1111950, // Ashina Castle }), // Hirata 1 (1, new List <string> { "hirata" }, new List <int> { 1000353, 1, 1005601, 1, 1000301, 1, 1000900, 1 }, new List <int> { 1001950, // Dragonspring - Hirata Estate 1001951, // Estate Path 1001952, // Bamboo Thicket Slope 1001953, // Hirata Estate - Main Hall 1001955, // Hirata Audience Chamber 1001954, // Hirata Estate - Hidden Temple }), // Ashina Castle to Genichiro (2, new List <string> { "ashinacastle" }, new List <int> { 8301, 1, 8302, 1, 8302, -1 }, new List <int> { 1111950, // Ashina Castle 1111951, // Upper Tower - Antechamber 1111957, // Upper Tower - Ashina Dojo 1111952, // Castle Tower Lookout }), // Ashina Castle to Reservoir to Dungeon (2, new List <string> { "ashinareservoir" }, new List <int> { 8302, 1, 1120300, 0 }, new List <int> { 1111950, // Ashina Castle 1121951, // Ashina Reservoir 1301951, // Bottomless Hole }), // Dungeon (2, new List <string> { "dungeon" }, new List <int> { }, new List <int> { 1111955, // Abandoned Dungeon Entrance 1301950, // Underground Waterway 1301951, // Bottomless Hole }), // Senpou temple (2, new List <string> { "senpou" }, new List <int> { }, new List <int> { 2001950, // Senpou Temple, Mt. Kongo 2001951, // Shugendo 2001952, // Temple Grounds 2001953, // Main Hall }), // Hidden Forest to Water Mill (3, new List <string> { "mibuvillage" }, new List <int> { 1700850, 1, 1700520, 1 }, new List <int> { 1501950, // Hidden Forest 1501951, // Mibu Village 1501952, // Water Mill }), // End of Ashina Depths (3, new List <string> { "mibuvillage" }, new List <int> { }, new List <int> { 1501952, // Water Mill 1501953, // Wedding Cave Door }), // Most of Sunken Valley (3, new List <string> { "ashinacastle", "sunkenvalley" }, new List <int> { 8301, 1, 8301, -1, 8302, 1, 8302, -1 }, new List <int> { 1111952, // Castle Tower Lookout 1111956, // Old Grave 1111954, // Great Serpent Shrine 1701957, // Under-Shrine Valley 1701950, // Sunken Valley 1701951, // Gun Fort 1701952, // Riven Cave 1701958, // Bodhisattva Valley 1701953, // Guardian Ape's Watering Hole }), // Sunken Valley to Poison Pool path (3, new List <string> { "sunkenvalley" }, new List <int> { 1700850, 0, 1700520, 0 }, new List <int> { 1701958, // Bodhisattva Valley 1701954, // Poison Pool 1701956, // Guardian Ape's Burrow }), // Ashina Castle Revisited, also down to Masanaga (4, new List <string> { "ashinacastle" }, new List <int> { 8301, 0, 8302, 1, 8302, -1 }, new List <int> { 1111955, // Abandoned Dungeon Entrance 1111950, // Ashina Castle 1111951, // Upper Tower - Antechamber 1111957, // Upper Tower - Ashina Dojo 1111952, // Castle Tower Lookout 1111956, // Old Grave 1111954, // Great Serpent Shrine }), // Fountainhead (5, new List <string> { "fountainhead" }, new List <int> { }, new List <int> { 2501950, // Fountainhead Palace 2501951, // Vermilion Bridge 2501956, // Mibu Manor 2501952, // Flower Viewing Stage 2501958, // Near Pot Noble 2501953, // Great Sakura 2501954, // Palace Grounds 2501955, // Sanctuary }), // Hirata Revisited (5, new List <string> { "hirata" }, new List <int> { 1000353, 0, 1005601, 0, 1000301, 0, 1000900, 0 }, new List <int> { 1001952, // Bamboo Thicket Slope 1001953, // Hirata Estate - Main Hall 1001955, // Hirata Audience Chamber 1001954, // Hirata Estate - Hidden Temple }), // Ashina Castle End to Outskirts (5, new List <string> { "ashinacastle", "ashinaoutskirts" }, new List <int> { 8302, 0 }, new List <int> { 1111953, // Upper Tower - Kuro's Room 1111956, // Old Grave 1101952, // Outskirts Wall - Stairway 1101951, // Outskirts Wall - Gate Path }), // Ashina Castle End to Reservoir (5, new List <string> { "ashinacastle", "ashinareservoir" }, new List <int> { 8302, 0 }, new List <int> { 1111953, // Upper Tower - Kuro's Room 1111957, // Upper Tower - Ashina Dojo 1111951, // Upper Tower - Antechamber 1111950, // Ashina Castle 1121951, // Ashina Reservoir 1121950, // Near Secret Passage }), }; FMG bonfires = new GameEditor(GameSpec.FromGame.SDT).LoadBnd(@"C:\Program Files (x86)\Steam\steamapps\common\Sekiro\msg\engus\menu.msgbnd.dcx", (p, n) => FMG.Read(p))["NTC_\u30e1\u30cb\u30e5\u30fc\u30c6\u30ad\u30b9\u30c8"]; Dictionary <int, string> names = new Dictionary <int, string>(); foreach (PARAM.Row r in game.Params["BonfireWarpParam"].Rows) { // break; int entity = (int)r["WarpEventId"].Value; string bonfire = bonfires[(int)r["BonfireNameId"].Value]; if (bonfire != null && entity > 0) { names[entity] = bonfire; // Console.WriteLine($"{entity}, // {bonfire}"); } } Dictionary <int, Vector3> points = new Dictionary <int, Vector3>(); // Find location of all bonfires foreach (KeyValuePair <string, MSBS> entry in game.Smaps) { if (!game.Locations.ContainsKey(entry.Key)) { continue; } string map = game.Locations[entry.Key]; MSBS msb = entry.Value; foreach (MSBS.Part.Object e in msb.Parts.Objects) { if (allBonfires.Contains(e.EntityID)) { points[e.EntityID] = e.Position; } } } string pathText(int p) { int first = paths[p].Item4.First(); int last = paths[p].Item4.Last(); return($"#{paths[p].Item1} {names[first]}->{names[last]}"); } bool investigateScaling = false; List <List <EnemyClass> > typeGroups = new List <List <EnemyClass> > { new List <EnemyClass> { EnemyClass.Boss, EnemyClass.TutorialBoss }, new List <EnemyClass> { EnemyClass.Miniboss }, new List <EnemyClass> { EnemyClass.Basic, EnemyClass.FoldingMonkey, EnemyClass.OldDragon } }; List <EnemyClass> types = typeGroups.SelectMany(c => c).ToList(); Dictionary <int, EnemyInfo> infos = fullInfos.Values.Where(e => types.Contains(e.Class)).ToDictionary(e => e.ID, e => e); if (!investigateScaling) { infos.Remove(1110920); infos.Remove(1110900); infos.Remove(1120800); } Dictionary <int, List <int> > possiblePaths = new Dictionary <int, List <int> >(); bool explainCat = false; for (int i = 0; i < paths.Count; i++) { if (explainCat) { Console.WriteLine($"--- Processing {pathText(i)}"); } (int section, List <string> maps, List <int> cond, List <int> order) = paths[i]; Dictionary <int, List <int> > eventFlags = new Dictionary <int, List <int> >(); HashSet <int> excludeEntity = new HashSet <int>(); HashSet <int> expectEntity = new HashSet <int>(); HashSet <int> getEntity = new HashSet <int>(); for (int j = 0; j < cond.Count; j += 2) { int check = cond[j]; int val = cond[j + 1]; if (check >= 1000000) { if (val == 0) { expectEntity.Add(check); } else { excludeEntity.Add(check); } } else { AddMulti(eventFlags, check, val); } } foreach (KeyValuePair <string, MSBS> entry in game.Smaps) { if (!game.Locations.ContainsKey(entry.Key)) { continue; } string map = game.Locations[entry.Key]; MSBS msb = entry.Value; if (!maps.Contains(map)) { continue; } foreach (MSBS.Part.Enemy e in msb.Parts.Enemies) { if (!infos.ContainsKey(e.EntityID)) { continue; } points[e.EntityID] = e.Position; names[e.EntityID] = game.ModelName(e.ModelName); List <int> ids = new List <int> { e.EntityID }; ids.AddRange(e.EntityGroupIDs.Where(id => id > 0)); if (excludeEntity.Overlaps(ids)) { if (explainCat) { Console.WriteLine($"excluded: {string.Join(",", ids)} from {string.Join(",", excludeEntity)}"); } continue; } else if (expectEntity.Overlaps(ids)) { getEntity.UnionWith(ids); } else if (eventFlags.Count > 0) { // If not explicitly expected, do a check for game progression Dictionary <int, int> flags = new Dictionary <int, int>(); if (e.EventFlagID != -1) { flags[e.EventFlagID] = e.EventFlagCompareState; } if (e.UnkT48 != -1) { flags[e.UnkT48] = e.UnkT4C; } if (e.UnkT50 != -1) { flags[e.UnkT50] = 1; } bool mismatch = false; foreach (KeyValuePair <int, List <int> > flagPair in eventFlags) { int flag = flagPair.Key; List <int> cmps = flagPair.Value; int cmp = flags.TryGetValue(flag, out int tmp) ? tmp : -1; if (explainCat && e.EntityID == 9999999) { Console.WriteLine($"for {e.EntityID} expected {flag} = {string.Join(",", cmps)}, found result {cmp}"); } if (!cmps.Contains(cmp)) { if (explainCat) { Console.WriteLine($"excluded: {string.Join(",", ids)} with {flag} = {cmp} (not {string.Join(",", cmps)})"); } mismatch = true; } } if (mismatch) { continue; } } if (explainCat) { Console.WriteLine($"added: {string.Join(",", ids)}"); } AddMulti(possiblePaths, e.EntityID, i); } } List <int> missing = expectEntity.Except(getEntity).ToList(); if (missing.Count > 0) { throw new Exception($"Missing {string.Join(",", missing)} in {string.Join(",", maps)}"); } } // Hardcode headless into Senpou, because it is out of the way and sort of a singleton possiblePaths[1100330] = new List <int> { 7 }; Console.WriteLine("Categories"); Dictionary <int, (int, float)> chosenPath = new Dictionary <int, (int, float)>(); foreach (EnemyInfo info in infos.Values) { if (!possiblePaths.TryGetValue(info.ID, out List <int> pathList)) { throw new Exception($"{info.ID} has no categorization: {info.DebugText}"); } if (paths[pathList[0]].Item2.Contains("hirata")) { // If Hirata, greedily choose pre-revisited Hirata pathList = new List <int> { pathList[0] }; } float score = float.PositiveInfinity; Vector3 pos = points[info.ID]; foreach (int path in pathList) { (int section, List <string> maps, List <int> cond, List <int> order) = paths[path]; for (int i = 0; i < order.Count - 1; i++) { Vector3 p1 = points[order[i]]; Vector3 p2 = points[order[i + 1]]; float dist1 = Vector3.Distance(p1, pos); float dist2 = Vector3.Distance(p2, pos); float dist = dist1 + dist2; if (info.ID == 9999999) { Console.WriteLine($"Found dist {dist1} to {names[order[i]]}, and {dist2} to {names[order[i + 1]]}. TOTAL {dist}"); } if (dist < score) { score = dist; chosenPath[info.ID] = (path, i + Vector3.Distance(p1, pos) / dist); } } } if (float.IsInfinity(score)) { throw new Exception($"{info.ID} with paths {string.Join(",", pathList.Select(pathText))} had nothing checked for it"); } } // Put bosses in phase order foreach (int id in chosenPath.Keys.ToList()) { EnemyInfo info = infos[id]; if (info.Class == EnemyClass.Boss && info.OwnedBy != 0) { chosenPath[id] = chosenPath[info.OwnedBy]; } } if (investigateScaling) { Dictionary <int, MSBS.Part.Enemy> enemies = new Dictionary <int, MSBS.Part.Enemy>(); foreach (KeyValuePair <string, MSBS> entry in game.Smaps) { if (!game.Locations.ContainsKey(entry.Key)) { continue; } string map = game.Locations[entry.Key]; MSBS msb = entry.Value; foreach (MSBS.Part.Enemy e in msb.Parts.Enemies) { enemies[e.EntityID] = e; } } // Exclude these from scaling considerations, since they are not really part of the area (meant for when visiting later) HashSet <int> phantomGroups = new HashSet <int> { // Ashina phantoms 1505201, 1505211, 1705200, 1705201, 2005200, 2005201, // Sunken Valley phantoms 1505202, 1505212, 2005210, 2005211, // Mibu Village phantoms 1705220, 1705221, 2005220, 2005221, }; // haveSoulRate Unk85: NG+ only // EventFlagId: used for scaling speffect // There are these overall groups: vitality, damage, experience, cash. (is there haveSoulRate for cash/xp? maybe it's Unk85) List <string> scaleSp = "maxHpRate maxStaminaCutRate physAtkPowerRate magicAtkPowerRate fireAtkPowerRate thunderAtkPowerRate staminaAttackRate darkAttackPowerRate NewGameBonusUnk".Split(' ').ToList(); List <string> scaleNpc = "Hp getSoul stamina staminaRecoverBaseVal Experience".Split(' ').ToList(); List <string> allFields = scaleSp.Concat(scaleNpc).ToList(); // Disp: ModelDispMask0 -> ModelDispMask31 // Npc param has GameClearSpEffectID Dictionary <(string, int, int), List <float> > allScales = new Dictionary <(string, int, int), List <float> >(); Dictionary <int, int> allSections = new Dictionary <int, int>(); foreach (List <EnemyClass> typeGroup in typeGroups) { // Consider two enemies the same if they have the same think id, or same disp mask // Or for minibosses, if they are just the same model, that's probably fine Dictionary <string, List <int> > thinks = new Dictionary <string, List <int> >(); Dictionary <string, List <int> > masks = new Dictionary <string, List <int> >(); Dictionary <string, List <int> > bosses = new Dictionary <string, List <int> >(); List <string> order = new List <string>(); Dictionary <int, int> sections = new Dictionary <int, int>(); foreach (KeyValuePair <int, (int, float)> entry in chosenPath.OrderBy(e => (e.Value, e.Key))) { int id = entry.Key; EnemyInfo info = infos[id]; if (!typeGroup.Contains(info.Class)) { continue; } MSBS.Part.Enemy e = enemies[id]; int path = entry.Value.Item1; int section = paths[path].Item1; sections[id] = section; allSections[id] = section; if (e.EntityGroupIDs.Any(g => phantomGroups.Contains(g))) { continue; } string model = game.ModelName(e.ModelName); if (typeGroup.Contains(EnemyClass.Miniboss) || typeGroup.Contains(EnemyClass.Boss)) { AddMulti(bosses, model, id); continue; } string think = $"{model} {e.ThinkParamID}"; AddMulti(thinks, think, id); PARAM.Row npc = game.Params["NpcParam"][e.NPCParamID]; if (e.NPCParamID > 0 && npc != null) { uint mask = 0; for (int i = 0; i < 32; i++) { if ((byte)npc[$"ModelDispMask{i}"].Value == 1) { mask |= ((uint)1 << i); } } string dispMask = $"{model} 0x{mask:X8}"; AddMulti(masks, dispMask, id); } } foreach (KeyValuePair <string, List <int> > entry in thinks.Concat(masks.Concat(bosses))) { if (entry.Value.Count == 1) { continue; } List <int> secs = entry.Value.Select(i => sections[i]).Distinct().ToList(); if (secs.Count == 1) { continue; } Console.WriteLine($"{entry.Key}: {string.Join(",", entry.Value.Select(i => $"{i}[{sections[i]}]"))}"); SortedDictionary <string, List <(int, float)> > fieldValues = new SortedDictionary <string, List <(int, float)> >(); foreach (int id in entry.Value) { MSBS.Part.Enemy e = enemies[id]; PARAM.Row npc = game.Params["NpcParam"][e.NPCParamID]; if (e.NPCParamID == 0 || npc == null) { continue; } Dictionary <string, float> values = new Dictionary <string, float>(); foreach (string f in scaleNpc) { values[f] = float.Parse(npc[f].Value.ToString()); } int spVal = (int)npc["EventFlagId"].Value; // GameClearSpEffectID is for NG+ only, or time-of-day only, or something like that PARAM.Row sp = game.Params["SpEffectParam"][spVal]; if (spVal > 0 && sp != null) { foreach (string f in scaleSp) { values[f] = float.Parse(sp[f].Value.ToString()); } } foreach (KeyValuePair <string, float> val in values) { AddMulti(fieldValues, val.Key, (sections[id], val.Value)); } } foreach (KeyValuePair <string, List <(int, float)> > val in fieldValues) { // Console.WriteLine($" {val.Key}: {string.Join(", ", val.Value.OrderBy(v => v).Select(v => $"[{v.Item1}]{v.Item2}"))}"); Dictionary <int, float> bySection = val.Value.GroupBy(v => v.Item1).ToDictionary(g => g.Key, g => g.Select(v => v.Item2).Average()); List <string> sorts = new List <string>(); foreach (int i in bySection.Keys) { foreach (int j in bySection.Keys) { if (i >= j) { continue; } float ratio = bySection[j] / bySection[i]; if (float.IsNaN(ratio) || float.IsInfinity(ratio) || ratio == 1 || ratio == 0) { continue; } sorts.Add($"{i}{j}: {ratio:f3}x"); AddMulti(allScales, (val.Key, i, j), ratio); // Can be used for complete table, but easier to leave out for lower diagonal // AddMulti(allScales, (val.Key, j, i), 1 / ratio); } } if (sorts.Count > 0) { Console.WriteLine($" {val.Key}: {string.Join(", ", sorts)}"); } } } } foreach (string field in allFields) { Console.WriteLine($"-- {field} ({allScales.Where(k => k.Key.Item1 == field).Sum(e => e.Value.Count)})"); for (int i = 1; i <= 5; i++) { // row: the target class. column: the source class. value: how much to multiply to place the source in the target. Console.WriteLine(" " + string.Join(" ", Enumerable.Range(1, 5).Select(j => allScales.TryGetValue((field, j, i), out List <float> floats) ? $"{floats.Average():f5}," : " "))); } } foreach (EnemyInfo info in fullInfos.Values) { if (!allSections.ContainsKey(info.ID) && info.Class == EnemyClass.Helper && allSections.TryGetValue(info.OwnedBy, out int section)) { allSections[info.ID] = section; } } foreach (KeyValuePair <int, int> entry in allSections.OrderBy(e => (e.Value, e.Key))) { Console.WriteLine($" {entry.Key}: {entry.Value}"); } } bool debugOutput = false; foreach (List <EnemyClass> typeGroup in typeGroups) { List <string> order = new List <string>(); foreach (KeyValuePair <int, (int, float)> entry in chosenPath.OrderBy(e => (e.Value, e.Key))) { int id = entry.Key; EnemyInfo info = infos[id]; if (!typeGroup.Contains(info.Class)) { continue; } if (debugOutput) { Console.WriteLine($"{info.DebugText}\n- {pathText(entry.Value.Item1)}, progress {entry.Value.Item2}\n"); } order.Add($"{info.ExtraName ?? names[id]} {id}"); } for (int i = 0; i < order.Count; i++) { if (!debugOutput) { Console.WriteLine($" {order[i]}: {order[order.Count - 1 - i]}"); } } } }
private static void ReadMessagesIntoDatabase(SQLiteConnection con, string name, FMG msgFile) { var tableName = DesMsgFileNamesToEnglish.TryGetValue(name, out string value) ? value : name; Console.WriteLine("Tablename is " + tableName); // Create the table to write into // Oddly enough, DS1 seems to have multiple copies of the same data in the same file. // Not sure if that's a source issue, or bug. var sb = new StringBuilder(); sb.Append(@"CREATE TABLE IF NOT EXISTS'"); sb.Append(tableName); sb.Append("' ("); sb.Append(@"'id' INTEGER NOT NULL PRIMARY KEY,"); sb.Append(@"'message' TEXT);"); using (var cmd = new SQLiteCommand(sb.ToString(), con)) { cmd.ExecuteNonQuery(); } // Actually insert our data sb.Clear(); sb.Append(@"INSERT OR IGNORE INTO '"); sb.Append(tableName); sb.Append(@"' (id, message) VALUES($id, $message);"); using (var cmd = new SQLiteCommand(sb.ToString(), con)) { var idParam = cmd.CreateParameter(); idParam.ParameterName = @"$id"; cmd.Parameters.Add(idParam); var msgParam = cmd.CreateParameter(); msgParam.IsNullable = true; msgParam.ParameterName = @"$message"; cmd.Parameters.Add(msgParam); foreach (FMG.Entry entry in msgFile.Entries) { idParam.Value = entry.ID; msgParam.Value = entry.Text; cmd.ExecuteNonQuery(); } } }
public static void LoadAllFMG(bool forceReload) { if (!forceReload && GameTypeCurrentFmgsAreLoadedFrom == GameDataManager.GameType) { return; } FMG weaponNamesFmg = null; FMG protectorNamesFmg = null; FMG weaponNamesFmg_dlc1 = null; FMG protectorNamesFmg_dlc1 = null; FMG weaponNamesFmg_dlc2 = null; FMG protectorNamesFmg_dlc2 = null; if (GameDataManager.GameType == GameDataManager.GameTypes.DS1) { var msgbnd = BND3.Read($@"{GameDataManager.InterrootPath}\msg\ENGLISH\item.msgbnd"); weaponNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("武器名.fmg")).Bytes); protectorNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("防具名.fmg")).Bytes); } else if (GameDataManager.GameType == GameDataManager.GameTypes.DS1R) { var msgbnd = BND3.Read($@"{GameDataManager.InterrootPath}\msg\ENGLISH\item.msgbnd.dcx"); weaponNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("Weapon_name_.fmg")).Bytes); protectorNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("Armor_name_.fmg")).Bytes); } else if (GameDataManager.GameType == GameDataManager.GameTypes.DS3) { var msgbnd = BND4.Read($@"{GameDataManager.InterrootPath}\msg\engus\item_dlc2.msgbnd.dcx"); weaponNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("武器名.fmg")).Bytes); protectorNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("防具名.fmg")).Bytes); var weaponNamesFmg_dlc1_entry = msgbnd.Files.LastOrDefault(f => f.Name.EndsWith("武器名_dlc1.fmg")); if (weaponNamesFmg_dlc1_entry != null) { weaponNamesFmg_dlc1 = FMG.Read(weaponNamesFmg_dlc1_entry.Bytes); } var weaponNamesFmg_dlc2_entry = msgbnd.Files.LastOrDefault(f => f.Name.EndsWith("武器名_dlc2.fmg")); if (weaponNamesFmg_dlc2_entry != null) { weaponNamesFmg_dlc2 = FMG.Read(weaponNamesFmg_dlc2_entry.Bytes); } var protectorNamesFmg_dlc1_entry = msgbnd.Files.LastOrDefault(f => f.Name.EndsWith("防具名_dlc1.fmg")); if (protectorNamesFmg_dlc1_entry != null) { protectorNamesFmg_dlc1 = FMG.Read(protectorNamesFmg_dlc1_entry.Bytes); } var protectorNamesFmg_dlc2_entry = msgbnd.Files.LastOrDefault(f => f.Name.EndsWith("防具名_dlc2.fmg")); if (protectorNamesFmg_dlc2_entry != null) { protectorNamesFmg_dlc2 = FMG.Read(protectorNamesFmg_dlc2_entry.Bytes); } } else if (GameDataManager.GameType == GameDataManager.GameTypes.BB) { var msgbnd = BND4.Read($@"{GameDataManager.InterrootPath}\msg\engus\item.msgbnd.dcx"); weaponNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("武器名.fmg")).Bytes); protectorNamesFmg = FMG.Read(msgbnd.Files.Last(f => f.Name.EndsWith("防具名.fmg")).Bytes); } WeaponNames.Clear(); void DoWeaponEntry(FMG.Entry entry) { if (string.IsNullOrWhiteSpace(entry.Text) || !ParamManager.EquipParamWeapon.ContainsKey(entry.ID)) { return; } if (GameDataManager.GameType == GameDataManager.GameTypes.DS3 && (entry.ID % 10000) != 0) { return; } else if ((entry.ID % 1000) != 0) { return; } WeaponNames.Add(entry.ID, entry.Text); } if (weaponNamesFmg != null) { foreach (var entry in weaponNamesFmg.Entries) { DoWeaponEntry(entry); } } if (weaponNamesFmg_dlc1 != null) { foreach (var entry in weaponNamesFmg_dlc1.Entries) { DoWeaponEntry(entry); } } if (weaponNamesFmg_dlc2 != null) { foreach (var entry in weaponNamesFmg_dlc2.Entries) { DoWeaponEntry(entry); } } ProtectorNames_HD.Clear(); ProtectorNames_BD.Clear(); ProtectorNames_AM.Clear(); ProtectorNames_LG.Clear(); void DoProtectorParamEntry(FMG.Entry entry) { if (string.IsNullOrWhiteSpace(entry.Text)) { return; } if (entry.ID < 1000000 && GameDataManager.GameType == GameDataManager.GameTypes.DS3) { return; } if (ParamManager.EquipParamProtector.ContainsKey(entry.ID)) { var protectorParam = ParamManager.EquipParamProtector[entry.ID]; if (protectorParam.HeadEquip) { ProtectorNames_HD.Add(entry.ID, entry.Text); } else if (protectorParam.BodyEquip) { ProtectorNames_BD.Add(entry.ID, entry.Text); } else if (protectorParam.ArmEquip) { ProtectorNames_AM.Add(entry.ID, entry.Text); } else if (protectorParam.LegEquip) { ProtectorNames_LG.Add(entry.ID, entry.Text); } } } if (protectorNamesFmg != null) { foreach (var entry in protectorNamesFmg.Entries) { DoProtectorParamEntry(entry); } } if (protectorNamesFmg_dlc1 != null) { foreach (var entry in protectorNamesFmg_dlc1.Entries) { DoProtectorParamEntry(entry); } } if (protectorNamesFmg_dlc2 != null) { foreach (var entry in protectorNamesFmg_dlc2.Entries) { DoProtectorParamEntry(entry); } } WeaponNames = WeaponNames.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_HD = ProtectorNames_HD.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_BD = ProtectorNames_BD.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_AM = ProtectorNames_AM.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); ProtectorNames_LG = ProtectorNames_LG.OrderBy(x => x.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); foreach (var protector in ParamManager.EquipParamProtector) { if (protector.Value.HeadEquip && !ProtectorNames_HD.ContainsKey((int)protector.Key)) { ProtectorNames_HD.Add((int)protector.Key, $"<Head {protector.Key}>"); } else if (protector.Value.BodyEquip && !ProtectorNames_BD.ContainsKey((int)protector.Key)) { ProtectorNames_BD.Add((int)protector.Key, $"<Body {protector.Key}>"); } else if (protector.Value.ArmEquip && !ProtectorNames_AM.ContainsKey((int)protector.Key)) { ProtectorNames_AM.Add((int)protector.Key, $"<Arms {protector.Key}>"); } else if (protector.Value.LegEquip && !ProtectorNames_LG.ContainsKey((int)protector.Key)) { ProtectorNames_LG.Add((int)protector.Key, $"<Legs {protector.Key}>"); } } foreach (var weapon in ParamManager.EquipParamWeapon) { if (!WeaponNames.ContainsKey((int)weapon.Key)) { WeaponNames.Add((int)weapon.Key, $"<Weapon {weapon.Key}>"); } } GameTypeCurrentFmgsAreLoadedFrom = GameDataManager.GameType; }
void handleLevelSelect(FMG.LevelSelect ls) { if(ls) { Debug.Log ("found levelselect"); GameObject objectToReplace = ls.levelButton; UnityEngine.UI.Image img = objectToReplace.GetComponent<UnityEngine.UI.Image>(); if(img) { img.sprite = buttonSprite; Debug.Log ("found image"); } UnityEngine.UI.Text text = objectToReplace.GetComponentInChildren<UnityEngine.UI.Text>(); if(text) { text.font = font; text.color = fontColor; Debug.Log ("found text"); } //PrefabUtility.ReplacePrefab(objectToReplace, objectToReplace, ReplacePrefabOptions.ConnectToPrefab); } }
private static bool UnpackFile(string sourceFile) { string sourceDir = Path.GetDirectoryName(sourceFile); string filename = Path.GetFileName(sourceFile); string targetDir = $"{sourceDir}\\{filename.Replace('.', '-')}"; if (File.Exists(targetDir)) { targetDir += "-ybr"; } if (DCX.Is(sourceFile)) { Console.WriteLine($"Decompressing DCX: {filename}..."); byte[] bytes = DCX.Decompress(sourceFile, out DCX.Type compression); if (BND3.Is(bytes)) { Console.WriteLine($"Unpacking BND3: {filename}..."); BND3 bnd = BND3.Read(bytes); bnd.Compression = compression; bnd.Unpack(filename, targetDir); } else if (BND4.Is(bytes)) { Console.WriteLine($"Unpacking BND4: {filename}..."); BND4 bnd = BND4.Read(bytes); bnd.Compression = compression; bnd.Unpack(filename, targetDir); } else if (TPF.Is(bytes)) { Console.WriteLine($"Unpacking TPF: {filename}..."); TPF tpf = TPF.Read(bytes); tpf.Compression = compression; tpf.Unpack(filename, targetDir); } else if (sourceFile.EndsWith(".gparam.dcx")) { Console.WriteLine($"Unpacking GPARAM: {filename}..."); GPARAM gparam = GPARAM.Read(bytes); gparam.Unpack(sourceFile); } else { Console.WriteLine($"File format not recognized: {filename}"); return(true); } } else { if (BND3.Is(sourceFile)) { Console.WriteLine($"Unpacking BND3: {filename}..."); BND3 bnd = BND3.Read(sourceFile); bnd.Unpack(filename, targetDir); } else if (BND4.Is(sourceFile)) { Console.WriteLine($"Unpacking BND4: {filename}..."); BND4 bnd = BND4.Read(sourceFile); bnd.Unpack(filename, targetDir); } else if (BXF3.IsBHD(sourceFile)) { string bdtExtension = Path.GetExtension(filename).Replace("bhd", "bdt"); string bdtFilename = $"{Path.GetFileNameWithoutExtension(filename)}{bdtExtension}"; string bdtPath = $"{sourceDir}\\{bdtFilename}"; if (File.Exists(bdtPath)) { Console.WriteLine($"Unpacking BXF3: {filename}..."); BXF3 bxf = BXF3.Read(sourceFile, bdtPath); bxf.Unpack(filename, bdtFilename, targetDir); } else { Console.WriteLine($"BDT not found for BHD: {filename}"); return(true); } } else if (BXF4.IsBHD(sourceFile)) { string bdtExtension = Path.GetExtension(filename).Replace("bhd", "bdt"); string bdtFilename = $"{Path.GetFileNameWithoutExtension(filename)}{bdtExtension}"; string bdtPath = $"{sourceDir}\\{bdtFilename}"; if (File.Exists(bdtPath)) { Console.WriteLine($"Unpacking BXF4: {filename}..."); BXF4 bxf = BXF4.Read(sourceFile, bdtPath); bxf.Unpack(filename, bdtFilename, targetDir); } else { Console.WriteLine($"BDT not found for BHD: {filename}"); return(true); } } else if (TPF.Is(sourceFile)) { Console.WriteLine($"Unpacking TPF: {filename}..."); TPF tpf = TPF.Read(sourceFile); tpf.Unpack(filename, targetDir); } else if (sourceFile.EndsWith(".fmg")) { Console.WriteLine($"Unpacking FMG: {filename}..."); FMG fmg = FMG.Read(sourceFile); fmg.Unpack(sourceFile); } else if (sourceFile.EndsWith(".fmg.xml")) { Console.WriteLine($"Repacking FMG: {filename}..."); YFMG.Repack(sourceFile); } else if (sourceFile.EndsWith(".gparam")) { Console.WriteLine($"Unpacking GPARAM: {filename}..."); GPARAM gparam = GPARAM.Read(sourceFile); gparam.Unpack(sourceFile); } else if (sourceFile.EndsWith(".gparam.xml") || sourceFile.EndsWith(".gparam.dcx.xml")) { Console.WriteLine($"Repacking GPARAM: {filename}..."); YGPARAM.Repack(sourceFile); } else if (sourceFile.EndsWith(".luagnl")) { Console.WriteLine($"Unpacking LUAGNL: {filename}..."); LUAGNL gnl = LUAGNL.Read(sourceFile); gnl.Unpack(sourceFile); } else if (sourceFile.EndsWith(".luagnl.xml")) { Console.WriteLine($"Repacking LUAGNL: {filename}..."); YLUAGNL.Repack(sourceFile); } else if (LUAINFO.Is(sourceFile)) { Console.WriteLine($"Unpacking LUAINFO: {filename}..."); LUAINFO info = LUAINFO.Read(sourceFile); info.Unpack(sourceFile); } else if (sourceFile.EndsWith(".luainfo.xml")) { Console.WriteLine($"Repacking LUAINFO: {filename}..."); YLUAINFO.Repack(sourceFile); } else { Console.WriteLine($"File format not recognized: {filename}"); return(true); } } return(false); }
private static void PatchFMG(IBinder sourceBND, IBinder destBND, IBinder refBND, string destLang) { int iFile = 0; //File counter int iRef = 0; //Reference index counter int entriesAdded = 0; //Total added per file int entriesOverwritten = 0; //Total overwritten per file Log.Add($"{ new DirectoryInfo(Path.GetDirectoryName(destLang)).Name } { Path.GetFileName(destLang) } start"); foreach (var file in sourceBND.Files) //For each FMG in the source BND file { if (file.ID > destBND.Files[iFile].ID) //Compatability for using program from non-English folders. Does nothing in the English folder. { while ((file.ID > destBND.Files[iFile].ID) && (iFile < destBND.Files.Count - 1)) { //ConsoleLog("Skipped " + destBND.Files[iFile].ID); //Debug if (iFile < destBND.Files.Count - 1) { iFile++; } } } if (file.ID == destBND.Files[iFile].ID) //If the file IDs match, update. If not, skip until they do match { //Add the source and destination FMG Log.Add($"Destination: { Path.GetFileName(destBND.Files[iFile].Name) } / Source: { Path.GetFileName(file.Name) }"); //ConsoleLog(file.ID + " = true"); // Debug FMG sourceFMG = FMG.Read(file.Bytes); FMG destFMG = FMG.Read((destBND.Files[iFile]).Bytes); //Make dictionaries out of the FMG files Dictionary <int, string> sourceDict = sourceFMG.Entries.GroupBy(x => x.ID).Select(x => x.First()).ToDictionary(x => x.ID, x => x.Text); Dictionary <int, string> destDict = destFMG.Entries.GroupBy(x => x.ID).Select(x => x.First()).ToDictionary(x => x.ID, x => x.Text); entriesAdded = AddNew(entriesAdded, sourceDict, destDict); //Make a refFMG and refDict if refBND isn't null Dictionary <int, string> refDict = MakeRef(refBND, iRef); //Make dicitonary based on comparing sourceDict to refDict if (refDict != null) { entriesOverwritten = UpdateText(entriesOverwritten, refDict, sourceDict, destDict); } //Clear and rewrite FMG file RewriteFMG(destFMG, destDict); //Replace old null entries if no reference. if (NoRef) { Log.Add("Changed:"); entriesOverwritten = NullOverwrite(entriesOverwritten, sourceFMG, destFMG); } #region Debug Stuff //foreach (var entry in destFMG.Entries) //{ // ConsoleLog(entry); //} #endregion //Write the new files destBND.Files[iFile].Bytes = destFMG.Write(); //Add to counter if it's not already maxed if (iFile < destBND.Files.Count - 1) { iFile++; } } #region Debug Stuff //else //Debug //{ // ConsoleLog(file.ID + " = false"); //} #endregion iRef++; } //Print stats for entire BND file ConsoleLog($"Patched: { new DirectoryInfo(Path.GetDirectoryName(destLang)).Name } { Path.GetFileName(destLang) }: { entriesAdded } entries added and { entriesOverwritten } entries overwritten"); //Append log file Log.Add($"{ new DirectoryInfo(Path.GetDirectoryName(destLang)).Name } { Path.GetFileName(destLang) } end"); File.AppendAllLines($@"{ Directory.GetCurrentDirectory() }\LangPatchLog.txt", Log); Log.Clear(); }
public void Write(RandomizerOptions opt, Permutation permutation) { // Overall: 9020, and 9200 to 9228 // Exclude 9209 and 9215 and 9228 bc it's useful // Also, 9221 9223 9225 are 'purchase to read' // Can replace everything in quotes // Or after the first :\n // Or after the last \n\n // If the message ends with ' -?Name' can leave that. space or ideographic space (3000). multiple whitespace either way // // Alternatively: replace individual text, like 'moon-view tower' // defeat a named enemy // defeat a formidable foe // defeat a powerful enemy FMG itemDesc = game.ItemFMGs["アイテム説明"]; FMG eventText = game.MenuFMGs["イベントテキスト"]; if (opt["writehints"]) { List <int> eventIds = new List <int> { 12000000, 12000331, 12000021, 12000261, 12000275, 12000321, 12000285, 12000241, 12000011, 12000311, 12000231, 12000291, 12000341, }; eventIds.Sort(); HintData write = new HintData(); void createHint(int id, string name, string text) { Hint hint = new Hint { ID = id, Name = name, Versions = new List <HintTemplate> { new HintTemplate { Type = "default", Text = text.Split('\n').ToList(), } }, }; write.Hints.Add(hint); } foreach (KeyValuePair <ItemKey, string> entry in game.Names()) { ItemKey key = entry.Key; if (!(key.Type == ItemType.GOOD && (key.ID == 9020 || key.ID >= 9200 && key.ID <= 9228))) { continue; } createHint(key.ID, entry.Value, itemDesc[key.ID]); } foreach (int id in eventIds) { createHint(id, null, eventText[id]); } ISerializer serializer = new SerializerBuilder().DisableAliases().Build(); using (var writer = File.CreateText("hints.txt")) { serializer.Serialize(writer, write); } return; } IDeserializer deserializer = new DeserializerBuilder().Build(); HintData hints; using (var reader = File.OpenText("dists/Base/hints.txt")) { hints = deserializer.Deserialize <HintData>(reader); } // Preprocess some items Dictionary <HintType, TypeHint> typeNames = hints.Types.ToDictionary(e => e.Name, e => e); Dictionary <ItemCategory, List <ItemKey> > categories = ((ItemCategory[])Enum.GetValues(typeof(ItemCategory))).ToDictionary(e => e, e => new List <ItemKey>()); Dictionary <ItemCategory, string> categoryText = new Dictionary <ItemCategory, string>(); foreach (ItemHint cat in hints.ItemCategories) { if (cat.Includes != null) { categories[cat.Name].AddRange(cat.Includes.Split(' ').Select(i => ann.Items.TryGetValue(i, out ItemKey key) ? key : throw new Exception($"Unrecognized name {i}"))); } if (cat.IncludesName != null) { categories[cat.Name].AddRange(phraseRe.Split(cat.IncludesName).Select(i => game.ItemForName(i))); } if (cat.Text != null) { categoryText[cat.Name] = cat.Text; } } if (opt["earlyhirata"]) { categories[ItemCategory.ExcludeHints].Add(ann.Items["younglordsbellcharm"]); } categories[ItemCategory.ExcludeHints].AddRange(permutation.NotRequiredKeyItems); // TODO: Exclude non-technically-required items... calculate this in key item permutations List <ItemKey> allItems = permutation.KeyItems.ToList(); if (opt["norandom_skills"]) { categories[ItemCategory.ImportantTool].Clear(); } else { allItems.AddRange(categories[ItemCategory.ImportantTool]); } allItems.AddRange(categories[ItemCategory.HintFodder]); Dictionary <string, ItemHintName> specialItemNames = hints.ItemNames.ToDictionary(n => n.Name, n => n); // Process areas Dictionary <string, AreaHint> areas = new Dictionary <string, AreaHint>(); HashSet <string> gameAreas = new HashSet <string>(ann.Areas.Keys); List <string> getAreasForName(string names) { List <string> nameList = new List <string>(); foreach (string name in names.Split(' ')) { if (name.EndsWith("*")) { string prefix = name.Substring(0, name.Length - 1); List <string> matching = gameAreas.Where(n => n.StartsWith(prefix)).ToList(); if (matching.Count == 0) { throw new Exception($"Unrecognized area in hint config: {name}"); } nameList.AddRange(matching); } else { if (!gameAreas.Contains(name)) { throw new Exception($"Unrecognized area in hint config: {name}"); } nameList.Add(name); } } return(nameList); } foreach (AreaHint area in hints.Areas) { if (area.Name == null || area.Includes == null) { throw new Exception($"Missing data in area hint grouping {area.Name}"); } areas[area.Name] = area; area.Areas.UnionWith(getAreasForName(area.Includes)); if (area.Excludes != null) { area.Areas.ExceptWith(getAreasForName(area.Excludes)); } if (area.LaterIncludes != null) { area.LaterAreas.UnionWith(getAreasForName(area.LaterIncludes)); if (!area.LaterAreas.IsSubsetOf(area.Areas)) { throw new Exception($"Error in hint config: later areas of {area.Name} are not a subset of all areas"); } } area.EarlyAreas.UnionWith(area.Areas.Except(area.LaterAreas)); if (area.Parent != null) { if (!areas.TryGetValue(area.Parent, out AreaHint parent)) { throw new Exception($"Error in hint config: parent of {area.Name} does not exist: {area.Parent}"); } area.Parents.Add(parent); area.Parents.UnionWith(parent.Parents); } if (area.Present != null) { area.Types = area.Present.Split(' ').Select(t => (HintType)Enum.Parse(typeof(HintType), t)).ToList(); } } bool printText = opt["hinttext"]; // Process items to search for List <ItemCategory> categoryOverrides = new List <ItemCategory> { ItemCategory.RequiredKey, ItemCategory.RequiredAbility, ItemCategory.ImportantTool, ItemCategory.HintFodder }; HashSet <string> chests = new HashSet <string> { "o005300", "o005400", "o255300" }; List <Placement> placements = new List <Placement>(); Dictionary <ItemKey, Placement> itemPlacement = new Dictionary <ItemKey, Placement>(); foreach (ItemKey key in allItems) { if (categories[ItemCategory.ExcludeHints].Contains(key)) { continue; } if (!permutation.SkillAssignment.TryGetValue(key, out ItemKey lookup)) { lookup = key; } SlotKey targetKey = permutation.GetFiniteTargetKey(lookup); ItemLocation itemLoc = data.Location(targetKey); if (!ann.Slots.TryGetValue(itemLoc.LocScope, out SlotAnnotation slot)) { continue; } ItemCategory category = ItemCategory.RequiredItem; foreach (ItemCategory cat in categoryOverrides) { // Use the last applicable category if (categories[cat].Contains(key)) { category = cat; } } List <HintType> types = new List <HintType>(); if (slot.HasTag("boss") || slot.HasTag("bosshint")) { types.Add(HintType.Boss); types.Add(HintType.Enemy); } else if (slot.HasTag("miniboss")) { types.Add(HintType.Miniboss); types.Add(HintType.Enemy); } else if (slot.HasTag("enemyhint")) { types.Add(HintType.Enemy); } else if (slot.HasTag("carp")) { types.Add(HintType.Carp); } else if (itemLoc.Keys.Any(k => k.Type == LocationKey.LocationType.SHOP && k.ID / 100 != 11005)) { types.Add(HintType.Shop); } else { if (slot.Area.EndsWith("_underwater") || slot.HasTag("underwater")) { types.Add(HintType.Underwater); } if (itemLoc.Keys.Any(k => k.Type == LocationKey.LocationType.LOT && k.Entities.Any(e => chests.Contains(e.ModelName)))) { types.Add(HintType.Chest); } types.Add(HintType.Treasure); } string name = game.Name(key); Placement placement = new Placement { Item = key, FullName = name, Category = category, LateEligible = categories[ItemCategory.LatenessHints].Contains(key), Important = category != ItemCategory.HintFodder, Area = slot.Area, Types = types, }; if (placement.Important) { placements.Add(placement); } itemPlacement[key] = placement; if (printText) { Console.WriteLine(placement); } } foreach (Hint hint in hints.Hints) // Lovely { hint.Types = hint.Versions.Where(v => v.Type != "default").ToDictionary(v => v.Type, v => v); } // Classify early and late areas HashSet <string> early = new HashSet <string>(permutation.IncludedAreas.Where(e => !e.Value.Contains("ashinacastle")).Select(e => e.Key)); HashSet <string> late = new HashSet <string>(permutation.IncludedAreas.Where(e => e.Value.Contains("fountainhead_bridge")).Select(e => e.Key)); // Start hints Random random = new Random((int)opt.Seed); List <Hint> sources = hints.Hints.Where(s => s.Types.Any(e => e.Key != "default")).ToList(); Shuffle(random, sources); sources = sources.OrderBy(s => (s.HasInfix("bad") && s.Types.ContainsKey("hint")) ? 1 : 0).ToList(); string choose(List <string> items) { return(items.Count == 1 ? items[0] : Choice(random, items)); } // Process all hint types. There are 20 item locations in the entire game, plus 13 fixed texts, for a total of 33 Regex format = new Regex(@"\(([^\)]*)\)"); if (printText) { Console.WriteLine($"No hint items: {string.Join(", ", categories[ItemCategory.ExcludeHints].Select(k => game.Name(k)))}"); } void addHint(Hint hint, HintTemplate t, Placement mainPlacement, Placement otherPlacement = null) { string text = printText ? string.Join(" ", t.Text.Select(l => l.Trim())) : string.Join("\n", t.Text); bool positive = !t.Type.Contains("bad"); foreach (Match m in format.Matches(text)) { string variable = m.Groups[1].Value; string[] parts = variable.Split('_'); string kind = parts[0].ToLowerInvariant(); bool upper = char.IsUpper(parts[0][0]); string subkind = parts.Length > 1 ? parts[1] : null; string value; Placement placement = kind == "location2" && otherPlacement != null ? otherPlacement : mainPlacement; if (kind == "item") { if (positive) { if (placement.LateHint || !categoryText.ContainsKey(placement.Category)) { value = placement.FullName; if (specialItemNames.TryGetValue(value, out ItemHintName vagueName)) { value = choose(vagueName.GetNames()); } } else { value = categoryText[placement.Category]; } } else { // Shouldn't be used in this context, but fall back value = "nothing"; } } else if (kind == "type") { HintType type = placement.Types.FirstOrDefault(); value = choose(typeNames[placement.Types[0]].GetNames(subkind)); } else if (kind == "location" || kind == "location2") { bool prep; if (subkind == null) { prep = false; } else if (subkind == "preposition") { prep = true; } else { throw new Exception($"Unknown hint config variable {variable}"); } if (positive || placement.Types.Count == 0) { value = placement.AreaHint == null ? (positive ? "somewhere" : "anywhere") : choose(placement.AreaHint.GetNames(prep)); } else { if (prep || placement.AreaHint != null) { value = (prep ? "from " : "") + choose(typeNames[placement.Types[0]].GetNames("noun")) + " " + (placement.AreaHint == null ? (positive ? "somewhere" : "anywhere") : choose(placement.AreaHint.GetNames(true))); } else { value = value = choose(typeNames[placement.Types[0]].GetNames("gerund")); } } } else { throw new Exception($"Unknown hint variable {variable}"); } if (upper) { value = value[0].ToString().ToUpperInvariant() + value.Substring(1); } text = text.Replace($"({variable})", value); } if (printText) { Console.WriteLine(text + "\n"); } if (hint.ID < 10000) { itemDesc[hint.ID] = text; } else { eventText[hint.ID] = text; } } AreaHint mostSpecificArea(string name) { AreaHint selected = null; foreach (AreaHint area in hints.Areas) { if (area.EarlyAreas.Contains(name)) { if (selected == null || area.Parents.Contains(selected)) { selected = area; } } } return(selected); } T pop <T>(List <T> list) { T ret = list[list.Count - 1]; list.RemoveAt(list.Count - 1); return(ret); } // In priority order: // Item hints: Find item at (type) and (location). Always filled in. 1 of these. HashSet <ItemKey> exactKey = new HashSet <ItemKey>(); foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("itemhint")).ToList()) { HintTemplate t = hint.Types["itemhint"]; if (!ann.Items.TryGetValue(t.Req, out ItemKey key)) { throw new Exception($"Unrecognized name {t.Req}"); } if (!itemPlacement.TryGetValue(key, out Placement placement)) { continue; } exactKey.Add(key); placement = placement.Copy(); placement.AreaHint = mostSpecificArea(placement.Area); addHint(hint, t, placement); sources.Remove(hint); } // Location hints: Find (item/item category/nothing) at (location). Always filled in. 2 of these. foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("locationhint")).ToList()) { HintTemplate t = hint.Types["locationhint"]; List <string> reqAreas = t.Req.Split(' ').ToList(); List <Placement> places = placements.Where(p => reqAreas.Contains(p.Area)).ToList(); if (places.Count == 0 && hint.Types.TryGetValue("locationbadhint", out HintTemplate t2)) { addHint(hint, t2, null); } else { Placement placement = places[0].Copy(); addHint(hint, t, placement); } sources.Remove(hint); } // Global negative hint: There is nothing at (type). Always include as many as applicable for likely types, treasure/chest/boss/miniboss/enemy/underwater/shop (but more like 1-2) List <HintType> present = placements.SelectMany(p => p.Types).Distinct().ToList(); List <HintType> absent = new List <HintType> { HintType.Treasure, HintType.Chest, HintType.Boss, HintType.Miniboss, HintType.Enemy, HintType.Underwater, HintType.Shop } .Except(present).ToList(); foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("badhint")).ToList()) { if (absent.Count == 0) { break; } HintType type = pop(absent); HintTemplate t = hint.Types["badhint"]; addHint(hint, t, new Placement { Types = new List <HintType> { type }, }); sources.Remove(hint); } // Positive hint: There is (item/item category) at (type) in (location). Include one per key item, up to 13, and special items, currently 3. List <Placement> toPlace = placements.Where(p => !exactKey.Contains(p.Item)).ToList(); foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("hint")).ToList()) { if (toPlace.Count == 0) { break; } HintTemplate t = hint.Types["hint"]; Placement placement = pop(toPlace); placement = placement.Copy(); placement.AreaHint = mostSpecificArea(placement.Area); addHint(hint, t, placement); sources.Remove(hint); } // Lateness hint: There is (item) at (type) (late/early). Include one per location item, up to 6. toPlace = placements.Where(p => p.LateEligible).ToList(); foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("hint")).ToList()) { if (toPlace.Count == 0) { break; } HintTemplate t = hint.Types["hint"]; Placement placement = pop(toPlace); placement = placement.Copy(); placement.LateHint = true; string info = early.Contains(placement.Area) ? "an early game location" : (late.Contains(placement.Area) ? "a late game location" : "a mid game location"); string prep = early.Contains(placement.Area) ? "in the early game" : (late.Contains(placement.Area) ? "in the late game" : "in the mid game"); placement.AreaHint = new AreaHint { Name = info, Vague = info, VaguePrep = prep }; addHint(hint, t, placement); sources.Remove(hint); } // So far, around 25 hints created, with ~10 left to go. At this point, pick randomly from either of these categories. List <AreaHint> withoutRedundantChildren(List <AreaHint> allAreas) { return(allAreas.Where(area => area.Parents.Count == 0 || !area.Parents.Any(p => allAreas.Contains(p))).ToList()); } List <AreaHint> areasWithoutPlacements(HintType type = HintType.None) { HashSet <string> importantAreas = new HashSet <string>(placements.Where(p => type == HintType.None || p.Types.Contains(type)).Select(p => p.Area)); return(hints.Areas.Where(h => (type == HintType.None || h.Types.Contains(type)) && !importantAreas.Overlaps(h.Areas)).ToList()); } // Area negative hint: There is nothing in (location). Can be included for all such areas, but eliminate a hint if its parent also applies. Dictionary <HintType, List <AreaHint> > negativeHints = new Dictionary <HintType, List <AreaHint> >(); List <AreaHint> unimportantAreas = areasWithoutPlacements(); negativeHints[HintType.None] = withoutRedundantChildren(unimportantAreas); foreach (HintType noType in new[] { HintType.Boss, HintType.Miniboss, HintType.Treasure }) { negativeHints[noType] = withoutRedundantChildren(areasWithoutPlacements(noType)).Except(unimportantAreas).ToList(); } negativeHints[HintType.Enemy] = new List <AreaHint>(); foreach (AreaHint allEnemy in negativeHints[HintType.Boss].Intersect(negativeHints[HintType.Miniboss]).ToList()) { // Add 'no powerful enemy' hints if no boss and no miniboss. negativeHints[HintType.Enemy].Add(allEnemy); negativeHints[HintType.Boss].Remove(allEnemy); negativeHints[HintType.Miniboss].Remove(allEnemy); } List <Placement> negatives = new List <Placement>(); foreach (KeyValuePair <HintType, List <AreaHint> > entry in negativeHints) { List <HintType> types = entry.Key == HintType.None ? new List <HintType>() : new List <HintType> { entry.Key }; foreach (AreaHint area in entry.Value) { negatives.Add(new Placement { AreaHint = area, Types = types, }); } } // Area type hint: There is nothing at (type) in (location). Include this for treasure and for miniboss/boss/enemy, eliminating when parent (or parent type) applies. Shuffle(random, negatives); negatives = negatives.OrderBy(a => a.AreaHint.AreaRank).ToList(); // If there are at least 4 negative hints, allow up to 3 of the remainder to become fodder. List <Hint> negativeHintTemplates = sources.Where(s => s.Types.ContainsKey("badhint") || s.Types.ContainsKey("badhint2")).ToList(); if (negatives.Count >= 4 && negativeHintTemplates.Count > 4) { int cutoffIndex = Math.Max(negativeHintTemplates.Count - 3, 4); negativeHintTemplates.RemoveRange(cutoffIndex, negativeHintTemplates.Count - cutoffIndex); } foreach (Hint hint in negativeHintTemplates) { if (negatives.Count == 0) { break; } HintTemplate t = hint.Types.ContainsKey("badhint") ? hint.Types["badhint"] : hint.Types["badhint2"]; Placement placement = pop(negatives); Placement otherPlacement = t.Type == "badhint2" && negatives.Count > 0 ? pop(negatives) : null; addHint(hint, t, placement, otherPlacement); sources.Remove(hint); } if (printText) { Console.WriteLine($"{sources.Count} remaining hints: [{string.Join(", ", sources.Select(s => string.Join("/", s.Types.Keys)))}]"); } if (sources.Count > 0) { // At this point, pull in misc hints for somewhat useful items toPlace = categories[ItemCategory.HintFodder].Select(k => itemPlacement[k]).ToList(); Shuffle(random, toPlace); foreach (Hint hint in sources.Where(s => s.Types.ContainsKey("hint")).ToList()) { if (toPlace.Count == 0) { break; } HintTemplate t = hint.Types["hint"]; Placement placement = pop(toPlace); placement = placement.Copy(); placement.AreaHint = mostSpecificArea(placement.Area); addHint(hint, t, placement); sources.Remove(hint); } } // Need to figure out which items are strictly required to beat the game // Also, for bad hints, find all strictly required items plus key items // List all locations which can have hints scoped to them. The entirety of Sunken Valley, or just lower/upper, or burrow // Most key items get minimum specificity // Required side area items will get early/lateness specificity when that applies // Skills/prosthetics will get maximum specificity, maybe even two for Mikiri // Mortal Blade is excluded, since it has its own explicit hint // Young Lord Bell Charm is excluded if earlyhirata }
public void WriteEventConfig(AnnotationData ann, Events events, RandomizerOptions opt) { GameEditor editor = new GameEditor(FromGame.DS3); editor.Spec.GameDir = "fogdist"; Dictionary <string, MSB3> maps = editor.Load(@"Base", path => ann.Specs.ContainsKey(GameEditor.BaseName(path)) ? MSB3.Read(path) : null, "*.msb.dcx"); Dictionary <string, EMEVD> emevds = editor.Load(@"Base", path => ann.Specs.ContainsKey(GameEditor.BaseName(path)) || path.Contains("common") ? EMEVD.Read(path) : null, "*.emevd.dcx"); void deleteEmpty <K, V>(Dictionary <K, V> d) { foreach (K key in d.Keys.ToList()) { if (d[key] == null) { d.Remove(key); } } } // Should this be in GameEditor? deleteEmpty(maps); deleteEmpty(emevds); editor.Spec.NameDir = @"fogdist\Names"; Dictionary <string, string> modelNames = editor.LoadNames("ModelName", n => n); SortedDictionary <int, string> chars = new SortedDictionary <int, string>(editor.LoadNames("CharaInitParam", n => int.Parse(n))); Dictionary <string, List <string> > description = new Dictionary <string, List <string> >(); Dictionary <int, string> entityNames = new Dictionary <int, string>(); Dictionary <int, List <int> > groupIds = new Dictionary <int, List <int> >(); Dictionary <(string, string), MSB3.Event.ObjAct> objacts = new Dictionary <(string, string), MSB3.Event.ObjAct>(); HashSet <int> highlightIds = new HashSet <int>(); HashSet <int> selectIds = new HashSet <int>(); foreach (Entrance e in ann.Warps.Concat(ann.Entrances)) { int id = e.ID; AddMulti(description, id.ToString(), (ann.Warps.Contains(e) ? "" : "fog gate ") + e.Text); selectIds.Add(e.ID); highlightIds.Add(e.ID); } HashSet <string> gameObjs = new HashSet <string>(); foreach (GameObject obj in ann.Objects) { if (int.TryParse(obj.ID, out int id)) { AddMulti(description, id.ToString(), obj.Text); selectIds.Add(id); highlightIds.Add(id); } else { gameObjs.Add($"{obj.Area}_{obj.ID}"); } } Dictionary <string, Dictionary <string, FMG> > fmgs = new GameEditor(FromGame.DS3).LoadBnds($@"msg\engus", (data, name) => FMG.Read(data), ext: "*_dlc2.msgbnd.dcx"); void addFMG(FMG fmg, string desc) { foreach (FMG.Entry e in fmg.Entries) { if (e.ID > 25000 && !string.IsNullOrWhiteSpace(e.Text)) { highlightIds.Add(e.ID); AddMulti(description, e.ID.ToString(), desc + " " + "\"" + e.Text.Replace("\r", "").Replace("\n", "\\n") + "\""); } } } addFMG(fmgs["item_dlc2"]["NPC名"], "name"); addFMG(fmgs["menu_dlc2"]["イベントテキスト"], "text"); foreach (KeyValuePair <string, MSB3> entry in maps) { string map = ann.Specs[entry.Key].Name; MSB3 msb = entry.Value; foreach (MSB3.Part e in msb.Parts.GetEntries()) { string shortName = $"{map}_{e.Name}"; if (modelNames.TryGetValue(e.ModelName, out string modelDesc)) { if (e is MSB3.Part.Enemy en && modelDesc == "Human NPC" && en.CharaInitID > 0) { modelDesc = CharacterName(chars, en.CharaInitID); } else if (e is MSB3.Part.Player) { modelDesc = "Warp Point"; } AddMulti(description, shortName, modelDesc); } AddMulti(description, shortName, $"{map} {e.GetType().Name.ToString().ToLowerInvariant()} {e.Name}"); // {(e.EventEntityID > 0 ? $" {e.EventEntityID}" : "")} if (e.EventEntityID > 10) { highlightIds.Add(e.EventEntityID); string idStr = e.EventEntityID.ToString(); if (description.ContainsKey(idStr)) { AddMulti(description, shortName, description[idStr]); } description[idStr] = description[shortName]; if (e is MSB3.Part.Player || e.ModelName == "o000100") { selectIds.Add(e.EventEntityID); } if (selectIds.Contains(e.EventEntityID)) { gameObjs.Add(shortName); } foreach (int id in e.EventEntityGroups) { if (id > 0) { AddMulti(groupIds, id, e.EventEntityID); highlightIds.Add(id); } } } } foreach (MSB3.Region r in msb.Regions.GetEntries()) { if (r.EventEntityID < 1000000) { continue; } AddMulti(description, r.EventEntityID.ToString(), $"{map} {r.GetType().Name.ToLowerInvariant()} region {r.Name}"); highlightIds.Add(r.EventEntityID); } foreach (MSB3.Event e in msb.Events.GetEntries()) { if (e is MSB3.Event.ObjAct oa) { // It can be null, basically for commented out objacts string part = oa.PartName ?? oa.PartName2; if (part == null) { continue; } string desc = description.TryGetValue($"{map}_{part}", out List <string> p) ? string.Join(" - ", p) : throw new Exception($"{map} {oa.Name}"); objacts[(map, part)] = oa;
public Assignment SplitAll() { // Add skills as item drops in the world. Esoteric texts still work, but they are removed from randomizer pool. Assignment ret = new Assignment(); // First, create skill items, including editing item fmgs SortedDictionary <int, ItemKey> newSkills = new SortedDictionary <int, ItemKey>(); SortedDictionary <int, ItemKey> oldSkills = new SortedDictionary <int, ItemKey>(); 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"), }; // Note: there are some events in common which can be used to detect skills which are already granted by emevd. // But they all have a text of -1 so it's unnecessary to scan emevd for this. // For example: // Initialize Event (Event Slot ID: 4, Event ID: 450, Parameters: 6719){, 3, 2450, 620} // Initialize Event (Event Slot ID: 0, Event ID: 460, Parameters: 6710){, 2470, 600} PARAM.Row baseGood = game.Params["EquipParamGoods"][2470]; int baseId = 6405; FMG itemName = game.ItemFMGs["アイテム名"]; FMG itemDesc = game.ItemFMGs["アイテム説明"]; FMG weaponName = game.ItemFMGs["武器名"]; FMG weaponDesc = game.ItemFMGs["武器説明"]; SortedDictionary <ItemKey, string> gameNames = game.Names(); bool explain = false; HashSet <int> copiedWeapons = new HashSet <int>(); foreach (PARAM.Row r in game.Params["SkillParam"].Rows) { int skillId = (int)r.ID; if (skillId >= 700) { continue; } int text = (byte)r["Unk7"].Value; if (!texts.ContainsKey(text)) { continue; } int descItem = (int)r["SkilLDescriptionId"].Value; if (copiedWeapons.Contains(descItem)) { continue; } copiedWeapons.Add(descItem); PARAM.Row weaponRow = game.Params["EquipParamWeapon"][descItem]; int sortId = (int)weaponRow["sortId"].Value; short iconId = (short)weaponRow["iconId"].Value; int good = baseId++; PARAM.Row newGood = game.AddRow("EquipParamGoods", good); GameEditor.CopyRow(baseGood, newGood); ItemKey goodKey = new ItemKey(ItemType.GOOD, good); newSkills[skillId] = goodKey; gameNames[goodKey] = weaponName[descItem]; itemName[good] = weaponName[descItem]; itemDesc[good] = weaponDesc[descItem]; newGood["sortId"].Value = sortId; newGood["iconId"].Value = iconId; // These should be set in base row, but do this just in case // Don't show up in inventory newGood["goodsType"].Value = (byte)7; // But pop up on acquisition newGood["Unk20"].Value = (byte)6; if (explain) { Console.WriteLine($"-- {r.ID} -> {good}: {descItem}, {weaponName[descItem]}"); } ret.Assign[new ItemKey(ItemType.WEAPON, descItem)] = goodKey; } game.Params["EquipParamGoods"].Rows.Sort((a, b) => a.ID.CompareTo(b.ID)); // Second, add event scripting to grant skills, with new common_func EMEVD common = game.Emevds["common"]; int grantId = 11615600; EMEVD.Event grantEv = new EMEVD.Event(grantId); List <string> grantCmds = new List <string> { "IF Player Has/Doesn't Have Item (0,3,X0_4,1)", "Grant Skill (X4_4)" }; for (int i = 0; i < grantCmds.Count; i++) { (EMEVD.Instruction ins, List <EMEVD.Parameter> ps) = events.ParseAddArg(grantCmds[i], i); grantEv.Instructions.Add(ins); grantEv.Parameters.AddRange(ps); } common.Events.Add(grantEv); int slot = 0; foreach (KeyValuePair <int, ItemKey> entry in newSkills) { common.Events[0].Instructions.Add(new EMEVD.Instruction(2000, 0, new List <object> { slot++, grantId, entry.Value.ID, entry.Key })); } // Third, edit drops // Remove text drops ann.ItemGroups["remove"].AddRange(texts.Values); // Add skill drops foreach (ItemKey item in newSkills.Values) { data.AddLocationlessItem(item); } // Copy restrictions from the weapons over to the goods ann.CopyRestrictions(ret.Assign); foreach (KeyValuePair <ItemKey, ItemKey> entry in ret.Assign) { ItemKey weapon = entry.Key; ItemKey good = entry.Value; // Mikiri Counter in hint log if (weapon.ID == 200300) { ann.ItemGroups["upgradehints"].Add(good); } // Carp scalesmen if (!ann.ExcludeTags.ContainsKey(weapon)) { ann.ItemGroups["premium"].Add(good); } } // For balancing Dancing Dragon Mask, greatly reduce enemy xp drops // All NG bosses together give 93k XP. This gives enough for 45 skill points. // So allow 15 levels/3 AP upgrades, or 9 XP. (Next threshhold: 4 AP upgrades, 13k XP) // (Or not, since only got 9 levels in a full run, just double it.) foreach (PARAM.Row row in game.Params["NpcParam"].Rows) { row["Experience"].Value = (int)row["Experience"].Value / 5; } foreach (PARAM.Row row in game.Params["GameAreaParam"].Rows) { row["BonusExperience"].Value = (int)row["BonusExperience"].Value / 5; } // Also in this mode, acquire skills option is removed from Sculptor's Idols, in case it has been there from previous runs. Done in PermutationWriter. return(ret); }
private void LoadText() { if (Sekiro) { ItemFMGs = Editor.LoadBnd($@"{dir}\Base\item.msgbnd.dcx", (data, path) => FMG.Read(data)); ItemFMGs = MaybeOverrideFromModDir(ItemFMGs, @"msg\engus\item.msgbnd.dcx", path => Editor.LoadBnd(path, (data, path2) => FMG.Read(data))); MenuFMGs = Editor.LoadBnd($@"{dir}\Base\menu.msgbnd.dcx", (data, path) => FMG.Read(data)); MenuFMGs = MaybeOverrideFromModDir(MenuFMGs, @"msg\engus\menu.msgbnd.dcx", path => Editor.LoadBnd(path, (data, path2) => FMG.Read(data))); foreach (string lang in MiscSetup.Langs) { if (lang == "engus") { continue; } OtherItemFMGs[lang] = Editor.LoadBnd($@"{dir}\Base\msg\{lang}\item.msgbnd.dcx", (data, path) => FMG.Read(data)); OtherItemFMGs[lang] = MaybeOverrideFromModDir(OtherItemFMGs[lang], $@"msg\{lang}\item.msgbnd.dcx", path => Editor.LoadBnd(path, (data, path2) => FMG.Read(data))); } } else { ItemFMGs = Editor.LoadBnd($@"{dir}\Base\item_dlc2.msgbnd.dcx", (data, path) => FMG.Read(data)); ItemFMGs = MaybeOverrideFromModDir(ItemFMGs, @"msg\engus\item_dlc2.msgbnd.dcx", path => Editor.LoadBnd(path, (data, path2) => FMG.Read(data))); } }