mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-10-02 05:06:35 -05:00
fix loading bug
data didn't load correctly if a false pointer or text interrupted the data
This commit is contained in:
@@ -18,6 +18,7 @@ namespace HavenSoft.HexManiac.Core {
|
||||
public static void RaiseCanExecuteChanged(this StubCommand self) => self?.CanExecuteChanged.Invoke(self, EventArgs.Empty);
|
||||
|
||||
public static void Raise(this EventHandler handler, object source) => handler?.Invoke(source, EventArgs.Empty);
|
||||
public static void Raise<T>(this EventHandler<T> handler, object source, T args) => handler?.Invoke(source, args);
|
||||
|
||||
public static T LimitToRange<T>(this T value, T lower, T upper) where T : IComparable<T> {
|
||||
if (upper.CompareTo(lower) < 0) throw new ArgumentException($"upper value {upper} is less than lower value {lower}");
|
||||
|
||||
@@ -639,8 +639,12 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
public bool DestinationDataMatchesPointerFormat(IDataModel owner, ModelDelta token, int source, int destination, IReadOnlyList<ArrayRunElementSegment> sourceSegments, int parentIndex) {
|
||||
if (destination == Pointer.NULL) return true;
|
||||
var run = owner.GetNextRun(destination);
|
||||
if (run.Start < destination && run is not ITableRun) return false;
|
||||
if (run.Start > destination || (run.Start == destination && (run is NoInfoRun || run is PointerRun))) {
|
||||
bool possibleFalseRun = false;
|
||||
if (run.Start < destination && run is not ITableRun) {
|
||||
if (run is PCSRun || run is PointerRun) possibleFalseRun = true; // this could have been added automatically and is blocking the correct interpretation of the data
|
||||
else return false;
|
||||
}
|
||||
if ((run.Start < destination && possibleFalseRun) || run.Start > destination || (run.Start == destination && (run is NoInfoRun || run is PointerRun))) {
|
||||
// hard case: no format found, so check the data
|
||||
if (destination < 0 || destination >= owner.Count) return false;
|
||||
return Factory.GetStrategy(InnerFormat)?.TryAddFormatAtDestination(owner, token, source, destination, Name, sourceSegments, parentIndex) ?? false;
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
var banks = new ModelTable(model, bankTable.Start, null, bankTable);
|
||||
for (int i = 0; i < banks.Count; i++) {
|
||||
var maps = banks[i].GetSubTable("maps");
|
||||
if (maps == null) return results;
|
||||
if (maps == null) continue;
|
||||
for (int j = 0; j < maps.Count; j++) {
|
||||
var mapText = "maps." + BlockMapViewModel.MapIDToText(model, i * 1000 + j);
|
||||
results.Add(new(i, j, mapText));
|
||||
|
||||
@@ -449,7 +449,8 @@ namespace HexManiac.Core.Models.Runs.Sprites {
|
||||
return canvas;
|
||||
}
|
||||
|
||||
private void EstimateBlockCount(ref int blockCount, int blockStart, int attributeStart) {
|
||||
private void EstimateBlockCount(ref int blockCount, int blockStart, int attributeStart) => EstimateBlockCount(model, ref blockCount, blockStart, attributeStart);
|
||||
public static void EstimateBlockCount(IDataModel model, ref int blockCount, int blockStart, int attributeStart) {
|
||||
var blockLength = 0x10;
|
||||
IFormattedRun run;
|
||||
for (run = model.GetNextRun(blockStart + 1); run.Start < attributeStart; run = model.GetNextRun(run.Start + run.Length)) {
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
}
|
||||
|
||||
var matchedMaps = viewPort.Model.GetMatchingMaps(currentSelection);
|
||||
if (matchedMaps.Count == 1) {
|
||||
if (matchedMaps.Count == 1 || matchedMaps.Any(map => map.Name == currentSelection)) {
|
||||
viewPort?.Goto?.Execute(currentSelection);
|
||||
ControlVisible = false;
|
||||
ShowAutoCompleteOptions = false;
|
||||
|
||||
@@ -13,11 +13,10 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using static HavenSoft.HexManiac.Core.ViewModels.Map.MapSliderIcons;
|
||||
using static IronPython.Modules._ast;
|
||||
|
||||
namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
public class BlockMapViewModel : ViewModelCore, IPixelViewModel {
|
||||
|
||||
private readonly Format format;
|
||||
private readonly IFileSystem fileSystem;
|
||||
private readonly IEditableViewPort viewPort;
|
||||
private readonly IDataModel model;
|
||||
@@ -387,38 +386,23 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
private MapRepointer mapRepointer;
|
||||
public MapRepointer MapRepointer => mapRepointer;
|
||||
|
||||
public BlockMapViewModel(IFileSystem fileSystem, IEditableViewPort viewPort, int group, int map) {
|
||||
public BlockMapViewModel(IFileSystem fileSystem, IEditableViewPort viewPort, Format format, int group, int map) {
|
||||
this.format = format;
|
||||
this.fileSystem = fileSystem;
|
||||
this.viewPort = viewPort;
|
||||
this.model = viewPort.Model;
|
||||
this.tokenFactory = () => viewPort.ChangeHistory.CurrentChange;
|
||||
(this.group, this.map) = (group, map);
|
||||
Transparent = -1;
|
||||
InitTableRef(!model.IsFRLG());
|
||||
Header = new(GetMapModel(), tokenFactory);
|
||||
var mapModel = GetMapModel();
|
||||
Header = new(mapModel, tokenFactory);
|
||||
RefreshMapSize();
|
||||
PrimaryTiles = PrimaryBlocks = model.IsFRLG() ? 640 : 512;
|
||||
PrimaryPalettes = model.IsFRLG() ? 7 : 6;
|
||||
|
||||
(LeftEdge, TopEdge) = (-PixelWidth / 2, -PixelHeight / 2);
|
||||
|
||||
mapRepointer = new MapRepointer();
|
||||
}
|
||||
|
||||
private string _bld, _layout, _objects, _warps, _scripts, _signposts, _events, _connections, _header, _map;
|
||||
private void InitTableRef(bool isRSE) {
|
||||
_bld = $"[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> block<> animation<> attributes<>]1";
|
||||
if (isRSE) _bld = $"[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> block<> attributes<> animation<>]1";
|
||||
_layout = $"[width:: height:: borderblock<> blockmap<`blm`> blockdata1<{_bld}> blockdata2<{_bld}> borderwidth. borderheight. unused:]1";
|
||||
if (isRSE) _layout = $"[width:: height:: borderblock<> blockmap<`blm`> blockdata1<{_bld}> blockdata2<{_bld}>]1";
|
||||
_objects = "[id. graphics. unused: x:500 y:500 elevation. moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag: unused:]/objectCount";
|
||||
_warps = "[x:500 y:500 elevation. warpID. map. bank.]/warps";
|
||||
_scripts = "[x:500 y:500 elevation: trigger: index:: script<`xse`>]/scriptCount";
|
||||
_signposts = "[x:500 y:500 elevation. kind. unused: arg::|h]/signposts";
|
||||
_events = $"[objectCount. warpCount. scriptCount. signpostCount. objects<{_objects}> warps<{_warps}> scripts<{_scripts}> signposts<{_signposts}>]1";
|
||||
_connections = "[count:: connections<[direction:: offset:: mapGroup. mapNum. unused:]/count>]1";
|
||||
_header = "music: layoutID: regionSectionID. cave. weather. mapType. allowBiking. flags.|t|allowEscaping.|allowRunning.|showMapName::: floorNum. battleType.";
|
||||
_map = $"[layout<{_layout}> events<{_events}> mapscripts<[type. pointer<>]!00> connections<{_connections}> {_header}]";
|
||||
mapRepointer = new MapRepointer(format, fileSystem, model, viewPort.ChangeHistory, MapID);
|
||||
}
|
||||
|
||||
public IReadOnlyList<BlockMapViewModel> GetNeighbors(MapDirection direction) {
|
||||
@@ -778,12 +762,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 enumViewModel = new EnumViewModel(mapBanks.Count.Range(i => i.ToString()).ToArray());
|
||||
var option = fileSystem.ShowOptions(
|
||||
"Pick a group",
|
||||
"Which map group do you want to add the new map to?",
|
||||
new[] { new[] { enumViewModel } },
|
||||
new VisualOption { Index = 1, Option = "OK", ShortDescription = "Insert New Map" });
|
||||
var option = MapRepointer.GetMapBankForNewMap("Which map group do you want to add the new map to?");
|
||||
if (option == -1) return;
|
||||
|
||||
var info = (ConnectionInfo)obj;
|
||||
@@ -797,27 +776,19 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
newConnection.Offset = info.Offset;
|
||||
newConnection.Direction = info.Direction;
|
||||
|
||||
newConnection.MapGroup = enumViewModel.Choice;
|
||||
newConnection.MapGroup = option;
|
||||
|
||||
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);
|
||||
|
||||
ITableRun mapTable;
|
||||
if (mapBanks.Count == newConnection.MapGroup) {
|
||||
var newTable = model.RelocateForExpansion(token, mapBanks.Run, mapBanks.Run.Length + mapBanks.Run.ElementLength);
|
||||
newTable = newTable.Append(token, 1);
|
||||
model.ObserveRunWritten(token, newTable);
|
||||
mapBanks = new ModelTable(model, newTable.Start, tokenFactory, newTable);
|
||||
var tableStart = model.FindFreeSpace(model.FreeSpaceStart, 8);
|
||||
mapTable = new TableStreamRun(model, tableStart, SortedSpan.One(mapBanks[newConnection.MapGroup].Start), $"[map<{_map}1>]", null, new DynamicStreamStrategy(model, null), 0);
|
||||
model.UpdateArrayPointer(token, null, null, -1, mapBanks[newConnection.MapGroup].Start, tableStart);
|
||||
} else {
|
||||
mapTable = mapBanks[newConnection.MapGroup].GetSubTable("maps").Run;
|
||||
}
|
||||
newConnection.MapNum = mapTable.ElementCount;
|
||||
mapTable = mapTable.Append(token, 1);
|
||||
model.ObserveRunWritten(token, mapTable);
|
||||
var address = CreateNewMap(token, info.Size, info.Size);
|
||||
model.UpdateArrayPointer(token, null, null, -1, mapTable.Start + mapTable.Length - 4, address);
|
||||
|
||||
var otherMap = new BlockMapViewModel(fileSystem, viewPort, newConnection.MapGroup, newConnection.MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
var otherMap = new BlockMapViewModel(fileSystem, viewPort, format, newConnection.MapGroup, newConnection.MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
otherMap.UpdateLayoutID();
|
||||
info = new ConnectionInfo(info.Size, -info.Offset, info.OppositeDirection);
|
||||
newConnection = otherMap.AddConnection(info);
|
||||
@@ -841,7 +812,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
var bank = mapBanks[group];
|
||||
var maps = bank.GetSubTable("maps");
|
||||
for (int map = 0; map < maps.Count; map++) {
|
||||
var mapVM = new BlockMapViewModel(fileSystem, viewPort, group, map) { allOverworldSprites = allOverworldSprites };
|
||||
var mapVM = new BlockMapViewModel(fileSystem, viewPort, format, group, map) { allOverworldSprites = allOverworldSprites };
|
||||
var newInfo = mapVM.CanConnect(info.OppositeDirection);
|
||||
if (newInfo != null) options[mapVM.MapID] = newInfo;
|
||||
}
|
||||
@@ -850,11 +821,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
// select which map to add
|
||||
var keys = options.Keys.ToList();
|
||||
var enumViewModel = new EnumViewModel(keys.Select(key => MapIDToText(model, key)).ToArray());
|
||||
|
||||
var option = fileSystem.ShowOptions(
|
||||
"Pick a group",
|
||||
"Which map group do you want to add the new map to?",
|
||||
"Which map do you want to connect to?",
|
||||
new[] { new[] { enumViewModel } },
|
||||
new VisualOption { Index = 1, Option = "OK", ShortDescription = "Insert New Map" });
|
||||
new VisualOption { Index = 1, Option = "OK", ShortDescription = "Connect Existing Map" });
|
||||
if (option == -1) return;
|
||||
var choice = keys[enumViewModel.Choice];
|
||||
|
||||
@@ -864,7 +836,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
newConnection.MapGroup = choice / 1000;
|
||||
newConnection.MapNum = choice % 1000;
|
||||
|
||||
var otherMap = new BlockMapViewModel(fileSystem, viewPort, newConnection.MapGroup, newConnection.MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
var otherMap = new BlockMapViewModel(fileSystem, viewPort, format, newConnection.MapGroup, newConnection.MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
info = options[choice];
|
||||
newConnection = otherMap.AddConnection(info);
|
||||
newConnection.Offset = info.Offset;
|
||||
@@ -907,7 +879,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
var map = GetMapModel();
|
||||
var connectionsAndCountTable = map.GetSubTable("connections");
|
||||
if (connectionsAndCountTable == null) {
|
||||
var newConnectionsAndCountTable = CreateNewConnections(token);
|
||||
var newConnectionsAndCountTable = MapRepointer.CreateNewConnections(token);
|
||||
model.UpdateArrayPointer(token, null, null, -1, map.Start + 12, newConnectionsAndCountTable);
|
||||
connectionsAndCountTable = map.GetSubTable("connections");
|
||||
}
|
||||
@@ -1011,28 +983,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
return new ModelArrayElement(model, newRun.Start, newRun.ElementCount - 1, tokenFactory, newRun);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new map using the existing borderblocks and block data.
|
||||
/// Creates event data with 0 events, creates connection data with 0 connections.
|
||||
/// Copies all the flags from the current map
|
||||
/// </summary>
|
||||
private int CreateNewMap(ModelDelta token, int width, int height) {
|
||||
var currentMap = GetMapModel();
|
||||
var mapStart = model.FindFreeSpace(model.FreeSpaceStart, 28);
|
||||
// music: layoutID: regionSectionID. cave. weather. mapType. allowBiking. flags. floorNum. battleType.
|
||||
for (int i = 16; i < 28; i++) token.ChangeData(model, mapStart + i, model[currentMap.Start + i]);
|
||||
|
||||
WritePointerAndSource(token, mapStart + 0, CreateNewLayout(token, width, height));
|
||||
WritePointerAndSource(token, mapStart + 4, CreateNewEvents(token));
|
||||
WritePointerAndSource(token, mapStart + 8, CreateNewMapScripts(token));
|
||||
WritePointerAndSource(token, mapStart + 12, CreateNewConnections(token));
|
||||
|
||||
var table = new TableStreamRun(model, mapStart, SortedSpan<int>.None, _map, null, new FixedLengthStreamStrategy(1));
|
||||
model.ObserveRunWritten(token, table);
|
||||
|
||||
return mapStart;
|
||||
}
|
||||
|
||||
private void Erase(ITableRun table, ModelDelta token) {
|
||||
foreach (var source in table.PointerSources) {
|
||||
model.ClearPointer(token, source, table.Start);
|
||||
@@ -1072,7 +1022,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
}
|
||||
|
||||
private BlockMapViewModel GetNeighbor(ConnectionModel connection, Border border) {
|
||||
var vm = new BlockMapViewModel(fileSystem, viewPort, connection.MapGroup, connection.MapNum) {
|
||||
var vm = new BlockMapViewModel(fileSystem, viewPort, format, connection.MapGroup, connection.MapNum) {
|
||||
IncludeBorders = IncludeBorders,
|
||||
SpriteScale = SpriteScale,
|
||||
allOverworldSprites = allOverworldSprites,
|
||||
@@ -1090,8 +1040,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
private void RefreshPaletteCache(ModelArrayElement layout = null, BlocksetModel blockModel1 = null, BlocksetModel blockModel2 = null) {
|
||||
if (blockModel1 == null || blockModel2 == null) {
|
||||
if (layout == null) layout = GetLayout();
|
||||
if (blockModel1 == null) blockModel1 = new BlocksetModel(model, layout.GetAddress("blockdata1"));
|
||||
if (blockModel2 == null) blockModel2 = new BlocksetModel(model, layout.GetAddress("blockdata2"));
|
||||
if (blockModel1 == null) blockModel1 = new BlocksetModel(model, layout.GetAddress(Format.PrimaryBlockset));
|
||||
if (blockModel2 == null) blockModel2 = new BlocksetModel(model, layout.GetAddress(Format.SecondaryBlockset));
|
||||
}
|
||||
|
||||
palettes = BlockmapRun.ReadPalettes(blockModel1, blockModel2, PrimaryPalettes);
|
||||
@@ -1129,8 +1079,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
private void RefreshBlockRenderCache(ModelArrayElement layout = null, BlocksetModel blockModel1 = null, BlocksetModel blockModel2 = null) {
|
||||
if (blocks == null || tiles == null || palettes == null) {
|
||||
if (layout == null) layout = GetLayout();
|
||||
if (blockModel1 == null) blockModel1 = new BlocksetModel(model, layout.GetAddress("blockdata1"));
|
||||
if (blockModel2 == null) blockModel2 = new BlocksetModel(model, layout.GetAddress("blockdata2"));
|
||||
if (blockModel1 == null) blockModel1 = new BlocksetModel(model, layout.GetAddress(Format.PrimaryBlockset));
|
||||
if (blockModel2 == null) blockModel2 = new BlocksetModel(model, layout.GetAddress(Format.SecondaryBlockset));
|
||||
}
|
||||
if (blocks == null) RefreshBlockCache(layout, blockModel1, blockModel2);
|
||||
if (tiles == null) RefreshTileCache(layout, blockModel1, blockModel2);
|
||||
@@ -1353,12 +1303,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
for (int i = 0; i < connections.Count; i++) {
|
||||
if (connections[i].Direction != direction) continue;
|
||||
if (direction == MapDirection.Up || direction == MapDirection.Down) {
|
||||
var map = new BlockMapViewModel(fileSystem, viewPort, connections[i].MapGroup, connections[i].MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
var map = new BlockMapViewModel(fileSystem, viewPort, format, connections[i].MapGroup, connections[i].MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
var removeWidth = map.pixelWidth / 16;
|
||||
var removeOffset = connections[i].Offset;
|
||||
foreach (int j in removeWidth.Range()) availableSpace.Remove(j + removeOffset);
|
||||
} else if (direction == MapDirection.Left || direction == MapDirection.Right) {
|
||||
var map = new BlockMapViewModel(fileSystem, viewPort, connections[i].MapGroup, connections[i].MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
var map = new BlockMapViewModel(fileSystem, viewPort, format, connections[i].MapGroup, connections[i].MapNum) { allOverworldSprites = allOverworldSprites };
|
||||
var removeHeight = map.pixelHeight / 16;
|
||||
var removeOffset = connections[i].Offset;
|
||||
foreach (int j in removeHeight.Range()) availableSpace.Remove(j + removeOffset);
|
||||
@@ -1402,56 +1352,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
return null;
|
||||
}
|
||||
|
||||
private int CreateNewLayout(ModelDelta token, int width, int height) {
|
||||
// width:: height:: borderblock<> blockmap<`blm`> blockdata1<> blockdata2<> borderwidth. borderheight. unused:
|
||||
var myLayout = GetLayout();
|
||||
var blockmapLength = width * height * 2;
|
||||
var blockmapStart = model.FindFreeSpace(model.FreeSpaceStart, blockmapLength + 28);
|
||||
var layoutStart = blockmapStart + blockmapLength;
|
||||
token.ChangeData(model, blockmapStart, new byte[blockmapLength]);
|
||||
|
||||
model.WriteValue(token, layoutStart + 0, width);
|
||||
model.WriteValue(token, layoutStart + 4, height);
|
||||
WritePointerAndSource(token, layoutStart + 8, myLayout.GetAddress("borderblock"));
|
||||
WritePointerAndSource(token, layoutStart + 12, blockmapStart);
|
||||
WritePointerAndSource(token, layoutStart + 16, myLayout.GetAddress("blockdata1"));
|
||||
WritePointerAndSource(token, layoutStart + 20, myLayout.GetAddress("blockdata2"));
|
||||
if (myLayout.HasField("borderwidth")) {
|
||||
model.WriteValue(token, layoutStart + 24, myLayout.GetValue("borderwidth"));
|
||||
model.WriteValue(token, layoutStart + 25, myLayout.GetValue("borderwidth"));
|
||||
model.WriteMultiByteValue(layoutStart + 26, 2, token, 0);
|
||||
}
|
||||
return layoutStart;
|
||||
}
|
||||
|
||||
private void WritePointerAndSource(ModelDelta token, int source, int destination) {
|
||||
model.WritePointer(token, source, destination);
|
||||
model.ObserveRunWritten(token, NoInfoRun.FromPointer(model, source));
|
||||
}
|
||||
|
||||
private int CreateNewEvents(ModelDelta token) {
|
||||
// objectCount. warpCount. scriptCount. signpostCount. objects<> warps<> scripts<> signposts<>
|
||||
var eventStart = model.FindFreeSpace(model.FreeSpaceStart, 20);
|
||||
token.ChangeData(model, eventStart, new byte[20]);
|
||||
return eventStart;
|
||||
}
|
||||
|
||||
private int CreateNewMapScripts(ModelDelta token) {
|
||||
// mapscripts<[type. pointer<>]!00>
|
||||
var eventStart = model.FindFreeSpace(model.FreeSpaceStart, 4);
|
||||
token.ChangeData(model, eventStart, new byte[] { 0, 0xFF, 0xFF, 0xFF });
|
||||
return eventStart;
|
||||
}
|
||||
|
||||
private int CreateNewConnections(ModelDelta token) {
|
||||
// count:: connections<>
|
||||
var connectionStart = model.FindFreeSpace(model.FreeSpaceStart, 8);
|
||||
token.ChangeData(model, connectionStart, new byte[8]);
|
||||
var run = new TableStreamRun(model, connectionStart, SortedSpan<int>.None, $"[{ConnectionInfo.ConnectionTableContent}]", null, new FixedLengthStreamStrategy(1));
|
||||
model.ObserveRunWritten(token, run);
|
||||
return connectionStart;
|
||||
}
|
||||
|
||||
private void GotoAddress(int address) => viewPort.Goto.Execute(address);
|
||||
|
||||
private void HandleBlocksChanged(object sender, byte[][] blocks) {
|
||||
@@ -1546,6 +1451,56 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
|
||||
public record Border(int North, int East, int South, int West);
|
||||
|
||||
public class Format {
|
||||
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";
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
/// Represents the entire map editor tab, with all visible controls, maps, edit boxes, etc
|
||||
/// </summary>
|
||||
public class MapEditorViewModel : ViewModelCore, ITabContent {
|
||||
private readonly Format format;
|
||||
private readonly IFileSystem fileSystem;
|
||||
private readonly IEditableViewPort viewPort;
|
||||
private readonly IDataModel model;
|
||||
@@ -23,6 +24,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
|
||||
public IViewPort ViewPort => viewPort;
|
||||
public IFileSystem FileSystem => fileSystem;
|
||||
public Format Format => format;
|
||||
|
||||
private long blockset;
|
||||
private int blockset1, blockset2;
|
||||
@@ -211,7 +213,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePrimaryMap(new BlockMapViewModel(fileSystem, viewPort, bank, map) {
|
||||
UpdatePrimaryMap(new BlockMapViewModel(fileSystem, viewPort, format, bank, map) {
|
||||
IncludeBorders = primaryMap?.IncludeBorders ?? true,
|
||||
SpriteScale = primaryMap?.SpriteScale ?? .5,
|
||||
});
|
||||
@@ -239,9 +241,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
history.Redo.CanExecuteChanged += (sender, e) => redo.RaiseCanExecuteChanged();
|
||||
history.Bind(nameof(history.HasDataChange), (sender, e) => NotifyPropertyChanged(nameof(Name)));
|
||||
|
||||
PrimaryTiles = model.IsFRLG() ? 640 : 512;
|
||||
var isFRLG = model.IsFRLG();
|
||||
PrimaryTiles = isFRLG ? 640 : 512;
|
||||
this.format = new Format(!isFRLG);
|
||||
|
||||
var map = new BlockMapViewModel(fileSystem, viewPort, 3, 19);
|
||||
var map = new BlockMapViewModel(fileSystem, viewPort, format, 3, 19);
|
||||
templates = new(model, viewPort.Tools.CodeTool.ScriptParser, map.AllOverworldSprites);
|
||||
UpdatePrimaryMap(map);
|
||||
for (int i = 0; i < 0x40; i++) CollisionOptions.Add(i.ToString("X2"));
|
||||
|
||||
@@ -1,5 +1,536 @@
|
||||
namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
using HavenSoft.HexManiac.Core.Models;
|
||||
using HavenSoft.HexManiac.Core.Models.Runs;
|
||||
using HavenSoft.HexManiac.Core.ViewModels.Tools;
|
||||
using HexManiac.Core.Models.Runs.Sprites;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace HavenSoft.HexManiac.Core.ViewModels.Map {
|
||||
public record ChangeMapEventArgs(int Bank, int Map);
|
||||
|
||||
public class MapRepointer : ViewModelCore {
|
||||
|
||||
private readonly Format format;
|
||||
private readonly IFileSystem fileSystem;
|
||||
private readonly IDataModel model;
|
||||
private readonly ChangeHistory<ModelDelta> history;
|
||||
private readonly int mapID; // bank * 1000 + map
|
||||
|
||||
#region Commands
|
||||
|
||||
private StubCommand
|
||||
repointLayout,
|
||||
repointBorderBlock,
|
||||
repointBlockMap,
|
||||
repointPrimaryBlockset,
|
||||
repointSecondaryBlockset,
|
||||
repointPrimaryTileset,
|
||||
repointSecondaryTileset,
|
||||
expandPrimaryTileset,
|
||||
expandSecondaryTileset,
|
||||
repointPrimaryBlocks,
|
||||
repointSecondaryBlocks,
|
||||
expandPrimaryBlocks,
|
||||
expandSecondaryBlocks,
|
||||
repointPrimaryPalette,
|
||||
repointSecondaryPalette,
|
||||
expandPrimaryPalette,
|
||||
expandSecondaryPalette,
|
||||
createPrimaryTilesetAnimations,
|
||||
createSecondaryTilesetAnimations,
|
||||
duplicateMap;
|
||||
|
||||
public ICommand RepointLayout => StubCommand(ref repointLayout, ExecuteRepointLayout, CanRepointLayout);
|
||||
public ICommand RepointBorderBlock => StubCommand(ref repointBorderBlock, ExecuteRepointBorderBlock, CanRepointBorderBlock);
|
||||
public ICommand RepointBlockMap => StubCommand(ref repointBlockMap, ExecuteRepointBlockMap, CanRepointBlockMap);
|
||||
public ICommand RepointPrimaryBlockset => StubCommand(ref repointPrimaryBlockset, ExecuteRepointPrimaryBlockset, CanRepointPrimaryBlockset);
|
||||
public ICommand RepointSecondaryBlockset => StubCommand(ref repointSecondaryBlockset, ExecuteRepointSecondaryBlockset, CanRepointSecondaryBlockset);
|
||||
public ICommand RepointPrimaryTileset => StubCommand(ref repointPrimaryTileset, ExecuteRepointPrimaryTileset, CanRepointPrimaryTileset);
|
||||
public ICommand RepointSecondaryTileset => StubCommand(ref repointSecondaryTileset, ExecuteRepointSecondaryTileset, CanRepointSecondaryTileset);
|
||||
public ICommand ExpandPrimaryTileset => StubCommand(ref expandPrimaryTileset, ExecuteExpandPrimaryTileset, CanExpandPrimaryTileset);
|
||||
public ICommand ExpandSecondaryTileset => StubCommand(ref expandSecondaryTileset, ExecuteExpandSecondaryTileset, CanExpandSecondaryTileset);
|
||||
public ICommand RepointPrimaryBlocks => StubCommand(ref repointPrimaryBlocks, ExecuteRepointPrimaryBlocks, CanRepointPrimaryBlocks);
|
||||
public ICommand RepointSecondaryBlocks => StubCommand(ref repointSecondaryBlocks, ExecuteRepointSecondaryBlocks, CanRepointSecondaryBlocks);
|
||||
public ICommand ExpandPrimaryBlocks => StubCommand(ref expandPrimaryBlocks, ExecuteExpandPrimaryBlocks, CanExpandPrimaryBlocks);
|
||||
public ICommand ExpandSecondaryBlocks => StubCommand(ref expandSecondaryBlocks, ExecuteExpandSecondaryBlocks, CanExpandSecondaryBlocks);
|
||||
public ICommand RepointPrimaryPalette => StubCommand(ref repointPrimaryPalette, ExecuteRepointPrimaryPalette, CanRepointPrimaryPalette);
|
||||
public ICommand RepointSecondaryPalette => StubCommand(ref repointSecondaryPalette, ExecuteRepointSecondaryPalette, CanRepointSecondaryPalette);
|
||||
public ICommand ExpandPrimaryPalette => StubCommand(ref expandPrimaryPalette, ExecuteExpandPrimaryPalette, CanExpandPrimaryPalette);
|
||||
public ICommand ExpandSecondaryPalette => StubCommand(ref expandSecondaryPalette, ExecuteExpandSecondaryPalette, CanExpandSecondaryPalette);
|
||||
public ICommand CreatePrimaryTilesetAnimations => StubCommand(ref createPrimaryTilesetAnimations, ExecuteCreatePrimaryTilesetAnimations, CanCreatePrimaryTilesetAnimations);
|
||||
public ICommand CreateSecondaryTilesetAnimations => StubCommand(ref createSecondaryTilesetAnimations, ExecuteCreateSecondaryTilesetAnimations, CanCreateSecondaryTilesetAnimations);
|
||||
public ICommand DuplicateMap => StubCommand(ref duplicateMap, ExecuteDuplicateMap, CanDuplicateMap);
|
||||
|
||||
#endregion
|
||||
|
||||
public event EventHandler DataMoved;
|
||||
public event EventHandler<ChangeMapEventArgs> ChangeMap;
|
||||
|
||||
public MapRepointer(Format format, IFileSystem fileSystem, IDataModel model, ChangeHistory<ModelDelta> history, int mapID) {
|
||||
this.format = format;
|
||||
this.fileSystem = fileSystem;
|
||||
this.model = model;
|
||||
this.history = history;
|
||||
this.mapID = mapID;
|
||||
}
|
||||
|
||||
#region General
|
||||
|
||||
private bool CanRepointLayout() {
|
||||
var map = GetMapModel();
|
||||
if (map == null) return false;
|
||||
var layoutStart = map.GetAddress(Format.Layout);
|
||||
if (layoutStart < 0 || layoutStart >= model.Count) return false;
|
||||
var layoutRun = model.GetNextRun(layoutStart);
|
||||
return layoutRun.PointerSources != null && layoutRun.PointerSources.Count > 1;
|
||||
}
|
||||
|
||||
private void ExecuteRepointLayout() {
|
||||
var map = GetMapModel();
|
||||
var layoutStart = map.GetAddress(Format.Layout);
|
||||
var layoutRun = model.GetNextRun(layoutStart);
|
||||
var newDataStart = DuplicateData(layoutRun.Start, layoutRun.Length);
|
||||
map.SetAddress(Format.Layout, newDataStart);
|
||||
DataMoved.Raise(this);
|
||||
repointLayout.RaiseCanExecuteChanged();
|
||||
repointBorderBlock.RaiseCanExecuteChanged();
|
||||
repointBlockMap.RaiseCanExecuteChanged();
|
||||
repointPrimaryBlockset.RaiseCanExecuteChanged();
|
||||
repointSecondaryBlockset.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool CanRepointBorderBlock() => CanRepointLayoutMember(Format.BorderBlock);
|
||||
|
||||
private void ExecuteRepointBorderBlock() {
|
||||
var layout = GetLayout();
|
||||
var count = 4;
|
||||
if (layout.HasField(Format.BorderWidth) && layout.HasField(Format.BorderHeight)) {
|
||||
count = Math.Max(4, layout.GetValue(Format.BorderWidth) * layout.GetValue(Format.BorderHeight));
|
||||
}
|
||||
ExecuteRepointLayoutMember(Format.BorderBlock, count * 2, repointBorderBlock);
|
||||
}
|
||||
|
||||
private bool CanRepointBlockMap() => CanRepointLayoutMember(Format.BlockMap);
|
||||
|
||||
private void ExecuteRepointBlockMap() {
|
||||
var layout = GetLayout();
|
||||
var (width, height) = (layout.GetValue("width"), layout.GetValue("height"));
|
||||
ExecuteRepointLayoutMember(Format.BlockMap, width * height * 2, repointBlockMap);
|
||||
}
|
||||
|
||||
private bool CanRepointPrimaryBlockset() => CanRepointLayoutMember(Format.PrimaryBlockset);
|
||||
|
||||
private void ExecuteRepointPrimaryBlockset() {
|
||||
ExecuteRepointLayoutMember(Format.PrimaryBlockset, 6 * 4,
|
||||
repointPrimaryBlockset, repointPrimaryBlocks,
|
||||
repointPrimaryTileset, repointPrimaryPalette,
|
||||
expandPrimaryBlocks, expandPrimaryTileset,
|
||||
expandPrimaryPalette, createPrimaryTilesetAnimations);
|
||||
}
|
||||
|
||||
private bool CanRepointSecondaryBlockset() => CanRepointLayoutMember(Format.SecondaryBlockset);
|
||||
|
||||
private void ExecuteRepointSecondaryBlockset() {
|
||||
ExecuteRepointLayoutMember(Format.SecondaryBlockset, 6 * 4,
|
||||
repointSecondaryBlockset, repointSecondaryBlocks,
|
||||
repointSecondaryTileset, repointSecondaryPalette,
|
||||
expandSecondaryBlocks, expandSecondaryTileset,
|
||||
expandSecondaryPalette, createSecondaryTilesetAnimations);
|
||||
}
|
||||
|
||||
private bool CanDuplicateMap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ExecuteDuplicateMap() {
|
||||
var option = GetMapBankForNewMap("Duplicate map into which group?");
|
||||
if (option == -1) return;
|
||||
var table = AddNewMapToBank(option);
|
||||
var newMapStart = CreateNewMap(history.CurrentChange);
|
||||
model.UpdateArrayPointer(history.CurrentChange, null, null, -1, table.Start + table.Length - 4, newMapStart);
|
||||
ChangeMap.Raise(this, new(option, table.ElementCount - 1));
|
||||
repointLayout.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool CanRepointLayoutMember(string member) {
|
||||
var layout = GetLayout();
|
||||
if (layout == null) return false;
|
||||
var start = layout.GetAddress(member);
|
||||
var run = model.GetNextRun(start);
|
||||
return run.PointerSources != null && run.PointerSources.Count > 1;
|
||||
}
|
||||
|
||||
private void ExecuteRepointLayoutMember(string member, int length, params StubCommand[] commands) {
|
||||
var layout = GetLayout();
|
||||
var start = DuplicateData(layout.GetAddress(member), length);
|
||||
layout.SetAddress(member, start);
|
||||
DataMoved.Raise(this);
|
||||
foreach (var command in commands) command.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Blocks / Tilesets / Palettes
|
||||
|
||||
private bool CanRepointPrimaryBlocks() => CanRepointBlocksetMember(Format.PrimaryBlockset, Format.Blocks);
|
||||
|
||||
private void ExecuteRepointPrimaryBlocks() {
|
||||
var layout = GetLayout();
|
||||
var attributeSize = model.IsFRLG() ? 2 : 1;
|
||||
var (blockCount, _) = EstimateBlockCount(layout, true);
|
||||
ExecuteRepointBlocksetMember(Format.PrimaryBlockset, Format.Blocks, blockCount * 16);
|
||||
ExecuteRepointBlocksetMember(Format.PrimaryBlockset, Format.BlockAttributes, blockCount * attributeSize, repointPrimaryBlocks);
|
||||
}
|
||||
|
||||
private bool CanRepointSecondaryBlocks() => CanRepointBlocksetMember(Format.SecondaryBlockset, Format.Blocks);
|
||||
|
||||
private void ExecuteRepointSecondaryBlocks() {
|
||||
var layout = GetLayout();
|
||||
var attributeSize = model.IsFRLG() ? 2 : 1;
|
||||
var (blockCount, _) = EstimateBlockCount(layout, false);
|
||||
ExecuteRepointBlocksetMember(Format.PrimaryBlockset, Format.Blocks, blockCount * 16);
|
||||
ExecuteRepointBlocksetMember(Format.PrimaryBlockset, Format.BlockAttributes, blockCount * attributeSize, repointPrimaryBlocks);
|
||||
}
|
||||
|
||||
private bool CanExpandPrimaryBlocks() {
|
||||
var layout = GetLayout();
|
||||
var (blockCount, maxBlockCount) = EstimateBlockCount(layout, true);
|
||||
return blockCount < maxBlockCount;
|
||||
}
|
||||
|
||||
private void ExecuteExpandPrimaryBlocks() {
|
||||
ExecuteExpandBlocks(true);
|
||||
expandPrimaryBlocks.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool CanExpandSecondaryBlocks() {
|
||||
var layout = GetLayout();
|
||||
var (blockCount, maxBlockCount) = EstimateBlockCount(layout, false);
|
||||
return blockCount < maxBlockCount;
|
||||
}
|
||||
|
||||
private void ExecuteExpandSecondaryBlocks() {
|
||||
ExecuteExpandBlocks(false);
|
||||
expandPrimaryBlocks.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
private void ExecuteExpandBlocks(bool primary) {
|
||||
// expand blocks and attributes
|
||||
// neither have any formatting, so here's the plan:
|
||||
var token = history.CurrentChange;
|
||||
var layout = GetLayout();
|
||||
var (blockCount, maxBlockCount) = EstimateBlockCount(layout, primary);
|
||||
var attributeSize = model.IsFRLG() ? 2 : 1;
|
||||
// (1) copy all block/attribute data into a temp array
|
||||
var blocksetName = primary ? Format.PrimaryBlockset : Format.SecondaryBlockset;
|
||||
var blockStart = layout.GetSubTable(blocksetName)[0].GetAddress(Format.Blocks);
|
||||
var attributeStart = layout.GetSubTable(blocksetName)[0].GetAddress(Format.BlockAttributes);
|
||||
var blockData = Cut(blockStart, blockCount * 16);
|
||||
var attributeData = Cut(attributeStart, blockCount * attributeSize);
|
||||
// (2) repoint blocks/attrbutes
|
||||
blockStart = model.RelocateForExpansion(token, model.GetNextRun(blockStart), maxBlockCount * 16).Start;
|
||||
attributeStart = model.RelocateForExpansion(token, model.GetNextRun(attributeStart), maxBlockCount * attributeSize).Start;
|
||||
// (3) paste old data and expand
|
||||
Paste(blockStart, blockData, maxBlockCount * 16);
|
||||
Paste(attributeStart, attributeData, maxBlockCount * attributeSize);
|
||||
|
||||
DataMoved.Raise(this);
|
||||
}
|
||||
|
||||
private byte[] Cut(int start, int length) {
|
||||
var result = new byte[length];
|
||||
var token = history.CurrentChange;
|
||||
for (int i = 0; i < length; i++) {
|
||||
result[i] = model[start + i];
|
||||
token.ChangeData(model, start + i, 0xFF);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Paste(int start, byte[] data, int length) {
|
||||
var token = history.CurrentChange;
|
||||
token.ChangeData(model, start, data);
|
||||
for (int i = data.Length; i < length; i++) token.ChangeData(model, start + i, 0);
|
||||
}
|
||||
|
||||
private (int, int) EstimateBlockCount(ModelArrayElement layout, bool primary) {
|
||||
var blocksetName = primary ? Format.PrimaryBlockset : Format.SecondaryBlockset;
|
||||
var blockset = layout.GetSubTable(blocksetName)[0];
|
||||
var blockCount = model.IsFRLG() ? 640 : 512;
|
||||
if (!primary) blockCount = 1024 - blockCount;
|
||||
var maxBlockCount = blockCount;
|
||||
var blockStart = blockset.GetAddress(Format.Blocks);
|
||||
var attributeStart = blockset.GetAddress(Format.BlockAttributes);
|
||||
BlocksetModel.EstimateBlockCount(model, ref blockCount, blockStart, attributeStart);
|
||||
return (blockCount, maxBlockCount);
|
||||
}
|
||||
|
||||
|
||||
private bool CanRepointPrimaryTileset() => CanRepointBlocksetMember(Format.PrimaryBlockset, Format.Tileset);
|
||||
|
||||
private void ExecuteRepointPrimaryTileset() => ExecuteRepointTileset(Format.PrimaryBlockset);
|
||||
private void ExecuteRepointTileset(string blocksetName) {
|
||||
var maxTileCount = model.IsFRLG() ? 640 : 512;
|
||||
if (blocksetName == Format.SecondaryBlockset) maxTileCount = 1024 - maxTileCount;
|
||||
var layout = GetLayout();
|
||||
var blockset = layout.GetSubTable(blocksetName)[0];
|
||||
var isCompressed = blockset.GetValue("isCompressed") != 0;
|
||||
if (isCompressed) RepointCompressedTileset(blocksetName, maxTileCount);
|
||||
else RepointUncompressedTileset(blocksetName, maxTileCount);
|
||||
}
|
||||
|
||||
private bool CanRepointSecondaryTileset() => CanRepointBlocksetMember(Format.SecondaryBlockset, Format.Tileset);
|
||||
|
||||
private void ExecuteRepointSecondaryTileset() => ExecuteRepointTileset(Format.PrimaryBlockset);
|
||||
|
||||
private bool CanExpandPrimaryTileset() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ExecuteExpandPrimaryTileset() {
|
||||
// example uncompressed: emerald bank 25 map 0 -> primary tileset starts at 37AA48 and is 0x8000 bytes. 0x20 bytes is one tile, so that's 0x400 tiles, but the limit should be 0x200?
|
||||
// secondary tileset starts at 36B9DF and appears to be only 0xA60 bytes (0x53 tiles) long
|
||||
}
|
||||
|
||||
private bool CanExpandSecondaryTileset() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ExecuteExpandSecondaryTileset() {
|
||||
|
||||
}
|
||||
|
||||
private void RepointCompressedTileset(string blocksetName, int maxTileCount) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void RepointUncompressedTileset(string blocksetName, int maxTileCount) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private bool CanRepointPrimaryPalette() => CanRepointBlocksetMember(Format.PrimaryBlockset, Format.Palette);
|
||||
|
||||
private void ExecuteRepointPrimaryPalette() {
|
||||
ExecuteRepointBlocksetMember(Format.PrimaryBlockset, Format.Palette, 32, repointPrimaryPalette);
|
||||
}
|
||||
|
||||
private bool CanRepointSecondaryPalette() => CanRepointBlocksetMember(Format.SecondaryBlockset, Format.Palette);
|
||||
|
||||
private void ExecuteRepointSecondaryPalette() {
|
||||
ExecuteRepointBlocksetMember(Format.SecondaryBlockset, Format.Palette, 32, repointSecondaryPalette);
|
||||
}
|
||||
|
||||
private bool CanExpandPrimaryPalette() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ExecuteExpandPrimaryPalette() {
|
||||
|
||||
}
|
||||
|
||||
private bool CanExpandSecondaryPalette() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ExecuteExpandSecondaryPalette() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private bool CanRepointBlocksetMember(string blocksetName, string member) {
|
||||
var layout = GetLayout();
|
||||
if (layout == null) return false;
|
||||
var blockset = layout.GetSubTable(blocksetName);
|
||||
if (blockset == null) return false;
|
||||
var start = blockset[0].GetAddress(member);
|
||||
var run = model.GetNextRun(start);
|
||||
return run.PointerSources != null && run.PointerSources.Count > 1;
|
||||
}
|
||||
|
||||
private void ExecuteRepointBlocksetMember(string blocksetName, string member, int length, params StubCommand[] commands) {
|
||||
var layout = GetLayout();
|
||||
var blockset = layout.GetSubTable(blocksetName)[0];
|
||||
var start = DuplicateData(blockset.GetAddress(member), length);
|
||||
blockset.SetAddress(member, start);
|
||||
DataMoved.Raise(this);
|
||||
foreach (var command in commands) command.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Animations
|
||||
|
||||
private bool CanCreatePrimaryTilesetAnimations() {
|
||||
// we should be able to run the utility to create a tileset animation unless we already have
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ExecuteCreatePrimaryTilesetAnimations() {
|
||||
// TODO grab the code from the utility and use that here
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private bool CanCreateSecondaryTilesetAnimations() {
|
||||
// we should be able to run the utility to create a tileset animation unless we already have
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ExecuteCreateSecondaryTilesetAnimations() {
|
||||
// grab the code from the utility and use that here
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
public int GetMapBankForNewMap(string prompt) {
|
||||
var tokenFactory = () => history.CurrentChange;
|
||||
var mapBanks = new ModelTable(model, model.GetTable(HardcodeTablesModel.MapBankTable).Start, tokenFactory);
|
||||
var enumViewModel = new EnumViewModel(mapBanks.Count.Range(i => i.ToString()).ToArray());
|
||||
var option = fileSystem.ShowOptions(
|
||||
"Pick a group",
|
||||
prompt,
|
||||
new[] { new[] { enumViewModel } },
|
||||
new VisualOption { Index = 1, Option = "OK", ShortDescription = "Insert New Map" });
|
||||
if (option == -1) return option;
|
||||
return enumViewModel.Choice;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands the chosen map bank by one, adding a new map to the end.
|
||||
/// </summary>
|
||||
/// <returns>The table that contains the new map.</returns>
|
||||
public ITableRun AddNewMapToBank(int option) {
|
||||
var tokenFactory = () => history.CurrentChange;
|
||||
var token = history.CurrentChange;
|
||||
var mapBanks = new ModelTable(model, model.GetTable(HardcodeTablesModel.MapBankTable).Start, tokenFactory);
|
||||
ITableRun mapTable;
|
||||
if (mapBanks.Count == option) {
|
||||
var newTable = model.RelocateForExpansion(token, mapBanks.Run, mapBanks.Run.Length + mapBanks.Run.ElementLength);
|
||||
newTable = newTable.Append(token, 1);
|
||||
model.ObserveRunWritten(token, newTable);
|
||||
mapBanks = new ModelTable(model, newTable.Start, tokenFactory, newTable);
|
||||
var tableStart = model.FindFreeSpace(model.FreeSpaceStart, 8);
|
||||
mapTable = new TableStreamRun(model, tableStart, SortedSpan.One(mapBanks[option].Start), $"[map<{format.MapFormat}1>]", null, new DynamicStreamStrategy(model, null), 0);
|
||||
model.UpdateArrayPointer(token, null, null, -1, mapBanks[option].Start, tableStart);
|
||||
} else {
|
||||
mapTable = mapBanks[option].GetSubTable("maps").Run;
|
||||
}
|
||||
mapTable = mapTable.Append(token, 1);
|
||||
model.ObserveRunWritten(token, mapTable);
|
||||
return mapTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new map using the current map's layout.
|
||||
/// Creates event data with 0 events, map scripts data with 0 scripts, and connection data with 0 connections.
|
||||
/// Copies all the flags/header from the current map.
|
||||
/// </summary>
|
||||
public int CreateNewMap(ModelDelta token) {
|
||||
var currentMap = GetMapModel();
|
||||
var mapStart = model.FindFreeSpace(model.FreeSpaceStart, 28);
|
||||
// music: layoutID: regionSectionID. cave. weather. mapType. allowBiking. flags. floorNum. battleType.
|
||||
for (int i = 16; i < 28; i++) token.ChangeData(model, mapStart + i, model[currentMap.Start + i]);
|
||||
|
||||
WritePointerAndSource(token, mapStart + 4, CreateNewEvents(token));
|
||||
WritePointerAndSource(token, mapStart + 8, CreateNewMapScripts(token));
|
||||
WritePointerAndSource(token, mapStart + 12, CreateNewConnections(token));
|
||||
|
||||
var table = new TableStreamRun(model, mapStart, SortedSpan<int>.None, format.MapFormat, null, new FixedLengthStreamStrategy(1));
|
||||
model.ObserveRunWritten(token, table);
|
||||
|
||||
return mapStart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new layout using the existing layout's borderblock, blockmap, primary blockset, and secondary blockset.
|
||||
/// Once this new layout is assigned to a map, you'll want to update that map's layout ID by calling BlockMapViewModel.UpdateLayoutID()
|
||||
/// </summary>
|
||||
public int CreateNewLayout(ModelDelta token) {
|
||||
var layoutStart = model.FindFreeSpace(model.FreeSpaceStart, 28);
|
||||
var myLayout = GetLayout();
|
||||
|
||||
model.WriteValue(token, layoutStart + 0, myLayout.GetValue("width"));
|
||||
model.WriteValue(token, layoutStart + 4, myLayout.GetValue("height"));
|
||||
WritePointerAndSource(token, layoutStart + 8, myLayout.GetAddress(Format.BorderBlock));
|
||||
WritePointerAndSource(token, layoutStart + 12, myLayout.GetAddress(Format.BlockMap));
|
||||
WritePointerAndSource(token, layoutStart + 16, myLayout.GetAddress(Format.PrimaryBlockset));
|
||||
WritePointerAndSource(token, layoutStart + 20, myLayout.GetAddress(Format.SecondaryBlockset));
|
||||
if (myLayout.HasField(Format.BorderWidth)) {
|
||||
model.WriteValue(token, layoutStart + 24, myLayout.GetValue(Format.BorderWidth));
|
||||
model.WriteValue(token, layoutStart + 25, myLayout.GetValue(Format.BorderHeight));
|
||||
model.WriteMultiByteValue(layoutStart + 26, 2, token, 0);
|
||||
}
|
||||
return layoutStart;
|
||||
}
|
||||
|
||||
public int CreateNewBlockMap(ModelDelta token, int width, int height) {
|
||||
var blockmapLength = width * height * 2;
|
||||
var blockmapStart = model.FindFreeSpace(model.FreeSpaceStart, blockmapLength);
|
||||
token.ChangeData(model, blockmapStart, new byte[blockmapLength]);
|
||||
return blockmapStart;
|
||||
}
|
||||
|
||||
public int CreateNewBorderBlock(ModelDelta token) {
|
||||
var layout = GetLayout();
|
||||
var count = 4;
|
||||
if (layout.HasField(Format.BorderWidth) && layout.HasField(Format.BorderHeight)) {
|
||||
count = Math.Max(4, layout.GetValue(Format.BorderWidth) * layout.GetValue(Format.BorderHeight));
|
||||
}
|
||||
var myStart = layout.GetAddress(Format.BorderBlock);
|
||||
var start = model.FindFreeSpace(model.FreeSpaceStart, count * 2);
|
||||
for (int i = 0; i < count; i++) model.WriteMultiByteValue(start + i * 2, 2, token, model.ReadMultiByteValue(myStart + i * 2, 2));
|
||||
return start;
|
||||
}
|
||||
|
||||
public int CreateNewEvents(ModelDelta token) {
|
||||
// objectCount. warpCount. scriptCount. signpostCount. objects<> warps<> scripts<> signposts<>
|
||||
var eventStart = model.FindFreeSpace(model.FreeSpaceStart, 20);
|
||||
token.ChangeData(model, eventStart, new byte[20]);
|
||||
return eventStart;
|
||||
}
|
||||
|
||||
public int CreateNewMapScripts(ModelDelta token) {
|
||||
// mapscripts<[type. pointer<>]!00>
|
||||
var eventStart = model.FindFreeSpace(model.FreeSpaceStart, 4);
|
||||
token.ChangeData(model, eventStart, new byte[] { 0, 0xFF, 0xFF, 0xFF });
|
||||
return eventStart;
|
||||
}
|
||||
|
||||
public int CreateNewConnections(ModelDelta token) {
|
||||
// count:: connections<>
|
||||
var connectionStart = model.FindFreeSpace(model.FreeSpaceStart, 8);
|
||||
token.ChangeData(model, connectionStart, new byte[8]);
|
||||
var run = new TableStreamRun(model, connectionStart, SortedSpan<int>.None, $"[{ConnectionInfo.ConnectionTableContent}]", null, new FixedLengthStreamStrategy(1));
|
||||
model.ObserveRunWritten(token, run);
|
||||
return connectionStart;
|
||||
}
|
||||
|
||||
private ModelArrayElement GetMapModel() {
|
||||
return BlockMapViewModel.GetMapModel(model, mapID / 1000, mapID % 1000, () => history.CurrentChange);
|
||||
}
|
||||
|
||||
private ModelArrayElement GetLayout(ModelArrayElement map = null) {
|
||||
if (map == null) map = GetMapModel();
|
||||
if (map == null) return null;
|
||||
var layout = map.GetSubTable(Format.Layout);
|
||||
if (layout == null) return null;
|
||||
return layout[0];
|
||||
}
|
||||
|
||||
private int DuplicateData(int start, int length) {
|
||||
var newStart = model.FindFreeSpace(model.FreeSpaceStart, length);
|
||||
var token = history.CurrentChange;
|
||||
for (int i = 0; i < length; i++) token.ChangeData(model, newStart + i, model[start + i]);
|
||||
return newStart;
|
||||
}
|
||||
|
||||
private void WritePointerAndSource(ModelDelta token, int source, int destination) {
|
||||
model.WritePointer(token, source, destination);
|
||||
model.ObserveRunWritten(token, NoInfoRun.FromPointer(model, source));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
var maps = Model.GetMatchingMaps(str);
|
||||
if (maps.Count == 1 && mapper != null && !str.TryParseHex(out _)) {
|
||||
var previousMap = mapper.PrimaryMap;
|
||||
mapper.PrimaryMap = new BlockMapViewModel(mapper.FileSystem, this, maps[0].Group, maps[0].Map);
|
||||
mapper.PrimaryMap = new BlockMapViewModel(mapper.FileSystem, this, mapper.Format, maps[0].Group, maps[0].Map);
|
||||
args = new TabChangeRequestedEventArgs(mapper);
|
||||
RequestTabChange?.Invoke(this, args);
|
||||
if (args.RequestAccepted) {
|
||||
|
||||
Reference in New Issue
Block a user