From 93250ec061d5d1cecaa0e01611dcadca773ebdf8 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Fri, 1 May 2020 08:16:04 -0500 Subject: [PATCH] Add backbone for battle-script support battle scripts are almost identical to pokescripts in how they work. Each command has a single-byte code, followed by some number of arguments, each of which are 1 to 4 bytes. Because of these similarities, we can reuse the same engine code and just load the engine from another file. So the code tool can have a 4th mode and basically just work for free. Runs for battle scripts can be as simple as XSE runs: just a single byte, don't bothe decoding, show decoding only in the tool. Still to add: how to know which battle script commands are ending commands, or which ones point to other battle scripts, or which ones point to strings. How to view battle scripts in the editor? Etc. --- src/HexManiac.Core/HexManiac.Core.csproj | 3 + .../Models/Code/ScriptParser.cs | 43 ++- .../Models/Code/battleScriptReference.txt | 331 ++++++++++++++++++ src/HexManiac.Core/Models/Runs/XSERun.cs | 18 +- src/HexManiac.Core/Models/Singletons.cs | 20 +- .../ViewModels/Tools/CodeTool.cs | 58 ++- src/HexManiac.Core/ViewModels/ViewPort.cs | 4 +- src/HexManiac.WPF/Controls/TabView.xaml.cs | 3 +- 8 files changed, 426 insertions(+), 54 deletions(-) create mode 100644 src/HexManiac.Core/Models/Code/battleScriptReference.txt diff --git a/src/HexManiac.Core/HexManiac.Core.csproj b/src/HexManiac.Core/HexManiac.Core.csproj index 94f03abc..896e1cf5 100644 --- a/src/HexManiac.Core/HexManiac.Core.csproj +++ b/src/HexManiac.Core/HexManiac.Core.csproj @@ -158,6 +158,9 @@ + + Always + Always diff --git a/src/HexManiac.Core/Models/Code/ScriptParser.cs b/src/HexManiac.Core/Models/Code/ScriptParser.cs index 8c18da5c..3b9dc202 100644 --- a/src/HexManiac.Core/Models/Code/ScriptParser.cs +++ b/src/HexManiac.Core/Models/Code/ScriptParser.cs @@ -66,7 +66,10 @@ namespace HavenSoft.HexManiac.Core.Models.Code { } // TODO refactor to rely on CollectScripts rather than duplicate code - public void FormatScript(ModelDelta token, IDataModel model, int address, IReadOnlyList sources = null) { + public void FormatScript(ModelDelta token, IDataModel model, int address, IReadOnlyList sources = null) where TSERun : IScriptStartRun { + Func, IScriptStartRun> constructor = (a, s) => new XSERun(a, s); + if (typeof(TSERun) == typeof(BSERun)) constructor = (a, s) => new BSERun(a, s); + var processed = new List(); var toProcess = new List { address }; while (toProcess.Count > 0) { @@ -74,9 +77,9 @@ namespace HavenSoft.HexManiac.Core.Models.Code { toProcess.RemoveAt(toProcess.Count - 1); if (processed.Contains(address)) continue; var existingRun = model.GetNextRun(address); - if (!(existingRun is XSERun && existingRun.Start == address)) { + if (!(existingRun is TSERun && existingRun.Start == address)) { if (sources == null && existingRun.Start != address) sources = model.SearchForPointersToAnchor(token, address); - model.ObserveAnchorWritten(token, string.Empty, new XSERun(address, sources)); + model.ObserveAnchorWritten(token, string.Empty, constructor(address, sources)); sources = null; } int length = 0; @@ -265,7 +268,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code { string Decompile(IDataModel data, int start); } - public class ScriptLine : IScriptLine { + public abstract class ScriptLine : IScriptLine { private readonly List documentation = new List(); public const string Hex = "0123456789ABCDEF"; @@ -276,12 +279,11 @@ namespace HavenSoft.HexManiac.Core.Models.Code { public IReadOnlyList Documentation => documentation; public string Usage { get; } - private static readonly byte[] endCodes = new byte[] { 0x02, 0x03, 0x05, 0x08, 0x0A, 0x0C, 0x0D }; - public bool IsEndingCommand { get; } - public bool PointsToNextScript => LineCode.Count == 1 && LineCode[0].IsAny(4, 5, 6, 7); - public bool PointsToText => LineCode.Count == 1 && LineCode[0].IsAny(0x0F, 0x67); - public bool PointsToMovement => LineCode.Count == 1 && LineCode[0].IsAny(0x4F, 0x50); - public bool PointsToMart => LineCode.Count == 1 && LineCode[0].IsAny(0x86, 0x87, 0x88); + public virtual bool IsEndingCommand { get; } + public virtual bool PointsToNextScript { get; } + public virtual bool PointsToText { get; } + public virtual bool PointsToMovement { get; } + public virtual bool PointsToMart { get; } public ScriptLine(string engineLine) { var docSplit = engineLine.Split(new[] { '#' }, 2); @@ -308,7 +310,6 @@ namespace HavenSoft.HexManiac.Core.Models.Code { LineCode = lineCode; Args = args; CompiledByteLength = LineCode.Count + Args.Sum(arg => arg.Length); - IsEndingCommand = LineCode.Count == 1 && endCodes.Contains(LineCode[0]); } public void AddDocumentation(string doc) => documentation.Add(doc); @@ -410,6 +411,26 @@ namespace HavenSoft.HexManiac.Core.Models.Code { } } + public class XSEScriptLine : ScriptLine { + public XSEScriptLine(string engineLine) : base(engineLine) { } + + public override bool IsEndingCommand => LineCode.Count == 1 && LineCode[0].IsAny(0x02, 0x03, 0x05, 0x08, 0x0A, 0x0C, 0x0D); + public override bool PointsToNextScript => LineCode.Count == 1 && LineCode[0].IsAny(4, 5, 6, 7); + public override bool PointsToText => LineCode.Count == 1 && LineCode[0].IsAny(0x0F, 0x67); + public override bool PointsToMovement => LineCode.Count == 1 && LineCode[0].IsAny(0x4F, 0x50); + public override bool PointsToMart => LineCode.Count == 1 && LineCode[0].IsAny(0x86, 0x87, 0x88); + } + + public class BSEScriptLine : ScriptLine { + public BSEScriptLine(string engineLine) : base(engineLine) { } + + public override bool IsEndingCommand => true; + public override bool PointsToNextScript => false; + public override bool PointsToText => false; + public override bool PointsToMovement => false; + public override bool PointsToMart => false; + } + public class ScriptArg { public ArgType Type { get; } public string Name { get; } diff --git a/src/HexManiac.Core/Models/Code/battleScriptReference.txt b/src/HexManiac.Core/Models/Code/battleScriptReference.txt new file mode 100644 index 00000000..8e25ce7a --- /dev/null +++ b/src/HexManiac.Core/Models/Code/battleScriptReference.txt @@ -0,0 +1,331 @@ +00 attackcanceler +01 accuracycheck param0:: param1: +02 attackstring +03 ppreduce +04 critcalc +05 damagecalc +06 typecalc +07 adjustnormaldamage +08 adjustnormaldamage2 +09 attackanimation +0a waitanimation +0b healthbarupdate battler. +0c datahpupdate battler. +0d critmessage +0e effectivenesssound +0f resultmessage +10 printstring id: +11 printselectionstring id: +12 waitmessage param0: +13 printfromtable ptr<> +14 printselectionstringfromtable ptr<> +15 seteffectwithchance +16 seteffectprimary +17 seteffectsecondary +18 clearstatusfromeffect battler. +19 tryfaintmon battler. fromMove. ptr<> +1a dofaintanimation battler. +1b cleareffectsonfaint battler. +1c jumpifstatus battler. status1:: ptr<> +1d jumpifstatus2 battler. status2:: ptr<> +1e jumpifability param0. ability. ptr<> +1f jumpifsideaffecting battler. sidestatus: ptr<> +20 jumpifstat battler. ifflag. stat. value. ptr<> +21 jumpifstatus3condition battler. status3:: param2. ptr<> +22 jumpiftype battler. type. ptr<> +23 getexp battler. +24 atk24 ptr<> +25 movevaluescleanup +26 setmultihit value. +27 decrementmultihit value:: +28 goto ptr<> +29 jumpifbyte ifflag. param1:: param2. param3:: +2a jumpifhalfword ifflag. param1:: param2: param3:: +2b jumpifword ifflag. param1:: param2:: param3:: +2c jumpifarrayequal param0:: param1:: param2. param3:: +2d jumpifarraynotequal param0:: param1:: param2. param3:: +2e setbyte ptr<> param1. +2f addbyte ptr<> param1. +30 subbyte ptr<> param1. +31 copyarray param0:: param1:: param2. +32 copyarraywithindex param0:: param1:: param2:: param3. +33 orbyte ptr<> param1. +34 orhalfword ptr<> param1: +35 orword ptr<> param1:: +36 bicbyte ptr<> param1. +37 bichalfword ptr<> param1: +38 bicword ptr<> param1:: +39 pause param0: +3a waitstate +3b healthbar_update battler. +3c return +3d end +3e end2 +3f end3 +40 jumpifaffectedbyprotect ptr<> +41 call ptr<> +42 jumpiftype2 battler. type. ptr<> +43 jumpifabilitypresent ability. ptr<> +44 endselectionscript +45 playanimation battler. param1. param2:: +46 playanimation2 battler. param1:: param2:: +47 setgraphicalstatchangevalues +48 playstatchangeanimation battler. param1. param2. +49 moveend param0. param1. + +# Help macros for 5 uses of moveend command +#XX moveendall # All cases +# moveend 0, 0 +#XX se moveendcase case # Chosen case +# moveend 1, 0 +#XX om moveendfrom from # All cases from (inclusive) +# moveend 0, 0 +#XX moveendto {to} # All cases from 0 to (not inclusive) +# moveend 2, {to} +#XX om moveendfromto from, {to} # Cases from (inclusive) to (not inclusive) +# moveend 2, {to} + +4a typecalc2 +4b returnatktoball +4c getswitchedmondata battler. +4d switchindataupdate battler. +4e switchinanim battler. dontclearsubstitutebit. +4f jumpifcantswitch battler. ptr<> +50 openpartyscreen param0. param1:: +51 switchhandleorder battler. param1. +52 switchineffects battler. +53 trainerslidein battler. +54 playse song: +55 fanfare song: +56 playfaintcry battler. +57 atk57 +58 returntoball battler. +59 handlelearnnewmove param0:: param1:: param2. +5a yesnoboxlearnmove param0:: +5b yesnoboxstoplearningmove param0:: +5c hitanimation battler. +5d getmoneyreward addr:: +5e atk5E battler. +5f swapattackerwithtarget +60 incrementgamestat param0. +61 drawpartystatussummary battler. +62 hidepartystatussummary battler. +63 jumptocalledmove param0. +64 statusanimation battler. +65 status2animation battler. status2:: +66 chosenstatusanimation battler. param1. param2:: +67 yesnobox +68 cancelallactions +69 adjustsetdamage +6a removeitem battler. +6b atknameinbuff1 +6c drawlvlupbox +6d resetsentmonsvalue +6e setatktoplayer0 +6f makevisible battler. +70 recordlastability battler. +71 buffermovetolearn +72 jumpifplayerran ptr<> +73 hpthresholds battler. +74 hpthresholds2 battler. +75 useitemonopponent +76 various battler. param1. +77 setprotectlike +78 faintifabilitynotdamp +79 setatkhptozero +7a jumpifnexttargetvalid ptr<> +7b tryhealhalfhealth param0:: battler. +7c trymirrormove +7d setrain +7e setreflect +7f setseeded +80 manipulatedamage param0. +81 trysetrest param0:: +82 jumpifnotfirstturn ptr<> +83 nop +84 jumpifcantmakeasleep param0:: +85 stockpile +86 stockpiletobasedamage param0:: +87 stockpiletohpheal param0:: +88 negativedamage +89 statbuffchange param0. param1:: +8a normalisebuffs +8b setbide +8c confuseifrepeatingattackends +8d setmultihitcounter param0. +8e initmultihitstring +8f forcerandomswitch param0:: +90 tryconversiontypechange param0:: +91 givepaydaymoney +92 setlightscreen +93 tryKO param0:: +94 damagetohalftargethp +95 setsandstorm +96 weatherdamage +97 tryinfatuating param0:: +98 updatestatusicon battler. +99 setmist +9a setfocusenergy +9b transformdataexecution +9c setsubstitute +9d mimicattackcopy param0:: +9e metronome +9f dmgtolevel +a0 psywavedamageeffect +a1 counterdamagecalculator param0:: +a2 mirrorcoatdamagecalculator param0:: +a3 disablelastusedattack param0:: +a4 trysetencore param0:: +a5 painsplitdmgcalc param0:: +a6 settypetorandomresistance param0:: +a7 setalwayshitflag +a8 copymovepermanently param0:: +a9 trychoosesleeptalkmove param0:: +aa setdestinybond +ab trysetdestinybondtohappen +ac remaininghptopower +ad tryspiteppreduce param0:: +ae healpartystatus +af cursetarget param0:: +b0 trysetspikes param0:: +b1 setforesight +b2 trysetperishsong param0:: +b3 rolloutdamagecalculation +b4 jumpifconfusedandstatmaxed stat. ptr<> +b5 furycuttercalc +b6 happinesstodamagecalculation +b7 presentdamagecalculation +b8 setsafeguard +b9 magnitudedamagecalculation +ba jumpifnopursuitswitchdmg param0:: +bb setsunny +bc maxattackhalvehp param0:: +bd copyfoestats param0:: +be rapidspinfree +bf setdefensecurlbit +c0 recoverbasedonsunlight param0:: +c1 hiddenpowercalc +c2 selectfirstvalidtarget +c3 trysetfutureattack param0:: +c4 trydobeatup param0:: param1:: +c5 setsemiinvulnerablebit +c6 clearsemiinvulnerablebit +c7 setminimize +c8 sethail +c9 jumpifattackandspecialattackcannotfall ptr<> +ca setforcedtarget +cb setcharge +cc callterrainattack +cd cureifburnedparalysedorpoisoned param0:: +ce settorment param0:: +cf jumpifnodamage param0:: +d0 settaunt param0:: +d1 trysethelpinghand param0:: +d2 tryswapitems param0:: +d3 trycopyability param0:: +d4 trywish param0. param1:: +d5 trysetroots param0:: +d6 doubledamagedealtifdamaged +d7 setyawn param0:: +d8 setdamagetohealthdifference param0:: +d9 scaledamagebyhealthratio +da tryswapabilities param0:: +db tryimprison param0:: +dc trysetgrudge param0:: +dd weightdamagecalculation +de assistattackselect param0:: +df trysetmagiccoat param0:: +e0 trysetsnatch param0:: +e1 trygetintimidatetarget param0:: +e2 switchoutabilities battler. +e3 jumpifhasnohp battler. param1:: +e4 getsecretpowereffect +e5 pickup +e6 docastformchangeanimation +e7 trycastformdatachange +e8 settypebasedhalvers param0:: +e9 setweatherballtype +ea tryrecycleitem param0:: +eb settypetoterrain param0:: +ec pursuitrelated param0:: +ed snatchsetbattlers +ee removelightscreenreflect +ef handleballthrow +f0 givecaughtmon +f1 trysetcaughtmondexflags param0:: +f2 displaydexinfo +f3 trygivecaughtmonnick param0:: +f4 subattackerhpbydmg +f5 removeattackerstatus1 +f6 finishaction +f7 finishturn + +# various command changed to more readable macros +#XX cancelmultiturnmoves {battler} +# various {battler} 0 +#XX setmagiccoattarget {battler} +# various {battler} 1 +#XX getifcantrunfrombattle {battler} +# various {battler} 2 +#XX getmovetarget {battler} +# various {battler} 3 +#XX various4 {battler} +# various {battler} 4 +#XX resetintimidatetracebits {battler} +# various {battler} 5 +#XX updatechoicemoveonlvlup {battler} +# various {battler} 6 +#XX various7 {battler} +# various {battler} 7 +#XX various8 {battler} +# various {battler} 8 +#XX returnopponentmon1toball {battler} +# various {battler} 9 +#XX returnopponentmon2toball {battler} +# various {battler} 10 +#XX checkpokeflute {battler} +# various {battler} 11 +#XX waitfanfare {battler} +# various {battler} 12 + +# helpful macros +#XX setstatchanger {stat} {stages} {down} +# setbyte sSTATCHANGER {stat} | {stages} << 4 | {down} << 7 +#XX setmoveeffect {effect} +# setbyte cEFFECT_CHOOSER {effect} +#XX chosenstatus1animation {battler} {status} +# chosenstatusanimation {battler} 0x0 {status} +#XX chosenstatus2animation {battler} {status} +# chosenstatusanimation {battler} 0x1 {status} +#XX sethword {dst} {value} +# setbyte {dst} {value} & 0xFF +# setbyte {dst} + 1 ({value} >> 8) & 0xFF +#XX setword {dst} {value} +# setbyte {dst}, {value} & 0xFF +# setbyte {dst} + 1 ({value} >> 8) & 0xFF +# setbyte {dst} + 2 ({value} >> 16) & 0xFF +# setbyte {dst} + 3 ({value} >> 24) & 0xFF +#XX copybyte {dst} {src} +# copyarray {dst} {src} 0x1 +#XX copyhword {dst} {src} +# copyarray {dst} {src} 0x2 +#XX copyword {dst} {src} +# copyarray {dst} {src} 0x4 +#XX jumpifbytenotequal {byte1} {byte2} {jumpptr} +# jumpifarraynotequal {byte1} {byte2} 1 {jumpptr} +#XX jumpifbyteequal {byte1} {byte2} {jumpptr} +# jumpifarrayequal {byte1} {byte2} 1 {jumpptr} +#XX jumpifmove {move} {jumpptr} +# jumpifhalfword CMP_EQUAL gCurrentMove {move} {jumpptr} +#XX jumpifnotmove {move} {jumpptr} +# jumpifhalfword CMP_NOT_EQUAL gCurrentMove {move} {jumpptr} +#XX jumpifstatus3 {battler} {status} {jumpptr} +# jumpifstatus3condition {battler} {status} 0 {jumpptr} +#XX jumpifnostatus3 {battler} {status} {jumpptr} +# jumpifstatus3condition {battler} {status} 1 {jumpptr} +#XX jumpifmovehadnoeffect {jumpptr} +# jumpifbyte CMP_COMMON_BITS gMoveResultFlags MOVE_RESULT_NO_EFFECT {jumpptr} +#XX jumpifbattletype {flags} {jumpptr} +# jumpifword CMP_COMMON_BITS gBattleTypeFlags {flags} {jumpptr} +#XX jumpifnotbattletype {flags} {jumpptr} +# jumpifword CMP_NO_COMMON_BITS gBattleTypeFlags {flags} {jumpptr} diff --git a/src/HexManiac.Core/Models/Runs/XSERun.cs b/src/HexManiac.Core/Models/Runs/XSERun.cs index 7f4aff8f..e4d220a7 100644 --- a/src/HexManiac.Core/Models/Runs/XSERun.cs +++ b/src/HexManiac.Core/Models/Runs/XSERun.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; namespace HavenSoft.HexManiac.Core.Models.Runs { - public class XSERun : BaseRun { + public interface IScriptStartRun : IFormattedRun { } + + public class XSERun : BaseRun, IScriptStartRun { public static string SharedFormatString => "`xse`"; public override int Length => 1; @@ -15,4 +17,18 @@ namespace HavenSoft.HexManiac.Core.Models.Runs { protected override BaseRun Clone(IReadOnlyList newPointerSources) => new XSERun(Start, newPointerSources); } + + public class BSERun : BaseRun, IScriptStartRun { + public static string SharedFormatString => "`bse`"; + + public override int Length => 1; + + public override string FormatString => SharedFormatString; + + public BSERun(int start, IReadOnlyList sources = null) : base(start, sources) { } + + public override IDataFormat CreateDataFormat(IDataModel data, int index) => None.Instance; + + protected override BaseRun Clone(IReadOnlyList newPointerSources) => new BSERun(Start, newPointerSources); + } } diff --git a/src/HexManiac.Core/Models/Singletons.cs b/src/HexManiac.Core/Models/Singletons.cs index 3b6a1d41..99ebba04 100644 --- a/src/HexManiac.Core/Models/Singletons.cs +++ b/src/HexManiac.Core/Models/Singletons.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -17,23 +18,29 @@ namespace HavenSoft.HexManiac.Core.Models { private const string TableReferenceFileName = "resources/tableReference.txt"; private const string ThumbReferenceFileName = "resources/armReference.txt"; private const string ScriptReferenceFileName = "resources/scriptReference.txt"; + private const string BattleScriptReferenceFileName = "resources/battleScriptReference.txt"; public IMetadataInfo MetadataInfo { get; } public IReadOnlyDictionary GameReferenceTables { get; } public IReadOnlyList ThumbConditionalCodes { get; } public IReadOnlyList ThumbInstructionTemplates { get; } public IReadOnlyList ScriptLines { get; } + public IReadOnlyList BattleScriptLines { get; } public Singletons() { GameReferenceTables = CreateGameReferenceTables(); (ThumbConditionalCodes, ThumbInstructionTemplates) = LoadThumbReference(); - ScriptLines = LoadScriptReference(); + ScriptLines = LoadScriptReference(ScriptReferenceFileName); + BattleScriptLines = LoadScriptReference(BattleScriptReferenceFileName); MetadataInfo = new MetadataInfo(); } - private IReadOnlyList LoadScriptReference() { - if (!File.Exists(ScriptReferenceFileName)) return new List(); - var lines = File.ReadAllLines(ScriptReferenceFileName); + private IReadOnlyList LoadScriptReference(string file) where TLine : ScriptLine { + if (!File.Exists(file)) return new List(); + Func factory = line => new XSEScriptLine(line); + if (typeof(TLine) == typeof(BSEScriptLine)) factory = line => new BSEScriptLine(line); + + var lines = File.ReadAllLines(file); var scriptLines = new List(); ScriptLine active = null; foreach (var line in lines) { @@ -43,7 +50,7 @@ namespace HavenSoft.HexManiac.Core.Models { if (line.Trim().StartsWith("#") && active != null) { active.AddDocumentation(line.Trim()); } else { - active = new ScriptLine(line); + active = factory(line); scriptLines.Add(active); } } @@ -120,6 +127,7 @@ namespace HavenSoft.HexManiac.Core.Models { var assembly = Assembly.GetExecutingAssembly(); var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); VersionNumber = $"{fvi.FileMajorPart}.{fvi.FileMinorPart}.{fvi.FileBuildPart}"; + if (fvi.FilePrivatePart != 0) VersionNumber += "." + fvi.FilePrivatePart; } } } diff --git a/src/HexManiac.Core/ViewModels/Tools/CodeTool.cs b/src/HexManiac.Core/ViewModels/Tools/CodeTool.cs index 7aa6eefb..4c96b454 100644 --- a/src/HexManiac.Core/ViewModels/Tools/CodeTool.cs +++ b/src/HexManiac.Core/ViewModels/Tools/CodeTool.cs @@ -2,6 +2,7 @@ using HavenSoft.HexManiac.Core.Models.Code; using HavenSoft.HexManiac.Core.Models.Runs; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; @@ -9,7 +10,7 @@ using System.Linq; using System.Text; namespace HavenSoft.HexManiac.Core.ViewModels.Tools { - public enum CodeMode { Thumb, Script, Raw } + public enum CodeMode { Thumb, Script, BattleScript, Raw } public class CodeTool : ViewModelCore, IToolViewModel { public string Name => "Code Tool"; @@ -17,7 +18,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { private string content; private CodeMode mode; private readonly ThumbParser thumb; - private readonly ScriptParser script; + private readonly ScriptParser script, battleScript; private readonly IDataModel model; private readonly Selection selection; private readonly ChangeHistory history; @@ -26,7 +27,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { public bool IsReadOnly => Mode == CodeMode.Raw; public bool UseSingleContent => !UseMultiContent; - public bool UseMultiContent => Mode == CodeMode.Script; + public bool UseMultiContent => Mode.IsAny(CodeMode.Script, CodeMode.BattleScript); private bool showErrorText; public bool ShowErrorText { get => showErrorText; private set => TryUpdate(ref showErrorText, value); } @@ -61,12 +62,16 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { public ScriptParser ScriptParser => script; + public ScriptParser BattleScriptParser => battleScript; + public event EventHandler<(int originalLocation, int newLocation)> ModelDataMoved; public CodeTool(Singletons singletons, IDataModel model, Selection selection, ChangeHistory history) { thumb = new ThumbParser(singletons); script = new ScriptParser(singletons.ScriptLines); + battleScript = new ScriptParser(singletons.BattleScriptLines); script.CompileError += ObserveCompileError; + battleScript.CompileError += ObserveCompileError; this.model = model; this.selection = selection; this.history = history; @@ -92,9 +97,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { Content = RawParse(model, start, end - start + 1); } else if (length < 2 && mode != CodeMode.Script) { TryUpdate(ref content, string.Empty, nameof(Content)); - UpdateContents(-1); + UpdateContents(-1, null); } else if (mode == CodeMode.Script) { - UpdateContents(start); + UpdateContents(start, script); + } else if (mode == CodeMode.BattleScript) { + UpdateContents(start, battleScript); } else if (mode == CodeMode.Thumb) { TryUpdate(ref content, thumb.Parse(model, start, end - start + 1), nameof(Content)); } else { @@ -109,14 +116,14 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { /// /// /// - private void UpdateContents(int start, int currentScriptStart = -1) { - var scripts = script.CollectScripts(model, start); + private void UpdateContents(int start, ScriptParser parser, int currentScriptStart = -1) { + var scripts = parser?.CollectScripts(model, start) ?? new List(); for (int i = 0; i < scripts.Count; i++) { var scriptStart = scripts[i]; if (scriptStart == currentScriptStart && Contents.Count > i && Contents[i].Address == scriptStart) continue; - var scriptLength = script.FindLength(model, scriptStart); + var scriptLength = parser.FindLength(model, scriptStart); var label = scriptStart.ToString("X6"); - var content = script.Parse(model, scriptStart, scriptLength); + var content = parser.Parse(model, scriptStart, scriptLength); var body = new CodeBody { Address = scriptStart, Label = label, Content = content }; if (Contents.Count > i) { @@ -141,15 +148,16 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { } private void ScriptChanged(object viewModel, EventArgs e) { + var parser = mode == CodeMode.Script ? script : battleScript; var body = (CodeBody)viewModel; var codeContent = body.Content; var run = model.GetNextRun(body.Address) as XSERun; if (run == null || run.Start != body.Address) Debug.Fail("How did this happen?"); - int length = script.FindLength(model, run.Start); + int length = parser.FindLength(model, run.Start); using (ModelCacheScope.CreateScope(model)) { - CompileScriptChanges(run, length, ref codeContent, body == Contents[0]); + CompileScriptChanges(run, length, ref codeContent, parser, body == Contents[0]); body.ContentChanged -= ScriptChanged; body.HelpSourceChanged -= UpdateScriptHelpFromLine; @@ -161,7 +169,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { var start = Math.Min(model.Count - 1, selection.Scroll.ViewPointToDataIndex(selection.SelectionStart)); var end = Math.Min(model.Count - 1, selection.Scroll.ViewPointToDataIndex(selection.SelectionEnd)); if (start > end) (start, end) = (end, start); - UpdateContents(start, body.Address); + UpdateContents(start, parser, body.Address); } } @@ -193,29 +201,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { ModelDataChanged?.Invoke(this, ErrorInfo.NoError); } - private void CompileScriptChanges() { - var start = Math.Min(model.Count - 1, selection.Scroll.ViewPointToDataIndex(selection.SelectionStart)); - var end = Math.Min(model.Count - 1, selection.Scroll.ViewPointToDataIndex(selection.SelectionEnd)); - if (start > end) (start, end) = (end, start); - - var run = model.GetNextRun(start) as XSERun; - if (run == null || run.Start != start) return; - int length = end - start + 1; - - string codeContent = Content; - CompileScriptChanges(run, length, ref codeContent, true); - TryUpdate(ref content, codeContent, nameof(Content)); - } - - private void CompileScriptChanges(XSERun run, int length, ref string codeContent, bool updateSelection) { + private void CompileScriptChanges(XSERun run, int length, ref string codeContent, ScriptParser parser, bool updateSelection) { ShowErrorText = false; ErrorText = string.Empty; int start = run.Start; ignoreContentUpdates = true; { - var oldScripts = script.CollectScripts(model, run.Start); - var code = script.Compile(history.CurrentChange, model, ref codeContent, out var movedData); + var oldScripts = parser.CollectScripts(model, run.Start); + var code = parser.Compile(history.CurrentChange, model, ref codeContent, out var movedData); if (code == null) { ignoreContentUpdates = false; return; @@ -229,15 +223,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { model.ClearAnchor(history.CurrentChange, start, length); for (int i = 0; i < code.Length; i++) history.CurrentChange.ChangeData(model, run.Start + i, code[i]); for (int i = code.Length; i < length; i++) history.CurrentChange.ChangeData(model, run.Start + i, 0xFF); - script.FormatScript(history.CurrentChange, model, run.Start, run.PointerSources); + parser.FormatScript(history.CurrentChange, model, run.Start, run.PointerSources); foreach (var source in run.PointerSources) model.ObserveRunWritten(history.CurrentChange, new PointerRun(source)); // this change may have orphaned some existing scripts. Don't lose them! - var newScripts = script.CollectScripts(model, run.Start); + var newScripts = parser.CollectScripts(model, run.Start); foreach (var orphan in oldScripts.Except(newScripts)) { var orphanRun = model.GetNextRun(orphan); if (orphanRun.Start == orphan && string.IsNullOrEmpty(model.GetAnchorFromAddress(-1, orphan))) { - script.FormatScript(history.CurrentChange, model, orphan); + parser.FormatScript(history.CurrentChange, model, orphan); model.ObserveAnchorWritten(history.CurrentChange, $"xse{orphan:X6}", new XSERun(orphan)); } } diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index 362da683..84e2c53d 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -640,7 +640,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var address = Model.GetAddressFromAnchor(noChange, -1, anchor); var run = Model.GetNextRun(address); if (run is XSERun) { - tools.CodeTool.ScriptParser.FormatScript(noChange, Model, address); + tools.CodeTool.ScriptParser.FormatScript(noChange, Model, address); } } } @@ -1403,7 +1403,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { Model.ClearFormat(CurrentChange, address, length - 1); using (ModelCacheScope.CreateScope(Model)) { - tools.CodeTool.ScriptParser.FormatScript(CurrentChange, Model, address); + tools.CodeTool.ScriptParser.FormatScript(CurrentChange, Model, address); } SelectionStart = scroll.DataIndexToViewPoint(address); diff --git a/src/HexManiac.WPF/Controls/TabView.xaml.cs b/src/HexManiac.WPF/Controls/TabView.xaml.cs index 25d7e1f1..a4f7ce96 100644 --- a/src/HexManiac.WPF/Controls/TabView.xaml.cs +++ b/src/HexManiac.WPF/Controls/TabView.xaml.cs @@ -6,7 +6,6 @@ using HavenSoft.HexManiac.Core.ViewModels.Tools; using HavenSoft.HexManiac.WPF.Implementations; using System; using System.ComponentModel; -using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -45,7 +44,7 @@ namespace HavenSoft.HexManiac.WPF.Controls { public IFileSystem FileSystem => (IFileSystem)Application.Current.MainWindow.Resources["FileSystem"]; public TabView() { InitializeComponent(); - CodeModeSelector.ItemsSource = Enum.GetValues(typeof(CodeMode)).Cast(); + CodeModeSelector.ItemsSource = new[] { CodeMode.Thumb, CodeMode.Script, CodeMode.Raw }; // Enum.GetValues(typeof(CodeMode)).Cast().ToList(); timer = new DispatcherTimer(TimeSpan.FromSeconds(.6), DispatcherPriority.ApplicationIdle, BlinkCursor, Dispatcher); timer.Stop(); }