diff --git a/src/HexManiac.Core/Models/Code/ScriptParser.cs b/src/HexManiac.Core/Models/Code/ScriptParser.cs index f67f9f0d..8e46c1bd 100644 --- a/src/HexManiac.Core/Models/Code/ScriptParser.cs +++ b/src/HexManiac.Core/Models/Code/ScriptParser.cs @@ -83,6 +83,17 @@ namespace HavenSoft.HexManiac.Core.Models.Code { public ScriptLine GetLine(IDataModel model, int address) => engine.GetMatchingLine(model, address); + public IEnumerable DependsOn(string basename) { + foreach (var line in engine) { + foreach (var arg in line.Args) { + if (arg.EnumTableName == basename) { + yield return line; + break; + } + } + } + } + // TODO refactor to rely on CollectScripts rather than duplicate code public void FormatScript(ModelDelta token, IDataModel model, int address) where TSERun : IScriptStartRun { Func, IScriptStartRun> constructor = (a, s) => new XSERun(a, s); diff --git a/src/HexManiac.Core/Models/Code/scriptReference.txt b/src/HexManiac.Core/Models/Code/scriptReference.txt index 3b8e99b3..fb1d4b4d 100644 --- a/src/HexManiac.Core/Models/Code/scriptReference.txt +++ b/src/HexManiac.Core/Models/Code/scriptReference.txt @@ -124,17 +124,17 @@ 5A faceplayer # if the script was called by a person event, make that person face the player 5B spriteface npc: direction. -5C trainerbattle 00 trainer: arg: start<> playerwin<> -5C trainerbattle 01 trainer: arg: start<> playerwin<> winscript<> # doesn't play encounter music, continues with winscript -5C trainerbattle 02 trainer: arg: start<> playerwin<> winscript<> # does play encounter music, continues with winscript -5C trainerbattle 03 trainer: arg: playerwin<> # no intro text -5C trainerbattle 04 trainer: arg: start<> playerwin<> needmorepokemonText<> # double battles -5C trainerbattle 05 trainer: arg: start<> playerwin<> # clone of 0, but with rematch potential -5C trainerbattle 06 trainer: arg: start<> playerwin<> needmorepokemonText<> unknown<> # double battles -5C trainerbattle 07 trainer: arg: start<> playerwin<> needmorepokemonText<> # clone of 4, but with rematch potential -5C trainerbattle 08 trainer: arg: start<> playerwin<> needmorepokemonText<> unknown<> # clone of 6 -5C trainerbattle 09 trainer: arg: start<> playerwin<> # tutorial battle (can't lose) (set arg=3 for oak's naration) -5C trainerbattle other. trainer: arg: start<> playerwin<> # same as 0 +5C trainerbattle 00 trainer:data.trainers.stats arg: start<> playerwin<> +5C trainerbattle 01 trainer:data.trainers.stats arg: start<> playerwin<> winscript<> # doesn't play encounter music, continues with winscript +5C trainerbattle 02 trainer:data.trainers.stats arg: start<> playerwin<> winscript<> # does play encounter music, continues with winscript +5C trainerbattle 03 trainer:data.trainers.stats arg: playerwin<> # no intro text +5C trainerbattle 04 trainer:data.trainers.stats arg: start<> playerwin<> needmorepokemonText<> # double battles +5C trainerbattle 05 trainer:data.trainers.stats arg: start<> playerwin<> # clone of 0, but with rematch potential +5C trainerbattle 06 trainer:data.trainers.stats arg: start<> playerwin<> needmorepokemonText<> unknown<> # double battles +5C trainerbattle 07 trainer:data.trainers.stats arg: start<> playerwin<> needmorepokemonText<> # clone of 4, but with rematch potential +5C trainerbattle 08 trainer:data.trainers.stats arg: start<> playerwin<> needmorepokemonText<> unknown<> # clone of 6 +5C trainerbattle 09 trainer:data.trainers.stats arg: start<> playerwin<> # tutorial battle (can't lose) (set arg=3 for oak's naration) +5C trainerbattle other. trainer:data.trainers.stats arg: start<> playerwin<> # same as 0 # trainer battle takes different parameters depending on the # 'type', or the first parameter. # 'trainer' is the ID of the trainer battle diff --git a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs index 4534edee..c38e1c55 100644 --- a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs +++ b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs @@ -1,6 +1,7 @@ using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; +using HavenSoft.HexManiac.Core.ViewModels.Map; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -141,6 +142,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { private set => TryUpdate(ref enabled, value); } + private bool usageOptionsOpen; + public bool UsageOptionsOpen { get => usageOptionsOpen; set => Set(ref usageOptionsOpen, value); } + private string fieldFilter = string.Empty; public string FieldFilter { get => fieldFilter; @@ -468,18 +472,21 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { return s; }).ToList() : null; AddUsageChild(new ButtonArrayElementViewModel("trainer teams", () => { + UsageOptionsOpen = false; viewPort.OpenSearchResultsTab($"{elementName} within {HardcodeTablesModel.TrainerTableName}", selections, trainerAddresses); })); } if (plmResults.Count > 0) { AddUsageChild(new ButtonArrayElementViewModel("level-up moves", () => { + UsageOptionsOpen = false; viewPort.OpenSearchResultsTab($"{elementName} within {HardcodeTablesModel.LevelMovesTableName}", plmResults); })); } if (eggResults.Count > 0) { AddUsageChild(new ButtonArrayElementViewModel("egg moves", () => { + UsageOptionsOpen = false; viewPort.OpenSearchResultsTab($"{elementName} within {HardcodeTablesModel.EggMovesTableName}", eggResults); })); } @@ -490,15 +497,52 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { if (results.Count == 0) continue; var name = model.GetAnchorFromAddress(-1, table.Start); AddUsageChild(new ButtonArrayElementViewModel(name, name, () => { + UsageOptionsOpen = false; viewPort.OpenSearchResultsTab($"{elementName} within {name}", results); })); } if (streamResults.Count > 0) { AddUsageChild(new ButtonArrayElementViewModel("other streams", () => { + UsageOptionsOpen = false; viewPort.OpenSearchResultsTab($"{elementName} within streams", streamResults); })); } + + // only check XSE scripts for now + if (viewPort.Tools.CodeTool.ScriptParser.DependsOn(basename).Any()) { + AddUsageChild(new ButtonArrayElementViewModel("scripts", () => { + var results = new List<(int, int)>(FindXseScriptUses(basename, index)); + if (results.Count == 0) { + OnMessage?.Invoke(this, "No matches were found."); + } else { + UsageOptionsOpen = false; + viewPort.Tools.SelectedTool = viewPort.Tools.CodeTool; + viewPort.Tools.CodeTool.Mode = CodeMode.Script; + viewPort.OpenSearchResultsTab($"{elementName} within scripts", results); + viewPort.Tools.SelectedTool = viewPort.Tools.CodeTool; + viewPort.Tools.CodeTool.Mode = CodeMode.Script; + } + })); + } + } + + private IEnumerable<(int, int)> FindXseScriptUses(string basename, int index) { + var parser = viewPort.Tools.CodeTool.ScriptParser; + var lines = parser.DependsOn(basename).ToList(); + var filter = lines.Select(line => line.LineCode[0]).ToArray(); + foreach (var spot in Flags.GetAllScriptSpots(model, parser, Flags.GetAllTopLevelScripts(model), filter)) { + int check = spot.Address + spot.Line.LineCode.Count; + foreach (var arg in spot.Line.Args) { + var length = arg.Length(model, check); + if (arg.EnumTableName == basename) { + if (model.ReadMultiByteValue(check, length) == index) { + yield return (spot.Address, check + length - 1); + } + } + check += length; + } + } } private void ApplyFieldFilter() { diff --git a/src/HexManiac.WPF/Controls/TabView.xaml b/src/HexManiac.WPF/Controls/TabView.xaml index 114bbcc6..644cdd21 100644 --- a/src/HexManiac.WPF/Controls/TabView.xaml +++ b/src/HexManiac.WPF/Controls/TabView.xaml @@ -211,7 +211,7 @@ so we declare an extra ItemsControl just so we can access its HasItems property --> - +