From b8d2dc46451e5b8084a21c4218a42f3d8257acac Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 Nov 2019 13:44:59 -0500 Subject: [PATCH 1/7] Trainer Pokemon Team Support Well it's not pretty and it's not well tested, but the first phase of trainer pokemon teams is complete! The trainer pokemon team (TPT) is both a table and a stream: it's a table of pokemon, but it's also editable as a stream. This lets me show a trainer's pokemon as part of the same table as the trainer, but you can dive down for table support. --- src/HexManiac.Core/HexManiac.Core.csproj | 1 + src/HexManiac.Core/Models/AutoSearchModel.cs | 4 +- .../Models/HardcodeTablesModel.cs | 11 +- src/HexManiac.Core/Models/IDataModel.cs | 45 +-- src/HexManiac.Core/Models/PokemonModel.cs | 16 +- src/HexManiac.Core/Models/Runs/ArrayRun.cs | 93 ++++--- .../Models/Runs/ArrayRunElementSegment.cs | 11 +- .../Models/Runs/TrainerPokemonTeamRun.cs | 259 ++++++++++++++++++ src/HexManiac.Core/SystemExtensions.cs | 2 +- .../QuickEditItems/MakeTmsExpandable.cs | 2 +- .../Tools/IArrayElementViewModel.cs | 6 +- .../ViewModels/Tools/PCSTool.cs | 2 +- .../ViewModels/Tools/TableTool.cs | 42 +-- src/HexManiac.Core/ViewModels/ViewPort.cs | 2 +- .../Visitors/CompleteEditOperation.cs | 2 +- .../ViewModels/Visitors/ContextItemFactory.cs | 14 +- 16 files changed, 407 insertions(+), 105 deletions(-) create mode 100644 src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs diff --git a/src/HexManiac.Core/HexManiac.Core.csproj b/src/HexManiac.Core/HexManiac.Core.csproj index 4b48dca3..597219fc 100644 --- a/src/HexManiac.Core/HexManiac.Core.csproj +++ b/src/HexManiac.Core/HexManiac.Core.csproj @@ -59,6 +59,7 @@ + diff --git a/src/HexManiac.Core/Models/AutoSearchModel.cs b/src/HexManiac.Core/Models/AutoSearchModel.cs index d5136de0..975d6fd7 100644 --- a/src/HexManiac.Core/Models/AutoSearchModel.cs +++ b/src/HexManiac.Core/Models/AutoSearchModel.cs @@ -110,10 +110,10 @@ namespace HavenSoft.HexManiac.Core.Models { var pokenames = GetNextRun(GetAddressFromAnchor(noChangeDelta, -1, EggMoveRun.PokemonNameTable)) as ArrayRun; if (pokenames != null && TrySearch(this, noChangeDelta, format, out var pokestatdata, run => run.PointerSources.Count > 5)) { if (pokestatdata.ElementCount < pokenames.ElementCount) { - pokenames = pokenames.Append(pokestatdata.ElementCount - pokenames.ElementCount); + pokenames = pokenames.Append(noChangeDelta, pokestatdata.ElementCount - pokenames.ElementCount); ObserveAnchorWritten(noChangeDelta, EggMoveRun.PokemonNameTable, pokenames); } else if (pokestatdata.ElementCount > pokenames.ElementCount) { - pokestatdata = pokestatdata.Append(pokenames.ElementCount - pokestatdata.ElementCount); + pokestatdata = pokestatdata.Append(noChangeDelta, pokenames.ElementCount - pokestatdata.ElementCount); } ObserveAnchorWritten(noChangeDelta, "pokestats", pokestatdata); diff --git a/src/HexManiac.Core/Models/HardcodeTablesModel.cs b/src/HexManiac.Core/Models/HardcodeTablesModel.cs index ff55d6f0..284c34ce 100644 --- a/src/HexManiac.Core/Models/HardcodeTablesModel.cs +++ b/src/HexManiac.Core/Models/HardcodeTablesModel.cs @@ -13,6 +13,7 @@ namespace HavenSoft.HexManiac.Core.Models { /// Lengths of some tables are still calculated dynamically based on best-fit, so operations like adding pokemon from a separate tool should still be picked up correctly. /// public class HardcodeTablesModel : PokemonModel { + public const string ItemsTableName = "items"; private readonly string gameCode; private readonly ModelDelta noChangeDelta = new NoDataChangeDeltaModel(); @@ -111,7 +112,7 @@ namespace HavenSoft.HexManiac.Core.Models { case FireRed: case LeafGreen: case Emerald: source = 0x0001BC; break; case Ruby: case Sapphire: source = 0x010B64; break; } - var format = $"[hp. attack. def. speed. spatk. spdef. type1.types type2.types catchRate. baseExp. evs: item1:items item2:items genderratio. steps2hatch. basehappiness. growthrate. egg1. egg2. ability1.abilitynames ability2.abilitynames runrate. unknown. padding:]{EggMoveRun.PokemonNameTable}"; + var format = $"[hp. attack. def. speed. spatk. spdef. type1.types type2.types catchRate. baseExp. evs: item1:{ItemsTableName} item2:{ItemsTableName} genderratio. steps2hatch. basehappiness. growthrate. egg1. egg2. ability1.abilitynames ability2.abilitynames runrate. unknown. padding:]{EggMoveRun.PokemonNameTable}"; AddTable(source, "pokestats", format); // items @@ -120,7 +121,7 @@ namespace HavenSoft.HexManiac.Core.Models { case Ruby: case Sapphire: source = 0x0A98F0; break; } format = $"[name\"\"14 index: price: holdeffect: description<{PCSRun.SharedFormatString}> keyitemvalue. bagkeyitem. pocket. type. fieldeffect<> battleusage:: battleeffect<> battleextra::]"; - AddTable(source, "items", format); + AddTable(source, ItemsTableName, format); // movedata switch (gameCode) { @@ -184,7 +185,7 @@ namespace HavenSoft.HexManiac.Core.Models { case Ruby: case Sapphire: source = -1; break; } source = GetNextRun(source).Start; - AddTable(source, "itemimages", "[image<> palette<>]items"); + AddTable(source, "itemimages", $"[image<> palette<>]{ItemsTableName}"); // trainer teams switch (gameCode) { @@ -192,7 +193,7 @@ namespace HavenSoft.HexManiac.Core.Models { case Emerald: source = 0x03587C; break; case Ruby: case Sapphire: source = 0x00D890; break; } - AddTable(source, "trainerdata", "[structType.4 class.trainerclassnames introMusic. sprite. name\"\"12 item1:items item2:items item3:items item4:items doubleBattle:: ai:: pokemonCount:: pokemon<>]"); + AddTable(source, "trainerdata", $"[structType.4 class.trainerclassnames introMusic. sprite. name\"\"12 item1:{ItemsTableName} item2:{ItemsTableName} item3:{ItemsTableName} item4:{ItemsTableName} doubleBattle:: ai:: pokemonCount:: pokemon<`tpt`>]"); } private void DecodeStreams() { @@ -223,7 +224,7 @@ namespace HavenSoft.HexManiac.Core.Models { if (interruptingRun.Start < destination && interruptingRun is ArrayRun array) { var elementLength = array.ElementLength; var elementCount = (destination - array.Start) / array.ElementLength; - array = array.Append(elementCount - array.ElementCount); + array = array.Append(noChangeDelta, elementCount - array.ElementCount); ObserveAnchorWritten(noChangeDelta, GetAnchorFromAddress(-1, array.Start), array); } diff --git a/src/HexManiac.Core/Models/IDataModel.cs b/src/HexManiac.Core/Models/IDataModel.cs index 7e2ece9f..e0e585a7 100644 --- a/src/HexManiac.Core/Models/IDataModel.cs +++ b/src/HexManiac.Core/Models/IDataModel.cs @@ -30,7 +30,7 @@ namespace HavenSoft.HexManiac.Core.Models { bool TryGetUsefulHeader(int address, out string header); - bool IsAtEndOfArray(int dataIndex, out ArrayRun arrayRun); // is this byte the first one after the end of an array run? (also return true if the array is length 0 and starts right here) + bool IsAtEndOfArray(int dataIndex, out ITableRun tableRun); // is this byte the first one after the end of a table run? (also return true if the table is length 0 and starts right here) void ObserveRunWritten(ModelDelta changeToken, IFormattedRun run); void ObserveAnchorWritten(ModelDelta changeToken, string anchorName, IFormattedRun run); @@ -100,7 +100,7 @@ namespace HavenSoft.HexManiac.Core.Models { public virtual bool TryGetUsefulHeader(int address, out string header) { header = null; return false; } - public abstract bool IsAtEndOfArray(int dataIndex, out ArrayRun arrayRun); + public abstract bool IsAtEndOfArray(int dataIndex, out ITableRun tableRun); public virtual void Load(byte[] newData, StoredMetadata metadata) => RawData = newData; @@ -248,27 +248,30 @@ namespace HavenSoft.HexManiac.Core.Models { return format; } - public static ErrorInfo CompleteArrayExtension(this IDataModel model, ModelDelta changeToken, ref ArrayRun arrayRun) { - var currentArrayName = model.GetAnchorFromAddress(-1, arrayRun.Start); + public static ErrorInfo CompleteArrayExtension(this IDataModel model, ModelDelta changeToken, ref ITableRun table) { + var currentArrayName = model.GetAnchorFromAddress(-1, table.Start); - var visitedNames = new List() { model.GetAnchorFromAddress(-1, arrayRun.Start) }; - var visitedAddress = new List() { arrayRun.Start }; + var visitedNames = new List() { model.GetAnchorFromAddress(-1, table.Start) }; + var visitedAddress = new List() { table.Start }; - while (arrayRun.LengthFromAnchor != string.Empty) { - if (visitedNames.Contains(arrayRun.LengthFromAnchor)) { - // We kept going up the chain of tables but didn't find a top table. table length definitions are circular. - return new ErrorInfo($"Could not extend table safely. Table length has a circular dependency involving {arrayRun.LengthFromAnchor}."); + if (table is ArrayRun arrayRun) { + while (arrayRun.LengthFromAnchor != string.Empty) { + if (visitedNames.Contains(arrayRun.LengthFromAnchor)) { + // We kept going up the chain of tables but didn't find a top table. table length definitions are circular. + return new ErrorInfo($"Could not extend table safely. Table length has a circular dependency involving {arrayRun.LengthFromAnchor}."); + } + + var address = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, arrayRun.LengthFromAnchor); + visitedNames.Add(arrayRun.LengthFromAnchor); + visitedAddress.Add(address); + arrayRun = (ArrayRun)model.GetNextRun(address); } - - var address = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, arrayRun.LengthFromAnchor); - visitedNames.Add(arrayRun.LengthFromAnchor); - visitedAddress.Add(address); - arrayRun = (ArrayRun)model.GetNextRun(address); + table = arrayRun; } - ExtendArrayAndChildren(model, changeToken, arrayRun); + ExtendTableAndChildren(model, changeToken, table); - arrayRun = (ArrayRun)model.GetNextRun(model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, currentArrayName)); + table = (ITableRun)model.GetNextRun(model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, currentArrayName)); var changedNames = new List(); for (int i = 0; i < visitedNames.Count; i++) { @@ -281,9 +284,9 @@ namespace HavenSoft.HexManiac.Core.Models { return new ErrorInfo($"Tables {all} were moved. Pointers have been updated.", isWarningLevel: true); } - private static void ExtendArrayAndChildren(IDataModel model, ModelDelta changeToken, ArrayRun array) { - var newRun = (ArrayRun)model.RelocateForExpansion(changeToken, array, array.Length + array.ElementLength); - newRun = newRun.Append(1); + private static void ExtendTableAndChildren(IDataModel model, ModelDelta changeToken, ITableRun array) { + var newRun = (ITableRun)model.RelocateForExpansion(changeToken, array, array.Length + array.ElementLength); + newRun = newRun.Append(changeToken, 1); model.ObserveRunWritten(changeToken, newRun); } @@ -311,7 +314,7 @@ namespace HavenSoft.HexManiac.Core.Models { public override string GetAnchorFromAddress(int requestSource, int destination) => string.Empty; public override IFormattedRun GetNextRun(int dataIndex) => NoInfoRun.NullRun; public override IFormattedRun GetNextAnchor(int dataIndex) => NoInfoRun.NullRun; - public override bool IsAtEndOfArray(int dataIndex, out ArrayRun arrayRun) { arrayRun = null; return false; } + public override bool IsAtEndOfArray(int dataIndex, out ITableRun tableRun) { tableRun = null; return false; } public override void ObserveRunWritten(ModelDelta changeToken, IFormattedRun run) { } public override void ObserveAnchorWritten(ModelDelta changeToken, string anchorName, IFormattedRun run) { } public override void MassUpdateFromDelta(IReadOnlyDictionary runsToRemove, IReadOnlyDictionary runsToAdd, IReadOnlyDictionary namesToRemove, IReadOnlyDictionary namesToAdd, IReadOnlyDictionary unmappedPointersToRemove, IReadOnlyDictionary unmappedPointersToAdd, IReadOnlyDictionary matchedWordsToRemove, IReadOnlyDictionary matchedWordsToAdd) { } diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs index c52de5b6..85b6918a 100644 --- a/src/HexManiac.Core/Models/PokemonModel.cs +++ b/src/HexManiac.Core/Models/PokemonModel.cs @@ -394,10 +394,10 @@ namespace HavenSoft.HexManiac.Core.Models { return true; } - public override bool IsAtEndOfArray(int dataIndex, out ArrayRun arrayRun) { + public override bool IsAtEndOfArray(int dataIndex, out ITableRun arrayRun) { var index = BinarySearch(dataIndex); if (index >= 0 && runs[index].Length == 0) { - arrayRun = runs[index] as ArrayRun; + arrayRun = runs[index] as ITableRun; return arrayRun != null; } @@ -409,7 +409,7 @@ namespace HavenSoft.HexManiac.Core.Models { return false; } - arrayRun = runs[index] as ArrayRun; + arrayRun = runs[index] as ITableRun; return arrayRun != null && runs[index].Start + runs[index].Length == dataIndex; } @@ -531,7 +531,7 @@ namespace HavenSoft.HexManiac.Core.Models { if (anchor.Equals(table.LengthFromAnchor)) { if (arrayRun.ElementCount == table.ElementCount) continue; newTable = (ArrayRun)RelocateForExpansion(changeToken, table, arrayRun.ElementCount * table.ElementLength); - newTable = newTable.Append(arrayRun.ElementCount - table.ElementCount); + newTable = newTable.Append(changeToken, arrayRun.ElementCount - table.ElementCount); ObserveRunWritten(changeToken, newTable); } // option 2: this table includes a bit-array based on the given table @@ -636,6 +636,10 @@ namespace HavenSoft.HexManiac.Core.Models { run = runAttempt.MergeAnchor(run.PointerSources); ClearFormat(token, run.Start, run.Length); } + } else if (segment.InnerFormat == TrainerPokemonTeamRun.SharedFormatString) { + var runAttempt = new TrainerPokemonTeamRun(this, run.Start, run.PointerSources); + ClearFormat(token, run.Start, run.Length); + run = runAttempt; } else { throw new NotImplementedException(); } @@ -1239,6 +1243,8 @@ namespace HavenSoft.HexManiac.Core.Models { private static ErrorInfo TryParseFormat(IDataModel model, string format, int dataIndex, out IFormattedRun run) { run = new NoInfoRun(dataIndex); + var existingRun = model.GetNextRun(dataIndex); + if (existingRun.Start == run.Start) run = run.MergeAnchor(existingRun.PointerSources); if (format == PCSRun.SharedFormatString) { var length = PCSString.ReadString(model, dataIndex, true); @@ -1259,6 +1265,8 @@ namespace HavenSoft.HexManiac.Core.Models { } else if (format == PLMRun.SharedFormatString) { run = new PLMRun(model, dataIndex); if (run.Length == 0) return new ErrorInfo("Format specified was for pokemon level-up move data, but could not parse that location as level-up move data."); + } else if (format == TrainerPokemonTeamRun.SharedFormatString) { + run = new TrainerPokemonTeamRun(model, dataIndex, run.PointerSources); } else { var errorInfo = TryParse(model, format, dataIndex, null, out var arrayRun); if (errorInfo == ErrorInfo.NoError) { diff --git a/src/HexManiac.Core/Models/Runs/ArrayRun.cs b/src/HexManiac.Core/Models/Runs/ArrayRun.cs index 112f5069..4489886c 100644 --- a/src/HexManiac.Core/Models/Runs/ArrayRun.cs +++ b/src/HexManiac.Core/Models/Runs/ArrayRun.cs @@ -5,6 +5,54 @@ using System.Linq; using System.Text; namespace HavenSoft.HexManiac.Core.Models.Runs { + public interface ITableRun : IFormattedRun { + int ElementCount { get; } + int ElementLength { get; } + IReadOnlyList ElementNames { get; } + IReadOnlyList ElementContent { get; } + ITableRun Append(ModelDelta token, int length); + } + + public static class ITableRunExtensions { + public static ArrayOffset ConvertByteOffsetToArrayOffset(this ITableRun self, int byteOffset) { + var offset = byteOffset - self.Start; + int elementIndex = offset / self.ElementLength; + int elementOffset = offset % self.ElementLength; + int segmentIndex = 0, segmentOffset = elementOffset; + while (self.ElementContent[segmentIndex].Length <= segmentOffset) { + segmentOffset -= self.ElementContent[segmentIndex].Length; segmentIndex++; + } + return new ArrayOffset(elementIndex, segmentIndex, byteOffset - segmentOffset, segmentOffset); + } + + public static IDataFormat CreateSegmentDataFormat(this ITableRun self, IDataModel data, int index) { + var offsets = self.ConvertByteOffsetToArrayOffset(index); + var currentSegment = self.ElementContent[offsets.SegmentIndex]; + var position = index - offsets.SegmentStart; + if (currentSegment.Type == ElementContentType.Integer) { + if (currentSegment is ArrayRunEnumSegment enumSegment) { + var value = enumSegment.ToText(data, index); + return new IntegerEnum(offsets.SegmentStart, position, value, currentSegment.Length); + } else { + var value = ArrayRunElementSegment.ToInteger(data, offsets.SegmentStart, currentSegment.Length); + return new Integer(offsets.SegmentStart, position, value, currentSegment.Length); + } + } + + if (currentSegment.Type == ElementContentType.Pointer) { + var destination = data.ReadPointer(offsets.SegmentStart); + var destinationName = data.GetAnchorFromAddress(offsets.SegmentStart, destination); + return new Pointer(offsets.SegmentStart, position, destination, destinationName); + } + + if (currentSegment.Type == ElementContentType.BitArray) { + return new BitArray(offsets.SegmentStart, position, currentSegment.Length); + } + + throw new NotImplementedException(); + } + } + public class ArrayOffset { /// /// Ranges from 0 to ElementCount @@ -30,7 +78,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { } } - public class ArrayRun : BaseRun { + public class ArrayRun : BaseRun, ITableRun { public const char ExtendArray = '+'; public const char ArrayStart = '['; public const char ArrayEnd = ']'; @@ -291,7 +339,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { private string cachedCurrentString; private int currentCachedStartIndex = -1, currentCachedIndex = -1; public override IDataFormat CreateDataFormat(IDataModel data, int index) { - var offsets = ConvertByteOffsetToArrayOffset(index); + var offsets = this.ConvertByteOffsetToArrayOffset(index); var currentSegment = ElementContent[offsets.SegmentIndex]; if (currentSegment.Type == ElementContentType.PCS) { if (currentCachedStartIndex != offsets.SegmentStart || currentCachedIndex > offsets.SegmentOffset) { @@ -303,42 +351,11 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { return PCSRun.CreatePCSFormat(data, offsets.SegmentStart, index, cachedCurrentString); } - var position = index - offsets.SegmentStart; - if (currentSegment.Type == ElementContentType.Integer) { - if (currentSegment is ArrayRunEnumSegment enumSegment) { - var value = enumSegment.ToText(data, index); - return new IntegerEnum(offsets.SegmentStart, position, value, currentSegment.Length); - } else { - var value = ArrayRunElementSegment.ToInteger(data, offsets.SegmentStart, currentSegment.Length); - return new Integer(offsets.SegmentStart, position, value, currentSegment.Length); - } - } - - if (currentSegment.Type == ElementContentType.Pointer) { - var destination = data.ReadPointer(offsets.SegmentStart); - var destinationName = data.GetAnchorFromAddress(offsets.SegmentStart, destination); - return new Pointer(offsets.SegmentStart, position, destination, destinationName); - } - - if (currentSegment.Type == ElementContentType.BitArray) { - return new BitArray(offsets.SegmentStart, position, currentSegment.Length); - } - - throw new NotImplementedException(); + return this.CreateSegmentDataFormat(data, index); } - public ArrayOffset ConvertByteOffsetToArrayOffset(int byteOffset) { - var offset = byteOffset - Start; - int elementIndex = offset / ElementLength; - int elementOffset = offset % ElementLength; - int segmentIndex = 0, segmentOffset = elementOffset; - while (ElementContent[segmentIndex].Length <= segmentOffset) { - segmentOffset -= ElementContent[segmentIndex].Length; segmentIndex++; - } - return new ArrayOffset(elementIndex, segmentIndex, byteOffset - segmentOffset, segmentOffset); - } - - public ArrayRun Append(int elementCount) { + ITableRun ITableRun.Append(ModelDelta token, int elementCount) => Append(token, elementCount); + public ArrayRun Append(ModelDelta token, int elementCount) { var lastArrayCharacterIndex = FormatString.LastIndexOf(ArrayEnd); var newFormat = FormatString.Substring(0, lastArrayCharacterIndex + 1); if (newFormat != FormatString) newFormat += ElementCount + elementCount; @@ -404,7 +421,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { } public void AppendTo(IDataModel data, StringBuilder text, int start, int length) { - var offsets = ConvertByteOffsetToArrayOffset(start); + var offsets = this.ConvertByteOffsetToArrayOffset(start); length += offsets.SegmentOffset; for (int i = offsets.ElementIndex; i < ElementCount && length > 0; i++) { var offset = offsets.SegmentStart; @@ -618,7 +635,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { if (destination == Pointer.NULL) return true; if (0 > destination || destination > owner.Count) return false; if (segment is ArrayRunPointerSegment pointerSegment) { - if (!pointerSegment.DestinationDataMatchesPointerFormat(owner, new NoDataChangeDeltaModel(), destination)) return false; + if (!pointerSegment.DestinationDataMatchesPointerFormat(owner, new NoDataChangeDeltaModel(), start, destination)) return false; } return true; case ElementContentType.BitArray: diff --git a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs index d6511f19..c37b56ce 100644 --- a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs +++ b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs @@ -157,6 +157,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { get { if (InnerFormat == PCSRun.SharedFormatString) return true; if (InnerFormat == PLMRun.SharedFormatString) return true; + if (InnerFormat == TrainerPokemonTeamRun.SharedFormatString) return true; return false; } } @@ -165,7 +166,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { InnerFormat = innerFormat; } - public bool DestinationDataMatchesPointerFormat(IDataModel owner, ModelDelta token, int destination) { + public bool DestinationDataMatchesPointerFormat(IDataModel owner, ModelDelta token, int source, int destination) { if (destination == Pointer.NULL) return true; var run = owner.GetNextAnchor(destination); if (run.Start < destination) return false; @@ -187,11 +188,19 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { if (!(token is NoDataChangeDeltaModel)) owner.ObserveRunWritten(token, plmRun); return true; } + } else if (InnerFormat == TrainerPokemonTeamRun.SharedFormatString) { + var teamRun = new TrainerPokemonTeamRun(owner, destination, new[] { source }); + var length = teamRun.Length; + if (length >= 2) { + if (!(token is NoDataChangeDeltaModel)) owner.ObserveRunWritten(token, teamRun); + return true; + } } } else { // easy case: already have a useful format, just see if it matches if (InnerFormat == PCSRun.SharedFormatString) return run is PCSRun; if (InnerFormat == PLMRun.SharedFormatString) return run is PLMRun; + if (InnerFormat == TrainerPokemonTeamRun.SharedFormatString) return run is TrainerPokemonTeamRun; } return false; } diff --git a/src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs b/src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs new file mode 100644 index 00000000..21a415d6 --- /dev/null +++ b/src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs @@ -0,0 +1,259 @@ +using HavenSoft.HexManiac.Core.ViewModels.DataFormats; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace HavenSoft.HexManiac.Core.Models.Runs { + public class TrainerPokemonTeamRun : BaseRun, IStreamRun, ITableRun { + public const int TrainerFormat_StructTypeOffset = 0; + public const int TrainerFormat_PokemonCountOffset = 32; + public const int TrainerFormat_PointerOffset = 36; + + public static readonly string SharedFormatString = AsciiRun.StreamDelimeter + "tpt" + AsciiRun.StreamDelimeter; + + private const byte INCLUDE_MOVES = 1; + private const byte INCLUDE_ITEM = 2; + private readonly IDataModel model; + + public byte StructType { get; } + + public TrainerPokemonTeamRun(IDataModel model, int start, IReadOnlyList sources) : base(start, sources) { + this.model = model; + + // trainer format (abbreviated): + // 0 1 2 3 4-15 16 18 20 22 24 28 32 36 40 total + // [structType. class. introMusic. sprite. name\"\"12 item1: item2: item3: item4: doubleBattle:: ai:: pokemonCount:: pokemon<>] + if (sources.Count < 1) throw new ArgumentException("Trainer Pokemon Team streams must be pointed to!"); + var source = sources.First(); + StructType = model[source - TrainerFormat_PointerOffset]; + ElementCount = model[source - TrainerFormat_PointerOffset + TrainerFormat_PokemonCountOffset]; + + var segments = new List(); + segments.Add(new ArrayRunElementSegment("ivSpread", ElementContentType.Integer, 2)); + segments.Add(new ArrayRunElementSegment("level", ElementContentType.Integer, 2)); + segments.Add(new ArrayRunEnumSegment("mon", 2, EggMoveRun.PokemonNameTable)); + if ((StructType & INCLUDE_MOVES) != 0) { + segments.Add(new ArrayRunEnumSegment("move1", 2, EggMoveRun.MoveNamesTable)); + segments.Add(new ArrayRunEnumSegment("move2", 2, EggMoveRun.MoveNamesTable)); + segments.Add(new ArrayRunEnumSegment("move3", 2, EggMoveRun.MoveNamesTable)); + segments.Add(new ArrayRunEnumSegment("move4", 2, EggMoveRun.MoveNamesTable)); + } + if ((StructType & INCLUDE_ITEM) != 0) { + segments.Add(new ArrayRunEnumSegment("item", 2, HardcodeTablesModel.ItemsTableName)); + } else { + segments.Add(new ArrayRunElementSegment("padding", ElementContentType.Integer, 2)); + } + + ElementContent = segments; + ElementLength = ElementContent.Sum(segment => segment.Length); + } + + #region BaseRun + + public override int Length => ElementLength * ElementCount; + + public override string FormatString => SharedFormatString; + + public override IDataFormat CreateDataFormat(IDataModel data, int index) => this.CreateSegmentDataFormat(data, index); + + protected override BaseRun Clone(IReadOnlyList newPointerSources) => new TrainerPokemonTeamRun(model, Start, newPointerSources); + + #endregion + + #region ITableRun + + public int ElementCount { get; } + + public int ElementLength { get; } + + public IReadOnlyList ElementNames { get; } = new List(); + + public IReadOnlyList ElementContent { get; } + + public ITableRun Append(ModelDelta token, int length) { + var totalLength = ElementLength * (ElementCount + length); + var workingRun = this; + if (totalLength > workingRun.Length) workingRun = (TrainerPokemonTeamRun)model.RelocateForExpansion(token, workingRun, totalLength); + + // delete old elements + for (int i = -1; i >= length; i--) { + var start = workingRun.Start + workingRun.Length + i * ElementLength; + for (int j = 0; j < ElementLength; j++) token.ChangeData(model, start + j, 0xFF); + } + + // add new elements + for (int i = 0; i < length; i++) { + var start = workingRun.Start + workingRun.Length + i * ElementLength; + for (int j = 0; j < ElementLength; j++) token.ChangeData(model, start + j, 0x00); + } + + // update parent + var parent = workingRun.PointerSources[0] - TrainerFormat_PointerOffset; + model.WriteMultiByteValue(parent + TrainerFormat_PokemonCountOffset, 4, token, ElementCount + length); + return new TrainerPokemonTeamRun(model, workingRun.Start, workingRun.PointerSources); + } + + #endregion + + #region IStreamRun + + // example serialized pokemon: + // + // 50 Butterfree (31)@"Silk Powder" + // - "Stun Spore" + // - "Super Sonic" + // - "Aerial Ace" + // - "Silver Wind" + + public IStreamRun DeserializeRun(string content, ModelDelta token) { + var lines = content.Split(Environment.NewLine).Select(line => line.Trim()).ToArray(); + + // step 1: parse it into some data containers + var data = new TeamData(ModelCacheScope.GetCache(model), lines); + + // step 2: figure out what I need based on the data + var elementLength = data.MovesIncluded ? 16 : 8; + var totalLength = elementLength * data.Pokemon.Count; + var workingRun = this; + if (totalLength > workingRun.Length) workingRun = (TrainerPokemonTeamRun)model.RelocateForExpansion(token, workingRun, totalLength); + + // step 3: write the run data + for (int i = 0; i < data.Pokemon.Count; i++) { + int start = workingRun.Start + elementLength * i; + model.WriteMultiByteValue(start + 0, 2, token, data.Levels[i]); + model.WriteMultiByteValue(start + 2, 2, token, data.Pokemon[i]); + model.WriteMultiByteValue(start + 4, 2, token, data.IVs[i]); + start += 6; + if (data.MovesIncluded) { + for (int j = 0; j < 4; j++) model.WriteMultiByteValue(start + j * 2, 2, token, data.Moves[i * 4 + j]); + start += 8; + } + model.WriteMultiByteValue(start, 2, token, data.Items[i]); + } + + // step 4: write the parent data + var structType = (data.ItemsIncluded ? INCLUDE_ITEM : 0) ^ (data.MovesIncluded ? INCLUDE_MOVES : 0); + var parent = workingRun.PointerSources[0] - TrainerFormat_PointerOffset; + model.WriteMultiByteValue(parent + TrainerFormat_StructTypeOffset, 1, token, structType); + model.WriteMultiByteValue(parent + TrainerFormat_PokemonCountOffset, 4, token, data.Pokemon.Count); + + return new TrainerPokemonTeamRun(model, workingRun.Start, workingRun.PointerSources); + } + + public string SerializeRun() { + var cache = ModelCacheScope.GetCache(model); + var buffer = new StringBuilder(); + for (int i = 0; i < ElementCount; i++) { + var start = Start + ElementLength * i; + var ivSpread = model.ReadMultiByteValue(start + 0, 2) * 31 / 255; + var level = model.ReadMultiByteValue(start + 2, 2); + var pokeID = model.ReadMultiByteValue(start + 4, 2); + var pokemonNames = cache.GetOptions(EggMoveRun.PokemonNameTable); + var pokemon = pokemonNames.Count > pokeID ? pokemonNames[pokeID] : pokeID.ToString(); + var item = string.Empty; + if ((StructType & INCLUDE_ITEM) != 0) { + var itemID = model.ReadMultiByteValue(start + ElementLength - 2, 2); + var itemNames = cache.GetOptions(HardcodeTablesModel.ItemsTableName); + item = itemNames.Count > itemID ? itemNames[itemID] : itemID.ToString(); + item = "@" + item; + } + buffer.AppendLine($"{level} {pokemon} ({ivSpread}){item}"); + if ((StructType & INCLUDE_MOVES) != 0) { + var moveNames = cache.GetOptions(EggMoveRun.MoveNamesTable); + for(int j = 0; j < 4; j++) { + var moveID = model.ReadMultiByteValue(start + 6 + j * 2, 2); + var move = moveNames.Count > moveID ? moveNames[moveID] : moveID.ToString(); + buffer.AppendLine($"- {move}"); + } + } + if (i + 1 < ElementCount) buffer.AppendLine(); + } + return buffer.ToString(); + } + + private class TeamData { + private readonly List levels = new List(); + private readonly List pokemons = new List(); + private readonly List ivs = new List(); + private readonly List items = new List(); + private readonly List moves = new List(); + + public bool ItemsIncluded { get; private set; } + public bool MovesIncluded { get; private set; } + + public IReadOnlyList Levels => levels; + public IReadOnlyList Pokemon => pokemons; + public IReadOnlyList IVs => ivs; + public IReadOnlyList Items => items; + public IReadOnlyList Moves => moves; + + public TeamData(ModelCacheScope cache, string[] lines) { + var currentPokemonMoveCount = 0; + var moveNames = cache.GetOptions(EggMoveRun.MoveNamesTable); + var itemNames = cache.GetOptions(HardcodeTablesModel.ItemsTableName); + var pokemonNames = cache.GetOptions(EggMoveRun.PokemonNameTable); + + foreach (var line in lines) { + if (line is "") continue; + if (line.StartsWith("-")) { + if (pokemons.Count == 0) continue; + if (currentPokemonMoveCount > 3) continue; + MovesIncluded = true; + var move = line.Substring(1).Trim(); + var moveIndex = moveNames.IndexOfPartial(move); + if (moveIndex < 0) moveIndex = 0; + moves[(pokemons.Count - 1) * 4 + currentPokemonMoveCount] = moveIndex; + currentPokemonMoveCount++; + } else { + if (pokemons.Count == 6) continue; + + var levelTokenized = line.Split(new[] { ' ' }, 2); + if (levelTokenized.Length != 2) continue; + if (!int.TryParse(levelTokenized[0], out int level)) continue; + levels.Add(level); + + var itemTokenized = line.Split(new[] { '@' }, 2); + AddItem(itemNames, items, itemTokenized); + + var ivTokenized = line.Split(new[] { '(' }, 2); + AddIV(ivs, ivTokenized); + + var pokemon = ivTokenized[0].Trim(); + var pokemonIndex = pokemonNames.IndexOfPartial(pokemon); + if (pokemonIndex < 0) pokemonIndex = 0; + pokemons.Add(pokemonIndex); + moves.AddRange(new[] { 0, 0, 0, 0 }); + currentPokemonMoveCount = 0; + } + } + } + + private void AddIV(List ivs, string[] ivTokenized) { + if (ivTokenized.Length == 2) { + ivTokenized[1] = ivTokenized[1].Replace(")", "").Trim(); + if (int.TryParse(ivTokenized[1], out int fixedIV)) { + ivs.Add(fixedIV * 255 / 31); + } else { + ivs.Add(0); + } + } else { + ivs.Add(0); + } + } + + private void AddItem(IReadOnlyList itemNames, List items, string[] itemTokenized) { + if (itemTokenized.Length == 2) { + ItemsIncluded = true; + var itemIndex = itemNames.IndexOfPartial(itemTokenized[1]); + if (itemIndex < 0) itemIndex = 0; + items.Add(itemIndex); + } else { + items.Add(0); + } + } + } + + #endregion + } +} diff --git a/src/HexManiac.Core/SystemExtensions.cs b/src/HexManiac.Core/SystemExtensions.cs index 503ca170..cf836a43 100644 --- a/src/HexManiac.Core/SystemExtensions.cs +++ b/src/HexManiac.Core/SystemExtensions.cs @@ -44,7 +44,7 @@ namespace HavenSoft.HexManiac.Core { return true; } - public static int IndexOfPartial(this IList names, string input) { + public static int IndexOfPartial(this IReadOnlyList names, string input) { // perfect match first var matchIndex = names.IndexOf(input); if (matchIndex != -1) return matchIndex; diff --git a/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTmsExpandable.cs b/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTmsExpandable.cs index 5cc4a512..af1f29b1 100644 --- a/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTmsExpandable.cs +++ b/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTmsExpandable.cs @@ -201,7 +201,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.QuickEditItems { // clear HMs from the TmMove table table = (ArrayRun)model.GetNextRun(tmMovesAddress); - table = table.Append(-8); + table = table.Append(token, -8); model.ObserveAnchorWritten(token, TmMoves, table); for (int i = 0; i < 8; i++) model.WriteMultiByteValue(table.Start + table.Length + i * 2, 2, token, 0); diff --git a/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs b/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs index a5151450..f8795c89 100644 --- a/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs @@ -202,7 +202,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { get => selectedIndex; set { if (!TryUpdate(ref selectedIndex, value)) return; - var run = (ArrayRun)Model.GetNextRun(Start); + var run = (ITableRun)Model.GetNextRun(Start); var offsets = run.ConvertByteOffsetToArrayOffset(Start); var segment = (ArrayRunEnumSegment)run.ElementContent[offsets.SegmentIndex]; @@ -218,7 +218,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { public ComboBoxArrayElementViewModel(Selection selection, ChangeHistory history, IDataModel model, string name, int start, int length) { (this.history, Model, Name, Start, Length) = (history, model, name, start, length); - var run = (ArrayRun)Model.GetNextRun(Start); + var run = (ITableRun)Model.GetNextRun(Start); var offsets = run.ConvertByteOffsetToArrayOffset(start); var segment = (ArrayRunEnumSegment)run.ElementContent[offsets.SegmentIndex]; var optionSource = model.GetAddressFromAnchor(history.CurrentChange, -1, segment.EnumName); @@ -309,7 +309,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { Name = name; this.start = start; - var array = (ArrayRun)model.GetNextRun(start); + var array = (ITableRun)model.GetNextRun(start); var offset = array.ConvertByteOffsetToArrayOffset(start); segment = (ArrayRunBitArraySegment)array.ElementContent[offset.SegmentIndex]; var optionSource = model.GetAddressFromAnchor(history.CurrentChange, -1, segment.SourceArrayName); diff --git a/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs b/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs index 07cd3468..585545da 100644 --- a/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs +++ b/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs @@ -292,7 +292,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { } if (newRun.ElementCount != lines.Length) { - newRun = newRun.Append(lines.Length - newRun.ElementCount); + newRun = newRun.Append(history.CurrentChange, lines.Length - newRun.ElementCount); model.ObserveRunWritten(history.CurrentChange, newRun); history.CurrentChange.AddRun(newRun); } diff --git a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs index f3502b7c..e10c6b13 100644 --- a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs +++ b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs @@ -57,7 +57,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { set { if (TryUpdate(ref address, value)) { var run = model.GetNextRun(value); - if (run.Start > value || !(run is ArrayRun array)) { + if (run.Start > value || !(run is ITableRun)) { Enabled = false; CommandCanExecuteChanged(); return; @@ -93,11 +93,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { previous = new StubCommand { CanExecute = parameter => { - var array = model.GetNextRun(address) as ArrayRun; + var array = model.GetNextRun(address) as ITableRun; return array != null && array.Start < address; }, Execute = parameter => { - var array = (ArrayRun)model.GetNextRun(address); + var array = (ITableRun)model.GetNextRun(address); selection.SelectionStart = selection.Scroll.DataIndexToViewPoint(Address - array.ElementLength); selection.SelectionEnd = selection.Scroll.DataIndexToViewPoint(selection.Scroll.ViewPointToDataIndex(selection.SelectionStart) + array.ElementLength - 1); } @@ -105,11 +105,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { next = new StubCommand { CanExecute = parameter => { - var array = model.GetNextRun(address) as ArrayRun; + var array = model.GetNextRun(address) as ITableRun; return array != null && array.Start + array.Length > address + array.ElementLength; }, Execute = parameter => { - var array = (ArrayRun)model.GetNextRun(address); + var array = (ITableRun)model.GetNextRun(address); selection.SelectionStart = selection.Scroll.DataIndexToViewPoint(Address + array.ElementLength); selection.SelectionEnd = selection.Scroll.DataIndexToViewPoint(selection.Scroll.ViewPointToDataIndex(selection.SelectionStart) + array.ElementLength - 1); } @@ -117,12 +117,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { append = new StubCommand { CanExecute = parameter => { - var array = model.GetNextRun(address) as ArrayRun; + var array = model.GetNextRun(address) as ITableRun; return array != null && array.Start + array.Length == address + array.ElementLength; }, Execute = parameter => { using (ModelCacheScope.CreateScope(model)) { - var array = (ArrayRun)model.GetNextRun(address); + var array = (ITableRun)model.GetNextRun(address); var originalArray = array; var error = model.CompleteArrayExtension(history.CurrentChange, ref array); if (array.Start != originalArray.Start) { @@ -149,7 +149,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { foreach (var child in Children) child.DataChanged -= ForwardModelChanged; Children.Clear(); - var array = model.GetNextRun(Address) as ArrayRun; + var array = model.GetNextRun(Address) as ITableRun; if (array == null) { CurrentElementName = "The Table tool only works if your cursor is on table data."; return; @@ -159,6 +159,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { TryUpdate(ref selectedTableIndex, model.Arrays.IndexOf(array), nameof(SelectedTableIndex)); var basename = model.GetAnchorFromAddress(-1, array.Start); + if (string.IsNullOrEmpty(basename)) basename = array.Start.ToString("X6"); var index = (Address - array.Start) / array.ElementLength; if (array.ElementNames.Count > index) { CurrentElementName = $"{basename}/{index}" + Environment.NewLine + $"{basename}/{array.ElementNames[index]}"; @@ -166,20 +167,23 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { CurrentElementName = $"{basename}/{index}"; } - if (!string.IsNullOrEmpty(array.LengthFromAnchor)) basename = array.LengthFromAnchor; // basename is now a 'parent table' name, if there is one - AddChildrenFromTable(array, index); - foreach(var currentArray in model.Arrays) { - if (currentArray == array) continue; - var currentArrayName = model.GetAnchorFromAddress(-1, currentArray.Start); - if (currentArray.LengthFromAnchor == basename || currentArrayName == basename) { - Children.Add(new SplitterArrayElementViewModel(currentArrayName)); - AddChildrenFromTable(currentArray, index); + + if (array is ArrayRun arrayRun) { + if (!string.IsNullOrEmpty(arrayRun.LengthFromAnchor)) basename = arrayRun.LengthFromAnchor; // basename is now a 'parent table' name, if there is one + + foreach (var currentArray in model.Arrays) { + if (currentArray == arrayRun) continue; + var currentArrayName = model.GetAnchorFromAddress(-1, currentArray.Start); + if (currentArray.LengthFromAnchor == basename || currentArrayName == basename) { + Children.Add(new SplitterArrayElementViewModel(currentArrayName)); + AddChildrenFromTable(currentArray, index); + } } } } - private void AddChildrenFromTable(ArrayRun table, int index) { + private void AddChildrenFromTable(ITableRun table, int index) { var itemAddress = table.Start + table.ElementLength * index; foreach (var item in table.ElementContent) { IArrayElementViewModel viewModel = null; @@ -200,8 +204,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { viewModel.DataChanged += ForwardModelChanged; if (item is ArrayRunPointerSegment pointerSegment) { var destination = model.ReadPointer(itemAddress); - if (destination != Pointer.NULL && model.GetNextRun(destination) is IStreamRun && pointerSegment.DestinationDataMatchesPointerFormat(model, new NoDataChangeDeltaModel(), destination)) { - if (pointerSegment.InnerFormat == PCSRun.SharedFormatString || pointerSegment.InnerFormat == PLMRun.SharedFormatString) { + if (destination != Pointer.NULL && model.GetNextRun(destination) is IStreamRun && pointerSegment.DestinationDataMatchesPointerFormat(model, new NoDataChangeDeltaModel(), itemAddress, destination)) { + if (pointerSegment.InnerFormat == PCSRun.SharedFormatString || pointerSegment.InnerFormat == PLMRun.SharedFormatString || pointerSegment.InnerFormat == TrainerPokemonTeamRun.SharedFormatString) { var streamElement = new StreamArrayElementViewModel(history, (FieldArrayElementViewModel)viewModel, model, item.Name, itemAddress); streamElement.DataChanged += ForwardModelChanged; streamElement.DataMoved += ForwardModelDataMoved; diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index 3a4a7b74..a1e03bf7 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -174,7 +174,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var run = Model.GetNextRun(dataIndex); using (ModelCacheScope.CreateScope(Model)) { - if (run.Start <= dataIndex && run is ArrayRun array) { + if (run.Start <= dataIndex && run is ITableRun array) { var offsets = array.ConvertByteOffsetToArrayOffset(dataIndex); Tools.StringTool.Address = offsets.SegmentStart - offsets.ElementIndex * array.ElementLength; Tools.TableTool.Address = array.Start + array.ElementLength * offsets.ElementIndex; diff --git a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs index 605a041c..1f98baea 100644 --- a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs +++ b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs @@ -461,7 +461,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { var offsets = run.ConvertByteOffsetToArrayOffset(memoryLocation); var segment = run.ElementContent[offsets.SegmentIndex]; if (segment is ArrayRunPointerSegment pointerSegment) { - if (!pointerSegment.DestinationDataMatchesPointerFormat(Model, CurrentChange, pointerDestination)) { + if (!pointerSegment.DestinationDataMatchesPointerFormat(Model, CurrentChange, offsets.SegmentStart, pointerDestination)) { ErrorText = $"This pointer must point to {pointerSegment.InnerFormat} data."; return; } diff --git a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs index 86b7373f..6a52457e 100644 --- a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs +++ b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs @@ -66,7 +66,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { return; } - if (destination is ArrayRun) { + if (destination is ITableRun) { ViewPort.RaiseError("Cannot automatically duplicate a table. This operation is unsafe."); return; } @@ -85,7 +85,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { })); } - var arrayRun = ViewPort.Model.GetNextRun(pointerAddress) as ArrayRun; + var arrayRun = ViewPort.Model.GetNextRun(pointerAddress) as ITableRun; if (arrayRun != null && arrayRun.Start <= pointerAddress) Results.AddRange(GetTableChildren(arrayRun)); else Results.AddRange(GetFormattedChildren()); } @@ -123,7 +123,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { Results.Add(new ContextItem("Copy Selection", ViewPort.Copy.Execute) { ShortcutText = "Ctrl+C" }); var address = ViewPort.ConvertViewPointToAddress(point); - var arrayRun = ViewPort.Model.GetNextRun(address) as ArrayRun; + var arrayRun = ViewPort.Model.GetNextRun(address) as ITableRun; if (arrayRun != null && arrayRun.Start <= address) Results.AddRange(GetTableChildren(arrayRun)); else Results.AddRange(GetFormattedChildren()); } @@ -135,12 +135,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { public void Visit(Ascii ascii, byte data) => Results.AddRange(GetFormattedChildren()); public void Visit(Integer integer, byte data) { - var arrayRun = (ArrayRun)ViewPort.Model.GetNextRun(ViewPort.Tools.TableTool.Address); + var arrayRun = (ITableRun)ViewPort.Model.GetNextRun(ViewPort.Tools.TableTool.Address); Results.AddRange(GetTableChildren(arrayRun)); } public void Visit(IntegerEnum integer, byte data) { - var arrayRun = (ArrayRun)ViewPort.Model.GetNextRun(ViewPort.Tools.TableTool.Address); + var arrayRun = (ITableRun)ViewPort.Model.GetNextRun(ViewPort.Tools.TableTool.Address); Results.AddRange(GetTableChildren(arrayRun)); } @@ -159,13 +159,13 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { } public void Visit(BitArray array, byte data) { - var arrayRun = (ArrayRun)ViewPort.Model.GetNextRun(ViewPort.Tools.TableTool.Address); + var arrayRun = (ITableRun)ViewPort.Model.GetNextRun(ViewPort.Tools.TableTool.Address); Results.AddRange(GetTableChildren(arrayRun)); } public void Visit(MatchedWord word, byte data) => Results.AddRange(GetFormattedChildren()); - private IEnumerable GetTableChildren(ArrayRun array) { + private IEnumerable GetTableChildren(ITableRun array) { if (ViewPort.Tools.TableTool.Append.CanExecute(null)) { yield return new ContextItem("Extend Table", ViewPort.Tools.TableTool.Append.Execute); } From 204dee8476737c1fb23230ae5acd6874163b0778 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 Nov 2019 14:50:35 -0500 Subject: [PATCH 2/7] Run-Length fix I was hitting a Debug.Fail because I was clearing data incorrectly for runs. --- src/HexManiac.Core/Models/PokemonModel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs index 85b6918a..281112b1 100644 --- a/src/HexManiac.Core/Models/PokemonModel.cs +++ b/src/HexManiac.Core/Models/PokemonModel.cs @@ -192,7 +192,7 @@ namespace HavenSoft.HexManiac.Core.Models { if (i == runs.Count - 1 || runs[i].Start + runs[i].Length <= runs[i + 1].Start) continue; var debugRunStart1 = runs[i].Start.ToString("X6"); var debugRunStart2 = runs[i + 1].Start.ToString("X6"); - Debug.Fail("Conflict: there's a run that ends before the next run starts!"); + Debug.Fail("Conflict: there's a run that ends after the next run starts!"); } } @@ -605,8 +605,8 @@ namespace HavenSoft.HexManiac.Core.Models { // the pointer points to a known normal anchor var existingRun = runs[index]; changeToken.RemoveRun(existingRun); - UpdateNewRunFromPointerFormat(ref existingRun, segment as ArrayRunPointerSegment, changeToken); existingRun = existingRun.MergeAnchor(new[] { start }); + UpdateNewRunFromPointerFormat(ref existingRun, segment as ArrayRunPointerSegment, changeToken); index = BinarySearch(destination); // runs could've been removed during UpdateNewRunFromPointerFormat: search for the index again. if (index < 0) { runs.Insert(~index, existingRun); @@ -638,7 +638,7 @@ namespace HavenSoft.HexManiac.Core.Models { } } else if (segment.InnerFormat == TrainerPokemonTeamRun.SharedFormatString) { var runAttempt = new TrainerPokemonTeamRun(this, run.Start, run.PointerSources); - ClearFormat(token, run.Start, run.Length); + ClearFormat(token, run.Start, runAttempt.Length); run = runAttempt; } else { throw new NotImplementedException(); From 82b8e06fa04e74267ab37b8c147bf735cd303b40 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 Nov 2019 15:11:34 -0500 Subject: [PATCH 3/7] TPT streams get multi-selection like tables. --- src/HexManiac.Core/ViewModels/ViewPort.cs | 2 +- src/HexManiac.Tests/TableTests.cs | 26 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index a1e03bf7..77d02c3b 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -1217,7 +1217,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { if (even) return pair(index, index + 1); return pair(index - 1, index); } - if (!(run is ArrayRun array)) return (p, p); + if (!(run is ITableRun array)) return (p, p); var offset = array.ConvertByteOffsetToArrayOffset(index); var type = array.ElementContent[offset.SegmentIndex].Type; diff --git a/src/HexManiac.Tests/TableTests.cs b/src/HexManiac.Tests/TableTests.cs index 52198413..35a90e78 100644 --- a/src/HexManiac.Tests/TableTests.cs +++ b/src/HexManiac.Tests/TableTests.cs @@ -1,4 +1,5 @@ using HavenSoft.HexManiac.Core.Models; +using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.Tools; using System.Linq; using Xunit; @@ -17,5 +18,30 @@ namespace HavenSoft.HexManiac.Tests { // a combobox is used for numeric limit fields ViewPort.Tools.TableTool.Children.Single(child => child is ComboBoxArrayElementViewModel); } + + [Fact] + public void TrainerPokemonTeamEnumSelectionSelectsEntireEnum() { + ArrangeTrainerPokemonTeamData(0, 1); + + ViewPort.SelectionStart = new Point(0, 6); + + Assert.Equal(new Point(1, 6), ViewPort.SelectionEnd); + } + + private void ArrangeTrainerPokemonTeamData(byte structType, byte pokemonCount) { + CreateTextTable(EggMoveRun.PokemonNameTable, 0x100, "ABCDEFGHIJKLMNOP".Select(c => c.ToString()).ToArray()); + CreateTextTable(EggMoveRun.MoveNamesTable, 0x140, "abcdefghijklmnop".Select(c => c.ToString()).ToArray()); + CreateTextTable(HardcodeTablesModel.ItemsTableName, 0x180, "0123456789".Select(c => c.ToString()).ToArray()); + + Model[TrainerPokemonTeamRun.TrainerFormat_StructTypeOffset] = structType; + Model[TrainerPokemonTeamRun.TrainerFormat_PokemonCountOffset] = pokemonCount; + Model.WritePointer(new ModelDelta(), TrainerPokemonTeamRun.TrainerFormat_PointerOffset, 0x60); + + ViewPort.Goto.Execute("00"); + ViewPort.SelectionStart = new Point(4, 2); + ViewPort.Edit($"^trainertable[team<{TrainerPokemonTeamRun.SharedFormatString}>]1 "); + + ViewPort.Goto.Execute("00"); + } } } From f52ce35b5f90b33baa1e61e44cb93033ed68e1eb Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 Nov 2019 18:39:07 -0500 Subject: [PATCH 4/7] 'Find' works for TPT runs TPT runs contain pokemon, moves, and items. Now you can search for them. --- .../Models/Runs/TrainerPokemonTeamRun.cs | 30 +++++++++++++++++-- src/HexManiac.Core/ViewModels/ViewPort.cs | 6 ++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs b/src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs index 21a415d6..f13a3e29 100644 --- a/src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs +++ b/src/HexManiac.Core/Models/Runs/TrainerPokemonTeamRun.cs @@ -9,11 +9,16 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { public const int TrainerFormat_StructTypeOffset = 0; public const int TrainerFormat_PokemonCountOffset = 32; public const int TrainerFormat_PointerOffset = 36; + public const byte INCLUDE_MOVES = 1; + public const byte INCLUDE_ITEM = 2; + + public const int PokemonFormat_FixedIVStart = 0; + public const int PokemonFormat_LevelStart = 2; + public const int PokemonFormat_PokemonStart = 4; + public const int PokemonFormat_MoveStart = 6; public static readonly string SharedFormatString = AsciiRun.StreamDelimeter + "tpt" + AsciiRun.StreamDelimeter; - private const byte INCLUDE_MOVES = 1; - private const byte INCLUDE_ITEM = 2; private readonly IDataModel model; public byte StructType { get; } @@ -49,6 +54,27 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { ElementLength = ElementContent.Sum(segment => segment.Length); } + public IEnumerable Search(string parentArrayName, int id) { + for (int i = 0; i < ElementCount; i++) { + int start = Start + i * ElementLength; + if (parentArrayName == EggMoveRun.MoveNamesTable && (StructType & INCLUDE_MOVES) != 0) { + for (int j = 0; j < 4; j++) { + var index = start + PokemonFormat_MoveStart + j * 2; + var moveID = model.ReadMultiByteValue(index, 2); + if (moveID == id) yield return index; + } + } else if (parentArrayName == EggMoveRun.PokemonNameTable) { + var index = start + PokemonFormat_PokemonStart; + var pokemonID = model.ReadMultiByteValue(index, 2); + if (pokemonID == id) yield return index; + } else if (parentArrayName == HardcodeTablesModel.ItemsTableName && (StructType & INCLUDE_ITEM) != 0) { + var index = start + ElementLength - 2; + var itemID = model.ReadMultiByteValue(index, 2); + if (itemID == id) yield return index; + } + } + } + #region BaseRun public override int Length => ElementLength * ElementCount; diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index 77d02c3b..59f4f763 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -847,6 +847,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } } } + // option 3: the value is a move used by trainer teams + if (child is TrainerPokemonTeamRun team) { + foreach (var result in team.Search(parentArrayName, offsets.ElementIndex)) { + yield return (result, result + 1); + } + } } } From 03903c7aa187ba66dcd92eac60312ecea6c7a54d Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 Nov 2019 20:19:28 -0500 Subject: [PATCH 5/7] Allow manual hex editing of the trainer team --- src/HexManiac.Core/ViewModels/ViewPort.cs | 28 +++++++++---------- .../Visitors/CompleteEditOperation.cs | 16 +++++------ .../ViewModels/Visitors/StartCellEdit.cs | 4 +-- src/HexManiac.Tests/TableTests.cs | 10 +++++++ 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index 59f4f763..68655bc6 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -206,7 +206,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var result = "Address: " + left.ToString("X6"); var run = Model.GetNextRun(left); - if (run is ArrayRun array1 && array1.Start <= left) { + if (run is ITableRun array1 && array1.Start <= left) { var index = array1.ConvertByteOffsetToArrayOffset(left).ElementIndex; var basename = Model.GetAnchorFromAddress(-1, array1.Start); if (array1.ElementNames.Count > index) { @@ -216,7 +216,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } } else if (run.PointerSources != null && run.PointerSources.Count > 0 && string.IsNullOrEmpty(Model.GetAnchorFromAddress(-1, run.Start))) { var sourceRun = Model.GetNextRun(run.PointerSources[0]); - if (sourceRun is ArrayRun array2) { + if (sourceRun is ITableRun array2) { // we are an anchor that's pointed to from an array var offset = array2.ConvertByteOffsetToArrayOffset(run.PointerSources[0]); var index = offset.ElementIndex; @@ -442,7 +442,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var right = Math.Max(selectionStart, selectionEnd); var startRun = Model.GetNextRun(left); var endRun = Model.GetNextRun(right); - if (startRun == endRun && startRun.Start <= left && (startRun.Start < left || startRun.Start + startRun.Length - 1 > right) && startRun is ArrayRun arrayRun) { + if (startRun == endRun && startRun.Start <= left && (startRun.Start < left || startRun.Start + startRun.Length - 1 > right) && startRun is ITableRun arrayRun) { for (int i = 0; i < arrayRun.ElementCount; i++) { var start = arrayRun.Start + arrayRun.ElementLength * i; if (start + arrayRun.ElementLength <= left) continue; @@ -551,7 +551,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public bool IsTable(Point point) { var search = scroll.ViewPointToDataIndex(point); var run = Model.GetNextRun(search); - return run.Start <= search && run is ArrayRun; + return run.Start <= search && run is ITableRun; } public void Refresh() => RefreshBackingData(); @@ -597,11 +597,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels { TryCompleteEdit(point); return; } - if (key == ConsoleKey.Enter && run is ArrayRun arrayRun1) { + if (key == ConsoleKey.Enter && run is ITableRun arrayRun1) { var offsets = arrayRun1.ConvertByteOffsetToArrayOffset(offset); SilentScroll(offsets.SegmentStart + arrayRun1.ElementLength); } - if (key == ConsoleKey.Tab && run is ArrayRun arrayRun2) { + if (key == ConsoleKey.Tab && run is ITableRun arrayRun2) { var offsets = arrayRun2.ConvertByteOffsetToArrayOffset(offset); SilentScroll(offsets.SegmentStart + arrayRun2.ElementContent[offsets.SegmentIndex].Length); } @@ -668,7 +668,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var cellToText = new ConvertCellToText(Model, run.Start); var cell = currentView[point.X, point.Y]; - if (run is ArrayRun array) { + if (run is ITableRun array) { var offsets = array.ConvertByteOffsetToArrayOffset(index); if (array.ElementContent[offsets.SegmentIndex].Type == ElementContentType.PCS) { for (int i = index + 1; i < offsets.SegmentStart + array.ElementContent[offsets.SegmentIndex].Length; i++) history.CurrentChange.ChangeData(Model, i, 0x00); @@ -999,7 +999,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { if (currentRun is IStreamRun) { Tools.StringTool.Address = currentRun.Start; Tools.SelectedIndex = Tools.IndexOf(Tools.StringTool); - } else if (currentRun is ArrayRun array) { + } else if (currentRun is ITableRun array) { var offsets = array.ConvertByteOffsetToArrayOffset(byteOffset); if (format is PCS) { Tools.StringTool.Address = offsets.SegmentStart - offsets.ElementIndex * array.ElementLength; @@ -1016,7 +1016,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var index = scroll.ViewPointToDataIndex(SelectionStart); var run = Model.GetNextRun(index); if (run.Start > index) return; - if (run is ArrayRun array) { + if (run is ITableRun array) { var offsets = array.ConvertByteOffsetToArrayOffset(index); if (array.ElementContent[offsets.SegmentIndex].Type == ElementContentType.Pointer) { FollowLink(x, y); @@ -1121,7 +1121,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } else if (newText.StartsWith(GotoMarker.ToString())) { return Model.GetNewPointerAutocompleteOptions(newText, selectedIndex); } else if (originalFormat is IntegerEnum intEnum) { - var array = (ArrayRun)Model.GetNextRun(intEnum.Source); + var array = (ITableRun)Model.GetNextRun(intEnum.Source); var segment = (ArrayRunEnumSegment)array.ElementContent[array.ConvertByteOffsetToArrayOffset(intEnum.Source).SegmentIndex]; var options = segment.GetOptions(Model).Select(option => option + " "); // autocomplete needs to complete after selection, so add a space return AutoCompleteSelectionItem.Generate(options.Where(option => option.MatchesPartial(newText)), selectedIndex); @@ -1293,8 +1293,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var run = Model.GetNextRun(completeEditOperation.NewDataIndex); if (run.Start > completeEditOperation.NewDataIndex) run = new NoInfoRun(Model.Count); if (completeEditOperation.DataMoved) UpdateToolsFromSelection(run.Start); - if (run is ArrayRun) Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged); - if (run is ArrayRun || run is PCSRun || run is PLMRun || run is EggMoveRun) Tools.Schedule(Tools.StringTool.DataForCurrentRunChanged); + if (run is ITableRun) Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged); + if (run is ITableRun || run is IStreamRun) Tools.Schedule(Tools.StringTool.DataForCurrentRunChanged); if (completeEditOperation.MessageText != null) OnMessage?.Invoke(this, completeEditOperation.MessageText); if (completeEditOperation.ErrorText != null) OnError?.Invoke(this, completeEditOperation.ErrorText); } @@ -1464,8 +1464,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { RefreshBackingData(); } - if (run is ArrayRun && sender != Tools.StringTool && Model.GetNextRun(Tools.StringTool.Address).Start == run.Start) Tools.StringTool.DataForCurrentRunChanged(); - if (run is ArrayRun && sender != Tools.TableTool && Model.GetNextRun(Tools.TableTool.Address).Start == run.Start) Tools.TableTool.DataForCurrentRunChanged(); + if (run is ITableRun && sender != Tools.StringTool && Model.GetNextRun(Tools.StringTool.Address).Start == run.Start) Tools.StringTool.DataForCurrentRunChanged(); + if (run is ITableRun && sender != Tools.TableTool && Model.GetNextRun(Tools.TableTool.Address).Start == run.Start) Tools.TableTool.DataForCurrentRunChanged(); } private void ModelDataMovedByTool(object sender, (int originalLocation, int newLocation) locations) { diff --git a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs index 1f98baea..73993461 100644 --- a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs +++ b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs @@ -55,7 +55,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { public void Visit(Pointer pointer, byte data) { var run = Model.GetNextRun(memoryLocation); - if (run is ArrayRun && CurrentText[0] != PointerStart) { + if (run is ITableRun && CurrentText[0] != PointerStart) { ErrorText = "Pointers in tables cannot be removed without removing the table."; return; } @@ -215,7 +215,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { return; } - var run = (ArrayRun)Model.GetNextRun(memoryLocation); + var run = (ITableRun)Model.GetNextRun(memoryLocation); var offsets = run.ConvertByteOffsetToArrayOffset(memoryLocation); int length = run.ElementContent[offsets.SegmentIndex].Length; for (int i = 0; i < length; i++) { @@ -228,7 +228,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { } private void CompleteIntegerEnumEdit(IntegerEnum integer) { - var array = (ArrayRun)Model.GetNextRun(memoryLocation); + var array = (ITableRun)Model.GetNextRun(memoryLocation); var offsets = array.ConvertByteOffsetToArrayOffset(memoryLocation); var segment = (ArrayRunEnumSegment)array.ElementContent[offsets.SegmentIndex]; if (segment.TryParse(Model, CurrentText, out int value)) { @@ -259,7 +259,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { var currentRun = Model.GetNextRun(memoryLocation); if (currentRun.Start > memoryLocation) currentRun = null; - bool inArray = currentRun is ArrayRun && currentRun.Start <= memoryLocation; + bool inArray = currentRun is ITableRun && currentRun.Start <= memoryLocation; var sources = currentRun?.PointerSources; if (!inArray) { @@ -284,7 +284,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { if (fullValue == Pointer.NULL || (0 <= fullValue && fullValue < Model.Count)) { if (inArray) { - UpdateArrayPointer((ArrayRun)currentRun, fullValue); + UpdateArrayPointer((ITableRun)currentRun, fullValue); } else { Model.WritePointer(CurrentChange, memoryLocation, fullValue); Model.ObserveRunWritten(CurrentChange, new PointerRun(memoryLocation, sources)); @@ -318,7 +318,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { var newRunLength = PCSString.ReadString(Model, run.Start, true); Model.ObserveRunWritten(CurrentChange, new PCSRun(Model, run.Start, newRunLength, run.PointerSources)); } - } else if (run is ArrayRun arrayRun) { + } else if (run is ITableRun arrayRun) { var offsets = arrayRun.ConvertByteOffsetToArrayOffset(memoryLocation); CurrentChange.ChangeData(Model, memoryLocation, 0xFF); memoryLocation++; @@ -365,7 +365,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { run = new PCSRun(Model, run.Start, run.Length + extraBytesNeeded, run.PointerSources); Model.ObserveRunWritten(CurrentChange, run); } - } else if (run is ArrayRun arrayRun) { + } else if (run is ITableRun arrayRun) { // if the last characet is being edited for an array, truncate var offsets = arrayRun.ConvertByteOffsetToArrayOffset(memoryLocation); if (arrayRun.ElementContent[offsets.SegmentIndex].Length == position + 1) { @@ -457,7 +457,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { } } - private void UpdateArrayPointer(ArrayRun run, int pointerDestination) { + private void UpdateArrayPointer(ITableRun run, int pointerDestination) { var offsets = run.ConvertByteOffsetToArrayOffset(memoryLocation); var segment = run.ElementContent[offsets.SegmentIndex]; if (segment is ArrayRunPointerSegment pointerSegment) { diff --git a/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs b/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs index 44860dee..7cc79f13 100644 --- a/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs +++ b/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs @@ -97,7 +97,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { public void Visit(PCS pcs, byte data) { // don't let it start with a space unless it's in quotes (for copy/paste) var run = Model.GetNextRun(MemoryLocation); - if (run is ArrayRun array) { + if (run is ITableRun array) { var offsets = array.ConvertByteOffsetToArrayOffset(MemoryLocation); if (offsets.SegmentStart == MemoryLocation && Input == ' ') return; } @@ -124,7 +124,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { public void Visit(IntegerEnum integer, byte data) { if (!integer.CanStartWithCharacter(Input)) return; - var arrayRun = (ArrayRun)Model.GetNextRun(MemoryLocation); + var arrayRun = (ITableRun)Model.GetNextRun(MemoryLocation); var offsets = arrayRun.ConvertByteOffsetToArrayOffset(MemoryLocation); var segment = (ArrayRunEnumSegment)arrayRun.ElementContent[offsets.SegmentIndex]; var allOptions = segment.GetOptions(Model).Select(option => option + " "); diff --git a/src/HexManiac.Tests/TableTests.cs b/src/HexManiac.Tests/TableTests.cs index 35a90e78..48975532 100644 --- a/src/HexManiac.Tests/TableTests.cs +++ b/src/HexManiac.Tests/TableTests.cs @@ -28,6 +28,16 @@ namespace HavenSoft.HexManiac.Tests { Assert.Equal(new Point(1, 6), ViewPort.SelectionEnd); } + [Fact] + public void HexEditingWorksForTrainerPokemon() { + ArrangeTrainerPokemonTeamData(0, 1); + + ViewPort.SelectionStart = new Point(TrainerPokemonTeamRun.PokemonFormat_PokemonStart, 6); + ViewPort.Edit("C "); + + Assert.Equal(2, Model[0x60 + TrainerPokemonTeamRun.PokemonFormat_PokemonStart]); + } + private void ArrangeTrainerPokemonTeamData(byte structType, byte pokemonCount) { CreateTextTable(EggMoveRun.PokemonNameTable, 0x100, "ABCDEFGHIJKLMNOP".Select(c => c.ToString()).ToArray()); CreateTextTable(EggMoveRun.MoveNamesTable, 0x140, "abcdefghijklmnop".Select(c => c.ToString()).ToArray()); From 2da9f6fc1716ae2546b9f2b8b9f7b3eb2a5a9352 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 Nov 2019 21:54:08 -0500 Subject: [PATCH 6/7] Stream Edits can extend/move tpt runs. --- src/HexManiac.Core/Models/PokemonModel.cs | 15 +-------------- src/HexManiac.Tests/TableTests.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs index 281112b1..8644b311 100644 --- a/src/HexManiac.Core/Models/PokemonModel.cs +++ b/src/HexManiac.Core/Models/PokemonModel.cs @@ -1366,20 +1366,7 @@ namespace HavenSoft.HexManiac.Core.Models { } // move run - IFormattedRun newRun; - if (run is PCSRun pcs) { - newRun = new PCSRun(this, newStart, run.Length, run.PointerSources); - } else if (run is ArrayRun array) { - var array1 = array.Move(newStart); - UpdateAnchorsFromArrayMove(changeToken, array, array1); - newRun = array1; - } else if (run is EggMoveRun egg) { - newRun = new EggMoveRun(this, newStart); - } else if (run is PLMRun plm) { - newRun = new PLMRun(this, newStart); - } else { - throw new NotImplementedException(); - } + var newRun = run.Duplicate(newStart, run.PointerSources.ToArray()); int index = BinarySearch(run.Start); changeToken.RemoveRun(runs[index]); diff --git a/src/HexManiac.Tests/TableTests.cs b/src/HexManiac.Tests/TableTests.cs index 48975532..db89e3e2 100644 --- a/src/HexManiac.Tests/TableTests.cs +++ b/src/HexManiac.Tests/TableTests.cs @@ -1,6 +1,7 @@ using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.Tools; +using System; using System.Linq; using Xunit; @@ -38,6 +39,18 @@ namespace HavenSoft.HexManiac.Tests { Assert.Equal(2, Model[0x60 + TrainerPokemonTeamRun.PokemonFormat_PokemonStart]); } + [Fact] + public void CanExtendTrainerTeamViaStream() { + ArrangeTrainerPokemonTeamData(0, 1); + Model[0x6C] = 0x0B; // add a random data value so that extending will cause moving + + ViewPort.SelectionStart = new Point(0, 6); + var streamTool = ViewPort.Tools.StringTool; + streamTool.Content = $"10 A{Environment.NewLine}10 B"; + + Assert.NotEqual(0x60, Model.ReadPointer(0x24)); + } + private void ArrangeTrainerPokemonTeamData(byte structType, byte pokemonCount) { CreateTextTable(EggMoveRun.PokemonNameTable, 0x100, "ABCDEFGHIJKLMNOP".Select(c => c.ToString()).ToArray()); CreateTextTable(EggMoveRun.MoveNamesTable, 0x140, "abcdefghijklmnop".Select(c => c.ToString()).ToArray()); From 7e2e60ef4a5ce9b3995e7f1be9fab9fbf4039a18 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 Nov 2019 22:25:34 -0500 Subject: [PATCH 7/7] Update anchors pointed to from moved tables This was accidentally removed during the previous refactor. --- src/HexManiac.Core/Models/PokemonModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs index 8644b311..af05dc4c 100644 --- a/src/HexManiac.Core/Models/PokemonModel.cs +++ b/src/HexManiac.Core/Models/PokemonModel.cs @@ -493,7 +493,7 @@ namespace HavenSoft.HexManiac.Core.Models { /// Remove the sources that match the array's original location. /// Add new sources corresponding to the array's new location. /// - private void UpdateAnchorsFromArrayMove(ModelDelta changeToken, ArrayRun original, ArrayRun moved) { + private void UpdateAnchorsFromArrayMove(ModelDelta changeToken, ITableRun original, ITableRun moved) { int originalOffset = original.Start; int segmentOffset = moved.Start; // i loops over the different segments in the array @@ -1367,6 +1367,9 @@ namespace HavenSoft.HexManiac.Core.Models { // move run var newRun = run.Duplicate(newStart, run.PointerSources.ToArray()); + if (newRun is ITableRun array) { + UpdateAnchorsFromArrayMove(changeToken, (ITableRun)run, array); + } int index = BinarySearch(run.Start); changeToken.RemoveRun(runs[index]);