mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-05-20 20:28:05 -05:00
initial pass at the data buttons. Maybe it'll end up just being a menu with a bunch of items, but I feel like with so many operations, they need to be a bit more organized than just a simple hierarchy.
626 lines
25 KiB
C#
626 lines
25 KiB
C#
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<ModelDelta> history;
|
|
private readonly short[][] palettes;
|
|
private readonly int[][,] tiles;
|
|
private readonly byte[][] blocks;
|
|
private readonly byte[][] blockAttributes;
|
|
private readonly IDictionary<IPixelViewModel, int> 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<byte[][]> BlocksChanged;
|
|
public event EventHandler<byte[][]> BlockAttributesChanged;
|
|
|
|
private IPixelViewModel tileRender;
|
|
public IPixelViewModel TileRender => tileRender;
|
|
|
|
private IPixelViewModel drawTileRender;
|
|
public IPixelViewModel DrawTileRender => drawTileRender;
|
|
|
|
public BlockEditor(ChangeHistory<ModelDelta> 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<IPixelViewModel, int>();
|
|
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<string> BehaviorOptions { get; } = new();
|
|
public ObservableCollection<string> LayerOptions { get; } = new() { "Normal", "Covered", "Split", };
|
|
public ObservableCollection<string> TerrainOptions { get; } = new() { "Normal", "Grass", "Water" };
|
|
public ObservableCollection<string> 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<string> { 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<ReadonlyPaletteCollection> 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
|
|
}
|
|
}
|