feature requests

* choose unused var for map scripts
* show block uses when a single block is selected
This commit is contained in:
haven1433 2025-11-30 17:30:02 -06:00
parent 0f6ddb1abc
commit ab6e688208
5 changed files with 67 additions and 18 deletions

View File

@ -526,7 +526,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
(LeftEdge, TopEdge) = (-PixelWidth / 2, -PixelHeight / 2);
mapScriptCollection = new(viewPort);
mapScriptCollection = new(viewPort, eventTemplate);
mapScriptCollection.NewMapScriptsCreated += (sender, e) => GetMapModel().SetAddress("mapscripts", e.Address);
mapRepointer = new MapRepointer(format, fileSystem, viewPort, viewPort.ChangeHistory, MapID, () => {

View File

@ -1,5 +1,4 @@
using HavenSoft.HexManiac.Core;
using HavenSoft.HexManiac.Core.Models;
using HavenSoft.HexManiac.Core.Models;
using HavenSoft.HexManiac.Core.Models.Code;
using HavenSoft.HexManiac.Core.Models.Map;
using HavenSoft.HexManiac.Core.Models.Runs;
@ -980,7 +979,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public bool ShowTutorContent {
get {
var content = tutorContent.Value;
if (content != null && TutorOptions .AllOptions == null) {
if (content != null && TutorOptions.AllOptions == null) {
TutorOptions.Update(ComboOption.Convert(element.Model.GetOptions(HardcodeTablesModel.MoveTutors)), TutorNumber);
TutorOptions.Bind(nameof(TutorOptions.SelectedIndex), (sender, e) => TutorNumber = TutorOptions.SelectedIndex);
}
@ -990,7 +989,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
private TextEditorViewModel tutorInfoEditor, tutorWhichPokemonEditor, tutorFailedEditor, tutorSuccessEditor;
public TextEditorViewModel TutorInfoText => CreateTextEditor(ref tutorInfoEditor, () => tutorContent.Value?.InfoPointer);
public TextEditorViewModel TutorWhichPokemonText => CreateTextEditor(ref tutorWhichPokemonEditor , () => tutorContent.Value?.WhichPokemonPointer);
public TextEditorViewModel TutorWhichPokemonText => CreateTextEditor(ref tutorWhichPokemonEditor, () => tutorContent.Value?.WhichPokemonPointer);
public TextEditorViewModel TutorFailedText => CreateTextEditor(ref tutorFailedEditor, () => tutorContent.Value?.FailedPointer);
public TextEditorViewModel TutorSucessText => CreateTextEditor(ref tutorSuccessEditor, () => tutorContent.Value?.SuccessPointer);

View File

@ -185,6 +185,24 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
}
}
public bool IsSingleBlock => tilesToDraw is not null && tilesToDraw.GetLength(0) == 1 && tilesToDraw.GetLength(1) == 1;
public int BlockUsageCount {
get {
if (preferredCollisionsPrimary == null) CountCollisionForBlocks();
var layout = new LayoutModel(primaryMap?.GetLayout());
if (layout.PrimaryBlockset?.Start is not int primaryAddress) return -1;
if (layout.SecondaryBlockset?.Start is not int secondaryAddress) return -1;
if (!IsSingleBlock) return -1;
var isPrimary = drawBlockIndex < PrimaryBlocks;
if (isPrimary) {
return blockUsageCount[primaryAddress][drawBlockIndex];
} else {
return blockUsageCount[secondaryAddress][drawBlockIndex];
}
}
}
public ObservableCollection<string> CollisionOptions { get; } = new();
public bool BlockSelectionToggle { get; private set; }
@ -1075,7 +1093,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
private bool drawMultipleTiles;
public bool DrawMultipleTiles {
get => drawMultipleTiles;
private set => Set(ref drawMultipleTiles, value, old => NotifyPropertyChanged(nameof(BlockBagVisible)));
private set {
Set(ref drawMultipleTiles, value, old => NotifyPropertyChanged(nameof(BlockBagVisible)));
NotifyPropertiesChanged(nameof(IsSingleBlock), nameof(BlockUsageCount));
}
}
private bool blockEditorVisible;
@ -1708,9 +1729,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
});
}
public bool IsValid9GridSelection {
get {
return tilesToDraw != null && tilesToDraw.GetLength(0) == 3 && tilesToDraw.GetLength(1) == 3;
}
get => tilesToDraw != null && tilesToDraw.GetLength(0) == 3 && tilesToDraw.GetLength(1) == 3;
}
public void SelectBlock(int x, int y) {
@ -2078,9 +2097,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
private Dictionary<long, int[]> preferredCollisionsPrimary;
private Dictionary<long, int[]> preferredCollisionsSecondary;
private readonly AutoDictionary<long, AutoDictionary<int, int>> blockUsageCount = new(_ => new(_ => 0)); // for a tileset, for a block, the total number of times that block is used
private void CountCollisionForBlocks() {
preferredCollisionsPrimary = new Dictionary<long, int[]>();
preferredCollisionsSecondary = new Dictionary<long, int[]>();
blockUsageCount.Clear();
// step 1: count the usages of each collision for each block in each blockset
var banksTable = model.GetTable(HardcodeTablesModel.MapBankTable);
@ -2121,9 +2142,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
var tile = pair & 0x3FF;
var isPrimary = tile < PrimaryTiles;
if (isPrimary) {
blockUsageCount[address1][tile] += 1;
if (!blocks1[tile].TryGetValue(collision, out var value)) value = 0;
blocks1[tile][collision] = value + 1;
} else {
blockUsageCount[address2][tile] += 1;
if (!blocks2[tile].TryGetValue(collision, out var value)) value = 0;
blocks2[tile][collision] = value + 1;
}

View File

@ -11,6 +11,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public class MapScriptCollection : ViewModelCore {
private readonly IEditableViewPort viewPort;
private readonly EventTemplate eventTemplate;
private ModelArrayElement owner;
private int address;
@ -22,7 +23,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public event EventHandler<NewMapScriptsCreatedEventArgs> NewMapScriptsCreated;
public MapScriptCollection(IEditableViewPort viewPort) => this.viewPort = viewPort;
public MapScriptCollection(IEditableViewPort viewPort, EventTemplate eventTemplate) => (this.viewPort, this.eventTemplate) = (viewPort, eventTemplate);
public void Load(ModelArrayElement owner) {
foreach (var script in Scripts) script.DeleteMe -= HandleDelete;
@ -36,11 +37,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
AllowAddMoreScripts = true;
while (model[scriptStart] != 0) {
if (Scripts.Count == 10) {
Scripts.Add(new(viewPort, Pointer.NULL)); // 'UI is full' sentinel
Scripts.Add(new(viewPort, eventTemplate, Pointer.NULL)); // 'UI is full' sentinel
AllowAddMoreScripts = false;
break;
}
Scripts.Add(new(viewPort, scriptStart));
Scripts.Add(new(viewPort, eventTemplate, scriptStart));
AddDeleteHandler(Scripts.Count - 1);
scriptStart += 5;
}
@ -115,6 +116,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public class MapScriptViewModel : ViewModelCore {
private readonly IEditableViewPort viewPort;
private readonly EventTemplate eventTemplate;
private readonly int start;
private int scriptType, address;
private string displayAddress;
@ -128,8 +130,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public ObservableCollection<VisualOption> ScriptOptions { get; } = new();
public ObservableCollection<MapSubScriptViewModel> SubScripts { get; } = new();
public MapScriptViewModel(IEditableViewPort viewPort, int start) {
public MapScriptViewModel(IEditableViewPort viewPort, EventTemplate eventTemplate, int start) {
this.viewPort = viewPort;
this.eventTemplate = eventTemplate;
var model = viewPort.Model;
this.start = start;
if (start == Pointer.NULL) {
@ -193,7 +196,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
while (true) {
var currentValue = model.ReadMultiByteValue(destination, 2);
if (currentValue == 0 || currentValue == 0xFFFF) break;
var child = new MapSubScriptViewModel(viewPort, destination);
var child = new MapSubScriptViewModel(viewPort, eventTemplate, destination);
child.DeleteMe += HandleDelete;
SubScripts.Add(child);
destination += 8;
@ -292,7 +295,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
if (run.Start != tableRun.Start) {
Load();
} else {
SubScripts.Append(new MapSubScriptViewModel(viewPort, tableRun.Start + tableRun.ElementCount * tableRun.ElementLength - 4));
SubScripts.Append(new MapSubScriptViewModel(viewPort, eventTemplate, tableRun.Start + tableRun.ElementCount * tableRun.ElementLength - 4));
}
}
@ -333,6 +336,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
/// </summary>
public class MapSubScriptViewModel : ViewModelCore {
private readonly IEditableViewPort viewPort;
private readonly EventTemplate eventTemplate;
private int start, variable, val, address;
private string variableText, valueText, addressText;
@ -340,8 +344,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public event EventHandler<MapScriptDeleteEventArgs> DeleteMe;
public MapSubScriptViewModel(IEditableViewPort viewPort, int start) {
(this.viewPort, this.start) = (viewPort, start);
public MapSubScriptViewModel(IEditableViewPort viewPort, EventTemplate eventTemplate, int start) {
(this.viewPort, this.eventTemplate, this.start) = (viewPort, eventTemplate, start);
this.variable = viewPort.Model.ReadMultiByteValue(start, 2);
this.val = viewPort.Model.ReadMultiByteValue(start + 2, 2);
this.address = viewPort.Model.ReadPointer(start + 4);
@ -356,6 +360,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
if (!variableText.TryParseHex(out int result)) return;
variable = result;
viewPort.Model.WriteMultiByteValue(start, 2, viewPort.ChangeHistory.CurrentChange, variable);
NotifyPropertyChanged(nameof(CanGenerateNewVar));
});
}
@ -377,6 +382,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
});
}
public bool CanGenerateNewVar => variable == 0;
public void GenerateNewVar() {
variable = eventTemplate.FindNextUnusedVariable();
Variable = variable.ToString("X4");
}
public void Delete() {
var args = new MapScriptDeleteEventArgs();
DeleteMe.Raise(this, args);

View File

@ -134,6 +134,10 @@
</Style>
</Decorator.Style>
</Decorator>
<TextBlock Visibility="{Binding IsSingleBlock, Converter={StaticResource BoolToVisibility}}" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Top">
<Run Text="Usage Count:" />
<Run Text="{Binding BlockUsageCount, Mode=OneWay}" />
</TextBlock>
<CheckBox IsChecked="{Binding Use9Grid}" Content="Draw 9-Grid" VerticalAlignment="Top" Grid.Row="1" HorizontalAlignment="Center"
Visibility="{Binding IsValid9GridSelection, Converter={StaticResource BoolToVisibility}}">
<CheckBox.ToolTip>
@ -1324,7 +1328,19 @@
<local:AngleTextBox Direction="Out" TextBinding="{Binding Address, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
<UniformGrid Columns="4" Margin="20,0">
<local:AngleBorder Direction="Out" Content="Var:" />
<Grid>
<local:AngleBorder Direction="Out" Content="Var:" />
<local:AngleButton HorizontalAlignment="Right" Direction="Right" Command="{res:MethodCommand GenerateNewVar}"
Visibility="{Binding CanGenerateNewVar, Converter={StaticResource BoolToVisibility}}"
Width="22" Height="18" IsTabStop="False" Padding="0,2" BorderBrush="{DynamicResource Backlight}">
<local:AngleButton.ToolTip>
<TextBlock>
Automatically choose an unused var.
</TextBlock>
</local:AngleButton.ToolTip>
<Path Data="{res:Icon Add}" Fill="{DynamicResource Secondary}" Stretch="Fill" />
</local:AngleButton>
</Grid>
<local:AngleTextBox Direction="Right" TextBinding="{Binding Variable, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,2,0" />
<local:AngleBorder Direction="Out" Content="Value:" Margin="2,0,0,0" />
<local:AngleTextBox Direction="Right" TextBinding="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />