diff --git a/src/HexManiac.Core/Models/Code/ScriptParser.cs b/src/HexManiac.Core/Models/Code/ScriptParser.cs index 5129b27b..b8bbf5e1 100644 --- a/src/HexManiac.Core/Models/Code/ScriptParser.cs +++ b/src/HexManiac.Core/Models/Code/ScriptParser.cs @@ -7,7 +7,6 @@ using Microsoft.Scripting.Utils; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Text; @@ -670,7 +669,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code { // done with script lines, now write deferred data foreach (var deferred in deferredContent) { - deferred.WriteData(result, start); + deferred.WriteData(result, start); } if (updateBody != null) updateBody.StreamTypes = streamTypes; @@ -767,7 +766,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code { // match linecode (if there is one) if (tokens.Length > 1 && candidates.Any(candidate => candidate.LineCode.Count > 1) && tokens[1].TryParseInt(out var num)) candidates = candidates.Where(line => line.LineCode.Count < 2 || line.LineCode[1] == num).ToList(); - var isAfterToken = context.Index > 0 && + var isAfterToken = context.Index > 0 && context.Index < context.Line.Length && (context.Line.Length == context.Index || context.Line[context.Index] == ' ') && (char.IsLetterOrDigit(context.Line[context.Index - 1]) || context.Line[context.Index - 1].IsAny("_~'\"-.".ToCharArray())); if (isAfterToken) { @@ -971,7 +970,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code { } public static class ScriptExtensions { - public static MacroScriptLine GetMatchingMacro(this IReadOnlyList self, int gameHash, IReadOnlyListdata, int start) { + public static MacroScriptLine GetMatchingMacro(this IReadOnlyList self, int gameHash, IReadOnlyList data, int start) { return (MacroScriptLine)self.FirstOrDefault(option => option is MacroScriptLine && option.Matches(gameHash, data, start)); } @@ -1105,7 +1104,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code { } } - private (int, int) ReadSpriteTemplateField(string format,string content) { + private (int, int) ReadSpriteTemplateField(string format, string content) { content = content.Trim(); if (format.EndsWith(":") && content.TryParseInt(out var result)) return (result, 2); if (format.EndsWith("<>") && content.Trim('<', '>').TryParseHex(out result)) return (result, 4); diff --git a/src/HexManiac.Core/Models/PCSString.cs b/src/HexManiac.Core/Models/PCSString.cs index e7834f07..a2ede422 100644 --- a/src/HexManiac.Core/Models/PCSString.cs +++ b/src/HexManiac.Core/Models/PCSString.cs @@ -10,7 +10,7 @@ using System.Text; namespace HavenSoft.HexManiac.Core.Models { public interface ITextConverter { List Convert(string text, out bool containsBadCharacters); - string Convert(IReadOnlyList data, int startIndex, int length); + string Convert(IReadOnlyList data, int startIndex, int length, bool includeNewlineCharacters = false); bool AnyMacroStartsWith(string input); /// @@ -36,8 +36,8 @@ namespace HavenSoft.HexManiac.Core.Models { return PCSString.Convert(text, gameCode, out containsBadCharacters); } - public string Convert(IReadOnlyList data, int startIndex, int length) { - return PCSString.Convert(gameCode, data, startIndex, length); + public string Convert(IReadOnlyList data, int startIndex, int length, bool includeNewlineCharacters = false) { + return PCSString.Convert(gameCode, data, startIndex, length, includeNewlineCharacters); } public bool AnyMacroStartsWith(string input) { @@ -138,7 +138,7 @@ namespace HavenSoft.HexManiac.Core.Models { private static IReadOnlyDictionary> GetTextMacrosFromReference(string[] reference) { var macros = new Dictionary>(); - foreach(var game in new[] { "AXVE", "AXPE", "BPRE", "BPGE", "BPEE" }) { + foreach (var game in new[] { "AXVE", "AXPE", "BPRE", "BPGE", "BPEE" }) { macros.Add(game, GetTextMacrosFromReference(reference, game)); } return macros; @@ -148,7 +148,7 @@ namespace HavenSoft.HexManiac.Core.Models { var allIndex = new Dictionary>>(); foreach (var macroKvp in allMacros) { var index = new IReadOnlyDictionary[256]; - foreach(var macro in macroKvp.Value) { + foreach (var macro in macroKvp.Value) { var (text, bytes) = macro; if (bytes.Length < 1) continue; var lead = bytes[0]; @@ -240,7 +240,7 @@ namespace HavenSoft.HexManiac.Core.Models { public static string Convert(IReadOnlyList data, int startIndex, int length) => Convert(string.Empty, data, startIndex, length); - public static string Convert(string macroSet, IReadOnlyList data, int startIndex, int length) { + public static string Convert(string macroSet, IReadOnlyList data, int startIndex, int length, bool includeNewlineCharacters = false) { var result = new StringBuilder("\"", length * 2); if (!TextMacrosIndex.TryGetValue(macroSet, out var textMacros)) textMacros = null; @@ -261,13 +261,16 @@ namespace HavenSoft.HexManiac.Core.Models { // this line optimized for maximum speed. Otherwise would like to use the Newlines array. if (length > 1 && (currentByte == 0xFA || currentByte == 0xFB || currentByte == 0xFE)) { - if (currentByte == 0xFB) { + if (currentByte == 0xFB) { // paragraph + if (includeNewlineCharacters) result.Append("\\pn"); result.AppendLine(Environment.NewLine); nextExpectedNewline = NewlineMode.Wrap; } else if (currentByte == 0xFE && nextExpectedNewline == NewlineMode.Wrap) { + if (includeNewlineCharacters) result.Append("\\n"); result.AppendLine(); nextExpectedNewline = NewlineMode.Feed; } else if (currentByte == 0xFA && nextExpectedNewline == NewlineMode.Feed) { + if (includeNewlineCharacters) result.Append("\\l"); result.AppendLine(); nextExpectedNewline = NewlineMode.Feed; } else { @@ -324,7 +327,7 @@ namespace HavenSoft.HexManiac.Core.Models { bool foundMatch = false; // check macros - if (input[index] == '[' && textMacros!=null) { + if (input[index] == '[' && textMacros != null) { var closeMacro = input.Substring(index).IndexOf(']') + index; if (closeMacro > index) { var candidate = input.Substring(index, closeMacro + 1 - index); diff --git a/src/HexManiac.Core/Models/Runs/PCSRun.cs b/src/HexManiac.Core/Models/Runs/PCSRun.cs index 05cdd92a..ec766d96 100644 --- a/src/HexManiac.Core/Models/Runs/PCSRun.cs +++ b/src/HexManiac.Core/Models/Runs/PCSRun.cs @@ -83,7 +83,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { protected override BaseRun Clone(SortedSpan newPointerSources) => new PCSRun(model, Start, Length, newPointerSources); - public void AppendTo(IDataModel model, StringBuilder builder, int start, int length, int depth) => builder.Append(model.TextConverter.Convert(model, Start, Length)); + public void AppendTo(IDataModel model, StringBuilder builder, int start, int length, int depth) => builder.Append(model.TextConverter.Convert(model, Start, Length, includeNewlineCharacters: true)); public void Clear(IDataModel model, ModelDelta changeToken, int start, int length) { for (int i = 0; i < length; i++) { diff --git a/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs b/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs index a8ea6787..80910a26 100644 --- a/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs +++ b/src/HexManiac.Core/ViewModels/Map/BlockEditor.cs @@ -562,6 +562,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { } public void GetSelectionFromTile(IPixelViewModel tileImage) { + if (tileImage is null) return; ShowTiles = true; var index = indexForTileImage[tileImage]; var (pal, hFlip, vFlip, tileIndex) = LzTilemapRun.ReadTileData(blocks[blockIndex], index, 2); diff --git a/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs b/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs index 01882abe..eadd515e 100644 --- a/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Map/BlockMapViewModel.cs @@ -101,7 +101,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { if (events[i].Equals(selectedEvent)) { selectionIndex = categories[currentCategory].Count - 1; selectedCategory = currentCategory; - }; + } + ; } // remove unused categories @@ -225,10 +226,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { public double WidthRatio => 80.0 / PixelWidth / Math.Min(1, SpriteScale); public double HeightRatio => 80.0 / PixelHeight / Math.Min(1, SpriteScale); private bool showBeneath; - public bool ShowBeneath { get => showBeneath; set => Set(ref showBeneath, value, old => { - NotifyPropertiesChanged(nameof(WidthRatio), nameof(HeightRatio)); - tutorials.Complete(Tutorial.SpaceBar_ShowBeneath); - } ); } + public bool ShowBeneath { + get => showBeneath; set => Set(ref showBeneath, value, old => { + NotifyPropertiesChanged(nameof(WidthRatio), nameof(HeightRatio)); + tutorials.Complete(Tutorial.SpaceBar_ShowBeneath); + }); + } private MapDisplayOptions showEvents; public MapDisplayOptions ShowEvents { get => showEvents; set => SetEnum(ref showEvents, value, old => ClearPixelCache()); } @@ -1044,8 +1047,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { var defaultBlock = grid[1, 1] & 0x3FF; var key = corner7.MostCommonKey(); corners[0, 0] = key != 0 ? key : defaultBlock; key = corner9.MostCommonKey(); corners[1, 0] = key != 0 ? key : defaultBlock; - key = corner1.MostCommonKey(); corners[0, 1] = key != 0 ? key : defaultBlock; - key = corner3.MostCommonKey(); corners[1, 1] = key != 0 ? key : defaultBlock; + key = corner1.MostCommonKey(); corners[0, 1] = key != 0 ? key : defaultBlock; + key = corner3.MostCommonKey(); corners[1, 1] = key != 0 ? key : defaultBlock; innerCornersFor9Grid = corners; } @@ -2242,7 +2245,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { var (borderWidth, borderHeight) = (borderBlockCopy.PixelWidth / 16, borderBlockCopy.PixelHeight / 16); for (int y = 0; y < height + border.North + border.South; y++) { for (int x = 0; x < width + border.West + border.East; x++) { - if (y < border.North || x < border.West || y >= border.North + height || x >= border.West + width) { + if ((y < border.North || x < border.West || y >= border.North + height || x >= border.West + width) && borderWidth > 0 && borderHeight > 0) { var (xEdge, yEdge) = (x - border.West - width, y - border.North - height); var (rightEdge, bottomEdge) = (xEdge >= 0, yEdge >= 0); // top/left @@ -2637,7 +2640,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map { return MapIDToText(model, group, map); } - public static string MapIDToText(IDataModel model, int group, int map){ + public static string MapIDToText(IDataModel model, int group, int map) { var offset = model.IsFRLG() ? 0x58 : 0; var mapBanks = new ModelTable(model, model.GetTable(HardcodeTablesModel.MapBankTable).Start); diff --git a/src/HexManiac.Tests/Before_Baseclass/StringModelTests.cs b/src/HexManiac.Tests/Before_Baseclass/StringModelTests.cs index 66c3b1a1..9dde6d01 100644 --- a/src/HexManiac.Tests/Before_Baseclass/StringModelTests.cs +++ b/src/HexManiac.Tests/Before_Baseclass/StringModelTests.cs @@ -786,6 +786,18 @@ namespace HavenSoft.HexManiac.Tests { Assert.Equal(1, value); // matches "cde" over "abc" because it starts with "c" } + [Fact] + public void TextContainsNewline_Copy_IncludeNewlines() { + SetFullModel(0xFF); + ViewPort.Edit("^text\"\" Line1\\nLine2"); + + ViewPort.SelectionStart = new(0, 0); + ViewPort.ExpandSelection(0, 0); + ViewPort.Copy.Execute(FileSystem); + + Assert.Contains("\\n", FileSystem.CopyText.value); + } + private void HackTextConverter(string game) { var converter = New.PCSConverter(game); var property = Model.GetType().GetProperty(nameof(Model.TextConverter));