using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Runs.Sprites; using HavenSoft.HexManiac.Core.ViewModels.Images; using HavenSoft.HexManiac.Core.ViewModels.Tools; using HexManiac.Core.Models.Runs.Sprites; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; /* #define MB_NORMAL 0x00 #define MB_SECRET_BASE_WALL 0x01 #define MB_TALL_GRASS 0x02 #define MB_LONG_GRASS 0x03 #define MB_UNUSED_04 0x04 #define MB_UNUSED_05 0x05 #define MB_DEEP_SAND 0x06 #define MB_SHORT_GRASS 0x07 #define MB_CAVE 0x08 #define MB_LONG_GRASS_SOUTH_EDGE 0x09 #define MB_NO_RUNNING 0x0A #define MB_INDOOR_ENCOUNTER 0x0B #define MB_MOUNTAIN_TOP 0x0C #define MB_BATTLE_PYRAMID_WARP 0x0D #define MB_MOSSDEEP_GYM_WARP 0x0E #define MB_MT_PYRE_HOLE 0x0F #define MB_POND_WATER 0x10 #define MB_SEMI_DEEP_WATER 0x11 #define MB_DEEP_WATER 0x12 #define MB_WATERFALL 0x13 #define MB_SOOTOPOLIS_DEEP_WATER 0x14 #define MB_OCEAN_WATER 0x15 #define MB_PUDDLE 0x16 #define MB_SHALLOW_WATER 0x17 #define MB_UNUSED_SOOTOPOLIS_DEEP_WATER 0x18 #define MB_NO_SURFACING 0x19 #define MB_UNUSED_SOOTOPOLIS_DEEP_WATER_2 0x1A #define MB_STAIRS_OUTSIDE_ABANDONED_SHIP 0x1B #define MB_SHOAL_CAVE_ENTRANCE 0x1C #define MB_UNUSED_1D 0x1D #define MB_UNUSED_1E 0x1E #define MB_UNUSED_1F 0x1F #define MB_ICE 0x20 #define MB_SAND 0x21 #define MB_SEAWEED 0x22 #define MB_UNUSED_23 0x23 #define MB_ASHGRASS 0x24 #define MB_FOOTPRINTS 0x25 #define MB_THIN_ICE 0x26 #define MB_CRACKED_ICE 0x27 #define MB_HOT_SPRINGS 0x28 #define MB_LAVARIDGE_GYM_B1F_WARP 0x29 #define MB_SEAWEED_NO_SURFACING 0x2A #define MB_REFLECTION_UNDER_BRIDGE 0x2B #define MB_UNUSED_2C 0x2C #define MB_UNUSED_2D 0x2D #define MB_UNUSED_2E 0x2E #define MB_UNUSED_2F 0x2F #define MB_IMPASSABLE_EAST 0x30 #define MB_IMPASSABLE_WEST 0x31 #define MB_IMPASSABLE_NORTH 0x32 #define MB_IMPASSABLE_SOUTH 0x33 #define MB_IMPASSABLE_NORTHEAST 0x34 #define MB_IMPASSABLE_NORTHWEST 0x35 #define MB_IMPASSABLE_SOUTHEAST 0x36 #define MB_IMPASSABLE_SOUTHWEST 0x37 #define MB_JUMP_EAST 0x38 #define MB_JUMP_WEST 0x39 #define MB_JUMP_NORTH 0x3A #define MB_JUMP_SOUTH 0x3B #define MB_JUMP_NORTHEAST 0x3C #define MB_JUMP_NORTHWEST 0x3D #define MB_JUMP_SOUTHEAST 0x3E #define MB_JUMP_SOUTHWEST 0x3F #define MB_WALK_EAST 0x40 #define MB_WALK_WEST 0x41 #define MB_WALK_NORTH 0x42 #define MB_WALK_SOUTH 0x43 #define MB_SLIDE_EAST 0x44 #define MB_SLIDE_WEST 0x45 #define MB_SLIDE_NORTH 0x46 #define MB_SLIDE_SOUTH 0x47 #define MB_TRICK_HOUSE_PUZZLE_8_FLOOR 0x48 #define MB_UNUSED_49 0x49 #define MB_UNUSED_4A 0x4A #define MB_UNUSED_4B 0x4B #define MB_UNUSED_4C 0x4C #define MB_UNUSED_4D 0x4D #define MB_UNUSED_4E 0x4E #define MB_UNUSED_4F 0x4F #define MB_EASTWARD_CURRENT 0x50 #define MB_WESTWARD_CURRENT 0x51 #define MB_NORTHWARD_CURRENT 0x52 #define MB_SOUTHWARD_CURRENT 0x53 #define MB_UNUSED_54 0x54 #define MB_UNUSED_55 0x55 #define MB_UNUSED_56 0x56 #define MB_UNUSED_57 0x57 #define MB_UNUSED_58 0x58 #define MB_UNUSED_59 0x59 #define MB_UNUSED_5A 0x5A #define MB_UNUSED_5B 0x5B #define MB_UNUSED_5C 0x5C #define MB_UNUSED_5D 0x5D #define MB_UNUSED_5E 0x5E #define MB_UNUSED_5F 0x5F #define MB_NON_ANIMATED_DOOR 0x60 #define MB_LADDER 0x61 #define MB_EAST_ARROW_WARP 0x62 #define MB_WEST_ARROW_WARP 0x63 #define MB_NORTH_ARROW_WARP 0x64 #define MB_SOUTH_ARROW_WARP 0x65 #define MB_CRACKED_FLOOR_HOLE 0x66 #define MB_AQUA_HIDEOUT_WARP 0x67 #define MB_LAVARIDGE_GYM_1F_WARP 0x68 #define MB_ANIMATED_DOOR 0x69 #define MB_UP_ESCALATOR 0x6A #define MB_DOWN_ESCALATOR 0x6B #define MB_WATER_DOOR 0x6C #define MB_WATER_SOUTH_ARROW_WARP 0x6D #define MB_DEEP_SOUTH_WARP 0x6E #define MB_UNUSED_6F 0x6F #define MB_BRIDGE_OVER_OCEAN 0x70 #define MB_BRIDGE_OVER_POND_LOW 0x71 #define MB_BRIDGE_OVER_POND_MED 0x72 #define MB_BRIDGE_OVER_POND_HIGH 0x73 #define MB_PACIFIDLOG_VERTICAL_LOG_TOP 0x74 #define MB_PACIFIDLOG_VERTICAL_LOG_BOTTOM 0x75 #define MB_PACIFIDLOG_HORIZONTAL_LOG_LEFT 0x76 #define MB_PACIFIDLOG_HORIZONTAL_LOG_RIGHT 0x77 #define MB_FORTREE_BRIDGE 0x78 #define MB_UNUSED_79 0x79 #define MB_BRIDGE_OVER_POND_MED_EDGE_1 0x7A #define MB_BRIDGE_OVER_POND_MED_EDGE_2 0x7B #define MB_BRIDGE_OVER_POND_HIGH_EDGE_1 0x7C #define MB_BRIDGE_OVER_POND_HIGH_EDGE_2 0x7D #define MB_UNUSED_BRIDGE 0x7E #define MB_BIKE_BRIDGE_OVER_BARRIER 0x7F #define MB_COUNTER 0x80 #define MB_UNUSED_81 0x81 #define MB_UNUSED_82 0x82 #define MB_PC 0x83 #define MB_CABLE_BOX_RESULTS_1 0x84 #define MB_REGION_MAP 0x85 #define MB_TELEVISION 0x86 #define MB_POKEBLOCK_FEEDER 0x87 #define MB_UNUSED_88 0x88 #define MB_SLOT_MACHINE 0x89 #define MB_ROULETTE 0x8A #define MB_CLOSED_SOOTOPOLIS_DOOR 0x8B #define MB_TRICK_HOUSE_PUZZLE_DOOR 0x8C #define MB_PETALBURG_GYM_DOOR 0x8D #define MB_RUNNING_SHOES_INSTRUCTION 0x8E #define MB_QUESTIONNAIRE 0x8F #define MB_SECRET_BASE_SPOT_RED_CAVE 0x90 #define MB_SECRET_BASE_SPOT_RED_CAVE_OPEN 0x91 #define MB_SECRET_BASE_SPOT_BROWN_CAVE 0x92 #define MB_SECRET_BASE_SPOT_BROWN_CAVE_OPEN 0x93 #define MB_SECRET_BASE_SPOT_YELLOW_CAVE 0x94 #define MB_SECRET_BASE_SPOT_YELLOW_CAVE_OPEN 0x95 #define MB_SECRET_BASE_SPOT_TREE_LEFT 0x96 #define MB_SECRET_BASE_SPOT_TREE_LEFT_OPEN 0x97 #define MB_SECRET_BASE_SPOT_SHRUB 0x98 #define MB_SECRET_BASE_SPOT_SHRUB_OPEN 0x99 #define MB_SECRET_BASE_SPOT_BLUE_CAVE 0x9A #define MB_SECRET_BASE_SPOT_BLUE_CAVE_OPEN 0x9B #define MB_SECRET_BASE_SPOT_TREE_RIGHT 0x9C #define MB_SECRET_BASE_SPOT_TREE_RIGHT_OPEN 0x9D #define MB_UNUSED_9E 0x9E #define MB_UNUSED_9F 0x9F #define MB_BERRY_TREE_SOIL 0xA0 #define MB_UNUSED_A1 0xA1 #define MB_UNUSED_A2 0xA2 #define MB_UNUSED_A3 0xA3 #define MB_UNUSED_A4 0xA4 #define MB_UNUSED_A5 0xA5 #define MB_UNUSED_A6 0xA6 #define MB_UNUSED_A7 0xA7 #define MB_UNUSED_A8 0xA8 #define MB_UNUSED_A9 0xA9 #define MB_UNUSED_AA 0xAA #define MB_UNUSED_AB 0xAB #define MB_UNUSED_AC 0xAC #define MB_UNUSED_AD 0xAD #define MB_UNUSED_AE 0xAE #define MB_UNUSED_AF 0xAF #define MB_SECRET_BASE_PC 0xB0 #define MB_SECRET_BASE_REGISTER_PC 0xB1 #define MB_SECRET_BASE_SCENERY 0xB2 #define MB_SECRET_BASE_TRAINER_SPOT 0xB3 #define MB_SECRET_BASE_DECORATION 0xB4 #define MB_HOLDS_SMALL_DECORATION 0xB5 #define MB_UNUSED_B6 0xB6 #define MB_SECRET_BASE_NORTH_WALL 0xB7 #define MB_SECRET_BASE_BALLOON 0xB8 #define MB_SECRET_BASE_IMPASSABLE 0xB9 #define MB_SECRET_BASE_GLITTER_MAT 0xBA #define MB_SECRET_BASE_JUMP_MAT 0xBB #define MB_SECRET_BASE_SPIN_MAT 0xBC #define MB_SECRET_BASE_SOUND_MAT 0xBD #define MB_SECRET_BASE_BREAKABLE_DOOR 0xBE #define MB_SECRET_BASE_SAND_ORNAMENT 0xBF #define MB_IMPASSABLE_SOUTH_AND_NORTH 0xC0 #define MB_IMPASSABLE_WEST_AND_EAST 0xC1 #define MB_SECRET_BASE_HOLE 0xC2 #define MB_HOLDS_LARGE_DECORATION 0xC3 #define MB_SECRET_BASE_TV_SHIELD 0xC4 #define MB_PLAYER_ROOM_PC_ON 0xC5 #define MB_SECRET_BASE_DECORATION_BASE 0xC6 #define MB_SECRET_BASE_POSTER 0xC7 #define MB_UNUSED_C8 0xC8 #define MB_UNUSED_C9 0xC9 #define MB_UNUSED_CA 0xCA #define MB_UNUSED_CB 0xCB #define MB_UNUSED_CC 0xCC #define MB_UNUSED_CD 0xCD #define MB_UNUSED_CE 0xCE #define MB_UNUSED_CF 0xCF #define MB_MUDDY_SLOPE 0xD0 #define MB_BUMPY_SLOPE 0xD1 #define MB_CRACKED_FLOOR 0xD2 #define MB_ISOLATED_VERTICAL_RAIL 0xD3 #define MB_ISOLATED_HORIZONTAL_RAIL 0xD4 #define MB_VERTICAL_RAIL 0xD5 #define MB_HORIZONTAL_RAIL 0xD6 #define MB_UNUSED_D7 0xD7 #define MB_UNUSED_D8 0xD8 #define MB_UNUSED_D9 0xD9 #define MB_UNUSED_DA 0xDA #define MB_UNUSED_DB 0xDB #define MB_UNUSED_DC 0xDC #define MB_UNUSED_DD 0xDD #define MB_UNUSED_DE 0xDE #define MB_UNUSED_DF 0xDF #define MB_PICTURE_BOOK_SHELF 0xE0 #define MB_BOOKSHELF 0xE1 #define MB_POKEMON_CENTER_BOOKSHELF 0xE2 #define MB_VASE 0xE3 #define MB_TRASH_CAN 0xE4 #define MB_SHOP_SHELF 0xE5 #define MB_BLUEPRINT 0xE6 #define MB_CABLE_BOX_RESULTS_2 0xE7 #define MB_WIRELESS_BOX_RESULTS 0xE8 #define MB_TRAINER_HILL_TIMER 0xE9 #define MB_SKY_PILLAR_CLOSED_DOOR 0xEA #define MB_UNUSED_EB 0xEB #define MB_UNUSED_EC 0xEC #define MB_UNUSED_ED 0xED #define MB_UNUSED_EE 0xEE #define MB_UNUSED_EF 0xEF #define NUM_METATILE_BEHAVIORS 0xF0 #define MB_INVALID 0xFF */ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public abstract class TileAttribute { public int Behavior { get; set; } public int Layer { get; set; } public abstract byte[] Serialize(); public static TileAttribute Create(byte[] data) { if (data.Length == 2) return new AttributeRSE(data); return new AttributeFRLG(data); } } /* [METATILE_ATTRIBUTE_BEHAVIOR] = 0x000001ff, [METATILE_ATTRIBUTE_TERRAIN] = 0x00003e00, 00=normal 01=grass 10=water [METATILE_ATTRIBUTE_2] = 0x0003c000, [METATILE_ATTRIBUTE_3] = 0x00fc0000, [METATILE_ATTRIBUTE_ENCOUNTER_TYPE] = 0x07000000, 00=none, 01=land, 10=water [METATILE_ATTRIBUTE_5] = 0x18000000, [METATILE_ATTRIBUTE_LAYER_TYPE] = 0x60000000, 00=normal, 01=covered, 10=split, (reserve 11 for triple-layer tile expansion) [METATILE_ATTRIBUTE_7] = 0x80000000 */ public class AttributeFRLG : TileAttribute { public int Terrain { get; set; } public int Encounter { get; set; } public AttributeFRLG(byte[] data) { Behavior = data[0]; Terrain = data[1] >> 1; Encounter = data[3] & 3; Layer = data[3] >> 5; Debug.Assert((data.ReadMultiByteValue(0, 4) & 0x9CFFF900) == 0, "Expected attribute mask 9CFFF900 to be zero!"); } public override byte[] Serialize() { var result = new byte[4]; result[0] = (byte)Behavior; result[1] = (byte)(Terrain << 1); result[2] = 0; result[3] = (byte)((Layer << 5) | Encounter); return result; } } /* #define METATILE_ATTR_BEHAVIOR_MASK 0x00FF 'behavior' (see below) #define METATILE_ATTR_LAYER_MASK 0x3000 00=normal, 01=covered, 10=split */ public class AttributeRSE : TileAttribute { public AttributeRSE(byte[] data) { Behavior = data[0]; Layer = data[1] >> 4; Debug.Assert((data[1] & 0xF) == 0, "Expected attribute mask 0F00 to be zero!"); } public override byte[] Serialize() { var result = new byte[2]; result[0] = (byte)Behavior; result[1] = (byte)(Layer << 4); return result; } } public class BlockEditor : ViewModelCore { private readonly ChangeHistory history; private readonly short[][] palettes; private readonly int[][,] tiles; private readonly byte[][] blocks; private readonly byte[][] blockAttributes; private readonly IDictionary indexForTileImage; private readonly CanvasPixelViewModel[] images; private int hoverTile; private int layerMode; public int LayerMode { get => layerMode; set => Set(ref layerMode, value, arg => EnterTile(images[hoverTile])); } private int blockIndex = 0; public int BlockIndex { get => blockIndex; set => Set(ref blockIndex, value.LimitToRange(0, blocks.Length - 1), UpdateBlockUI); } private static readonly List<((int, int), (int, int))> bottomLayerTogether, topLayerTogether, twoSets; static BlockEditor() { int p0 = 0, p1 = 24, p2 = 48, p3 = 72, p4 = 96, shortD = 8; bottomLayerTogether = new() { ((p1 - shortD, p1), (p1, p1 - shortD)), ((p3 , p1), (p2, p1 - shortD)), ((p1 - shortD, p2), (p1, p3 )), ((p3 , p2), (p2, p3 )), ((p1 , p0), (p0, p1 )), ((p3 - shortD, p0), (p3, p1 )), ((p1 , p3), (p0, p3 - shortD)), ((p3 - shortD, p3), (p3, p3 - shortD)), }; topLayerTogether = new() { ((p1 , p0), (p0, p1 )), ((p3 - shortD, p0), (p3, p1 )), ((p1 , p3), (p0, p3 - shortD)), ((p3 - shortD, p3), (p3, p3 - shortD)), ((p1 - shortD, p1), (p1, p1 - shortD)), ((p3 , p1), (p2, p1 - shortD)), ((p1 - shortD, p2), (p1, p3 )), ((p3 , p2), (p2, p3 )), }; twoSets = new() { ((p2 - shortD, p2), (p2, p2 - shortD)), ((p4 , p2), (p3, p2 - shortD)), ((p2 - shortD, p3), (p2, p4 )), ((p4 , p3), (p3, p4 )), ((p0 - shortD, p0), (p0, p0 - shortD)), ((p2 , p0), (p1, p0 - shortD)), ((p0 - shortD, p1), (p0, p2 )), ((p2 , p1), (p1, p2 )), }; } public event EventHandler AutoscrollTiles; public event EventHandler BlocksChanged; public event EventHandler BlockAttributesChanged; private IPixelViewModel tileRender; public IPixelViewModel TileRender => tileRender; private IPixelViewModel drawTileRender; public IPixelViewModel DrawTileRender => drawTileRender; public BlockEditor(ChangeHistory history, IDataModel listSource, short[][] palettes, int[][,] tiles, byte[][] blocks, byte[][] blockAttributes) { this.history = history; this.palettes = palettes; this.tiles = tiles; this.blocks = blocks; this.blockAttributes = blockAttributes; images = new CanvasPixelViewModel[8]; indexForTileImage = new Dictionary(); if (listSource.TryGetList("MapAttributeBehaviors", out var list)) { foreach (var item in list) BehaviorOptions.Add(item); } foreach (var palette in palettes) Palettes.Add(new ReadonlyPaletteCollection(palette)); } public IPixelViewModel LeftTopBack => images[0]; public IPixelViewModel RightTopBack => images[1]; public IPixelViewModel LeftBottomBack => images[2]; public IPixelViewModel RightBottomBack => images[3]; public IPixelViewModel LeftTopFront => images[4]; public IPixelViewModel RightTopFront => images[5]; public IPixelViewModel LeftBottomFront => images[6]; public IPixelViewModel RightBottomFront => images[7]; private static readonly string[] imageNames = "LeftTopBack,RightTopBack,LeftBottomBack,RightBottomBack,LeftTopFront,RightTopFront,LeftBottomFront,RightBottomFront".Split(","); #region FlipV / FlipH private int flipVLeft, flipVTop, flipHLeft, flipHTop; public int FlipVLeft { get => flipVLeft; private set => Set(ref flipVLeft, value); } public int FlipVTop { get => flipVTop; private set => Set(ref flipVTop, value); } public int FlipHLeft { get => flipHLeft; private set => Set(ref flipHLeft, value); } public int FlipHTop { get => flipHTop; private set => Set(ref flipHTop, value); } private bool flipVVisible, flipHVisible; public bool FlipVVisible { get => flipVVisible; set => Set(ref flipVVisible, value); } public bool FlipHVisible { get => flipHVisible; set => Set(ref flipHVisible, value); } public void FlipH() { var (pal, hFlip, vFlip, tile) = LzTilemapRun.ReadTileData(blocks[blockIndex], hoverTile, 2); hFlip = !hFlip; LzTilemapRun.WriteTileData(blocks[blockIndex], hoverTile, pal, hFlip, vFlip, tile); var newImage = BlocksetModel.Read(blocks[blockIndex], hoverTile, tiles, palettes); images[hoverTile].Fill(newImage.PixelData); BlocksChanged?.Invoke(this, blocks); } public void FlipV() { var (pal, hFlip, vFlip, tile) = LzTilemapRun.ReadTileData(blocks[blockIndex], hoverTile, 2); vFlip = !vFlip; LzTilemapRun.WriteTileData(blocks[blockIndex], hoverTile, pal, hFlip, vFlip, tile); var newImage = BlocksetModel.Read(blocks[blockIndex], hoverTile, tiles, palettes); images[hoverTile].Fill(newImage.PixelData); BlocksChanged?.Invoke(this, blocks); } #endregion public void EnterTile(IPixelViewModel tile) { FlipVVisible = true; FlipHVisible = true; var index = indexForTileImage[tile]; ((FlipVLeft, FlipVTop), (FlipHLeft, FlipHTop)) = layerMode == 0 ? twoSets[index] : layerMode == 1 ? bottomLayerTogether[index] : topLayerTogether[index]; hoverTile = indexForTileImage[tile]; } public void DrawOnTile(IPixelViewModel tile) { if (!showTiles) return; var index = indexForTileImage[tile]; LzTilemapRun.WriteTileData(blocks[blockIndex], index, drawPalette, drawFlipH, drawFlipV, drawTile); var newImage = BlocksetModel.Read(blocks[blockIndex], index, tiles, palettes); images[hoverTile].Fill(newImage.PixelData); BlocksChanged?.Invoke(this, blocks); } public void GetSelectionFromTile(IPixelViewModel tileImage) { ShowTiles = true; var index = indexForTileImage[tileImage]; var (pal, hFlip, vFlip, tileIndex) = LzTilemapRun.ReadTileData(blocks[blockIndex], index, 2); drawTile = tileIndex; (drawFlipV, drawFlipH) = (vFlip, hFlip); PaletteSelection = pal; NotifyPropertiesChanged(nameof(TileSelectionX), nameof(TileSelectionY)); AutoscrollTiles.Raise(this); AnimateTileSelection(); history.ChangeCompleted(); UpdateDrawTileRender(); } public void ExitTiles() { FlipVVisible = false; FlipHVisible = false; } #region Attribute UI private int behavior, layer, terrain, encounter; public int Behavior { get => behavior; set => Set(ref behavior, value, SaveAttributes); } public int Layer { get => layer; set => Set(ref layer, value, SaveAttributes); } public int Terrain { get => terrain; set => Set(ref terrain, value, SaveAttributes); } public int Encounter { get => encounter; set => Set(ref encounter, value, SaveAttributes); } public ObservableCollection BehaviorOptions { get; } = new(); public ObservableCollection LayerOptions { get; } = new() { "Normal", "Covered", "Split", }; public ObservableCollection TerrainOptions { get; } = new() { "Normal", "Grass", "Water" }; public ObservableCollection EncounterOptions { get; } = new() { "Normal", "Grass", "Water" }; private bool hasTerrainAndEncounter; public bool HasTerrainAndEncounter { get => hasTerrainAndEncounter; set => Set(ref hasTerrainAndEncounter, value); } private void UpdateAttributeUI() { var attributes = TileAttribute.Create(blockAttributes[blockIndex]); behavior = attributes.Behavior; layer = attributes.Layer; if (attributes is AttributeFRLG fr) { terrain = fr.Terrain; encounter = fr.Encounter; } new List { nameof(Behavior), nameof(Layer), nameof(Terrain), nameof(Encounter) }.ForEach(NotifyPropertyChanged); } private void SaveAttributes(int arg = default) { var attributes = TileAttribute.Create(blockAttributes[blockIndex]); attributes.Behavior = behavior; attributes.Layer = layer; if (attributes is AttributeFRLG fr) { fr.Terrain = terrain; fr.Encounter = encounter; } blockAttributes[blockIndex] = attributes.Serialize(); BlockAttributesChanged?.Invoke(this, blockAttributes); } #endregion private void UpdateBlockUI(int old) { for (int i = 0; i < 8; i++) images[i] = null; UpdateBlockUI(); } private void UpdateBlockUI() { if (blockIndex == -1) return; for (int i = 0; i < 8; i++) { if (images[i] == null) { var image = BlocksetModel.Read(blocks[blockIndex], i, tiles, palettes); images[i] = new CanvasPixelViewModel(image.PixelWidth, image.PixelHeight, image.PixelData) { SpriteScale = 3 }; indexForTileImage[images[i]] = i; NotifyPropertyChanged(imageNames[i]); } } indexForTileImage.Clear(); for (int i = 0; i < 8; i++) indexForTileImage[images[i]] = i; UpdateAttributeUI(); } #region Tile UI const int TilesPerRow = 16, PixelPerTile = 24; private bool showTiles; public bool ShowTiles { get => showTiles; set => Set(ref showTiles, value, arg => { if (showTiles && tileRender == null) UpdateTileRender(drawPalette); }); } public void HideTiles() => ShowTiles = false; // hack: a DataTrigger can watch this property and start an animation whenever this property changes. // the value doesn't matter, just the change private bool tileSelectionToggle; public bool TileSelectionToggle { get => tileSelectionToggle; set => Set(ref tileSelectionToggle, value); } private void AnimateTileSelection() => TileSelectionToggle = !TileSelectionToggle; private int drawTile, drawPalette; private bool drawFlipV, drawFlipH; public int TileSelectionX { get => (drawTile % TilesPerRow) * PixelPerTile; set { var (x, y) = (drawTile % TilesPerRow, drawTile / TilesPerRow); x = value / PixelPerTile; drawTile = y * TilesPerRow + x; if (drawTile >= tiles.Length) drawTile = tiles.Length - 1; NotifyPropertyChanged(); drawFlipH = drawFlipV = false; UpdateDrawTileRender(); } } public int TileSelectionY { get => (drawTile / TilesPerRow) * PixelPerTile; set { var (x, y) = (drawTile % TilesPerRow, drawTile / TilesPerRow); y = value / PixelPerTile; drawTile = y * TilesPerRow + x; if (drawTile >= tiles.Length) { drawTile = tiles.Length - 1; NotifyPropertiesChanged(nameof(TileSelectionX)); } NotifyPropertyChanged(); drawFlipH = drawFlipV = false; history.ChangeCompleted(); UpdateDrawTileRender(); } } public int PaletteSelection { get => drawPalette; set => Set(ref drawPalette, value, arg => UpdateTileRender(drawPalette)); } public ObservableCollection Palettes { get; } = new(); private void UpdateTileRender(int paletteIndex) { var tileLines = (tiles.Length + TilesPerRow - 1) / TilesPerRow; var pixelData = new short[8 * 8 * TilesPerRow * tileLines]; for (int i = 0; i < pixelData.Length; i++) pixelData[i] = short.MinValue; var render = new CanvasPixelViewModel(8 * 16, 8 * 16 * 4, pixelData) { SpriteScale = 3, Transparent = short.MinValue }; var palette = palettes[paletteIndex]; for (int y = 0; y < 64; y++) { for (int x = 0; x < 16; x++) { var tile = new ReadonlyPixelViewModel(8, 8, SpriteTool.Render(tiles[y * 16 + x], palette, 0, 0), palette[0]); if (tile == null || tile.PixelData.Length == 0) break; render.Draw(tile, x * 8, y * 8); } } tileRender = render; NotifyPropertyChanged(nameof(TileRender)); UpdateDrawTileRender(); } private void UpdateDrawTileRender() { var palette = palettes[drawPalette]; drawTileRender = new CanvasPixelViewModel(8, 8, SpriteTool.Render(tiles[drawTile], palette, 0, 0)) { Transparent = palette[0], SpriteScale = 4 }; NotifyPropertiesChanged(nameof(DrawTileRender)); } #endregion } }