allow record segments to be pointers

Allow record segment formats to be pointers depending on the source value
This commit is contained in:
haven1433 2023-02-24 22:02:34 -06:00
parent 35e361a673
commit 07608b0f9c
9 changed files with 185 additions and 19 deletions

View File

@ -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

View File

@ -9,7 +9,7 @@ using System.Linq;
namespace HavenSoft.HexManiac.Core.Models.Map {
public record AllMapsModel(ModelTable Table) : IEnumerable<MapBankModel> {
public static AllMapsModel Create(IDataModel model, Func<ModelDelta> tokenFactory) => new(model.GetTableModel("data.maps.banks", tokenFactory));
public static AllMapsModel Create(IDataModel model, Func<ModelDelta> tokenFactory = null) => new(model.GetTableModel("data.maps.banks", tokenFactory));
public IEnumerator<MapBankModel> GetEnumerator() => Enumerate().GetEnumerator();
@ -118,6 +118,13 @@ namespace HavenSoft.HexManiac.Core.Models.Map {
return warps.Select(obj => new WarpEventModel(obj)).ToList();
}
}
public List<SignpostEventModel> 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";

View File

@ -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 {
/// <param name="changeToken"></param>
/// <param name="start"></param>
private void AddPointerToAnchor(ArrayRunElementSegment segment, IReadOnlyList<ArrayRunElementSegment> 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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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<IEventViewModel> GetEvents() {
if (allOverworldSprites == null) allOverworldSprites = RenderOWs(model);
if (defaultOverworldSprite == null) defaultOverworldSprite = GetDefaultOW(model);
var map = GetMapModel();
var results = new List<IEventViewModel>();
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);

View File

@ -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 {

View File

@ -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.";

View File

@ -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<Pointer>(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());