Merge branch 'trainer-pokemon-teams' into usability-updates-3-2

This commit is contained in:
Benjamin Popp
2019-11-05 11:10:49 -06:00
18 changed files with 518 additions and 148 deletions

View File

@@ -59,6 +59,7 @@
<Compile Include="Models\Runs\HeaderRow.cs" />
<Compile Include="Models\Runs\ModelCacheScope.cs" />
<Compile Include="Models\Runs\PLMRun.cs" />
<Compile Include="Models\Runs\TrainerPokemonTeamRun.cs" />
<Compile Include="Models\Runs\WordRun.cs" />
<Compile Include="ViewModels\AutoCompleteSelectionItem.cs" />
<Compile Include="ViewModels\QuickEditItems\IQuickEditItem.cs" />

View File

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

View File

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

View File

@@ -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);
@@ -101,7 +101,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;
@@ -251,27 +251,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<string>() { model.GetAnchorFromAddress(-1, arrayRun.Start) };
var visitedAddress = new List<int>() { arrayRun.Start };
var visitedNames = new List<string>() { model.GetAnchorFromAddress(-1, table.Start) };
var visitedAddress = new List<int>() { 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<string>();
for (int i = 0; i < visitedNames.Count; i++) {
@@ -284,9 +287,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);
}
@@ -314,7 +317,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<int, IFormattedRun> runsToRemove, IReadOnlyDictionary<int, IFormattedRun> runsToAdd, IReadOnlyDictionary<int, string> namesToRemove, IReadOnlyDictionary<int, string> namesToAdd, IReadOnlyDictionary<int, string> unmappedPointersToRemove, IReadOnlyDictionary<int, string> unmappedPointersToAdd, IReadOnlyDictionary<int, string> matchedWordsToRemove, IReadOnlyDictionary<int, string> matchedWordsToAdd) { }

View File

@@ -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!");
}
}
@@ -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;
}
@@ -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.
/// </summary>
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
@@ -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
@@ -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);
@@ -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, runAttempt.Length);
run = runAttempt;
} else {
throw new NotImplementedException();
}
@@ -1249,6 +1253,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);
@@ -1269,6 +1275,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) {
@@ -1368,19 +1376,9 @@ 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());
if (newRun is ITableRun array) {
UpdateAnchorsFromArrayMove(changeToken, (ITableRun)run, array);
}
int index = BinarySearch(run.Start);

View File

@@ -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<string> ElementNames { get; }
IReadOnlyList<ArrayRunElementSegment> 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 {
/// <summary>
/// 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 = ']';
@@ -296,7 +344,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) {
@@ -308,42 +356,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;
@@ -429,7 +446,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;
@@ -643,7 +660,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:

View File

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

View File

@@ -0,0 +1,285 @@
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 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 readonly IDataModel model;
public byte StructType { get; }
public TrainerPokemonTeamRun(IDataModel model, int start, IReadOnlyList<int> 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<ArrayRunElementSegment>();
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);
}
public IEnumerable<int> 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;
public override string FormatString => SharedFormatString;
public override IDataFormat CreateDataFormat(IDataModel data, int index) => this.CreateSegmentDataFormat(data, index);
protected override BaseRun Clone(IReadOnlyList<int> newPointerSources) => new TrainerPokemonTeamRun(model, Start, newPointerSources);
#endregion
#region ITableRun
public int ElementCount { get; }
public int ElementLength { get; }
public IReadOnlyList<string> ElementNames { get; } = new List<string>();
public IReadOnlyList<ArrayRunElementSegment> 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<int> levels = new List<int>();
private readonly List<int> pokemons = new List<int>();
private readonly List<int> ivs = new List<int>();
private readonly List<int> items = new List<int>();
private readonly List<int> moves = new List<int>();
public bool ItemsIncluded { get; private set; }
public bool MovesIncluded { get; private set; }
public IReadOnlyList<int> Levels => levels;
public IReadOnlyList<int> Pokemon => pokemons;
public IReadOnlyList<int> IVs => ivs;
public IReadOnlyList<int> Items => items;
public IReadOnlyList<int> 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<int> 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<string> itemNames, List<int> 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
}
}

View File

@@ -44,7 +44,7 @@ namespace HavenSoft.HexManiac.Core {
return true;
}
public static int IndexOfPartial(this IList<string> names, string input) {
public static int IndexOfPartial(this IReadOnlyList<string> names, string input) {
// perfect match first
var matchIndex = names.IndexOf(input);
if (matchIndex != -1) return matchIndex;

View File

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

View File

@@ -201,7 +201,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];
@@ -217,7 +217,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public ComboBoxArrayElementViewModel(Selection selection, ChangeHistory<ModelDelta> 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);
@@ -330,7 +330,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);

View File

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

View File

@@ -58,7 +58,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;
@@ -95,11 +95,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);
}
@@ -107,11 +107,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);
}
@@ -119,12 +119,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(viewPort.CurrentChange, ref array);
if (array.Start != originalArray.Start) {
@@ -151,7 +151,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;
@@ -161,6 +161,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]}";
@@ -168,18 +169,18 @@ 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
// add content from this table
AddChildrenFromTable(array, index);
// add content from related tables
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);
}
}
}
@@ -210,7 +211,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
}
}
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;
@@ -231,9 +232,9 @@ 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) {
var streamElement = new StreamArrayElementViewModel(viewPort, (FieldArrayElementViewModel)viewModel, model, item.Name, itemAddress);
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);
int parentIndex = Children.Count - 1;
var streamElementName = item.Name;
var streamAddress = itemAddress;

View File

@@ -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;
@@ -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;
@@ -558,7 +558,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();
@@ -604,11 +604,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);
}
@@ -702,7 +702,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);
@@ -868,6 +868,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
if (child is PLMRun plmRun && parentArrayName == EggMoveRun.MoveNamesTable) {
foreach (var result in plmRun.Search(parentArrayName, offsets.ElementIndex)) yield return result;
}
// 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);
}
}
}
}
@@ -1014,7 +1020,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;
@@ -1031,7 +1037,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);
@@ -1150,7 +1156,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);
@@ -1252,7 +1258,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;
@@ -1322,8 +1328,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);
}
@@ -1493,8 +1499,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) {

View File

@@ -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,11 +457,11 @@ 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) {
if (!pointerSegment.DestinationDataMatchesPointerFormat(Model, CurrentChange, pointerDestination)) {
if (!pointerSegment.DestinationDataMatchesPointerFormat(Model, CurrentChange, offsets.SegmentStart, pointerDestination)) {
ErrorText = $"This pointer must point to {pointerSegment.InnerFormat} data.";
return;
}

View File

@@ -63,7 +63,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
Results.Add(new ContextItem("Repoint to New Copy", arg => ViewPort.RepointToNewCopy(pointerAddress)));
}
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());
}
@@ -101,7 +101,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());
}
@@ -113,12 +113,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));
}
@@ -137,13 +137,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<IContextItem> GetTableChildren(ArrayRun array) {
private IEnumerable<IContextItem> GetTableChildren(ITableRun array) {
if (ViewPort.Tools.TableTool.Append.CanExecute(null)) {
yield return new ContextItem("Extend Table", ViewPort.Tools.TableTool.Append.Execute);
}

View File

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

View File

@@ -1,5 +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;
@@ -17,5 +19,52 @@ 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);
}
[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]);
}
[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());
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");
}
}
}