Embedding Python Scripting in the Universal Pokemon Randomizer

Developed in November 2022

Data-Structures Existing Codebase Java Modding Python Scripting Tools


bannerImage
Embedding Python Scripting in the Universal Pokemon Randomizer
Introduction
Jython
Scripting
        Added Options
        Example functions
        Saved in Settings
        ROM Data Access
        Predefined functions
Syntax Highlighting
Console Window
Documentation
Similar Projects

Introduction

I grew up playing Pokemon on the DS. These days, many people play special challenge runs of the old games. One of these challenges is called a randomizer, where you run a program on a compiled of your game (called a ROM) and it randomizes all the Pokemon you encounter.
The most popular randomizer program is the Universal Pokemon Randomizer, which works on nearly every pokemon game that has every existed.
This program is written in Java, and it has many options for customizing your randomization.

However, I didn't feel the options given were flexible enough. In fact, I felt that there was no way they could ever be flexible enough. There was always some new ruleset I wanted to try that would never be supported.
There could only be one solution that would allow anyone to apply their creativity to the randomizer without modifying the code themselves, and that is to allow scripting.


Jython

The decision to go with Python was easily made. Many people know it and, with the Jython library, it is easily and cleanly integrated into a Java codebase.
The nicest part about it, in my opinion, is that you can directly import and use all classes in the Java codebase the Jython instance exists in. While that would usually be an encapsulation nightmare, for me it means that I don't need to write repetitive bindings to interface with the existing code.


Scripting

The randomizer has many different steps and options. I didn't want to fix what wasn't broken, so rather than replacing the existing workflow, I decided to extend it.

Added Options

Here, a script function is used to select starter pokemon, while a normal randomization option is used for the static encounters.

Here, a script function is used to select starter pokemon, while a normal randomization option is used for the static encounters.

Rather than removing or disabling everything in favor of scripting, I added a scripted version of every option. The result is that you can combine regular randomization settings with.
Since nearly every option already consisted of radio buttons, it was quite easy to add this extra option to each of them.

Nearly everything in the randomizer can be scripted like this, without affecting other options.

This includes:

  • Starter pokemon
  • Static encounters
  • In-game trades
  • Wild pokemon
  • Trainer pokemon
  • Totem pokemon
  • Ally pokemon
  • Held items for wild/static/trade/trainer/totem/ally pokemon
  • TM/HM moves
  • TM/HM compatibility

  • Field items
  • Special shop items and prices
  • Move data
  • Pokemon movesets (learnt moves/egg moves)
  • Pokemon base stats
  • Pokemon abilities
  • Pokemon types
  • Pokemon exp curves
  • Pokemon (mega) evolutions
  • Pokemon limit

Besides adding scripted versions of existing options, some of these (like the shop item prices) add completely new functionality to the randomizer.

Example functions

Example functions include the full declaration, documentation comments, and an example function body. This one picks a starter by accessing the pokepool by the starterindex, this usually picks Bulbasaur/Ivysaur/Venusaur and the starters, which is stupid but it's just an example.

Example functions include the full declaration, documentation comments, and an example function body. This one picks a starter by accessing the pokepool by the starterindex, this usually picks Bulbasaur/Ivysaur/Venusaur and the starters, which is stupid but it's just an example.

When you select a scripting option, an example function will be added to the scripting tab.

This removes the need for checking and copying from the documentation every time you want to want to script something.

The example functions are just placeholders, they never do any valuable randomizations.
Example functions are only added if there is no declaration of the function in the script yet, this is to prevent unexpected behavior when reselecting an option without having removed its scripting function.

Saved in Settings

The Universal Pokemon Randomizer includes a system for saving and loading your randomization settings.
Since I wanted a way to save and load scripts, this was a perfect system to tie into, especially since it will already select the scripting options to.

The files are simply binary data written and read in a specific order. So I just added the script text by starting off with the length of the script code string, and then outputting the string itself.

ROM Data Access

There's a lot of information available about a loaded ROM that can be relevant for scripts.
So, I added a predefined ROM class that will retrieve all this information. If you just write this import:

You can access the ROM's name, generation, extension, and much more in your script.
This also includes data for things that can be randomized, like lists of items and pokemon, and lists used by default options such as which items are considered "good" by the "only good items" option.

This can be useful for scripts that require a different order of randomization. In those cases, lists can be retrieved and processed at initialization in the desired order, and then they can be read from when the entry-point function is called.

Predefined functions

There are some utility functions that are very common to these scripts, so I predefined them to be used in any script without a required import.
These important ones are:

  • similarStrength - will apply the similar strength algorithm used in the default options to the given pokemon pool
  • hasType - will check if the given pokemon has the given type as either its primary or secondary type


Syntax Highlighting

Showcase of the syntax highlighting. This script selects starters by seeing if the original was fire/water/grass type and then picking a random non-evolved pokemon with that type.

Showcase of the syntax highlighting. This script selects starters by seeing if the original was fire/water/grass type and then picking a random non-evolved pokemon with that type.

I wrote some heavy python parsing code to support the best syntax highlighting I could.
The code is parsed to idenfity symbols for:

  • Comments
  • String literals
  • Numeric literals
  • Boolean literals
  • None literals
  • Keywords
  • Imports
  • Classes
  • Functions
  • Local variables
  • Function arguments
  • Class members
  • Enums
All while taking scope into account, so things are only highlighted if they exist in the current scope. Scope is tracked using a custom tree Data-Structure.
Since imported Java classes support reflection, these also have their classes, members, and methods defined in the global scope, so that their usages are also properly highlighted.

This highlighting happens in real time as you type.


Console Window

Showcase of the console window. This is running on a example script that will pick two random pokemon types at initialization, which all obtainable pokemon (wild/static/egg/trade) must have.

Showcase of the console window. This is running on a example script that will pick two random pokemon types at initialization, which all obtainable pokemon (wild/static/egg/trade) must have.

One thing you can't get around when embedding scripting is the fact that everyone will write scripts that don't work. You simply must include some bare minimum of debugging tools for scripting to be useful.
For me, that is this console window.

This required a bit of a rework, since usually when you run randomization the whole UI is disabled. So, I made the UI be only partially disabled. You can't change any options or your script, but you can switch tabs and click some buttons.
One of these buttons shows the console window, which receives a copy of all program output while randomization is in-progress.

This is done with a custom output stream that includes both the default and a second stream that feeds into this window. The reason for this is so that both prints and Java exceptions will also be shown in the window.


Documentation

Showcase of the scripting documentation in the browser.

Showcase of the scripting documentation in the browser.

Although the tools I made for this were coming along great, none of it would be useful without proper documentation. So I added some code that would generate HTML documentation pages on the user's PC and a button that opens the homepage to the UI.

Predefined functions are fully documented here, as well as classes and enums.
Every class and enum also shows what import is necessary to include it. Most imports are done automatically as you enable scripting options, but when you need to get something that isn't included by default for your options, this is the place you can copy-paste the import.

A lot of care was put into this documentation. Many details (like what the extrainfo variable of an evolution does per type) were very difficult to come by initially, and I'm very glad this resource now exists for anyone looking to script their randomizer.


Project tags: #Data-Structures, #Existing Codebase, #Java, #Modding, #Python, #Scripting, #Tools


Similar projects:

Minecraft Modding Thumbnail

Minecraft Modding

Keep reading

Apps and Applied Games for Revalidation Thumbnail

Apps and Applied Games for Revalidation

Keep reading

Text-to-Speech Tool with Azure Thumbnail

Text-to-Speech Tool with Azure

Keep reading

More projects...