diff --git a/src/HexManiac.Core/Models/Code/tableReference.txt b/src/HexManiac.Core/Models/Code/tableReference.txt index f713b9d9..6a830b0f 100644 --- a/src/HexManiac.Core/Models/Code/tableReference.txt +++ b/src/HexManiac.Core/Models/Code/tableReference.txt @@ -72,9 +72,9 @@ data.maps.theme.popup, ,,,, ,,,, 0D4C54, [theme.mapnamepopu graphics.maps.names.popup.palettes, ,,,, ,,,, 0D4CA0, `ucp4:012345` graphics.maps.names.popup.background, ,,,, ,,,, 0D4CA4, `ucs4x10x18|graphics.maps.names.popup.palettes` graphics.maps.names.popup.outline, ,,,, ,,,, 0D4C58, `ucs4x6x30|graphics.maps.names.popup.palettes` -data.maps.banks, 053324, 053324, 053344, 053344, , , , , , [maps<[map<[layout<[width:: height:: borderblock<[border:|h]4> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1>]1> events<[objectCount.100 warpCount.100 scriptCount.100 signpostCount.100 objects<[id. graphics. unused:1 x:|z y:|z elevation.10 moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/objectCount> warps<[x:|z y:|z elevation.10 warpID. map. bank.]/warpCount> scripts<[x:|z y:|z elevation:10 trigger: index:: script<`xse`>]/scriptCount> signposts<[x:|z y:|z elevation.10 kind. unused:1 arg::|h]/signpostCount>]1> mapscripts<[type. pointer<>]!00> connections<[count:: connections<[direction::mapdirections offset:: mapGroup. mapNum. unused:]/count>]1> music:songnames layoutID:data.maps.layouts+1 regionSectionID.data.maps.names cave. weather. mapType. unused. allowEscaping. showMapName. battleType.]1>]?>]34 -data.maps.banks, , , , , , , , , 084AA4, [maps<[map<[layout<[width:: height:: borderblock<[border:|h]4> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1>]1> events<[objectCount.100 warpCount.100 scriptCount.100 signpostCount.100 objects<[id. graphics. unused:1 x:|z y:|z elevation.10 moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/objectCount> warps<[x:|z y:|z elevation.10 warpID. map. bank.]/warpCount> scripts<[x:|z y:|z elevation:10 trigger: index:: script<`xse`>]/scriptCount> signposts<[x:|z y:|z elevation.10 kind. unused:1 arg::|h]/signpostCount>]1> mapscripts<[type. pointer<>]!00> connections<[count:: connections<[direction::mapdirections offset:: mapGroup. mapNum. unused:]/count>]1> music:songnames layoutID:data.maps.layouts+1 regionSectionID.data.maps.names cave. weather. mapType. unused: flags.|t|allowBiking.|allowEscaping.|allowRunning.|showMapName. battleType.]1>]?>]34 -data.maps.banks, , , , , 05524C, 05524C, 055260, 055260, , [maps<[map<[layout<[width:: height:: borderblock<> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> borderwidth. borderheight. unused:]1> events<[objectCount.100 warpCount.100 scriptCount.100 signpostCount.100 objects<[id. graphics. kind: x:|z y:|z elevation.10 moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/objectCount> warps<[x:|z y:|z elevation.10 warpID. map. bank.]/warpCount> scripts<[x:|z y:|z elevation:10 trigger: index:: script<`xse`>]/scriptCount> signposts<[x:|z y:|z elevation.10 kind. unused:1 arg::|h]/signpostCount>]1> mapscripts<[type. pointer<>]!00> connections<[count:: connections<[direction::mapdirections offset:: mapGroup. mapNum. unused:]/count>]1> music:songnames layoutID:data.maps.layouts+1 regionSectionID.data.maps.names+88 cave. weather. mapType. allowBiking. flags.|t|allowEscaping.|allowRunning.|showMapName. floorNum. battleType.]1>]?>]43 +data.maps.banks, 053324, 053324, 053344, 053344, , , , , , [maps<[map<[layout<[width:: height:: borderblock<[border:|h]4> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1>]1> events<[objectCount.100 warpCount.100 scriptCount.100 signpostCount.100 objects<[id. graphics. unused:1 x:|z y:|z elevation.10 moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/objectCount> warps<[x:|z y:|z elevation.10 warpID. map. bank.]/warpCount> scripts<[x:|z y:|z elevation:10 trigger: index:: script<`xse`>]/scriptCount> signposts<[x:|z y:|z elevation.10 kind. unused:1 arg::|s=kind(0=<>|1=<>|2=<>|3=<>|4=<>)]/signpostCount>]1> mapscripts<[type. pointer<>]!00> connections<[count:: connections<[direction::mapdirections offset:: mapGroup. mapNum. unused:]/count>]1> music:songnames layoutID:data.maps.layouts+1 regionSectionID.data.maps.names cave. weather. mapType. unused. allowEscaping. showMapName. battleType.]1>]?>]34 +data.maps.banks, , , , , , , , , 084AA4, [maps<[map<[layout<[width:: height:: borderblock<[border:|h]4> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> attributes<> animation<>]1>]1> events<[objectCount.100 warpCount.100 scriptCount.100 signpostCount.100 objects<[id. graphics. unused:1 x:|z y:|z elevation.10 moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/objectCount> warps<[x:|z y:|z elevation.10 warpID. map. bank.]/warpCount> scripts<[x:|z y:|z elevation:10 trigger: index:: script<`xse`>]/scriptCount> signposts<[x:|z y:|z elevation.10 kind. unused:1 arg::|s=kind(0=<>|1=<>|2=<>|3=<>|4=<>)]/signpostCount>]1> mapscripts<[type. pointer<>]!00> connections<[count:: connections<[direction::mapdirections offset:: mapGroup. mapNum. unused:]/count>]1> music:songnames layoutID:data.maps.layouts+1 regionSectionID.data.maps.names cave. weather. mapType. unused: flags.|t|allowBiking.|allowEscaping.|allowRunning.|showMapName. battleType.]1>]?>]34 +data.maps.banks, , , , , 05524C, 05524C, 055260, 055260, , [maps<[map<[layout<[width:: height:: borderblock<> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> borderwidth. borderheight. unused:]1> events<[objectCount.100 warpCount.100 scriptCount.100 signpostCount.100 objects<[id. graphics. kind: x:|z y:|z elevation.10 moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/objectCount> warps<[x:|z y:|z elevation.10 warpID. map. bank.]/warpCount> scripts<[x:|z y:|z elevation:10 trigger: index:: script<`xse`>]/scriptCount> signposts<[x:|z y:|z elevation.10 kind. unused:1 arg::|s=kind(0=<>|1=<>|2=<>|3=<>|4=<>)]/signpostCount>]1> mapscripts<[type. pointer<>]!00> connections<[count:: connections<[direction::mapdirections offset:: mapGroup. mapNum. unused:]/count>]1> music:songnames layoutID:data.maps.layouts+1 regionSectionID.data.maps.names+88 cave. weather. mapType. allowBiking. flags.|t|allowEscaping.|allowRunning.|showMapName. floorNum. battleType.]1>]?>]43 data.maps.layouts, 05326C, 05326C, 05328C, 05328C, , , , , , [layout<>]332 data.maps.layouts, , , , , 055194, 055194, 0551A8, 0551A8, , [layout<>]383 data.maps.layouts, , , , , , , , , 0849CC, [layout<>]441 diff --git a/src/HexManiac.Core/Models/Map/MapModel.cs b/src/HexManiac.Core/Models/Map/MapModel.cs index c0c8fee4..9774d3c5 100644 --- a/src/HexManiac.Core/Models/Map/MapModel.cs +++ b/src/HexManiac.Core/Models/Map/MapModel.cs @@ -9,7 +9,7 @@ using System.Linq; namespace HavenSoft.HexManiac.Core.Models.Map { public record AllMapsModel(ModelTable Table) : IEnumerable { - public static AllMapsModel Create(IDataModel model, Func tokenFactory) => new(model.GetTableModel("data.maps.banks", tokenFactory)); + public static AllMapsModel Create(IDataModel model, Func tokenFactory = null) => new(model.GetTableModel("data.maps.banks", tokenFactory)); public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); @@ -118,6 +118,13 @@ namespace HavenSoft.HexManiac.Core.Models.Map { return warps.Select(obj => new WarpEventModel(obj)).ToList(); } } + public List Signposts { + get { + if (Element == null) return new(); + if (!Element.TryGetSubTable(Format.Signposts, out var signposts)) return new(); + return signposts.Select(sp => new SignpostEventModel(sp)).ToList(); + } + } } public record BaseEventModel(ModelArrayElement Element) { @@ -145,6 +152,11 @@ namespace HavenSoft.HexManiac.Core.Models.Map { public int Map => Element.GetValue("map"); } + public record SignpostEventModel(ModelArrayElement Element) : BaseEventModel(Element) { + public int Kind => Element.GetValue("kind"); + public int Arg => Element.GetValue("arg"); + } + public class Format { public static string RegionSection => "regionSectionID"; public static string Events => "events"; diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs index ede5dd5b..ecc69bee 100644 --- a/src/HexManiac.Core/Models/PokemonModel.cs +++ b/src/HexManiac.Core/Models/PokemonModel.cs @@ -1133,16 +1133,32 @@ namespace HavenSoft.HexManiac.Core.Models { var shorterTable = Math.Min(arrayRun.ElementCount, previousTable?.ElementCount ?? arrayRun.ElementCount); // i loops over the different segments in the array for (int i = 0; i < arrayRun.ElementContent.Count; i++) { - if (arrayRun.ElementContent[i].Type != ElementContentType.Pointer) { segmentOffset += arrayRun.ElementContent[i].Length; continue; } + var segment = arrayRun.ElementContent[i]; + + // record segments _might_ be pointers... sometimes. Need to check every element + if (segment is ArrayRunRecordSegment recordSeg) { + for (int j = 0; j < elementCount; j++) { + // segment=recordSeg.CreateConcrete(this,segmentOffset) + var start = segmentOffset + arrayRun.ElementLength * j; + segment = recordSeg.CreateConcrete(this, start); + if (segment.Type == ElementContentType.Pointer) { + if (formatMatches && shorterTable - parentOffset > j) continue; // we can skip this one + changeAnchors(arrayRun.ElementContent[i], arrayRun.ElementContent, j, changeToken, start); + } + } + segmentOffset += segment.Length; + continue; + } + if (arrayRun.ElementContent[i].Type != ElementContentType.Pointer) { segmentOffset += segment.Length; continue; } // for a pointer segment, j loops over all the elements in the array var range = elementCount.Range(); - if (arrayRun.ElementContent[i] is ArrayRunPointerSegment pSeg && pSeg.InnerFormat.EndsWith("?")) range = range.Reverse(); + if (segment is ArrayRunPointerSegment pSeg && pSeg.InnerFormat.EndsWith("?")) range = range.Reverse(); foreach (int j in range) { if (formatMatches && shorterTable - parentOffset > j) continue; // we can skip this one var start = segmentOffset + arrayRun.ElementLength * j; - changeAnchors(arrayRun.ElementContent[i], arrayRun.ElementContent, j, changeToken, start); + changeAnchors(segment, arrayRun.ElementContent, j, changeToken, start); } - segmentOffset += arrayRun.ElementContent[i].Length; + segmentOffset += segment.Length; } } @@ -1300,6 +1316,7 @@ namespace HavenSoft.HexManiac.Core.Models { /// /// private void AddPointerToAnchor(ArrayRunElementSegment segment, IReadOnlyList segments, int parentIndex, ModelDelta changeToken, int start) { + if (segment is ArrayRunRecordSegment recordSeg) segment = recordSeg.CreateConcrete(this, start); var destination = ReadPointer(start); if (destination < 0 || destination >= Count) return; var index = BinarySearch(destination); diff --git a/src/HexManiac.Core/Models/Runs/ArrayRun.cs b/src/HexManiac.Core/Models/Runs/ArrayRun.cs index a57ba396..d0dfb0fd 100644 --- a/src/HexManiac.Core/Models/Runs/ArrayRun.cs +++ b/src/HexManiac.Core/Models/Runs/ArrayRun.cs @@ -165,10 +165,35 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { return false; } - public static ErrorInfo NotifyChildren(this ITableRun self, IDataModel model, ModelDelta token, int elementIndex, int segmentIndex) { + private static void UpdateRecordType(ITableRun self, IDataModel model, ModelDelta token, int elementIndex, int segmentIndex, ArrayRunRecordSegment recordSegment, int previousValue) { + var offset = self.ElementContent.Take(segmentIndex).Sum(seg => seg.Length); + var sourceSegment = self.ElementContent[segmentIndex]; + var elementStart = self.Start + self.ElementLength * elementIndex; + var newValue = model.ReadMultiByteValue(elementStart + offset, sourceSegment.Length); + + if (previousValue == newValue) return; + var previousConcrete = recordSegment.CreateConcrete(model.FormatRunFactory, model.TextConverter, previousValue); + var newConcrete = recordSegment.CreateConcrete(model.FormatRunFactory, model.TextConverter, newValue); + if ((previousConcrete.Type == ElementContentType.Pointer) == (newConcrete.Type == ElementContentType.Pointer)) return; + var pointerOffset = self.ElementContent.Until(seg => seg == recordSegment).Sum(seg => seg.Length); + var pointerDestination = model.ReadPointer(elementStart + pointerOffset); + if (previousConcrete.Type == ElementContentType.Pointer) { + // not a pointer anymore, remove format from destination + model.ClearPointer(token, elementStart + pointerOffset, pointerDestination); + } + if (newConcrete.Type == ElementContentType.Pointer) { + // now a pointer, add format to destination + model.UpdateArrayPointer(token, newConcrete, self.ElementContent, elementIndex, elementStart + pointerOffset, pointerDestination); + } + } + + public static ErrorInfo NotifyChildren(this ITableRun self, IDataModel model, ModelDelta token, int elementIndex, int segmentIndex, int previousValue = 0xDedBeef) { int offset = 0; var info = ErrorInfo.NoError; foreach (var segment in self.ElementContent) { + if (previousValue != 0xDedBeef && segment is ArrayRunRecordSegment recordSegment && recordSegment.MatchField == self.ElementContent[segmentIndex].Name) { + UpdateRecordType(self, model, token, elementIndex, segmentIndex, recordSegment, previousValue); + } if (segment is ArrayRunPointerSegment pointerSegment) { var pointerSource = self.Start + elementIndex * self.ElementLength + offset; var destination = model.ReadPointer(pointerSource); diff --git a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs index cb0dffa6..38ca8430 100644 --- a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs +++ b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs @@ -377,7 +377,19 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { if (matchFieldOffset == table.ElementLength) return defaultConcrete; var offsets = table.ConvertByteOffsetToArrayOffset(offset); var matchFieldValue = model.ReadMultiByteValue(table.Start + offsets.ElementIndex * table.ElementLength + matchFieldOffset, table.ElementContent[matchFieldIndex].Length); + return CreateConcrete(model.FormatRunFactory, model.TextConverter, matchFieldValue); + } + + public ArrayRunElementSegment CreateConcrete(IFormatRunFactory formatRunFactory, ITextConverter textConverter, int matchFieldValue) { + var defaultConcrete = new ArrayRunElementSegment(Name, ElementContentType.Integer, Length, TextConverter); if (!EnumForValue.TryGetValue(matchFieldValue, out var enumName)) return defaultConcrete; + + if (enumName.StartsWith("<") && enumName.EndsWith(">")) { + enumName = enumName.Substring(1, enumName.Length - 2); + if (enumName.Length > 0) return new ArrayRunPointerSegment(formatRunFactory, Name, enumName); + return new ArrayRunElementSegment(Name, ElementContentType.Pointer, 4, textConverter); + } + return new ArrayRunEnumSegment(Name, Length, enumName); } diff --git a/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs b/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs index 3a177eed..d22a682a 100644 --- a/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs @@ -2043,17 +2043,25 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { return list; } + public EventGroupModel EventGroup { + get { + if (allOverworldSprites == null) allOverworldSprites = RenderOWs(model); + if (defaultOverworldSprite == null) defaultOverworldSprite = GetDefaultOW(model); + var map = GetMapModel(); + var eventsTable = map.GetSubTable("events"); + if (eventsTable == null) return null; + var eventElements = eventsTable[0]; + if (eventElements == null) return null; + var events = new EventGroupModel(ViewPort.Tools.CodeTool.ScriptParser, GotoAddress, eventElements, allOverworldSprites, defaultOverworldSprite, BerryInfo, group, this.map); + events.DataMoved += HandleEventDataMoved; + return events; + } + } + private IReadOnlyList GetEvents() { - if (allOverworldSprites == null) allOverworldSprites = RenderOWs(model); - if (defaultOverworldSprite == null) defaultOverworldSprite = GetDefaultOW(model); - var map = GetMapModel(); var results = new List(); - var eventsTable = map.GetSubTable("events"); - if (eventsTable == null) return results; - var eventElements = eventsTable[0]; - if (eventElements == null) return results; - var events = new EventGroupModel(ViewPort.Tools.CodeTool.ScriptParser, GotoAddress, eventElements, allOverworldSprites, defaultOverworldSprite, BerryInfo, group, this.map); - events.DataMoved += HandleEventDataMoved; + var events = EventGroup; + if (events == null) return results; results.AddRange(events.Objects); results.AddRange(events.Warps); results.AddRange(events.Scripts); diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index ef894480..2ca2c9fc 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -1,5 +1,6 @@ using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Code; +using HavenSoft.HexManiac.Core.Models.Map; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.Models.Runs.Sprites; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; @@ -939,6 +940,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public IDataModel Model { get; } public IDataModel ModelFor(Point p) => Model; + public AllMapsModel Maps => AllMapsModel.Create(Model, () => CurrentChange); public bool FormattedDataIsSelected { get { diff --git a/src/HexManiac.Core/ViewModels/Visitors/CompleteCellEdit.cs b/src/HexManiac.Core/ViewModels/Visitors/CompleteCellEdit.cs index 9d852b40..301f31fd 100644 --- a/src/HexManiac.Core/ViewModels/Visitors/CompleteCellEdit.cs +++ b/src/HexManiac.Core/ViewModels/Visitors/CompleteCellEdit.cs @@ -510,6 +510,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { return; } } + var previousValue = Model.ReadMultiByteValue(integer.Source, integer.Length); Model.WriteMultiByteValue(integer.Source, integer.Length, CurrentChange, result); if (result >= Math.Pow(2L, integer.Length * 8)) ErrorText = $"Warning: number was too big to fit in the available space."; int runIndex = integer.Source - run.Start; @@ -519,7 +520,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { } if (run is ITableRun tableRun) { var offset = tableRun.ConvertByteOffsetToArrayOffset(integer.Source); - var info = tableRun.NotifyChildren(Model, CurrentChange, offset.ElementIndex, offset.SegmentIndex); + var info = tableRun.NotifyChildren(Model, CurrentChange, offset.ElementIndex, offset.SegmentIndex, previousValue); scroll.DataLength = Model.Count; if (info != null && info.IsWarning) MessageText = info.ErrorMessage; } @@ -1040,6 +1041,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { private void UpdateArrayPointer(ITableRun run, int pointerDestination) { var offsets = run.ConvertByteOffsetToArrayOffset(memoryLocation); var segment = run.ElementContent[offsets.SegmentIndex]; + if (segment is ArrayRunRecordSegment recordSeg) segment = recordSeg.CreateConcrete(Model, offsets.SegmentStart); if (segment is ArrayRunPointerSegment pointerSegment) { if (!pointerSegment.DestinationDataMatchesPointerFormat(Model, CurrentChange, offsets.SegmentStart, pointerDestination, run.ElementContent, -1)) { ErrorText = $"This pointer must point to {pointerSegment.InnerFormat} data."; diff --git a/src/HexManiac.Tests/TableTests.cs b/src/HexManiac.Tests/TableTests.cs index 41033545..63d44e2f 100644 --- a/src/HexManiac.Tests/TableTests.cs +++ b/src/HexManiac.Tests/TableTests.cs @@ -1384,6 +1384,94 @@ namespace HavenSoft.HexManiac.Tests { Assert.Equal(3, child.ElementCount); } + [Fact] + public void TableWithPointerRecord_GetConcrete_ReturnsPointerFormat() { + ViewPort.Edit("^parent[arg:: target::|s=arg(2=<>)]2 "); + + ViewPort.Edit("2 "); + var table = Model.GetTable("parent"); + var raw = (ArrayRunRecordSegment)table.ElementContent[1]; + var segment = raw.CreateConcrete(Model, 4); + + Assert.Equal(ElementContentType.Pointer, segment.Type); + Assert.IsType(ViewPort[ViewPort.ConvertAddressToViewPoint(4)].Format); + } + + [Fact] + public void TableWithPointerRecord_DestinationKnowsAboutRecord() { + ViewPort.Edit("^parent[target::|s=arg(0=<>) arg::]1 "); + ViewPort.Edit("<100>"); + Model.ResolveConflicts(); + var source = Model.GetNextRun(0x100).PointerSources.Single(); + Assert.Equal(0, source); + } + + [Fact] + public void RecordPointer_BecomesPointer_PointerAddedToExistingAnchor() { + SetFullModel(0xFF); + "00 00 00 00 00 01 00 08".ToByteArray().WriteInto(Model.RawData, 0); + ViewPort.Edit("^parent[arg:: target::|s=arg(2=<>)]1 @080 <100>"); + + ViewPort.Edit("@000 2 "); + + Assert.Equal(2, Model.GetNextRun(0x100).PointerSources.Count); + } + + [Fact] + public void RecordPointer_BecomesPointer_CreateNewAnchor() { + SetFullModel(0xFF); + "00 00 00 00 00 01 00 08".ToByteArray().WriteInto(Model.RawData, 0); + ViewPort.Edit("^parent[arg:: target::|s=arg(2=<>)]1 "); + + ViewPort.Edit("@000 2 "); + + Assert.Equal(4, Model.GetNextRun(0x100).PointerSources.Single()); + } + + [Fact] + public void RecordPointer_CreateAsPointer_CreateNewAnchor() { + SetFullModel(0xFF); + "02 00 00 00 00 01 00 08".ToByteArray().WriteInto(Model.RawData, 0); + + ViewPort.Edit("@000 ^parent[arg:: target::|s=arg(2=<>)]1 "); + + Assert.Equal(4, Model.GetNextRun(0x100).PointerSources.Single()); + } + + [Fact] + public void RecordPointer_CreateAsPointer_PointerAddedToExistingAnchor() { + SetFullModel(0xFF); + "02 00 00 00 00 01 00 08".ToByteArray().WriteInto(Model.RawData, 0); + + ViewPort.Edit("@080 <100> @000 ^parent[arg:: target::|s=arg(2=<>)]1 "); + + Assert.Equal(2, Model.GetNextRun(0x100).PointerSources.Count); + } + + [Fact] + public void RecordPointer_CeaseBeingPointer_PointerRemovedFromExistingAnchor() { + SetFullModel(0xFF); + "02 00 00 00 00 01 00 08".ToByteArray().WriteInto(Model.RawData, 0); + ViewPort.Edit("@080 <100> @000 ^parent[arg:: target::|s=arg(2=<>)]1 "); + + ViewPort.Edit("@000 0 "); + + Assert.Single(Model.GetNextRun(0x100).PointerSources); + } + + [Fact] + public void RecordPointerToText_RepointText_UpdateRecord() { + SetFullModel(0xFF); + "00 00 00 00 00 00 00 00".ToByteArray().WriteInto(Model.RawData, 0); + ViewPort.Edit("^parent[arg:: target::|s=arg(2=<\"\">)]1 2 <100> @100 Hello\" @108 dead"); + + // cause the repoint + ViewPort.Edit("@100 Hello World!"); + + var destination = Model.ReadPointer(4); + Assert.NotEqual(0x100, destination); + } + private void ArrangeTrainerPokemonTeamData(byte structType, byte pokemonCount, int trainerCount) { CreateTextTable(HardcodeTablesModel.PokemonNameTable, 0x180, "ABCDEFGHIJKLMNOP".Select(c => c.ToString()).ToArray()); CreateTextTable(HardcodeTablesModel.MoveNamesTable, 0x1B0, "qrstuvwxyz".Select(c => c.ToString()).ToArray());