private static NgParameterRange DetectPattern(NgParameterRange parameter, NgBlock dbgBlock) { if (parameter.Kind != NgParameterKind.FixedEnumeration) { return(parameter); } // Parse the numbers inside the strings. var unmappedEnums = new List <Parsed>(); foreach (var entry in parameter.FixedEnumeration) { unmappedEnums.Add(new Parsed { Original = entry.Value, Substrings = SplitIntoNumbers(entry.Value.Name) }); } unmappedEnums.Sort(); var outputChoice = new List <NgParameterRange>(); while (DetectLinearity(outputChoice, unmappedEnums, dbgBlock)) { } return(new NgParameterRange(outputChoice)); }
private static NgParameterRange GetList(NgBlock block) { if (block.Items.Count == 1 && block.Items[0].StartsWith("#")) { // Special list var list = block.Items[0]; // Dynamic list? if (list == "#ROOMS_255#") { return(new NgParameterRange(NgParameterKind.Rooms255)); } else if (list == "#SOUND_EFFECT_A#") { return(new NgParameterRange(NgParameterKind.SoundEffectsA)); } else if (list == "#SOUND_EFFECT_B#") { return(new NgParameterRange(NgParameterKind.SoundEffectsB)); } else if (list == "#SFX_1024#") { return(new NgParameterRange(NgParameterKind.Sfx1024)); } else if (list == "#NG_STRING_LIST_255#") { return(new NgParameterRange(NgParameterKind.NgStringsList255)); } else if (list == "#NG_STRING_LIST_ALL#") { return(new NgParameterRange(NgParameterKind.NgStringsAll)); } else if (list == "#PSX_STRING_LIST#") { return(new NgParameterRange(NgParameterKind.PsxStringsList)); } else if (list == "#PC_STRING_LIST#") { return(new NgParameterRange(NgParameterKind.PcStringsList)); } else if (list == "#STRING_LIST_255#") { return(new NgParameterRange(NgParameterKind.StringsList255)); } else if (list == "#MOVEABLES#") { return(new NgParameterRange(NgParameterKind.MoveablesInLevel)); } else if (list == "#SINK_LIST#") { return(new NgParameterRange(NgParameterKind.SinksInLevel)); } else if (list == "#STATIC_LIST#") { return(new NgParameterRange(NgParameterKind.StaticsInLevel)); } else if (list == "#FLYBY_LIST#") { return(new NgParameterRange(NgParameterKind.FlybyCamerasInLevel)); } else if (list == "#CAMERA_EFFECTS#") { return(new NgParameterRange(NgParameterKind.CamerasInLevel)); } else if (list == "#WAD-SLOTS#" || list == "#LARA_ANIM_SLOT#") { return(new NgParameterRange(NgParameterKind.WadSlots)); } else if (list == "#STATIC_SLOTS#") { return(new NgParameterRange(NgParameterKind.StaticsSlots)); } else if (list == "#LARA_POS_OCB#") { return(new NgParameterRange(NgParameterKind.LaraStartPosOcb)); } // Repeated strings if (list.StartsWith("#REPEAT#")) { var tokens = list.Replace("#REPEAT#", "").Split('#'); var radix = tokens[0].Replace("\"", ""); var start = int.Parse(tokens[1].Replace(",", "")); var end = int.Parse(tokens[2].Replace(",", "")); var enumeration = new SortedList <ushort, TriggerParameterUshort>(); for (var i = start; i < end; i++) { enumeration.Add(ToU16(i), new TriggerParameterUshort((ushort)i, radix + i)); } return(DetectPattern(new NgParameterRange(enumeration), block)); } // TXT lists var listName = list.Replace("#", ""); if (File.Exists("NG\\" + listName + ".txt")) { return(DetectPattern(new NgParameterRange(GetListFromTxt(listName)), block)); } else { throw new Exception("Missing NG file"); } } else { // Free list var enumeration = new SortedList <ushort, TriggerParameterUshort>(); foreach (var item in block.Items) { enumeration.Add(GetItemId(item), new TriggerParameterUshort(GetItemId(item), GetItemValue(item))); } return(DetectPattern(new NgParameterRange(enumeration), block)); } }
private static bool DetectLinearity(List <NgParameterRange> outChoice, List <Parsed> unmappedEnumsSorted, NgBlock dbgBlock) { if (unmappedEnumsSorted.Count <= 5) // It's not worth it if there are at most 5 elements { AddFixed(outChoice, unmappedEnumsSorted.Select(p => p.Original)); unmappedEnumsSorted.Clear(); return(false); } Parsed firstEnum = unmappedEnumsSorted.First(); ushort idStart = firstEnum.Original.Key; int count = 1; // Stop if there is a varying amount of numbers in the strings or if different values are fixed. for (int i = 1; i < unmappedEnumsSorted.Count; ++i) { Parsed entry = unmappedEnumsSorted[i]; // Don't compress certain strings if (entry.Original.Name.StartsWith("PUZZLE_ITEM", StringComparison.InvariantCulture) || entry.Original.Name.StartsWith("KEY_ITEM", StringComparison.InvariantCulture) || entry.Original.Name.StartsWith("Send command for ", StringComparison.InvariantCulture) || entry.Original.Name.Length == 2 && entry.Original.Name.StartsWith("F", StringComparison.InvariantCulture) || // F keys entry.Original.Name.Length == 7 && entry.Original.Name.StartsWith("Number", StringComparison.InvariantCulture)) // Number keys { break; } if (entry.Substrings.Count != firstEnum.Substrings.Count) { break; } for (int j = 0; j < entry.Substrings.Count; ++j) { if (entry.Substrings[j].Value != null != (firstEnum.Substrings[j].Value != null)) { goto ExitLoop0; } } for (int j = 0; j < entry.Substrings.Count; ++j) { if (entry.Substrings[j].Value == null) { if (entry.Substrings[j].String != firstEnum.Substrings[j].String) { goto ExitLoop0; } } } // Check if the run is linear // There can't be missing keys. if (idStart + count != entry.Original.Key) { break; } ++count; } ExitLoop0: if (count <= 5) // It's not worth it if there are at most 5 elements { AddFixed(outChoice, unmappedEnumsSorted.Take(count).Select(p => p.Original)); unmappedEnumsSorted.RemoveRange(0, count); return(true); } // String format now is perfectly fine. // We just have to find some kind of linear pattern Parsed secondEnum = unmappedEnumsSorted[1]; List <NgLinearParameter> linearParameters = new List <NgLinearParameter>(firstEnum.Substrings.Count); for (int i = 0; i < firstEnum.Substrings.Count; ++i) { if (firstEnum.Substrings[i].Value == null) { linearParameters.Add(new NgLinearParameter { FixedStr = firstEnum.Substrings[i].String }); } else { decimal xDistance = secondEnum.Original.Key - firstEnum.Original.Key; decimal yDistance = secondEnum.Substrings[i].Value.Value - firstEnum.Substrings[i].Value.Value; decimal factor = yDistance / xDistance; decimal add = firstEnum.Substrings[i].Value.Value - firstEnum.Original.Key * factor; linearParameters.Add(new NgLinearParameter { Factor = factor, Add = add }); } } // Eliminate unnecessary linear parameters // (i.e. linear with 0 slope) for (int i = 0; i < linearParameters.Count; ++i) { if (linearParameters[i].FixedStr != null) { if (linearParameters[i].Factor == 0) { linearParameters[i] = new NgLinearParameter { FixedStr = firstEnum.Substrings[i].String } } } } ; for (int i = linearParameters.Count - 1; i >= 1; --i) { if (linearParameters[i].FixedStr != null && linearParameters[i - 1].FixedStr != null) { linearParameters[i - 1] = new NgLinearParameter { FixedStr = linearParameters[i - 1].FixedStr + linearParameters[i].FixedStr }; linearParameters.RemoveAt(i); for (int j = 0; j < count; ++j) { unmappedEnumsSorted[j].Substrings.RemoveAt(i); } } } // Check that the linear pattern is indeed a good fit for (int i = 0; i < count; ++i) { var @enum = unmappedEnumsSorted[i]; for (int j = 0; j < linearParameters.Count; ++j) { if (linearParameters[j].FixedStr == null) { // Calculate prediction with the linear model decimal value = linearParameters[j].Factor * @enum.Original.Key + linearParameters[j].Add; // Check prediction if (@enum.Substrings[j].Value.Value != value) { count = i; goto ExitLoop1; } } } } ExitLoop1: if (count < 2 + linearParameters.Count * 2) // It's not worth it if there are at most 5 elements { AddFixed(outChoice, unmappedEnumsSorted.Take(count).Select(p => p.Original)); unmappedEnumsSorted.RemoveRange(0, count); return(true); } outChoice.Add(new NgParameterRange(new NgLinearModel { Start = idStart, EndInclusive = unchecked ((ushort)(idStart + count - 1)), Parameters = linearParameters })); unmappedEnumsSorted.RemoveRange(0, count); return(true); }
private static NgBlock ReadNgBlock(StreamReader reader) { string line = ""; while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); if (line.StartsWith("<START")) { break; } } if (reader.EndOfStream) { return(null); } var tokens = line.Split('_'); var block = new NgBlock(); // Block type if (tokens[1] == "TRIGGERWHAT" || tokens[1] == "TRIGGERTYPE" || tokens[1] == "TEXTS") { block.Type = NgBlockType.Trigger; } else if (tokens[1] == "EFFECT") { block.Type = NgBlockType.FlipEffect; } else if (tokens[1] == "ACTION") { block.Type = NgBlockType.Action; } else if (tokens[1] == "CONDITION") { block.Type = NgBlockType.Condition; } else { throw new Exception("Unknown token[1]"); } // Parameter type if (tokens[3].StartsWith("O")) { block.ParameterType = NgParameterType.Object; } else if (tokens[3].StartsWith("E")) { block.ParameterType = NgParameterType.Extra; } else if (tokens[3].StartsWith("T")) { block.ParameterType = NgParameterType.Timer; } else if (tokens[3].StartsWith("B")) { block.ParameterType = NgParameterType.Extra; } else { throw new Exception("Unknown token[3]"); } block.Id = ToU16(int.Parse(tokens[2])); while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); if (line.StartsWith(";") || line == "") { continue; } if (line.StartsWith("<END")) { break; } block.Items.Add(AdjustCardinalDirections(line)); } return(block); }