Talent modding, step by step
This guide takes you from nothing to a working talent mod. You do not need to know how to program. You will copy some commands, fill in one text file, and check the result in the game.
We use one example all the way through: Ice Shield, a Defense talent of The Snow Queen. By the end of the guide it will:
- be called Glacier Shield,
- give a stronger shield,
- last 9 seconds instead of 6.
The same steps work for any talent of any hero. Only the names change.
What you can and cannot change
Section titled “What you can and cannot change”| You want to… | Possible? | Step |
|---|---|---|
| Change a number that is the same at every rarity (a duration, a count, a cooldown) | Yes | Step 5 |
| Change a number that is different for each rarity | Yes | Step 6 |
| Rename a talent or rewrite its description | Yes | Step 7 |
| Give a talent a new icon | Yes | Step 7 |
| Make a talent do something completely different | Only for experienced modders | Going further |
| Add a brand-new talent next to the existing ones | No. Every hero has a fixed set of talent slots, and adding one breaks the hero. | — |
Words used on this page
Section titled “Words used on this page”| Word | Meaning |
|---|---|
| Talent | A card you pick when you level up. The game’s files call talents skills, so you will see both words. |
| Rarity | Common, Rare, Epic or Legendary. Many talent numbers are different for each rarity. |
| Mod | A folder that holds one text file, manifest.toml, which describes your changes. |
| Terminal | The window where you type commands. On Windows it is Command Prompt or PowerShell. On Linux it is called Terminal. |
| Apply | The moment RSMM copies your changes into the game. |
| RSMM | Ravenswatch Mod Manager, the tool this site documents. |
Step 1: Set up once
Section titled “Step 1: Set up once”You need three things installed:
- Ravenswatch (from Steam)
- Python 3.11 or newer (python.org). On Windows, tick “Add python.exe to PATH” in the installer.
- Git (git-scm.com)
Open a terminal and paste these lines one at a time:
git clone https://github.com/Ovilli/RavenswatchModManager.gitcd RavenswatchModManagerpython -m venv venvvenv\Scripts\activatepip install -e .pip install texture2ddecoder Pillowrsmm doctorgit clone https://github.com/Ovilli/RavenswatchModManager.gitcd RavenswatchModManagerpython3 -m venv .venvsource .venv/bin/activatepip install -e .pip install texture2ddecoder Pillowrsmm doctorThe second pip install line adds the two packages the next step needs to read
the game’s files.
rsmm doctor checks your setup and tells you if something is missing, for
example if it cannot find the game. A warning about the loader is fine for now:
you only need it in Step 10.
Next, make a readable copy of the game’s hero files. RSMM reads talents from
this copy, which it keeps in data/uncooked/. Run both lines:
python scripts/extract_uncooked.py --filter "EntitySettings\Heroes"python scripts/extract_uncooked.py --filter "Definitions\Heroes"Type the backslash \ exactly as shown, on Linux too. If the script says it
cannot find _Cooking, it could not find your game. Add
--game-dir "C:\path\to\Ravenswatch" to the end of both lines, using the
folder that contains Ravenswatch.exe.
To check that it worked, run:
rsmm talentsYou should see a list of heroes, each with a count of values. If you see that list, setup is finished.
Step 2: Find your talent’s names
Section titled “Step 2: Find your talent’s names”A talent has an English name on its card, and different names inside the game
files. For example, Scarlet’s Short Wick is called Secondary Quick Bombs
inside the files. You need the file names.
Open the Talent name lookup page, find your hero, and find your talent in the table. Write down three things:
| What | Ice Shield example | Used for |
|---|---|---|
| Hero spelling (at the top of the hero’s section) | Snow_Queen |
Telling RSMM which hero |
| Search word | Defense Shield |
Finding the talent’s numbers |
| Rename with | Skill_Defense_Shield |
Changing the name or description |
Step 3: Look inside the talent
Section titled “Step 3: Look inside the talent”This step uses a small helper script that shows everything about one talent: its text and all its numbers.
Create a new file called talent_info.py directly in the RavenswatchModManager
folder, next to README.md. Open it in any text editor (Notepad works), paste
the code below and save.
# talent_info.py - print everything you need to mod one talent.## python talent_info.py <Hero> "<Search word>" <Rename with>## All three values come from the Talent name lookup page.import sysfrom pathlib import Path
from rsmm.engine import talent_values as TVfrom rsmm.engine.text_patches import _pristine, lang_path_for, parse_text_filefrom rsmm.sdk.kinds.skills import _install_bank
if len(sys.argv) != 4: sys.exit('Usage: python talent_info.py <Hero> "<Search word>" <Rename with>')hero, word, key = sys.argv[1], sys.argv[2].lower(), sys.argv[3]
folder = Path("data/uncooked/EntitySettings/Heroes") / f"Hero_{hero}"if not folder.is_dir(): sys.exit(f"No folder {folder}\nCheck the hero spelling, or extract the game files first.")
# 1. The text on the card, exactly as the game stores it.bank = _install_bank(hero)if bank is None: print("(Could not find the game install, so the card text is not shown.)")else: keys = parse_text_file(_pristine(bank[0])).entries english = parse_text_file(_pristine(lang_path_for(bank[0], "EN"))).entries text = dict(zip(keys, english)) for suffix in ("_Name", "_Title", "_Desc"): if key + suffix in text: print(f"{suffix[1:].upper():<6} {text[key + suffix]}")
# 2. Every number that belongs to the talent.for f in sorted(folder.glob("*.entity.ot.EntitySettingsResource.gen")): data = f.read_bytes() lines = [] plain = TV.list_talent_values(data) for v in plain: if word in v.label.lower(): lock = " <- LOCKED (read 'shadowed' in the tutorial)" if v.is_overridden else "" lines.append(f" ONE NUMBER {v.label} = {v.value:g}{lock}") labels = {v.label for v in plain} selectors = {t for _, t in TV._iter_lstrings(data) if word in t.lower() and t.endswith("Selector") and not t.startswith("[")} for name in sorted(selectors - labels): try: unions = TV.list_union_values(data, name) except ValueError: continue nums = [f"index {i} = {v:g}" for i, (_, v, kind) in enumerate(unions) if kind in (0, 1)] if nums: lines.append(f" PER RARITY {name}: " + ", ".join(nums)) if lines: print(f'\nfile = "{f.name.split(".entity.")[0]}.entity"') print("\n".join(lines))Now run it with the three things you wrote down. Put the search word in quotes, because it contains a space:
python talent_info.py Snow_Queen "Defense Shield" Skill_Defense_ShieldFor Ice Shield it prints:
NAME Ice ShieldDESC • #DEFENSE@ grants a &{0}~ SHIELD to {1} and all Heroes passed for #{2}@ seconds
file = "Hero_Snow_Queen.entity" ONE NUMBER Skill Defense Shield Duration = 6 PER RARITY Skill Defense Shield Value Selector: index 1 = 2.8, index 3 = 2.4, index 5 = 2, index 7 = 1.6Here is what each line means:
| Line | Meaning |
|---|---|
NAME |
The talent’s name. |
DESC |
The description, exactly as the game stores it. The {0}, {1} and {2} are gaps. The game fills them in with numbers when it draws the card. |
file = "..." |
The hero file that holds these numbers. You will copy this whole line into your mod. |
ONE NUMBER |
A number that is the same at every rarity. Here the shield lasts 6 seconds. It is the {2} in for #{2}@ seconds, so when you change it the card shows the new number by itself. |
PER RARITY |
A list with one number per rarity. Here it is the shield’s strength. The card may show it in a different form, for example as a percentage, but this is the number behind it. |
The index tells you which rarity a number belongs to:
| index | Rarity |
|---|---|
| 1 | Legendary |
| 3 | Epic |
| 5 | Rare |
| 7 | Common |
In the game’s files the biggest number almost always sits at index 1, like the 2.8 here. If you are unsure, test it in the game (Step 10).
If the helper prints no numbers, try a shorter search word. For example, use
Shield instead of Defense Shield. A shorter word also shows numbers from
other talents, so read the names carefully.
If a line ends in <- LOCKED, that number is controlled by a per-rarity list
somewhere else, and changing it does nothing. Change the PER RARITY numbers
instead. The Troubleshooting section explains this.
Step 4: Create your mod
Section titled “Step 4: Create your mod”Inside the RavenswatchModManager folder there is a folder called mods.
Create a new folder inside it called glacier-shield. Inside that, create a
file called manifest.toml:
RavenswatchModManager/└── mods/ └── glacier-shield/ └── manifest.tomlPaste this into manifest.toml:
[mod]id = "glacier-shield"name = "Glacier Shield"version = "0.1.0"author = "YourName"description = "The Snow Queen's Ice Shield becomes Glacier Shield: stronger, and it lasts longer."license = "MIT"sdk_version = ">=3.0,<4"enabled = trueexperimental = truetags = ["talents", "snow-queen"]multiplayer_scope = "deterministic-shared"What the lines mean:
| Line | Meaning |
|---|---|
id |
Your mod’s name inside RSMM. It must match the folder name. Use lowercase letters and dashes only. |
name, description |
What people see in the mod list. |
author |
Your name. |
enabled = true |
RSMM installs this mod when you apply. |
experimental = true |
Needed because Step 7 renames a talent. See the note below. |
multiplayer_scope = "deterministic-shared" |
The mod changes the game, so everyone in a party needs it. See Online play. |
A few rules for this kind of file (it is called TOML):
- Text goes in
"double quotes". Numbers do not. - Put each setting on its own line.
- A
#starts a comment. Everything after it on that line is ignored.
Step 5: Change a number that is the same at every rarity
Section titled “Step 5: Change a number that is the same at every rarity”We make the shield last 9 seconds instead of 6. Add this to the end of
manifest.toml:
[[content]]kind = "talent"id = "ice_shield_duration"hero = "Snow_Queen"file = "Hero_Snow_Queen.entity"value_patches = [ { label = "Skill Defense Shield Duration", old = 6.0, new = 9.0 },]Line by line:
| Line | What to write |
|---|---|
[[content]] |
Starts one change. A mod can have as many as you like. |
kind = "talent" |
This change edits talent numbers. |
id |
A name you make up for this change. Use only letters, numbers and underscores _, with no spaces and no dashes. Every change in the mod needs its own id. |
hero |
The Hero spelling from Step 2. For kind = "talent", use the first spelling. |
file |
Copy the file = ... line from the helper’s output. |
label |
The name after ONE NUMBER, copied exactly, including capital letters. |
old |
The number the helper printed. This is a safety check: if the game file holds a different number, RSMM stops and tells you instead of changing the wrong thing. |
new |
Your new number. |
To change several numbers at once, add more lines between the square brackets. Separate them with commas:
value_patches = [ { label = "First Label", old = 6.0, new = 9.0 }, { label = "Second Label", old = 3.0, new = 4.0 },]Step 6: Change a number that depends on rarity
Section titled “Step 6: Change a number that depends on rarity”Now we make the shield stronger at every rarity. Add another block:
[[content]]kind = "talent"id = "ice_shield_strength"hero = "Snow_Queen"file = "Hero_Snow_Queen.entity"union_patches = [ { label = "Skill Defense Shield Value Selector", index = 7, old = 1.6, new = 2.0 }, # Common { label = "Skill Defense Shield Value Selector", index = 5, old = 2.0, new = 2.6 }, # Rare { label = "Skill Defense Shield Value Selector", index = 3, old = 2.4, new = 3.2 }, # Epic { label = "Skill Defense Shield Value Selector", index = 1, old = 2.8, new = 4.0 }, # Legendary]This works like Step 5, with two differences:
- The list is called
union_patchesinstead ofvalue_patches. - Each line has an
index, which picks the rarity. Copy it from the helper’sPER RARITYline.
Only include the rarities you want to change. One line on its own is fine.
Step 7: Change the name and the description
Section titled “Step 7: Change the name and the description”Add one more block:
[[content]]kind = "skill"id = "ice_shield_text"hero = "Snow_Queen"mode = "relabel"source = "Skill_Defense_Shield"name = "Glacier Shield"description = "• #DEFENSE@ grants a &{0}~ SHIELD to {1} and all Heroes passed for #{2}@ seconds"| Line | What to write |
|---|---|
kind = "skill" |
This change edits talent text. |
hero |
The second hero spelling from the lookup page. It differs only for Sun Wukong (Sun_Wukong here, SunWukong for kind = "talent"). |
mode = "relabel" |
Change the text of an existing talent. Always use this mode. |
source |
The Rename with value from Step 2. Do not use the English name. |
name |
The new name. Leave this line out to keep the original name. |
description |
The new description. Leave this line out to keep the original. |
How to write a description
Section titled “How to write a description”Start from the DESC line the helper printed, and change the words around the
codes.
Keep the number gaps ({0}, {1}, {2}, …). The game fills in each gap with
a number from the talent. You can move a gap or leave one out, but you cannot
add a new one. If the original description stops at {2}, a {3} shows up
empty.
The codes the game uses for formatting:
| Code | Result | Example |
|---|---|---|
#…@ |
Highlighted text, used for ability names and fixed numbers | #DEFENSE@, #{2}@ |
&…~ |
A number that grows with rarity (shown in green) | &{0}~ |
$…* |
A downside | ${0}%* |
• |
A bullet point at the start of a line | • #POWER@ deals more damage |
\n |
A new line | "• First line\n• Second line" |
A new icon (optional)
Section titled “A new icon (optional)”Put a PNG image in your mod folder, for example
mods/glacier-shield/art/glacier-shield.png. Then add this line to the same
kind = "skill" block:
icon = "art/glacier-shield.png"Use a square image. The game’s own talent icons are 128 × 128 pixels.
Step 8: Check the mod
Section titled “Step 8: Check the mod”Run:
rsmm lint glacier-shieldFor this guide’s mod, you should see:
[WARN] glacier-shield: content kind 'skill' is 'guess' — shipping under experimental opt-in (may not work in-game). [OK] glacier-shield (raw=0 patches=0 content=3 scope=deterministic-shared)That warning is expected. It is the “experimental” note from Step 4. A line that
says [FAIL] is a real problem: read it, fix the file and run lint again.
Step 9: Put it into the game
Section titled “Step 9: Put it into the game”Close Ravenswatch first. Then run:
rsmm restore --allrsmm applyrestore --all puts all the game’s original files back, and apply then
installs every enabled mod fresh. Always run both. If you only run apply,
parts of an older version of your mod can stay behind.
While apply runs, look for your mod in the output:
| You see | Meaning |
|---|---|
[content] glacier-shield: emitted 15 file(s) |
It worked. (The count is 1 hero file + 1 hero file + 13 language files.) |
[content] glacier-shield: emit failed: ... |
Your mod was not installed. The text after failed: says why. See Troubleshooting. |
apply keeps going after a failed mod so that other mods still install. Scroll
up and check. Missing an error here is the most common reason a mod “does
nothing”.
Now start the game and play The Snow Queen. When a level-up offers Ice Shield, the card should say Glacier Shield and show the new numbers.
Step 10 (optional): Get the talent at the start of every run
Section titled “Step 10 (optional): Get the talent at the start of every run”Waiting for the game to offer your talent can take many runs. A small test file can give you the talent automatically at the start of each run.
This uses the RSMM loader, a file RSMM adds to the game so mods can run Lua
scripts. Set it up once (and again after every restore --all, which removes it):
rsmm install-loaderrsmm doctorClose Steam first, then:
rsmm install-loaderrsmm run --set-launch-options --no-launchrsmm doctorThe second command sets Ravenswatch’s Steam launch options. Without them, the game on Linux never loads the loader.
The first time, install-loader downloads the ready-made loader for you, so you
do not need to build anything. Close the game before you run it.
Then create a file called init.lua in your mod folder, next to manifest.toml:
-- TESTING ONLY. Gives you the talent at the start of every run.-- Delete this file before you share or publish the mod.local R = require "rsmm"
local TALENT = "Defense Shield" -- the "Search word" from the lookup pagelocal RARITY = 0 -- 0 Common, 1 Rare, 2 Epic, 3 Legendary
local done = falseR.on("run:start", function() done = false end)R.schedule.every(3, function() if done or not R.entity.ready() then return end if #R.talent.controllers() == 0 then return end done = true R.talent.dump() R.talent.grant(TALENT, RARITY)end)Start a run with the right hero. After a few seconds you have the talent. To
test a different rarity, change RARITY, close the game, and run
rsmm restore --all, rsmm apply and rsmm install-loader again.
To see what happened, run:
rsmm log --grep talent| You see | Meaning |
|---|---|
[rsmm.talent] granted ... |
It worked. |
grant refused: no talent matches ... |
The name in TALENT did not match. The lines above it list the name of every talent the hero has. Copy a part of the right name into TALENT. |
grant refused: all ten talent slots are full |
Start a new run. |
Step 11: Share your mod
Section titled “Step 11: Share your mod”- Delete
init.luaif you made one. - Run
rsmm lint glacier-shieldand fix any[FAIL]. - Run
rsmm pack glacier-shield. This createsdist/glacier-shield.zip. - Upload the zip in the desktop app’s Registry tab, or run
rsmm publish glacier-shield(the first time, runrsmm publish login).
Undo everything
Section titled “Undo everything”To remove every mod and return the game to normal, close the game and run:
rsmm restore --allTo turn off only this mod, run:
rsmm disable glacier-shieldrsmm restore --allrsmm applyThe finished mod
Section titled “The finished mod”Here is the complete manifest.toml from this guide:
[mod]id = "glacier-shield"name = "Glacier Shield"version = "0.1.0"author = "YourName"description = "The Snow Queen's Ice Shield becomes Glacier Shield: stronger, and it lasts longer."license = "MIT"sdk_version = ">=3.0,<4"enabled = trueexperimental = truetags = ["talents", "snow-queen"]multiplayer_scope = "deterministic-shared"
# Lasts 9 seconds instead of 6.[[content]]kind = "talent"id = "ice_shield_duration"hero = "Snow_Queen"file = "Hero_Snow_Queen.entity"value_patches = [ { label = "Skill Defense Shield Duration", old = 6.0, new = 9.0 },]
# A stronger shield at every rarity.[[content]]kind = "talent"id = "ice_shield_strength"hero = "Snow_Queen"file = "Hero_Snow_Queen.entity"union_patches = [ { label = "Skill Defense Shield Value Selector", index = 7, old = 1.6, new = 2.0 }, # Common { label = "Skill Defense Shield Value Selector", index = 5, old = 2.0, new = 2.6 }, # Rare { label = "Skill Defense Shield Value Selector", index = 3, old = 2.4, new = 3.2 }, # Epic { label = "Skill Defense Shield Value Selector", index = 1, old = 2.8, new = 4.0 }, # Legendary]
# The new name. The description keeps the original's number gaps.[[content]]kind = "skill"id = "ice_shield_text"hero = "Snow_Queen"mode = "relabel"source = "Skill_Defense_Shield"name = "Glacier Shield"description = "• #DEFENSE@ grants a &{0}~ SHIELD to {1} and all Heroes passed for #{2}@ seconds"Troubleshooting
Section titled “Troubleshooting”Find the message you got in the left column. The messages below are exactly what RSMM prints, with this guide’s example values.
| Message | What it means | How to fix it |
|---|---|---|
current value 6.0 != expected 5.0 |
The old number does not match the game file. |
Copy the number from the helper’s output. After a game update, run the helper again, because the number may have changed. |
value label 'Skill Defense Shield Duratoin' not found in Snow_Queen's entity files |
The label has a typo. |
Copy it exactly from the helper, including capital letters. |
no vanilla hero dir for 'SnowQueen' under EntitySettings/Heroes (is data/uncooked present?) |
The hero spelling is wrong, or you skipped the extraction in Step 1. | Use the spelling from the lookup page, and run the Step 1 extraction. |
id 'ice-shield' must match ^[A-Za-z0-9_]+$ |
A block’s id contains a dash or a space. |
Use only letters, numbers and _. |
skip block missing kind/id |
A [[content]] block has no id line. |
Add one. |
union_patches need `file` to select exactly one entity file (matched 30: ...) |
The block has no file line. |
Add the file = ... line from the helper. |
'Skill Special Marked Value' is shadowed: its value is sourced from a selector/reference ... |
The helper marked this number LOCKED. The game ignores it and reads a per-rarity list instead, so changing it would do nothing. |
Change the PER RARITY numbers with union_patches instead. |
no text key for 'Ice Shield' — tried Skill_Ice_Shield_Name |
source holds the English name. |
Use the Rename with value from the lookup page. |
text key 'Skill_Power_More_Range_Name' not in bank |
This talent’s name cannot be changed (all of Juliet’s, and a few others). | Remove the name line and keep only description. |
content kind 'skill' is 'guess' ... Set [mod] experimental = true |
The mod renames a talent but has no experimental = true. |
Add it to the [mod] section. |
no install text bank for 'Snow_Queen' reachable |
RSMM cannot find your game. | Run rsmm doctor and follow what it says. |
The game shows no change, and there was no error:
- Did you close the game before
rsmm apply? - Scroll up in the
applyoutput. Does it sayemittedfor your mod, orfailed? - Is
enabled = truein your manifest?rsmm listshows every mod and whether it is on. - Did you change a
LOCKEDnumber? The game ignores those. - Did you change the rarity you are actually playing? Check the index table in Step 3.
Going further
Section titled “Going further”This guide covers the changes that are safe for beginners. Ready to change what a talent does, not just its numbers? The advanced guide shows how to swap the attack a talent plays, add “if you own this talent” rules, give a talent an extra stat bonus, and build new effects out of pieces the game already has.
