Sorting Level Up Moves Explained - haven1433/HexManiacAdvance GitHub Wiki
This feature was recommended by Petuuuhhh. It runs through all the level-up moves for every pokemon and sorts them by the last field in the table.
- For vanilla moves, there's only one field: 2 bytes that represent both the level (7 high bits) and the move (9 low bits). This means that the list gets sorted by the level that the move is learned, but also gets sorted by move. If 2 moves are learned at the same level, the move with the lower ID will get listed first.
- For people using the Jambo Hack, if you've configured HexManiacAdvance to read your move data correctly, the list will get sorted by only the level, since the fields are separate.
- For people using HexManiacAdvance's built-in move expansion utility, the list will get sorted by only the level, since the fields are separate.
Since this utility is so simple, it's also a good explanation on how to build your own utilities in HMA as a developer.
(See LevelUpMoveSorter.cs)
All the "do this complicated thing for me" items in the menu implement the IQuickEditItem
interface:
- Name: The name that you want to show up in the menu.
- Description: When you click on the item, a small dialog will pop up to show you this description. It's meant to describe more fully what is about to happen, so the user has the option to back out.
- *WikiLink: The small dialog also has a link. The link should point to a page like this one that provides a much longer and more thorough explanation of what the utility does and how it works. It should give the hacker a reasonable understanding so they can decide if the utility is safe to use for the current state of their hack.
- CanRunChanged and TabChanged: These are how the quick edit item communicates with the rest of the application. The app will call TabChanged every time the user switches the active tab, and you should raise the CanRunChanged event any time the utility may get enabled or disabled. For example, the Level-Up Move Sorter only can be run on tabs that represent a rom. For other sorts of tabs like the pokedex reorder utility, search results, or an image editor, the sorter cannot be run. So every time the tab changes, the sorter raises the CanRunChanged event, because the application needs to check if running the sorter is still possible.
If that was all confusing, then just use this as a rule of thumb: you probably want to implement TabChanged like this:
public void TabChanged() => CanRunChanged?.Invoke(this, EventArgs.Empty);
Sorting Level-Up Moves only works if the current tab represents a rom that has Level-Up moves. CanRun lets the application know if running this utility makes sense.
public bool CanRun(IViewPort viewPort) {
if (!(viewPort is IEditableViewPort)) return false;
return viewPort.Model.GetTable(HardcodeTablesModel.LevelMovesTableName) != null;
}
Let's break it down:
- The tab has to be editable. A search result tab is an example of a viewport that isn't editable.
- The rom must have a table for Level-Up Moves.
That's really all there is to it. To understand when this method gets run, see the note above about TabChanged and CanRunChanged.
** Creating a Utility: Run The Run method contains the code for what your utility actually does. You can set the entire rom to 0xFF, or write some silly text into freespace... you can make the utility do whatever you want. In this case, the utility needs to look through the level-up learnsets for each pokemon, and sort them.
public async Task<ErrorInfo> Run(IViewPort viewPort) {
var viewModel = (IEditableViewPort)viewPort;
var model = viewModel.Model;
var levelUpMoveTable = model.GetTable(HardcodeTablesModel.LevelMovesTableName);
for (int i = 0; i < levelUpMoveTable.ElementCount; i++) {
var destination = levelUpMoveTable.ReadPointer(model, i);
var moveset = model.GetNextRun(destination) as ITableRun;
if (moveset == null) continue;
var sortedMoves = moveset.Sort(model, moveset.ElementContent.Count - 1);
viewModel.ChangeHistory.CurrentChange.ChangeData(model, moveset.Start, sortedMoves);
await viewModel.UpdateProgress((double)i / levelUpMoveTable.ElementCount);
}
viewModel.ClearProgress();
viewModel.ChangeHistory.ChangeCompleted();
viewModel.Refresh();
return ErrorInfo.NoError;
}
- We know from the checks in
CanRun
that the viewPort we're being passed is editable. Since this object represents the current view that the user has for the data, including things like a screensize and individual cells being displayed, we'll call this our viewModel: it represents a model of the user's view. - The
levelUpMoveTable
is the actual table for level-up moves stored in the rom. It represents a series of pointers, one per pokemon, that lead to that pokemon's level-up move learnset. -
i
represents the current ID of the pokemon we're editing. -
destination
tells us the address of a particular pokemon's moveset - Once we read the moveset as a table, we verify that it's actually a table: the user may have done something weird in their rom or the data may be bugged. So we need to check that the data is in the form they expect.
- We then get a new set of bytes from the
Sort
helper method, which actually does the hard work for us. These new bytes need to be written back to the rom. - When we're ready to write the bytes, we want to insert the change into the history of changes so that the user can Undo if they want to.
- Finally, we update a progress bar so the user knows how long the process will take.
After the loop, there's a little cleanup: we clear the progress bar, mark the end of the current history interaction, and update the UI.
(See EditorViewModel.cs in the constructor) Once you've created your custom IQuickEditItem, you just need to create a new instance in the EditorViewModel constructor. Look for ReorderDex and LevelUpMoveSorter to see how to do it.