Development
So you would like to start developing your own preset? Fantastic! We would love to see your ideas implemented in game and welcome new randomizer modes. To get started you’ll need to complete the installation of the randomizer per our guide.
Next we’ll need to take a tour of the JSON files used for presets. Here is a Sample JSON file that has an example of almost every feature of the randomizer being used.
There is also a lot of documentation within the Randomizer code, itself. All you need to do is open randomize in Visual Studio Code and read the orange text.
You may also want to familiarize yourself with the information from constants.js so you know how to identify various check locations and the Zone ID’s.
Then, lastly, you need to make sure that any time you reference and item, it matches exactly with the spelling and capitalization of the item in items.js.
A general breakdown of what is and is not required is this, utilizing the Sample.json as a reference:
"Join me in remaking this world!"
Sample.JSON Sections:
metadata:
• Necessity: Required
• Used In: All Presets
• Use: Used to establish all of the identifying information about the Preset
• Fields:
o id: This is the name of the preset which the Randomizer will use. Must be lowercase
o name: This is the display name of the preset
o description: This will tell the player a little bit about the preset before they try to generate it
o author: Your name and the names of your collaborators go here
o weight: To be updated at a later time when merging with the main randomizer
comment:
• Necessity: Optional
• Used In: Sample.json
• Use: Delivering information to future programmers
inherits:
• Necessity: Optional if using LockLocation. Required if not.
• Used In: Safe, Expedition, Lycanthrope
• Use: Used to pull properties from another preset file which can be overwritten by the this preset file
relicLocationsExtension:
• Necessity: Optional
• Used in: Adventure, Expedition
• Use: Allows the preset to change which relic extension to use
• Options are:
o false (Classic)
o guarded
o spread
o equipment
preventLeaks:
• Necessity: Optional (Recommended)
• Used In: O.G.
• Use: This flag, when set to false, can cause more items to spawn in various areas at the cost of potentially giving away progression item data to the player
stats:
• Necessity: Optional
• Used In: O.G., Guarded O.G., Nimble
• Use: Set to “false” to remove item stat randomization
music:
• Necessity: Optional
• Used In: Boss Rush
• Use: Set to “false” to disable music randomization
turkeyMode:
• Necessity: Optional (Recommended)
• Used In: Boss Rush
• Use: Set to “false” to disable turkeys in sub-weapon vats, randomized cape colors, 2nd castle music decoupling, and accessibility features
alias:
• Necessity: Optional
• Used In: Glitch, Forge
• Use: Can be used to alias game aspects with another name
• Fields:
o comment: Identifies which element is being renamed and why
o zone: Any one of the castle zones (NO3, NP3, TOP, etc.) to be aliased
o relic: Any one of the relics to be aliased
o alias: What the name of the elements will be called in the rest of the file
replaceRelic:
• Necessity: Optional
• Used In: Glitch, Forge
• Use: Used for replacing relics with items to substitute for progression or to remove unwanted relics.
• Fields:
o comment: Explain which item is replacing the relic and why
o relic: The relic being replaced
o item: The item replacing it
placeRelic:
• Necessity: Optional (may not always function correctly)
• Used In: Rat Race, Forge
• Use: Forces the randomizer to place a relic or one of a selection of relics in a location
• Fields:
o Comment: Identify which relics are being placed where and why
o Relic: The relic or selection of relics to be placed in the location
o Location: The location where the relics are to be placed
lockLocation:
• Necessity: Optional if using “Inherits”, required if not
• Used In: Casual, Speedrun
• Use: Used to designate what is in logic for your preset
• Fields:
o location: Which relic or equipment location is being affected
o comment: Tells others what the location’s logic philosophy is
o locks: Which relics or combinations of relics / items are required
o block: Prevents the relics listed from appearing at the location
o escapeRequires: Ensures that some combination of the relics acquired before and including this check, in logic, will allow the player to escape the check
enemyDrops:
• Necessity: Optional
• Used In: Speedrun, Gem Farmer, Boss Rush
• Use: Forces certain enemies to drop certain items
• Fields:
o comment: Tells other programmers what the enemies are dropping or why
o enemy: Any enemy or can use “*” to indicate ALL ENEMIES
o level: Identifies the level of the enemy that needs to have it’s drops changed. Required for enemies that have multiple versions throughout the castles
o items: Tells the randomizer which items to put where, both fields are required
blockDrops:
• Necessity: Optional
• Used In: Scavenger, Speedrun
• Use: Used to prevent enemies from dropping certain items
• Fields:
o enemy: Any enemy or can use “*” to indicate ALL ENEMIES
o level: Identifies the level of the enemy that needs to have it’s drops changed. Required for enemies that have multiple version throughout the castles
o items: Used to indicate the items being prevented from dropping
startingEquipment:
• Necessity: Optional
• Used In: Nimble, Expedition, Lycanthrope
• Use: Used to define which starting gear to give the player
• Fields:
o slot: Tells which slot to edit the item for
- Options Are:
• Right hand
• Left hand
• Head
• Body
• Cloak
• Other
• AxeArmor
• Luck Mode
o item: Any of the items in the game spelled exactly as they are in items.js
blockEquipment:
• Necessity: Optional
• Used In: Speedrun, Gem Farmer, Rat Race
• Use: Used to tell which equipment cannot be starting gear
• Fields:
o slot: Tells which slot to edit the item for
- Options Are:
• Right hand
• Left hand
• Head
• Body
• Cloak
• Other
• AxeArmor
• Luck Mode
o item: Any of the items in the game spelled exactly as they are in items.js
itemLocations:
• Necessity: Optional (may not always function correctly)
• Used In: Glitch, Boss Rush
• Use: Used to place items directly into certain known item locations
• Fields:
o comment: Identifies what is placed here and why
o zone: Any one of the castle zones (NO3, NP3, TOP, etc.) to be aliased
o item: The item which normally appears in the game in a non-randomized representing which slot to place the new item
o index: If there are multiple items, index will tell which of those items to replace
o replacement: The item to place in that location. This can also be a list of items to randomize into this location
prologueRewards:
• Necessity: Optional
• Used In: Forge, Bounty Hunter
• Use: Used to give the player items at the start as reward for their performance in the prologue
• Fields:
o item: The item to replace
- Options are:
• Heart Refresh
• Neutron bomb
• Potion
o replacement: The item to use instead of the non-randomized item
blockRewards:
• Necessity: Optional
• Used In: Forge, Bounty Hunter
• Use: Used to prevent the player from receiving an item as a reward from the prologue
• Fields:
o item: The item to replace
- Options are:
• Heart Refresh
• Neutron bomb
• Potion
o replacement: The item to prevent from being given
writes:
• Necessity: Optional
• Used In: Lycanthrope, Warlock, Rat Race
• Use: Used to make direct changes to the ROM outside of the canned capabilities of the Randomizer
• Fields:
o comment: Identifies what the writes are doing or noting important aspects of the changes
o address: The ROM address to update
o type: How long the write will be
- Options are:
• char – 2 digits
• short – 4 digits
• word – 8 digits
o value: The actual code to write to the ROM at the defined address.
- Options are:
• Actual values written in "0x0" format
• "random" to get a random value matching char, short, word, or long
• "random1" to get a random digit between 0 and 1
• "random3" to get a random digit between 0 and 3
• "random10" to get a random digit between 1 and 10
• "random99" to get a random digit between 1 and 99
• "randomrelic" to get a random relic address
• Additional Notes in the How To Make Writes section of this page
complexityGoal:
• Necessity: Required
• Used In: All Presets
• Use: Identifies the complexity and which items / relics are intended to be placed at the end of the chain of complexity
• Fields:
o min: Identifies the minimum complexity where the final relic can be found
o max: Identifies the maximum complexity the preset can place a final relic
o comment: Notes the goals of the preset or the philosophy of the preset
o goals: Can list a single relic, or array of relics, or several arrays of relics / items that are the goals at the end of the chain of complexity
lockLocationAllowed:
• Necessity: Required (May not be required after preset added to SOTN.io, but this is still brand new)
• Used In: Aperture, Forge
• Use: Not required for the Randomizer but required for the tracker. Tells the Tracker how to identify the out of logic checks in the preset
• Fields:
o Location: Which relic or equipment location is being affected
o Comment: Tells others what the location’s logic philosophy is
o Locks: Which relics or combinations of relics / items are required
o Block: Prevents the relics listed from appearing at the location
o EscapeRequires: Ensures that some combination of the relics acquired before and including this check, out of logic, will allow the player to escape the check
Building and Testing Presets:
Once you have completed that changes you want to make and are ready to test the preset, you need to complete the following steps to build it and test it:
Save the file with the same name as the "id" field from the metadata section of the file. Make sure there's no spaces or special characters in either.
Save the file to the GithubDesktop\Repositories\SotN-Randomizer\presets folder.
Then open the package.json file in GithubDesktop\Repositories\SotN-Randomizer
Add your preset to the list under "presets": [ using the formatting as established by the other presets in the list.
Save package.json and close it.
Open the index.js in GithubDesktop\Repositories\SotN-Randomizer\build\presets
Add your preset to the list under exports = [ using the formatting as established by the other presets in the list.
Save index.js and close it.
Open the folder in Git Bash from Github Desktop.
Type: npm run build-presets -- [preset name] (without the brackets)
Press enter and you should see a positive response from Git Bash saying that it successfully built the preset.
If you encounter an error when building the preset, there is something wrong with your file structure or the JSON file wasn't saved to the correct file folder.
Now you should be able to attempt to generate a seed. If you encounter an error generating the seed, debugging can be difficult so make sure to double check how you capitalized item / relic names, location names, or zone names. Make sure your punctuation is correct and try again if you correct a mistake. If you still can't figure it out, take it to the #tech-support channel of the Long Library and someone might be able to help you there.
How to Make Writes:
You might be wondering how to make writes work for you. This is not going to be an expansive guide, but this will help you get started. This section will be expanded upon as we get more and better examples.
Example 1: Game Shark
Most of the time, you can infer something that can be done with writes directly from Game Shark codes available for the game. Writing these is fairly easy and we can do by understanding how the randomizer needs it broken down.
First, we need to tell it that we're writing to RAM, not ROM. That's where "injected code" comes in. In the Sample.JSON you saw that there was a section with the comment:
"Jump to injected code (Used for editing things requiring to be edited in RAM)"
Anything that is a Game Shark Code is going to occupy this space.
First, establish which RAM section we're writing to, then we'll use the address to tell it where to write, and then the actual value to write what we need the change to be. Use this diagram to find the information.
So to establish the address section to write to, we need to establish that we're going to write to RAM:
{
"comment": "Jump to injected code (Used for editing things requiring to be edited in RAM)",
"address": "0x000fa97c",
"type": "word",
"value": "0x0c04db00"
}
Then we need to tell the randomizer what value we want to write. I'm going to use the Player Level V99 code as an example, here.
{
"address": "0x00158c98",
"type": "word",
"value": "0x34020fff",
"comment": "ori v0, 0x0fff"
}
Now let's tell it where to write, starting with the section.
{
"comment": "Write to 8009 RAM addresses (Granting relics, stats, items, etc.)",
"type": "word",
"value": "0x3c038009",
"comment": "lui v1, 0x8009"
}
And now, we give it a specific sub address to write to.
{
"comment": "Player Level V99",
"type": "word",
"value": "0xa0627bee",
"comment": "sb v0, 0x7bee (v1)"
}
Next we return from the injected code and close the operation.
{
"comment": "Return from injected code",
"type": "word",
"value": "0x0803924f",
"comment": "j 0x800e493c"
}, {
"comment": "Finish Return from injected code (Writes after this allow for direct editing of the ROM)",
"type": "word",
"value": "0x00000000",
"comment": "nop"
}
Finally, our writes section with just this code should look like this:
"writes": [{
"comment": "Jump to injected code (Used for editing things requiring to be edited in RAM)",
"address": "0x000fa97c",
"type": "word",
"value": "0x0c04db00"
}, {
"address": "0x00158c98",
"type": "word",
"value": "0x34020fff",
"comment": "ori v0, 0x0fff"
}, {
"comment": "Write to 8009 RAM addresses (Granting relics, stats, items, etc.)",
"type": "word",
"value": "0x3c038009",
"comment": "lui v1, 0x8009"
}, {
"comment": "Player Level V99",
"type": "word",
"value": "0xa0627bee",
"comment": "sb v0, 0x7bee (v1)"
}, {
"comment": "Return from injected code",
"type": "word",
"value": "0x0803924f",
"comment": "j 0x800e493c"
}, {
"comment": "Finish Return from injected code (Writes after this allow for direct editing of the ROM)",
"type": "word",
"value": "0x00000000",
"comment": "nop"
}],
Important to note, here, that "sub address" isn't actually a thing. The whole RAM address is actually 97BEE, but we can't denote that the way that it's specified in either the Game Shark code or the randomizer's writes section.
Additional Notes:
It should be noted that there was specific intent behind every preset which has been released on SOTN.io and similar stands true for every preset available through TinMan on the Long Library Discord. There are elements which make those presets worthwhile. While not all of them need to be true at once, a good candidate for being added will have most of these checked:
The preset teaches players something new about the game
The preset is functionally unique from every preset before it
The preset works with an aspect of the game that players don't normally see
The preset is complete and a satisfying experience to the player
The preset can be completed by the average player of the game, assuming they've beaten the game
These design philosophies will guide any preset development in a positive direction for the community.
There are also things which cannot be done through the Randomizer yet:
Assigning only one of an enemy's items and leaving the other item randomized
Randomizing enemy stats
Randomizing relics to appear from enemies
Counting enemies in complexity
Randomizing where doors lead (There is an Area Randomizer developed by Mottzilla but there may be other compatibility issues depending on the preset.)
Starting location randomization (This is also something which was achieved by Mottzilla and Talic had laid some groundwork for this to become an option.)
Placing a progression item in a location that is not a location used by the current extension
There are also limitations which cannot be completed by the randomizer because of limitations with the coding of the game. These are hard barriers that completely prevent the items in question. A few examples include but are not limited to:
Enemy locations
Randomizing Jewel Doors, Mist Gates, Spike Hallways, and Switches
Boss locations
Editing the AI of the enemies or Bosses
Randomizing room layouts (Adding / removing stairs, platforms, etc.)
Adding playable Maria to the PS1 version of Symphony of the Night
Please remember that development will require a lot of patience, especially if you want to implement writes that change the code of the game asid from teh randomizer's canned capabilities. We look forward to seeing your vision brought to life!