/// <summary> /// Uses the hint, as well as this sprite's table location (if any), to find palettes that can be applied to this sprite. /// (1) if the sprite's hint is the name of a palette, return that. Example: title screen pokemon sprite/palette pair. /// (2) if the sprite's hint is the name of an enum table, use that enum's source as a list of palettes and get the appropriate one from the matching index of the enum table. Example: pokemon icons /// (3) if the sprite's hint is a table name followed by a key=value pair, go grab the a palette from the element within that table such that it's key equals that value. Example: Overworld sprites /// (4) if the sprite's hint is a table name, return all palettes within the matching index of that table. Example: trainer sprites/palettes. /// (5) if the sprite has no hint, return all palettes in arrays with matching length from the same index. Example: pokemon sprites. Leaving it empty allows both normal and shiny palettes to match. /// </summary> public static IReadOnlyList <IPaletteRun> FindRelatedPalettes(this ISpriteRun spriteRun, IDataModel model, int primarySource = -1, string hint = null, bool includeAllTableIndex = false) { // find all palettes that could be applied to this sprite run var noChange = new NoDataChangeDeltaModel(); var results = new List <IPaletteRun>(); if (spriteRun?.SpriteFormat.BitsPerPixel < 4) { return(results); // 1- and 2-bit sprites don't have palettes } hint = hint ?? spriteRun?.SpriteFormat.PaletteHint; if (primarySource == -1) { var pointerCount = spriteRun?.PointerSources?.Count ?? 0; for (int i = 0; i < pointerCount; i++) { if (!(model.GetNextRun(spriteRun.PointerSources[i]) is ArrayRun)) { continue; } primarySource = spriteRun.PointerSources[i]; break; } } var spriteTable = model.GetNextRun(primarySource) as ITableRun; var offset = spriteTable?.ConvertByteOffsetToArrayOffset(primarySource) ?? new ArrayOffset(-1, -1, -1, -1); if (primarySource < 0) { offset = new ArrayOffset(-1, -1, -1, -1); } if (!string.IsNullOrEmpty(hint)) { var address = model.GetAddressFromAnchor(noChange, -1, hint); var run = model.GetNextRun(address); if (run is IPaletteRun palRun && palRun.Start == address) { // option 1: hint is to a palette results.Add(palRun); return(results); } else if (run is ArrayRun enumArray && enumArray.ElementContent.Count == 1 && enumArray.ElementContent[0] is ArrayRunEnumSegment enumSegment) { // option 2: hint is to index into paletteTable, and I'm in a table var paletteTable = model.GetNextRun(model.GetAddressFromAnchor(noChange, -1, enumSegment.EnumName)) as ITableRun; if (offset.ElementIndex != -1 && paletteTable != null) { var paletteIndex = model.ReadMultiByteValue(enumArray.Start + enumArray.ElementLength * offset.ElementIndex, enumArray.ElementLength); var destination = model.ReadPointer(paletteTable.Start + paletteTable.ElementLength * paletteIndex); var tempRun = model.GetNextRun(destination); if (tempRun is IPaletteRun pRun && pRun.Start == destination) { results.Add(pRun); } } }
private static ISpriteRun Resize(IDataModel model, ModelDelta token, ISpriteRun spriteRun, int tileWidth, int tileHeight) { if (spriteRun == null) { return(spriteRun); } spriteRun = model.RelocateForExpansion(token, spriteRun, tileWidth * tileHeight * TileSize); var format = spriteRun.SpriteFormat; // extract existing tile data var existingTiles = new byte[format.TileWidth, format.TileHeight][]; for (int x = 0; x < format.TileWidth; x++) { for (int y = 0; y < format.TileHeight; y++) { var tileIndex = y * format.TileWidth + x; existingTiles[x, y] = new byte[TileSize]; Array.Copy(model.RawData, spriteRun.Start + tileIndex * TileSize, existingTiles[x, y], 0, TileSize); } } // rewrite it with the new dimensions for (int x = 0; x < tileWidth; x++) { for (int y = 0; y < tileHeight; y++) { var tileIndex = y * tileWidth + x; var start = spriteRun.Start + tileIndex * TileSize; for (int i = 0; i < TileSize; i++) { if (x < format.TileWidth && y < format.TileHeight) { token.ChangeData(model, start + i, existingTiles[x, y][i]); } else { token.ChangeData(model, start + i, 0); } } } } return(spriteRun); }
/// <summary> /// Uses the hint, as well as this sprite's table location (if any), to find palettes that can be applied to this sprite. /// (1) if the sprite's hint is the name of a palette, return that. Example: title screen pokemon sprite/palette pair. /// (2) if the sprite's hint is the name of an enum table, use that enum's source as a list of palettes and get the appropriate one from the matching index of the enum table. Example: pokemon icons /// (3) if the sprite's hint is a table name followed by a key=value pair, go grab the a palette from the element within that table such that it's key equals that value. Example: Overworld sprites /// (4) if the sprite's hint is a table name, return all palettes within the matching index of that table. Example: trainer sprites/palettes. /// (5) if the sprite has no hint, return all palettes in arrays with matching length from the same index. Example: pokemon sprites. Leaving it empty allows both normal and shiny palettes to match. /// </summary> public static IReadOnlyList <IPaletteRun> FindRelatedPalettes(this ISpriteRun spriteRun, IDataModel model, int primarySource = -1, string hint = null) { // find all palettes that could be applied to this sprite run var noChange = new NoDataChangeDeltaModel(); var results = new List <IPaletteRun>(); hint = hint ?? spriteRun?.SpriteFormat.PaletteHint; if (primarySource == -1) { var pointerCount = spriteRun?.PointerSources?.Count ?? 0; for (int i = 0; i < pointerCount; i++) { if (!(model.GetNextRun(spriteRun.PointerSources[i]) is ArrayRun)) { continue; } primarySource = spriteRun.PointerSources[i]; break; } } var spriteTable = model.GetNextRun(primarySource) as ArrayRun; var offset = spriteTable?.ConvertByteOffsetToArrayOffset(primarySource) ?? new ArrayOffset(-1, -1, -1, -1); if (primarySource < 0) { offset = new ArrayOffset(-1, -1, -1, -1); } if (!string.IsNullOrEmpty(hint)) { var run = model.GetNextRun(model.GetAddressFromAnchor(noChange, -1, hint)); if (run is IPaletteRun palRun) { // option 1: hint is to a palette results.Add(palRun); return(results); } else if (run is ArrayRun enumArray && enumArray.ElementContent.Count == 1 && enumArray.ElementContent[0] is ArrayRunEnumSegment enumSegment) { // option 2: hint is to index into paletteTable, and I'm in a table var paletteTable = model.GetNextRun(model.GetAddressFromAnchor(noChange, -1, enumSegment.EnumName)) as ArrayRun; if (offset.ElementIndex != -1) { var paletteIndex = model.ReadMultiByteValue(enumArray.Start + enumArray.ElementLength * offset.ElementIndex, enumArray.ElementLength); if (model.GetNextRun(model.ReadPointer(paletteTable.Start + paletteTable.ElementLength * paletteIndex)) is IPaletteRun pRun) { results.Add(pRun); } } } else if (hint.Contains(":")) { // option 3: hint is a table name, followed by a identifier=value pair var tableKeyPair = hint.Split(':'); var identifierValuePair = tableKeyPair.Length == 2 ? tableKeyPair[1].Split("=") : new string[0]; if (identifierValuePair.Length == 2) { var paletteTable = model.GetNextRun(model.GetAddressFromAnchor(noChange, -1, tableKeyPair[0])) as ITableRun; var segment = paletteTable?.ElementContent.FirstOrDefault(seg => seg.Name == identifierValuePair[0]); var pSegment = paletteTable?.ElementContent.FirstOrDefault(seg => seg is ArrayRunPointerSegment pSeg && PaletteRun.TryParsePaletteFormat(pSeg.InnerFormat, out var _)); var rawValue = identifierValuePair[1]; int keyValue; if (segment is ArrayRunEnumSegment eSegment) { keyValue = eSegment.GetOptions(model).ToList().IndexOf(rawValue); } else if (segment is ArrayRunHexSegment hSegment) { int.TryParse(rawValue, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out keyValue); } else { int.TryParse(rawValue, out keyValue); } if (segment != null) { var segmentOffset = paletteTable.ElementContent.Until(seg => seg == segment).Sum(seg => seg.Length); var pSegmentOffset = paletteTable.ElementContent.Until(seg => seg == pSegment).Sum(seg => seg.Length); var tableIndex = Enumerable.Range(0, paletteTable.ElementCount).FirstOrDefault(i => model.ReadMultiByteValue(paletteTable.Start + i * paletteTable.ElementLength + segmentOffset, segment.Length) == keyValue); var paletteStart = model.ReadPointer(paletteTable.Start + tableIndex * paletteTable.ElementLength + pSegmentOffset); if (model.GetNextRun(paletteStart) is IPaletteRun pRun) { results.Add(pRun); } } } } // option 4: I'm in a table, and my hint is a table name if (offset.ElementIndex == -1) { return(results); } if (!(run is ArrayRun array)) { return(results); } results.AddRange(model.GetPointedChildren <IPaletteRun>(array, offset.ElementIndex)); }