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.
This commit is contained in:
Benjamin Popp 2020-05-01 08:16:04 -05:00
parent 102c44ad6b
commit 93250ec061
8 changed files with 426 additions and 54 deletions

View File

@ -158,6 +158,9 @@
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Models\Code\battleScriptReference.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Models\Code\scriptReference.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

View File

@ -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<int> sources = null) {
public void FormatScript<TSERun>(ModelDelta token, IDataModel model, int address, IReadOnlyList<int> sources = null) where TSERun : IScriptStartRun {
Func<int, IReadOnlyList<int>, IScriptStartRun> constructor = (a, s) => new XSERun(a, s);
if (typeof(TSERun) == typeof(BSERun)) constructor = (a, s) => new BSERun(a, s);
var processed = new List<int>();
var toProcess = new List<int> { 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<string> documentation = new List<string>();
public const string Hex = "0123456789ABCDEF";
@ -276,12 +279,11 @@ namespace HavenSoft.HexManiac.Core.Models.Code {
public IReadOnlyList<string> 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<byte>(4, 5, 6, 7);
public bool PointsToText => LineCode.Count == 1 && LineCode[0].IsAny<byte>(0x0F, 0x67);
public bool PointsToMovement => LineCode.Count == 1 && LineCode[0].IsAny<byte>(0x4F, 0x50);
public bool PointsToMart => LineCode.Count == 1 && LineCode[0].IsAny<byte>(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<byte>(0x02, 0x03, 0x05, 0x08, 0x0A, 0x0C, 0x0D);
public override bool PointsToNextScript => LineCode.Count == 1 && LineCode[0].IsAny<byte>(4, 5, 6, 7);
public override bool PointsToText => LineCode.Count == 1 && LineCode[0].IsAny<byte>(0x0F, 0x67);
public override bool PointsToMovement => LineCode.Count == 1 && LineCode[0].IsAny<byte>(0x4F, 0x50);
public override bool PointsToMart => LineCode.Count == 1 && LineCode[0].IsAny<byte>(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; }

View File

@ -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}

View File

@ -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<int> 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<int> sources = null) : base(start, sources) { }
public override IDataFormat CreateDataFormat(IDataModel data, int index) => None.Instance;
protected override BaseRun Clone(IReadOnlyList<int> newPointerSources) => new BSERun(Start, newPointerSources);
}
}

View File

@ -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<string, GameReferenceTables> GameReferenceTables { get; }
public IReadOnlyList<ConditionCode> ThumbConditionalCodes { get; }
public IReadOnlyList<IInstruction> ThumbInstructionTemplates { get; }
public IReadOnlyList<ScriptLine> ScriptLines { get; }
public IReadOnlyList<ScriptLine> BattleScriptLines { get; }
public Singletons() {
GameReferenceTables = CreateGameReferenceTables();
(ThumbConditionalCodes, ThumbInstructionTemplates) = LoadThumbReference();
ScriptLines = LoadScriptReference();
ScriptLines = LoadScriptReference<XSEScriptLine>(ScriptReferenceFileName);
BattleScriptLines = LoadScriptReference<BSEScriptLine>(BattleScriptReferenceFileName);
MetadataInfo = new MetadataInfo();
}
private IReadOnlyList<ScriptLine> LoadScriptReference() {
if (!File.Exists(ScriptReferenceFileName)) return new List<ScriptLine>();
var lines = File.ReadAllLines(ScriptReferenceFileName);
private IReadOnlyList<ScriptLine> LoadScriptReference<TLine>(string file) where TLine : ScriptLine {
if (!File.Exists(file)) return new List<ScriptLine>();
Func<string, ScriptLine> 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>();
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;
}
}
}

View File

@ -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<ModelDelta> 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<ModelDelta> 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 {
/// </summary>
/// <param name="start"></param>
/// <param name="currentScriptStart"></param>
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<int>();
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<XSERun>(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<XSERun>(history.CurrentChange, model, orphan);
model.ObserveAnchorWritten(history.CurrentChange, $"xse{orphan:X6}", new XSERun(orphan));
}
}

View File

@ -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<XSERun>(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<XSERun>(CurrentChange, Model, address);
}
SelectionStart = scroll.DataIndexToViewPoint(address);

View File

@ -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<CodeMode>();
CodeModeSelector.ItemsSource = new[] { CodeMode.Thumb, CodeMode.Script, CodeMode.Raw }; // Enum.GetValues(typeof(CodeMode)).Cast<CodeMode>().ToList();
timer = new DispatcherTimer(TimeSpan.FromSeconds(.6), DispatcherPriority.ApplicationIdle, BlinkCursor, Dispatcher);
timer.Stop();
}