diff --git a/src/HexManiac.Core/Models/Map/MapModel.cs b/src/HexManiac.Core/Models/Map/MapModel.cs new file mode 100644 index 00000000..4beabf00 --- /dev/null +++ b/src/HexManiac.Core/Models/Map/MapModel.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Linq; + +namespace HavenSoft.HexManiac.Core.Models.Map { + public record MapModel(ModelArrayElement Element) { + public LayoutModel Layout => Element.TryGetSubTable(Format.Layout, out var table) ? new(table[0]) : null; + public EventGroupModel Events => Element.TryGetSubTable(Format.Events, out var table) ? new(table[0]) : null; + } + + public record LayoutModel(ModelArrayElement Element) { + public int Width => Element.GetValue("width"); + public int Height => Element.GetValue("height"); + public ModelArrayElement PrimaryBlockset => Element.TryGetSubTable(Format.PrimaryBlockset, out var table) ? table[0] : null; + public ModelArrayElement SecondaryBlockset => Element.TryGetSubTable(Format.SecondaryBlockset, out var table) ? table[0] : null; + } + + public record EventGroupModel(ModelArrayElement Element) { + public List Objects { + get { + if (!Element.TryGetSubTable(Format.Objects, out var objects)) return null; + return objects.Select(obj => new ObjectEventModel(obj)).ToList(); + } + } + public List Warps { + get { + if (!Element.TryGetSubTable(Format.Warps, out var warps)) return null; + return warps.Select(obj => new WarpEventModel(obj)).ToList(); + } + } + } + + public record BaseEventModel(ModelArrayElement Element) { + public int X => Element.GetValue("x"); + public int Y => Element.GetValue("y"); + public int Elevation => Element.GetValue("elevation"); + } + + public record ObjectEventModel(ModelArrayElement Element) : BaseEventModel(Element) { + + } + + public record WarpEventModel(ModelArrayElement Element) : BaseEventModel(Element) { + + } + + public class Format { + public static string RegionSection => "regionSectionID"; + public static string Events => "events"; + public static string Layout => "layout"; + public static string Warps => "warps"; + public static string Objects => "objects"; + public static string Connections => "connections"; + public static string Scripts => "scripts"; + public static string Signposts => "signposts"; + public static string ObjectCount => "objectCount"; + public static string WarpCount => "warpCount"; + public static string ScriptCount => "scriptCount"; + public static string SignpostCount => "signpostCount"; + public static string BorderBlock => "borderblock"; + public static string BlockMap => "blockmap"; + public static string PrimaryBlockset => "blockdata1"; + public static string SecondaryBlockset => "blockdata2"; + public static string Tileset => "tileset"; + public static string BlockAttributes => "attributes"; + public static string Blocks => "block"; + public static string TileAnimationRoutine => "animation"; + public static string Palette => "pal"; + public static string BorderWidth => "borderwidth"; + public static string BorderHeight => "borderheight"; + + public string BlockDataFormat { get; } + public string LayoutFormat { get; } + public string ObjectsFormat { get; } + public string WarpsFormat { get; } + public string ScriptsFormat { get; } + public string SignpostsFormat { get; } + public string EventsFormat { get; } + public string ConnectionsFormat { get; } + public string HeaderFormat { get; } + public string MapFormat { get; } + + public Format(bool isRSE) { + BlockDataFormat = $"[isCompressed. isSecondary. padding: {Tileset}<> {Palette}<`ucp4:0123456789ABCDEF`> {Blocks}<> {TileAnimationRoutine}<> {BlockAttributes}<>]1"; + if (isRSE) BlockDataFormat = $"[isCompressed. isSecondary. padding: {Tileset}<> {Palette}<`ucp4:0123456789ABCDEF`> {Blocks}<> {BlockAttributes}<> {TileAnimationRoutine}<>]1"; + LayoutFormat = $"[width:: height:: {BorderBlock}<> {BlockMap}<`blm`> {PrimaryBlockset}<{BlockDataFormat}> {SecondaryBlockset}<{BlockDataFormat}> {BorderWidth}. {BorderHeight}. unused:]1"; + if (isRSE) LayoutFormat = $"[width:: height:: {BorderBlock}<> {BlockMap}<`blm`> {PrimaryBlockset}<{BlockDataFormat}> {PrimaryBlockset}<{BlockDataFormat}>]1"; + ObjectsFormat = $"[id. graphics. unused: x:500 y:500 elevation. moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/{ObjectCount}"; + WarpsFormat = $"[x:500 y:500 elevation. warpID. map. bank.]/{WarpCount}"; + ScriptsFormat = $"[x:500 y:500 elevation: trigger: index:: script<`xse`>]/{ScriptCount}"; + SignpostsFormat = $"[x:500 y:500 elevation. kind. unused: arg::|h]/{SignpostCount}"; + EventsFormat = $"[{ObjectCount}. {WarpCount}. {ScriptCount}. {SignpostCount}. {Objects}<{ObjectsFormat}> {Warps}<{WarpsFormat}> {Scripts}<{ScriptsFormat}> {Signposts}<{SignpostsFormat}>]1"; + ConnectionsFormat = "[count:: connections<[direction:: offset:: mapGroup. mapNum. unused:]/count>]1"; + HeaderFormat = "music: layoutID: regionSectionID. cave. weather. mapType. allowBiking. flags.|t|allowEscaping.|allowRunning.|showMapName::: floorNum. battleType."; + MapFormat = $"[{Layout}<{LayoutFormat}> events<{EventsFormat}> mapscripts<[type. pointer<>]!00> {Connections}<{ConnectionsFormat}> {HeaderFormat}]"; + } + } +} diff --git a/src/HexManiac.Core/Models/ModelTable.cs b/src/HexManiac.Core/Models/ModelTable.cs index 861c7a47..f87ca920 100644 --- a/src/HexManiac.Core/Models/ModelTable.cs +++ b/src/HexManiac.Core/Models/ModelTable.cs @@ -301,6 +301,11 @@ namespace HavenSoft.HexManiac.Core.Models { } } + public bool TryGetSubTable(string fieldName, out ModelTable table) { + table = GetSubTable(fieldName); + return table != null; + } + public ModelTable GetSubTable(string fieldName) { var elementOffset = table.ElementContent.Until(segment => segment.Name == fieldName).Sum(segment => segment.Length); var valueAddress = table.Start + table.ElementLength * arrayIndex + elementOffset; diff --git a/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs b/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs index 3b43759e..359b52cd 100644 --- a/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs +++ b/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs @@ -440,6 +440,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #endregion public void EnterTile(IPixelViewModel tile) { + FlipVVisible = true; FlipHVisible = true; var index = indexForTileImage[tile]; diff --git a/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs b/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs index 823a3a4e..7d3a8910 100644 --- a/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs @@ -1,4 +1,5 @@ using HavenSoft.HexManiac.Core.Models; +using HavenSoft.HexManiac.Core.Models.Map; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; using HavenSoft.HexManiac.Core.ViewModels.Images; @@ -32,8 +33,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #region SelectedEvent - private IEventModel selectedEvent; - public IEventModel SelectedEvent { + private IEventViewModel selectedEvent; + public IEventViewModel SelectedEvent { get => selectedEvent; set { var oldValue = selectedEvent; @@ -43,7 +44,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } - private void HandleSelectedEventChanged(IEventModel old) { + private void HandleSelectedEventChanged(IEventViewModel old) { if (old == selectedEvent) return; if (old != null) { old.EventVisualUpdated -= RefreshFromEventChange; @@ -61,15 +62,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { private void CycleActiveEvent(object sender, EventCycleDirection direction) { // organize events into categories var events = GetEvents(); - var categories = new List> { new(), new(), new(), new(), new() }; + var categories = new List> { new(), new(), new(), new(), new() }; int selectionIndex = -1, selectedCategory = -1; for (int i = 0; i < events.Count; i++) { int currentCategory = - events[i] is ObjectEventModel ? 0 : - events[i] is WarpEventModel ? 1 : - events[i] is ScriptEventModel ? 2 : - events[i] is SignpostEventModel ? 3 : - events[i] is FlyEventModel ? 4 : + events[i] is ObjectEventViewModel ? 0 : + events[i] is WarpEventViewModel ? 1 : + events[i] is ScriptEventViewModel ? 2 : + events[i] is SignpostEventViewModel ? 3 : + events[i] is FlyEventViewModel ? 4 : -1; categories[currentCategory].Add(events[i]); @@ -194,7 +195,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { private byte[][] blocks; private byte[][] blockAttributes; private readonly List blockRenders = new(); // one image per block - private IReadOnlyList eventRenders; + private IReadOnlyList eventRenders; public IReadOnlyList BlockRenders { get { if (blockRenders.Count == 0) RefreshBlockRenderCache(); @@ -514,6 +515,118 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { NotifyPropertyChanged(nameof(SpriteScale)); } + // check for other warps on this same tile and see what primary/secondary blockset is expected for the new map. + // if no reasonable tileset is found, just use the current map's blocksets + public BlockMapViewModel CreateMapForWarp(WarpEventViewModel warp) { + // what bank should the new map go in? + var option = MapRepointer.GetMapBankForNewMap("Which map bank do you want to add the new map to?"); + if (option == -1) return null; + var token = tokenFactory(); + var myLayout = GetLayout(); + + // give me this block + var (width, height) = (myLayout.GetValue("width"), myLayout.GetValue("height")); + var start = myLayout.GetAddress("blockmap"); + var modelAddress = start + (warp.Y * width + warp.X) * 2; + var data = model.ReadMultiByteValue(modelAddress, 2); + int blockIndex = data & 0x3FF; + + // give me all maps that use this blockset + var borderBlockAddress = myLayout.GetAddress(Format.BorderBlock); + var primaryBlocksetAddress = myLayout.GetAddress(Format.PrimaryBlockset); + var secondaryBlocksetAddress = myLayout.GetAddress(Format.SecondaryBlockset); + + var maps = new List(); + foreach (var bank in model.GetTableModel(HardcodeTablesModel.MapBankTable)) { + if (bank == null) continue; + foreach (var mapList in bank.GetSubTable("maps")) { + if (mapList == null) continue; + var mapTable = mapList.GetSubTable("map"); + if (mapTable == null) continue; + var map = mapTable[0]; + var layoutTable = map.GetSubTable(Format.Layout); + if (layoutTable == null) continue; + var layout = layoutTable[0]; + var primary = layout.GetAddress(Format.PrimaryBlockset); + var secondary = layout.GetAddress(Format.SecondaryBlockset); + if (primary != primaryBlocksetAddress && blockIndex < PrimaryBlocks) continue; + if (secondary != secondaryBlocksetAddress && blockIndex >= PrimaryBlocks) continue; + maps.Add(map); + } + } + + // give me all warps in those maps (except for this warp itself) + // give me all warps that are on this tile + var warps = new List(); + foreach (var map in maps) { + var layoutTable = map.GetSubTable("layout"); + if (layoutTable == null) continue; + var blockmapStart = layoutTable[0].GetAddress(Format.BlockMap); + var candidateWidth = layoutTable[0].GetValue("width"); + var eventTable = map.GetSubTable(Format.Events); + if (eventTable == null) continue; + var warpTable = eventTable[0].GetSubTable(Format.Warps); + if (warpTable == null) continue; + foreach (var w in warpTable) { + if (w.Start == warp.Element.Start) continue; + var p = new Point(w.GetValue("x"), w.GetValue("y")); + var warpBlockIndex = model.ReadMultiByteValue(blockmapStart + (p.Y * width + p.X) * 2, 2) & 0x3FF; + if (warpBlockIndex != blockIndex) continue; + warps.Add(w); + } + } + + + // give me maps that those warp to + // give me all the primary/secondary blocksets for those maps + // give me all the borders for those maps + var primaryBlocksets = new Dictionary { { primaryBlocksetAddress, 0 } }; + var secondaryBlocksets = new Dictionary { { secondaryBlocksetAddress, 0 } }; + var borders = new Dictionary { { borderBlockAddress, 0 } }; + foreach (var w in warps) { + var (targetBank, targetMap) = (w.GetValue("bank"), w.GetValue("map")); + var m = GetMapModel(model, targetBank, targetMap, tokenFactory); + var targetLayout = GetLayout(m); + var primary = targetLayout.GetAddress(Format.PrimaryBlockset); + var secondary = targetLayout.GetAddress(Format.SecondaryBlockset); + var border = targetLayout.GetAddress(Format.BorderBlock); + if (!primaryBlocksets.ContainsKey(primary)) primaryBlocksets[primary] = 0; + if (!secondaryBlocksets.ContainsKey(secondary)) secondaryBlocksets[secondary] = 0; + if (!borders.ContainsKey(border)) borders[border] = 0; + primaryBlocksets[primary]++; + secondaryBlocksets[secondary]++; + borders[border] = 0; + } + + // use the most frequent primary/secondary blockset and border blocks + var bestPrimaryCount = primaryBlocksets.Values.Max(); + primaryBlocksetAddress = primaryBlocksets.Keys.First(key => primaryBlocksets[key] == bestPrimaryCount); + var bestSecondaryCount = secondaryBlocksets.Values.Max(); + secondaryBlocksetAddress = secondaryBlocksets.Keys.First(key => secondaryBlocksets[key] == bestSecondaryCount); + var bestBorderCount = borders.Values.Max(); + borderBlockAddress = borders.Keys.First(key => borders[key] == bestBorderCount); + + // create a new 9x9 map + var newMap = CreateNewMap(token, option, 9, 9); + var newLayout = newMap.GetLayout(); + newLayout.SetAddress(Format.BorderBlock, borderBlockAddress); + newLayout.SetAddress(Format.PrimaryBlockset, primaryBlocksetAddress); + newLayout.SetAddress(Format.SecondaryBlockset, secondaryBlocksetAddress); + + // TODO fill with 'background' tile? + + // TODO fill the border + + // place reverse warp at bottom heading back + (warp.Bank, warp.Map, warp.WarpID) = (newMap.group, newMap.map, 1); + var returnWarp = newMap.CreateWarpEvent(group, map); + returnWarp.WarpID = GetEvents().Where(e => e is WarpEventViewModel).Until(e => e.Equals(warp)).Count(); + (returnWarp.X, returnWarp.Y) = (4, 8); + + // TODO figure out which tiles should be in the 3x2 section around the warp + return newMap; + } + #region Draw / Paint /// @@ -635,7 +748,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #region Events - public void UpdateEventLocation(IEventModel ev, double x, double y) { + public void UpdateEventLocation(IEventViewModel ev, double x, double y) { (lastDrawX, lastDrawY) = (-1, -1); var layout = GetLayout(); var border = GetBorderThickness(layout); @@ -652,7 +765,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { NotifyPropertyChanged(nameof(PixelData)); } - public IEventModel EventUnderCursor(double x, double y) { + public IEventViewModel EventUnderCursor(double x, double y) { var layout = GetLayout(); var border = GetBorderThickness(layout); var tileX = (int)((x - LeftEdge) / SpriteScale / 16) - border.West; @@ -767,6 +880,19 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #region Work Methods + private IEnumerable AllMaps() { + foreach (var bank in model.GetTableModel(HardcodeTablesModel.MapBankTable)) { + if (bank == null) continue; + foreach (var mapList in bank.GetSubTable("maps")) { + if (mapList == null) continue; + var mapTable = mapList.GetSubTable("map"); + if (mapTable == null) continue; + var map = mapTable[0]; + yield return new(map); + } + } + } + private void ResizeMapData(MapDirection direction, int amount) { if (amount == 0) return; var token = tokenFactory(); @@ -807,7 +933,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { private void ConnectNewMap(object obj) { var token = tokenFactory(); var mapBanks = new ModelTable(model, model.GetTable(HardcodeTablesModel.MapBankTable).Start, tokenFactory); - var option = MapRepointer.GetMapBankForNewMap("Which map group do you want to add the new map to?"); + var option = MapRepointer.GetMapBankForNewMap("Which map bank do you want to add the new map to?"); if (option == -1) return; var info = (ConnectionInfo)obj; @@ -823,20 +949,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { newConnection.Offset = info.Offset; newConnection.Direction = info.Direction; - newConnection.MapGroup = option; + var otherMap = CreateNewMap(token, option, info.Size, info.Size); - var mapTable = MapRepointer.AddNewMapToBank(option); - - newConnection.MapNum = mapTable.ElementCount - 1; - var address = MapRepointer.CreateNewMap(token); - var layoutStart = MapRepointer.CreateNewLayout(token); - WritePointerAndSource(token, layoutStart + 12, MapRepointer.CreateNewBlockMap(token, info.Size, info.Size)); - WritePointerAndSource(token, address + 0, layoutStart); - - model.UpdateArrayPointer(token, null, null, -1, mapTable.Start + mapTable.Length - 4, address); - - var otherMap = new BlockMapViewModel(fileSystem, viewPort, format, newConnection.MapGroup, newConnection.MapNum) { allOverworldSprites = allOverworldSprites }; - otherMap.UpdateLayoutID(); + newConnection.MapGroup = otherMap.group; + newConnection.MapNum = otherMap.map; info = new ConnectionInfo(info.Size, -info.Offset, info.OppositeDirection); newConnection = otherMap.AddConnection(info); newConnection.Offset = info.Offset; @@ -847,6 +963,30 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { NeighborsChanged.Raise(this); } + private BlockMapViewModel CreateNewMap(ModelDelta token, int bank, int width, int height) { + var mapTable = MapRepointer.AddNewMapToBank(bank); + var address = MapRepointer.CreateNewMap(token); + var layoutStart = MapRepointer.CreateNewLayout(token); + + // update width / height + model.WriteValue(token, layoutStart + 0, width); + model.WriteValue(token, layoutStart + 4, height); + + // TODO update border block (layoutStart + 8) + + // update blockmap (layoutStart + 12) + WritePointerAndSource(token, layoutStart + 12, MapRepointer.CreateNewBlockMap(token, width, height)); + + // TODO update primary blockset (layoutStart + 16) + // TODO update primary blockset (layoutStart + 20) + + WritePointerAndSource(token, address + 0, layoutStart); + model.UpdateArrayPointer(token, null, null, -1, mapTable.Start + mapTable.Length - 4, address); + var otherMap = new BlockMapViewModel(fileSystem, viewPort, format, bank, mapTable.ElementCount - 1) { allOverworldSprites = allOverworldSprites }; + otherMap.UpdateLayoutID(); + return otherMap; + } + private void ConnectExistingMap(object obj) { var info = (ConnectionInfo)obj; var token = tokenFactory(); @@ -957,13 +1097,13 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { return newConnection; } - public ObjectEventModel CreateObjectEvent(int graphics, int scriptAddress) { + public ObjectEventViewModel CreateObjectEvent(int graphics, int scriptAddress) { var token = tokenFactory(); var map = GetMapModel(); var events = map.GetSubTable("events")[0]; var element = AddEvent(events, tokenFactory, "objectCount", "objects"); if (allOverworldSprites == null) allOverworldSprites = RenderOWs(model); - var newEvent = new ObjectEventModel(GotoAddress, element, allOverworldSprites) { + var newEvent = new ObjectEventViewModel(GotoAddress, element, allOverworldSprites) { X = 0, Y = 0, Elevation = 0, ObjectID = element.Table.ElementCount, @@ -981,29 +1121,29 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { return newEvent; } - public WarpEventModel CreateWarpEvent(int bank, int map) { + public WarpEventViewModel CreateWarpEvent(int bank, int map) { var mapModel = GetMapModel(); var events = mapModel.GetSubTable("events")[0]; var element = AddEvent(events, tokenFactory, "warpCount", "warps"); - var newEvent = new WarpEventModel(element) { X = 0, Y = 0, Elevation = 0, Bank = bank, Map = map, WarpID = 0 }; + var newEvent = new WarpEventViewModel(element) { X = 0, Y = 0, Elevation = 0, Bank = bank, Map = map, WarpID = 0 }; SelectedEvent = newEvent; return newEvent; } - public ScriptEventModel CreateScriptEvent() { + public ScriptEventViewModel CreateScriptEvent() { var map = GetMapModel(); var events = map.GetSubTable("events")[0]; var element = AddEvent(events, tokenFactory, "scriptCount", "scripts"); - var newEvent = new ScriptEventModel(GotoAddress, element) { X = 0, Y = 0, Elevation = 0, Index = 0, Trigger = 0, ScriptAddress = Pointer.NULL }; + var newEvent = new ScriptEventViewModel(GotoAddress, element) { X = 0, Y = 0, Elevation = 0, Index = 0, Trigger = 0, ScriptAddress = Pointer.NULL }; SelectedEvent = newEvent; return newEvent; } - public SignpostEventModel CreateSignpostEvent() { + public SignpostEventViewModel CreateSignpostEvent() { var map = GetMapModel(); var events = map.GetSubTable("events")[0]; var element = AddEvent(events, tokenFactory, "signpostCount", "signposts"); - var newEvent = new SignpostEventModel(element, GotoAddress) { X = 0, Y = 0, Elevation = 0, Kind = 0, Pointer = Pointer.NULL }; + var newEvent = new SignpostEventViewModel(element, GotoAddress) { X = 0, Y = 0, Elevation = 0, Kind = 0, Pointer = Pointer.NULL }; SelectedEvent = newEvent; return newEvent; } @@ -1019,7 +1159,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } - public FlyEventModel CreateFlyEvent() { + public FlyEventViewModel CreateFlyEvent() { var map = GetMapModel(); var region = map.GetValue(Format.RegionSection); if (model.IsFRLG()) region -= 88; @@ -1054,7 +1194,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { spawns[emptySpawn].SetValue("map", this.map); NotifyPropertyChanged(nameof(CanCreateFlyEvent)); - return new FlyEventModel(model, group, this.map, tokenFactory); + return new FlyEventViewModel(model, group, this.map, tokenFactory); } // TODO use this for connections as well, since the structure is the same @@ -1202,7 +1342,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { private void RefreshMapEvents() { if (eventRenders != null) return; - var list = new List(); + var list = new List(); var events = GetEvents(); foreach (var obj in events) { obj.Render(model); @@ -1353,15 +1493,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { var run = model.GetTable(HardcodeTablesModel.OverworldSprites); var ows = new ModelTable(model, run.Start, null, run); for (int i = 0; i < ows.Count; i++) { - list.Add(ObjectEventModel.Render(model, ows, i, 0)); + list.Add(ObjectEventViewModel.Render(model, ows, i, 0)); } return list; } - private IReadOnlyList GetEvents() { + private IReadOnlyList GetEvents() { if (allOverworldSprites == null) allOverworldSprites = RenderOWs(model); var map = GetMapModel(); - var results = new List(); + var results = new List(); var events = new EventGroupModel(GotoAddress, map.GetSubTable("events")[0], allOverworldSprites, group, this.map); events.DataMoved += HandleEventDataMoved; results.AddRange(events.Objects); @@ -1556,57 +1696,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public record Border(int North, int East, int South, int West); - public class Format { - public static string RegionSection => "regionSectionID"; - public static string Layout => "layout"; - public static string Warps => "warps"; - public static string Objects => "objects"; - public static string Connections => "connections"; - public static string Scripts => "scripts"; - public static string Signposts => "signposts"; - public static string ObjectCount => "objectCount"; - public static string WarpCount => "warpCount"; - public static string ScriptCount => "scriptCount"; - public static string SignpostCount => "signpostCount"; - public static string BorderBlock => "borderblock"; - public static string BlockMap => "blockmap"; - public static string PrimaryBlockset => "blockdata1"; - public static string SecondaryBlockset => "blockdata2"; - public static string Tileset => "tileset"; - public static string BlockAttributes => "attributes"; - public static string Blocks => "block"; - public static string TileAnimationRoutine => "animation"; - public static string Palette => "pal"; - public static string BorderWidth => "borderwidth"; - public static string BorderHeight => "borderheight"; - - public string BlockDataFormat { get; } - public string LayoutFormat { get; } - public string ObjectsFormat { get; } - public string WarpsFormat { get; } - public string ScriptsFormat { get; } - public string SignpostsFormat { get; } - public string EventsFormat { get; } - public string ConnectionsFormat { get; } - public string HeaderFormat { get; } - public string MapFormat { get; } - - public Format(bool isRSE) { - BlockDataFormat = $"[isCompressed. isSecondary. padding: {Tileset}<> {Palette}<`ucp4:0123456789ABCDEF`> {Blocks}<> {TileAnimationRoutine}<> {BlockAttributes}<>]1"; - if (isRSE) BlockDataFormat = $"[isCompressed. isSecondary. padding: {Tileset}<> {Palette}<`ucp4:0123456789ABCDEF`> {Blocks}<> {BlockAttributes}<> {TileAnimationRoutine}<>]1"; - LayoutFormat = $"[width:: height:: {BorderBlock}<> {BlockMap}<`blm`> {PrimaryBlockset}<{BlockDataFormat}> {SecondaryBlockset}<{BlockDataFormat}> {BorderWidth}. {BorderHeight}. unused:]1"; - if (isRSE) LayoutFormat = $"[width:: height:: {BorderBlock}<> {BlockMap}<`blm`> {PrimaryBlockset}<{BlockDataFormat}> {PrimaryBlockset}<{BlockDataFormat}>]1"; - ObjectsFormat = $"[id. graphics. unused: x:500 y:500 elevation. moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/{ObjectCount}"; - WarpsFormat = $"[x:500 y:500 elevation. warpID. map. bank.]/{WarpCount}"; - ScriptsFormat = $"[x:500 y:500 elevation: trigger: index:: script<`xse`>]/{ScriptCount}"; - SignpostsFormat = $"[x:500 y:500 elevation. kind. unused: arg::|h]/{SignpostCount}"; - EventsFormat = $"[{ObjectCount}. {WarpCount}. {ScriptCount}. {SignpostCount}. {Objects}<{ObjectsFormat}> {Warps}<{WarpsFormat}> {Scripts}<{ScriptsFormat}> {Signposts}<{SignpostsFormat}>]1"; - ConnectionsFormat = "[count:: connections<[direction:: offset:: mapGroup. mapNum. unused:]/count>]1"; - HeaderFormat = "music: layoutID: regionSectionID. cave. weather. mapType. allowBiking. flags.|t|allowEscaping.|allowRunning.|showMapName::: floorNum. battleType."; - MapFormat = $"[{Layout}<{LayoutFormat}> events<{EventsFormat}> mapscripts<[type. pointer<>]!00> {Connections}<{ConnectionsFormat}> {HeaderFormat}]"; - } - } - public record ConnectionInfo(int Size, int Offset, MapDirection Direction) { public const string SingleConnectionContent = "direction:: offset:: mapGroup. mapNum. unused:"; public const string SingleConnectionLength = "/count"; @@ -1744,10 +1833,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { var objectCount = events.GetValue("objectCount"); var objects = events.GetSubTable("objects"); - var objectList = new List(); + var objectList = new List(); if (objects != null) { for (int i = 0; i < objectCount; i++) { - var newEvent = new ObjectEventModel(gotoAddress, objects[i], ows); + var newEvent = new ObjectEventViewModel(gotoAddress, objects[i], ows); newEvent.DataMoved += (sender, e) => DataMoved.Raise(this, e); objectList.Add(newEvent); } @@ -1756,43 +1845,43 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { var warpCount = events.GetValue("warpCount"); var warps = events.GetSubTable("warps"); - var warpList = new List(); + var warpList = new List(); if (warps != null) { - for (int i = 0; i < warpCount; i++) warpList.Add(new WarpEventModel(warps[i])); + for (int i = 0; i < warpCount; i++) warpList.Add(new WarpEventViewModel(warps[i])); } Warps = warpList; var scriptCount = events.GetValue("scriptCount"); var scripts = events.GetSubTable("scripts"); - var scriptList = new List(); + var scriptList = new List(); if (scripts != null) { - for (int i = 0; i < scriptCount; i++) scriptList.Add(new ScriptEventModel(gotoAddress, scripts[i])); + for (int i = 0; i < scriptCount; i++) scriptList.Add(new ScriptEventViewModel(gotoAddress, scripts[i])); } Scripts = scriptList; var signpostCount = events.GetValue("signpostCount"); var signposts = events.GetSubTable("signposts"); - var signpostList = new List(); + var signpostList = new List(); if (signposts != null) { for (int i = 0; i < signpostCount; i++) { - var newEvent = new SignpostEventModel(signposts[i], gotoAddress); + var newEvent = new SignpostEventViewModel(signposts[i], gotoAddress); newEvent.DataMoved += (sender, e) => DataMoved.Raise(this, e); signpostList.Add(newEvent); } } Signposts = signpostList; - var flyEvent = new FlyEventModel(events.Model, bank, map, () => events.Token); + var flyEvent = new FlyEventViewModel(events.Model, bank, map, () => events.Token); if (flyEvent.Valid) { FlyEvent = flyEvent; } } - public IReadOnlyList Objects { get; } - public IReadOnlyList Warps { get; } - public IReadOnlyList Scripts { get; } - public IReadOnlyList Signposts { get; } - public FlyEventModel FlyEvent { get; } + public IReadOnlyList Objects { get; } + public IReadOnlyList Warps { get; } + public IReadOnlyList Scripts { get; } + public IReadOnlyList Signposts { get; } + public FlyEventViewModel FlyEvent { get; } /* * events<[objectCount. warpCount. scriptCount. signpostCount. diff --git a/src/HexManiac.Core/ViewModels/Map/EventTemplate.cs b/src/HexManiac.Core/ViewModels/Map/EventTemplate.cs index c4628896..c6adc900 100644 --- a/src/HexManiac.Core/ViewModels/Map/EventTemplate.cs +++ b/src/HexManiac.Core/ViewModels/Map/EventTemplate.cs @@ -89,7 +89,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public ObservableCollection AvailableTemplateTypes { get; } = new(); - public void ApplyTemplate(ObjectEventModel objectEventModel, ModelDelta token) { + public void ApplyTemplate(ObjectEventViewModel objectEventModel, ModelDelta token) { if (selectedTemplate == TemplateType.Trainer) CreateTrainer(objectEventModel, token); if (selectedTemplate == TemplateType.Npc) CreateNPC(objectEventModel, token); if (selectedTemplate == TemplateType.Item) CreateItem(objectEventModel, token); @@ -125,7 +125,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { // TODO use all-caps name or mixed-caps name depending on other trainers in the table // TODO use reference file to get names and before/win/after text - public void CreateTrainer(ObjectEventModel objectEventModel, ModelDelta token) { + public void CreateTrainer(ObjectEventViewModel objectEventModel, ModelDelta token) { // part 1: the team var availablePokemon = new List(); var dexName = HardcodeTablesModel.RegionalDexTableName; @@ -216,9 +216,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { model.ObserveRunWritten(token, new XSERun(scriptStart, SortedSpan.One(objectEventModel.Start + 16))); } - public TrainerEventContent GetTrainerContent(IEventModel eventModel) => GetTrainerContent(model, eventModel); - public static TrainerEventContent GetTrainerContent(IDataModel model, IEventModel eventModel) { - if (eventModel is not ObjectEventModel objectModel) return null; + public TrainerEventContent GetTrainerContent(IEventViewModel eventModel) => GetTrainerContent(model, eventModel); + public static TrainerEventContent GetTrainerContent(IDataModel model, IEventViewModel eventModel) { + if (eventModel is not ObjectEventViewModel objectModel) return null; var address = objectModel.ScriptAddress; if (address < 0) return null; // 5C 00 trainerFlag: 00 00 0F 00 09 06 02 @@ -255,7 +255,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #region NPC - public void CreateNPC(ObjectEventModel eventModel, ModelDelta token) { + public void CreateNPC(ObjectEventViewModel eventModel, ModelDelta token) { // loadpointer 0 ; callstd 2; end; text="I'm an NPC!" // 0F 00 09 02 02 "I'm an NPC!" var scriptStart = model.FindFreeSpace(model.FreeSpaceStart, 24); @@ -277,9 +277,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { model.ObserveRunWritten(token, new XSERun(scriptStart, SortedSpan.One(eventModel.Start + 16))); } - public int GetNPCTextPointer(IEventModel eventModel) => GetNPCTextPointer(model, eventModel); - public static int GetNPCTextPointer(IDataModel model, IEventModel eventModel) { - if (eventModel is not ObjectEventModel objectModel) return Pointer.NULL; + public int GetNPCTextPointer(IEventViewModel eventModel) => GetNPCTextPointer(model, eventModel); + public static int GetNPCTextPointer(IDataModel model, IEventViewModel eventModel) { + if (eventModel is not ObjectEventViewModel objectModel) return Pointer.NULL; var address = objectModel.ScriptAddress; if (address < 0) return Pointer.NULL; var expectedValues = new Dictionary { @@ -304,7 +304,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { private int itemID = 20; public int ItemID { get => itemID; set => Set(ref itemID, value); } - public void CreateItem(ObjectEventModel objectEventModel, ModelDelta token) { + public void CreateItem(ObjectEventViewModel objectEventModel, ModelDelta token) { // copyvarifnotzero 0x8000 item: // copyvarifnotzero 0x8001 1 // callstd 1 @@ -330,9 +330,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { model.ObserveRunWritten(token, new XSERun(scriptStart, SortedSpan.One(objectEventModel.Start + 16))); } - public int GetItemAddress(IEventModel eventModel) => GetItemAddress(model, eventModel); - public static int GetItemAddress(IDataModel model, IEventModel eventModel) { - if (eventModel is not ObjectEventModel objectModel) return Pointer.NULL; + public int GetItemAddress(IEventViewModel eventModel) => GetItemAddress(model, eventModel); + public static int GetItemAddress(IDataModel model, IEventViewModel eventModel) { + if (eventModel is not ObjectEventViewModel objectModel) return Pointer.NULL; var address = objectModel.ScriptAddress; if (address < 0) return Pointer.NULL; // 1A 00 80 item: 1A 01 80 01 00 09 01 02 @@ -361,7 +361,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #region Signpost - public void ApplyTemplate(SignpostEventModel signpost, ModelDelta token) { + public void ApplyTemplate(SignpostEventViewModel signpost, ModelDelta token) { signpost.Elevation = 0; signpost.Kind = 0; @@ -380,9 +380,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { signpost.Pointer = scriptStart; } - public int GetSignpostTextPointer(IEventModel eventModel) => GetSignpostTextPointer(model, eventModel); - public static int GetSignpostTextPointer(IDataModel model, IEventModel eventModel) { - if (eventModel is not SignpostEventModel signpost) return Pointer.NULL; + public int GetSignpostTextPointer(IEventViewModel eventModel) => GetSignpostTextPointer(model, eventModel); + public static int GetSignpostTextPointer(IDataModel model, IEventViewModel eventModel) { + if (eventModel is not SignpostEventViewModel signpost) return Pointer.NULL; if (!signpost.ShowPointer) return Pointer.NULL; int address = signpost.Pointer; var expectedValues = new Dictionary { diff --git a/src/HexManiac.Core/ViewModels/Map/IEventViewModel.cs b/src/HexManiac.Core/ViewModels/Map/IEventViewModel.cs index 90c5f873..f8abbd6a 100644 --- a/src/HexManiac.Core/ViewModels/Map/IEventViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Map/IEventViewModel.cs @@ -1,4 +1,5 @@ using HavenSoft.HexManiac.Core.Models; +using HavenSoft.HexManiac.Core.Models.Map; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.Models.Runs.Sprites; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; @@ -14,7 +15,7 @@ using System.Windows.Input; namespace HavenSoft.HexManiac.Core.ViewModels.Map { - public interface IEventModel : IEquatable, INotifyPropertyChanged { + public interface IEventViewModel : IEquatable, INotifyPropertyChanged { event EventHandler EventVisualUpdated; public event EventHandler CycleEvent; public ICommand CycleEventCommand { get; } @@ -31,7 +32,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public enum EventCycleDirection { PreviousCategory, PreviousEvent, NextEvent, NextCategory } - public class FlyEventModel : ViewModelCore, IEventModel { + public class FlyEventViewModel : ViewModelCore, IEventViewModel { private readonly ModelArrayElement flySpot; private readonly ModelArrayElement connectionEntry; @@ -71,7 +72,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public event EventHandler EventVisualUpdated; public event EventHandler CycleEvent; - public FlyEventModel(IDataModel model, int bank, int map, Func tokenFactory) { + public FlyEventViewModel(IDataModel model, int bank, int map, Func tokenFactory) { // get the region from the map var banks = model.GetTableModel(HardcodeTablesModel.MapBankTable, tokenFactory); if (banks == null) return; @@ -109,17 +110,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { flySpot.SetValue("y", 0); } - public bool Equals(IEventModel? other) { - if (other is not FlyEventModel fly) return false; + public bool Equals(IEventViewModel? other) { + if (other is not FlyEventViewModel fly) return false; return X == fly.X && Y == fly.Y && flySpot.Start == fly.flySpot.Start; } public void Render(IDataModel model) { - EventRender = BaseEventModel.BuildEventRender(UncompressedPaletteColor.Pack(31, 31, 0)); + EventRender = BaseEventViewModel.BuildEventRender(UncompressedPaletteColor.Pack(31, 31, 0)); } } - public abstract class BaseEventModel : ViewModelCore, IEventModel, IEquatable { + public abstract class BaseEventViewModel : ViewModelCore, IEventViewModel, IEquatable { public event EventHandler EventVisualUpdated; public event EventHandler CycleEvent; @@ -131,6 +132,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { protected readonly ModelArrayElement element; private readonly string parentLengthField; + public ModelArrayElement Element => element; public ModelDelta Token => element.Token; public string EventType => GetType().Name.Replace("EventModel", string.Empty); @@ -199,12 +201,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public IPixelViewModel EventRender { get; protected set; } - public BaseEventModel(ModelArrayElement element, string parentLengthField) => (this.element, this.parentLengthField) = (element, parentLengthField); + public BaseEventViewModel(ModelArrayElement element, string parentLengthField) => (this.element, this.parentLengthField) = (element, parentLengthField); public void Delete() => DeleteElement(parentLengthField); - public bool Equals(IEventModel other) { - if (other is not BaseEventModel bem) return false; + public virtual bool Equals(IEventViewModel other) { + if (other is not BaseEventViewModel bem) return false; return bem.element.Start == element.Start; } @@ -292,7 +294,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } - public class ObjectEventModel : BaseEventModel { + public class ObjectEventViewModel : BaseEventViewModel { private readonly Action gotoAddress; public event EventHandler DataMoved; @@ -577,7 +579,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #endregion - public ObjectEventModel(Action gotoAddress, ModelArrayElement objectEvent, IReadOnlyList sprites) : base(objectEvent, "objectCount") { + public ObjectEventViewModel(Action gotoAddress, ModelArrayElement objectEvent, IReadOnlyList sprites) : base(objectEvent, "objectCount") { this.gotoAddress = gotoAddress; for (int i = 0; i < sprites.Count; i++) Options.Add(VisualComboOption.CreateFromSprite(i.ToString(), sprites[i].PixelData, sprites[i].PixelWidth, i)); objectEvent.Model.TryGetList("FacingOptions", out var list); @@ -630,8 +632,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } - public class WarpEventModel : BaseEventModel { - public WarpEventModel(ModelArrayElement warpEvent) : base(warpEvent, "warpCount") { } + public class WarpEventViewModel : BaseEventViewModel { + public WarpEventViewModel(ModelArrayElement warpEvent) : base(warpEvent, "warpCount") { } public int WarpID { get => element.GetValue("warpID") + 1; @@ -689,10 +691,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } - public class ScriptEventModel : BaseEventModel { + public class ScriptEventViewModel : BaseEventViewModel { private readonly Action gotoAddress; - public ScriptEventModel(Action gotoAddress, ModelArrayElement scriptEvent) : base(scriptEvent, "scriptCount") { this.gotoAddress = gotoAddress; } + public ScriptEventViewModel(Action gotoAddress, ModelArrayElement scriptEvent) : base(scriptEvent, "scriptCount") { this.gotoAddress = gotoAddress; } public int Trigger { get => element.GetValue("trigger"); @@ -742,7 +744,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } - public class SignpostEventModel : BaseEventModel { + public class SignpostEventViewModel : BaseEventViewModel { // kind. arg::|h // kind = 0/1/2/3/4 => arg is a pointer to an XSE script // kind = 5/6/7 => arg is itemID: hiddenItemID. attr|t|quantity:::.|isUnderFoot. @@ -753,7 +755,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public event EventHandler DataMoved; - public SignpostEventModel(ModelArrayElement signpostEvent, Action gotoAddress) : base(signpostEvent, "signpostCount") { + public SignpostEventViewModel(ModelArrayElement signpostEvent, Action gotoAddress) : base(signpostEvent, "signpostCount") { new List { "Facing Any", "Facing North", diff --git a/src/HexManiac.Core/ViewModels/Map/MapEditorViewModel.cs b/src/HexManiac.Core/ViewModels/Map/MapEditorViewModel.cs index 20351151..57599368 100644 --- a/src/HexManiac.Core/ViewModels/Map/MapEditorViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Map/MapEditorViewModel.cs @@ -1,4 +1,5 @@ using HavenSoft.HexManiac.Core.Models; +using HavenSoft.HexManiac.Core.Models.Map; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; using HavenSoft.HexManiac.Core.ViewModels.Images; @@ -43,8 +44,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { set => Set(ref showHeaderPanel, value); } - private IEventModel selectedEvent; - public IEventModel SelectedEvent { + private IEventViewModel selectedEvent; + public IEventViewModel SelectedEvent { get => selectedEvent; set { if (selectedEvent == value) return; @@ -505,17 +506,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { primaryMap.RedrawEvents(); // editing blocks in a map can draw over events, we need to redraw events now that we have new blocks } - private void EventDown(double x, double y, IEventModel ev, int clickCount) { + private void EventDown(double x, double y, IEventViewModel ev, int clickCount) { SelectedEvent = ev; - if (SelectedEvent is WarpEventModel warp && clickCount == 2) { + if (SelectedEvent is WarpEventViewModel warp && clickCount == 2) { NavigateTo(warp.Bank, warp.Map); - } else if (clickCount == 2 && SelectedEvent is ObjectEventModel obj) { + } else if (clickCount == 2 && SelectedEvent is ObjectEventViewModel obj) { viewPort.Goto.Execute(obj.ScriptAddress); - } else if (clickCount == 2 && SelectedEvent is ScriptEventModel script) { + } else if (clickCount == 2 && SelectedEvent is ScriptEventViewModel script) { viewPort.Goto.Execute(script.ScriptAddress); } else if ( clickCount == 2 && - SelectedEvent is SignpostEventModel signpost && + SelectedEvent is SignpostEventViewModel signpost && signpost.ShowPointer && signpost.Pointer >= 0 && signpost.Pointer < model.Count @@ -615,22 +616,29 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } - public void SelectDown(double x, double y) { + public SelectionInteractionResult SelectDown(double x, double y) { PrimaryMap.BlockEditor.ShowTiles = false; PrimaryMap.BorderEditor.ShowBorderPanel = false; var map = MapUnderCursor(x, y); - if (map == null) return; + if (map == null) return SelectionInteractionResult.None; PrimaryMap = map; ShowHeaderPanel = false; var (blockIndex, collisionIndex) = map.GetBlock(x, y); SelectedEvent = null; + selectDownPosition = ToTilePosition(x, y); + UpdateHover(selectDownPosition.X, selectDownPosition.Y, 1, 1); + var ev = map.EventUnderCursor(x, y); + if (ev is WarpEventViewModel warp) { + interactionType = PrimaryInteractionType.None; + warpContext = warp; + return SelectionInteractionResult.ShowWarpMenu; + } if (blockIndex >= 0) { DrawBlockIndex = blockIndex; AutoscrollBlocks.Raise(this); } if (collisionIndex >= 0) CollisionIndex = collisionIndex; - selectDownPosition = ToTilePosition(x, y); - UpdateHover(selectDownPosition.X, selectDownPosition.Y, 1, 1); + return SelectionInteractionResult.None; } public void SelectMove(double x, double y) { @@ -699,6 +707,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { #endregion + #region Creating a new map for a warp + + private WarpEventViewModel warpContext; + public void CreateMapForWarp() { + BlockMapViewModel newMap = primaryMap.CreateMapForWarp(warpContext); + if (newMap == null) return; + NavigateTo(newMap.MapID); + } + + #endregion + public void Zoom(double x, double y, bool enlarge) { var map = MapUnderCursor(x, y); if (map == null) map = primaryMap; @@ -1014,6 +1033,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } } + public enum SelectionInteractionResult { None, ShowWarpMenu } + public enum EventCreationType { None, Object, Warp, Script, Signpost, Fly } public enum PrimaryInteractionType { None, Draw, Event } diff --git a/src/HexManiac.Core/ViewModels/Map/MapRepointer.cs b/src/HexManiac.Core/ViewModels/Map/MapRepointer.cs index 7a1df505..4b664d6e 100644 --- a/src/HexManiac.Core/ViewModels/Map/MapRepointer.cs +++ b/src/HexManiac.Core/ViewModels/Map/MapRepointer.cs @@ -1,4 +1,5 @@ using HavenSoft.HexManiac.Core.Models; +using HavenSoft.HexManiac.Core.Models.Map; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.Models.Runs.Sprites; using HavenSoft.HexManiac.Core.ViewModels.Tools; @@ -197,7 +198,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } private void ExecuteDuplicateMap() { - var option = GetMapBankForNewMap("Duplicate map into which group?"); + var option = GetMapBankForNewMap("Duplicate map into which bank?"); if (option == -1) return; var table = AddNewMapToBank(option); var newMapStart = CreateNewMap(history.CurrentChange); diff --git a/src/HexManiac.WPF/Controls/MapTab.xaml b/src/HexManiac.WPF/Controls/MapTab.xaml index a6f847ce..57744542 100644 --- a/src/HexManiac.WPF/Controls/MapTab.xaml +++ b/src/HexManiac.WPF/Controls/MapTab.xaml @@ -228,7 +228,7 @@ - + @@ -385,7 +385,7 @@ - + @@ -423,7 +423,7 @@ - + @@ -468,7 +468,7 @@ - + @@ -537,7 +537,7 @@ Visibility="{Binding ShowHiddenItemProperties, Converter={StaticResource BoolToVisibility}}" /> - + @@ -712,6 +712,11 @@ + + + + + diff --git a/src/HexManiac.WPF/Controls/MapTab.xaml.cs b/src/HexManiac.WPF/Controls/MapTab.xaml.cs index 5776aad6..500dcd0b 100644 --- a/src/HexManiac.WPF/Controls/MapTab.xaml.cs +++ b/src/HexManiac.WPF/Controls/MapTab.xaml.cs @@ -3,8 +3,10 @@ using HavenSoft.HexManiac.Core.ViewModels.Map; using System; using System.ComponentModel; using System.Windows; +using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using System.Xml.Linq; namespace HavenSoft.HexManiac.WPF.Controls { public partial class MapTab { @@ -88,7 +90,11 @@ namespace HavenSoft.HexManiac.WPF.Controls { vm.DragDown(p.X, p.Y); } else if (e.RightButton == MouseButtonState.Pressed) { withinMapInteraction = MouseButton.Right; - vm.SelectDown(p.X, p.Y); + var result = vm.SelectDown(p.X, p.Y); + if (result == SelectionInteractionResult.ShowWarpMenu) { + withinMapInteraction = MouseButton.XButton1; + ShowWarpCreation(element, e); + } } } @@ -183,6 +189,20 @@ namespace HavenSoft.HexManiac.WPF.Controls { return new(p.X - element.ActualWidth / 2, p.Y - element.ActualHeight / 2); } + private void ShowWarpCreation(FrameworkElement element, MouseEventArgs e) { + element.ContextMenu.IsEnabled = true; + element.ContextMenu.IsOpen = true; + } + + private void DisableMenuOnClose(object sender, ContextMenuEventArgs e) { + var menu = (ContextMenu)sender; + menu.IsEnabled = false; + } + + private void CreateMapForWarpClicked(object sender, RoutedEventArgs e) { + ViewModel.CreateMapForWarp(); + } + #endregion #region Border Interaction