mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-10-01 20:56:36 -05:00
draft
added right-click menu and a draft of the creation logic. Doesn't quite work right yet.
This commit is contained in:
97
src/HexManiac.Core/Models/Map/MapModel.cs
Normal file
97
src/HexManiac.Core/Models/Map/MapModel.cs
Normal file
@@ -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<ObjectEventModel> Objects {
|
||||
get {
|
||||
if (!Element.TryGetSubTable(Format.Objects, out var objects)) return null;
|
||||
return objects.Select(obj => new ObjectEventModel(obj)).ToList();
|
||||
}
|
||||
}
|
||||
public List<WarpEventModel> 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}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -440,6 +440,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
#endregion
|
||||
|
||||
public void EnterTile(IPixelViewModel tile) {
|
||||
|
||||
FlipVVisible = true;
|
||||
FlipHVisible = true;
|
||||
var index = indexForTileImage[tile];
|
||||
|
||||
@@ -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<List<IEventModel>> { new(), new(), new(), new(), new() };
|
||||
var categories = new List<List<IEventViewModel>> { 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<IPixelViewModel> blockRenders = new(); // one image per block
|
||||
private IReadOnlyList<IEventModel> eventRenders;
|
||||
private IReadOnlyList<IEventViewModel> eventRenders;
|
||||
public IReadOnlyList<IPixelViewModel> 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<ModelArrayElement>();
|
||||
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<ModelArrayElement>();
|
||||
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<int, int> { { primaryBlocksetAddress, 0 } };
|
||||
var secondaryBlocksets = new Dictionary<int, int> { { secondaryBlocksetAddress, 0 } };
|
||||
var borders = new Dictionary<int, int> { { 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
|
||||
|
||||
/// <summary>
|
||||
@@ -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<MapModel> 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<IEventModel>();
|
||||
var list = new List<IEventViewModel>();
|
||||
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<IEventModel> GetEvents() {
|
||||
private IReadOnlyList<IEventViewModel> GetEvents() {
|
||||
if (allOverworldSprites == null) allOverworldSprites = RenderOWs(model);
|
||||
var map = GetMapModel();
|
||||
var results = new List<IEventModel>();
|
||||
var results = new List<IEventViewModel>();
|
||||
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<ObjectEventModel>();
|
||||
var objectList = new List<ObjectEventViewModel>();
|
||||
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<WarpEventModel>();
|
||||
var warpList = new List<WarpEventViewModel>();
|
||||
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<ScriptEventModel>();
|
||||
var scriptList = new List<ScriptEventViewModel>();
|
||||
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<SignpostEventModel>();
|
||||
var signpostList = new List<SignpostEventViewModel>();
|
||||
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<ObjectEventModel> Objects { get; }
|
||||
public IReadOnlyList<WarpEventModel> Warps { get; }
|
||||
public IReadOnlyList<ScriptEventModel> Scripts { get; }
|
||||
public IReadOnlyList<SignpostEventModel> Signposts { get; }
|
||||
public FlyEventModel FlyEvent { get; }
|
||||
public IReadOnlyList<ObjectEventViewModel> Objects { get; }
|
||||
public IReadOnlyList<WarpEventViewModel> Warps { get; }
|
||||
public IReadOnlyList<ScriptEventViewModel> Scripts { get; }
|
||||
public IReadOnlyList<SignpostEventViewModel> Signposts { get; }
|
||||
public FlyEventViewModel FlyEvent { get; }
|
||||
|
||||
/*
|
||||
* events<[objectCount. warpCount. scriptCount. signpostCount.
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
|
||||
public ObservableCollection<TemplateType> 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<int>();
|
||||
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 <before> <win> 0F 00 <after> 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 <text>; callstd 2; end; text="I'm an NPC!"
|
||||
// 0F 00 <text> 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<int, byte> {
|
||||
@@ -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<int, byte> {
|
||||
|
||||
@@ -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<IEventModel>, INotifyPropertyChanged {
|
||||
public interface IEventViewModel : IEquatable<IEventViewModel>, INotifyPropertyChanged {
|
||||
event EventHandler EventVisualUpdated;
|
||||
public event EventHandler<EventCycleDirection> 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<EventCycleDirection> CycleEvent;
|
||||
|
||||
public FlyEventModel(IDataModel model, int bank, int map, Func<ModelDelta> tokenFactory) {
|
||||
public FlyEventViewModel(IDataModel model, int bank, int map, Func<ModelDelta> 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<IEventModel> {
|
||||
public abstract class BaseEventViewModel : ViewModelCore, IEventViewModel, IEquatable<IEventViewModel> {
|
||||
public event EventHandler EventVisualUpdated;
|
||||
public event EventHandler<EventCycleDirection> 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<int> gotoAddress;
|
||||
|
||||
public event EventHandler<DataMovedEventArgs> DataMoved;
|
||||
@@ -577,7 +579,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
|
||||
#endregion
|
||||
|
||||
public ObjectEventModel(Action<int> gotoAddress, ModelArrayElement objectEvent, IReadOnlyList<IPixelViewModel> sprites) : base(objectEvent, "objectCount") {
|
||||
public ObjectEventViewModel(Action<int> gotoAddress, ModelArrayElement objectEvent, IReadOnlyList<IPixelViewModel> 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<int> gotoAddress;
|
||||
|
||||
public ScriptEventModel(Action<int> gotoAddress, ModelArrayElement scriptEvent) : base(scriptEvent, "scriptCount") { this.gotoAddress = gotoAddress; }
|
||||
public ScriptEventViewModel(Action<int> 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<DataMovedEventArgs> DataMoved;
|
||||
|
||||
public SignpostEventModel(ModelArrayElement signpostEvent, Action<int> gotoAddress) : base(signpostEvent, "signpostCount") {
|
||||
public SignpostEventViewModel(ModelArrayElement signpostEvent, Action<int> gotoAddress) : base(signpostEvent, "signpostCount") {
|
||||
new List<string> {
|
||||
"Facing Any",
|
||||
"Facing North",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
</Grid>
|
||||
<ContentControl DataContext="{Binding SelectedEvent}" Content="{Binding}">
|
||||
<ContentControl.Resources>
|
||||
<DataTemplate DataType="{x:Type map:ObjectEventModel}">
|
||||
<DataTemplate DataType="{x:Type map:ObjectEventViewModel}">
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -385,7 +385,7 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type map:WarpEventModel}">
|
||||
<DataTemplate DataType="{x:Type map:WarpEventViewModel}">
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -423,7 +423,7 @@
|
||||
<local:AngleTextBox Grid.Row="4" Grid.Column="1" VerticalAlignment="Top" Margin="0,2,2,2" Direction="Out" TextBinding="{Binding WarpID, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type map:ScriptEventModel}">
|
||||
<DataTemplate DataType="{x:Type map:ScriptEventViewModel}">
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -468,7 +468,7 @@
|
||||
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type map:SignpostEventModel}">
|
||||
<DataTemplate DataType="{x:Type map:SignpostEventViewModel}">
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -537,7 +537,7 @@
|
||||
Visibility="{Binding ShowHiddenItemProperties, Converter={StaticResource BoolToVisibility}}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type map:FlyEventModel}">
|
||||
<DataTemplate DataType="{x:Type map:FlyEventViewModel}">
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -712,6 +712,11 @@
|
||||
<local:PixelImage />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ContextMenu>
|
||||
<ContextMenu IsEnabled="False" ContextMenuClosing="DisableMenuOnClose">
|
||||
<MenuItem Header="Create New Map" Click="CreateMapForWarpClicked" />
|
||||
</ContextMenu>
|
||||
</ItemsControl.ContextMenu>
|
||||
</ItemsControl>
|
||||
<ItemsControl Grid.Column="1" Grid.Row="1" Name="MapButtons" ItemsSource="{Binding MapButtons}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user