Advanced talent modding
The beginner tutorial changes a talent’s numbers and text. This page goes further and changes what a talent does. By the end you will know how to:
- make a talent play a different attack,
- add an “if the player has this talent, do that” rule,
- make a talent switch on a different effect,
- give a talent an extra bonus, such as a shorter cooldown, with a new number on its card,
- build a new “every N attacks” effect out of pieces the game already has.
You still do not write code. Everything is manifest.toml, like before. What
changes is that you now need to understand how a talent is put together inside
the game’s files, and this page teaches that first.
How much has been tested
Section titled “How much has been tested”Some of these techniques are proven in the game. Others build correct files but have not been played yet. Each recipe carries one of these labels:
| Label | Meaning |
|---|---|
| Tested in game | Someone played it, and it did what the recipe says. |
| Partly tested | Part of it was seen working in the game, and part was not checked yet. |
| Not yet tested | RSMM builds it correctly, but nobody has played it yet. |
Every example on this page builds without errors against the current game files. “Builds” is not the same as “works”, which is why the labels exist.
Before you start: four rules
Section titled “Before you start: four rules”- Change one thing, then test it. A wrong link does not show an error. The talent just stops working, or does something odd. If you change five things at once, you cannot tell which one broke it.
- Put all changes to one hero file in one mod. If two separate mods both
change
Hero_SunWukong.entity, only one of them ends up in the game. Inside one mod, as many[[content]]blocks as you like combine correctly. - Never touch your only copy of the game.
rsmm restore --allundoes everything RSMM did, so run it whenever something looks wrong. - Test with the talent actually owned. A talent’s effect only runs while you have the talent. Use the testing file from the beginner tutorial.
How a talent works inside
Section titled “How a talent works inside”A hero file, like Hero_SunWukong.entity, is a big collection of small named
parts. Each part does one job, and parts point at each other. A talent is not
one part. It is a small chain of them.
Here is Sun Wukong’s Stick Twirl, the talent that turns an attack right after a dash into a spinning attack. This is the real chain from the game’s files:
flowchart TD
A["Skill Controller Dash Attack<br/><i>the talent itself</i>"] -->|switches on| B["Skill Dash Attack<br/><i>State</i>"]
C["Basic Attack Is Skill Dash Attack Condition<br/><i>Tester</i>"] -->|checks| B
D["Basic Attack Clip Selector Step 1<br/><i>Selector</i>"] -->|asks| C
D -->|if yes| E["Skill Dash Attack Clip<br/><i>Anim Clip: the spin</i>"]
D -->|if no| F["Basic Attack Clip 1<br/><i>Anim Clip: the normal first hit</i>"]
A -->|card text| G["Skill String Desc Dash Attack<br/><i>String Format</i>"]
H["Skill Dash Attack Damage Multiplier Value Selector<br/><i>Value Selector</i>"] -->|reads the rarity of| A
Read it like this:
- When you pick Stick Twirl, its Skill Controller switches on a State
called
Skill Dash Attack. - When you attack, the Selector for the first hit asks a Tester: “is
Skill Dash Attackswitched on, and did the player just dash?” - If yes, it plays the spin (Anim Clip
Skill Dash Attack Clip). If no, it plays the normal first hit. - The Value Selector looks up which rarity the controller has, to choose the damage.
- The String Format fills in the numbers on the card.
The big idea: owning a talent switches on its State, and the rest of the hero checks that State. To change what a talent does, you change what points at what. That is called a rewire.
The parts you will meet
Section titled “The parts you will meet”| Type | What it does |
|---|---|
| Skill Controller | The talent itself. Holds its rarity and switches on its State. |
| State | Something that is on or off. A talent’s State is on while you own the talent. States can have children that run while they are on, such as a Modifier. |
| Tester | A yes/no question, for example “is this State on?” |
| Selector | Picks one of several options, based on Testers. |
| Value | One number. |
| Value Selector | One number per rarity. These are the PER RARITY lines from talent_info.py. |
| Anim Clip | One animation, together with the hits, effects and sounds that happen during it. |
| Modifier | Changes a stat, such as a cooldown, while its State is on. |
| Counter | Counts events and does something when it reaches a number. |
| Named Event Sender | Sends a named signal, such as HORDE_ATTACKS_COUNTER_INC, that Counters can listen to. |
| String Format | Fills the {0}, {1}, … gaps on a talent card with numbers. |
Names, labels and hidden links
Section titled “Names, labels and hidden links”Every part has a name, like Basic Attack Clip 4. When one part points at
another, the file stores two things:
- a hidden ID of the target, which the game actually follows, and
- a label, which is only a note for humans, like
[Anim Clip] Hero_SunWukong\Ability Basic\Basic Attack Clip 4.
A rewire changes the hidden ID and leaves the label alone. After a rewire, the label can therefore say one thing while the game does another. The inspector tool below always shows you where a link really goes.
The inspector: see how parts connect
Section titled “The inspector: see how parts connect”This helper shows every part of a hero, what each part points at, and what
points at it. Create talent_graph.py in the RavenswatchModManager folder,
next to talent_info.py, and paste this:
# talent_graph.py - see how the parts of a hero's talents connect.## python talent_graph.py <Hero> find "<word>" list parts whose name contains the word# python talent_graph.py <Hero> show "<Part name>" one part: what it uses, and what uses it## Options, added at the end:# --mod <mod-id> look at your mod's changed file instead of the original# --file <name> look in another file of the hero, e.g. --file Hero_Snow_Queen_Ice_Cloneimport reimport structimport sysfrom pathlib import Path
from rsmm.engine import cookedfrom rsmm.engine import entity_append as EA
args = sys.argv[1:]options = {}for flag in ("--mod", "--file"): if flag in args: at = args.index(flag) options[flag] = args[at + 1] if at + 1 < len(args) else "" del args[at:at + 2]if len(args) != 3 or args[1] not in ("find", "show"): sys.exit('Usage: python talent_graph.py <Hero> find|show "<text>" [--mod <id>] [--file <name>]')hero, mode, text = argsstem = options.get("--file") or f"Hero_{hero}"root = Path("mods") / options["--mod"] / "assets" if "--mod" in options else Path("data/uncooked")path = (root / "EntitySettings" / "Heroes" / f"Hero_{hero}" / f"{stem}.entity.ot.EntitySettingsResource.gen")if not path.is_file(): sys.exit(f"No file {path}\nCheck the spelling. For --mod, run rsmm apply first.")
BEGIN, END = cooked.MARK_BEGIN, cooked.MARK_ENDcf = cooked.parse(path.read_bytes())classes = [c.name for c in cf.classes]picker = classes.index("oCEntityCpntPicker")
def kind_of(class_name): words = re.sub(r"^oC(Dt)?(EntityCpnt|EntityComponent)?|Settings$", "", class_name) return re.sub(r"(?<=[a-z])(?=[A-Z])", " ", words)
# Every named part: name -> (kind, identity, section index)parts = {}for i, sec in enumerate(cf.sections[1:-1], start=1): p = sec.payload end = p.find(END) if end < 0 or end + 24 > len(p): continue at = end + 20 n = struct.unpack_from("<I", p, at)[0] if not 0 < n < 256: continue try: name = p[at + 4:at + 4 + n].decode("ascii") except UnicodeDecodeError: continue cls = struct.unpack_from("<I", p, 0)[0] parts[name] = (kind_of(classes[cls]) if cls < len(classes) else "?", p[at - 16:at], i)
# Testers and collectors keep their conditions in unnamed sub-parts.count, _ = EA._directory(cf)owned = set(range(count)) - set(EA.component_vector(cf.sections[-1].payload)[1])
def payloads(index, standalone=False, depth=0): """A part's own bytes plus those of every unnamed sub-part it owns.""" p = cf.sections[index].payload yield p if depth < 8: for off in EA._carrier_refs(p, classes, owned, standalone): sub = struct.unpack_from("<I", p, off)[0] yield from payloads(1 + sub, True, depth + 1)
def references(index): """(identity, label) of every reference a part makes.""" out = [] for payload in payloads(index): out += refs_in(payload) return out
def refs_in(payload): out, i = [], payload.find(BEGIN + struct.pack("<I", picker)) while i != -1: if i + 28 <= len(payload): n = struct.unpack_from("<I", payload, i + 24)[0] if 0 < n < 512: label = payload[i + 28:i + 28 + n].decode("utf-8", "replace") if label.startswith("["): out.append((payload[i + 8:i + 24], label)) i = payload.find(BEGIN + struct.pack("<I", picker), i + 1) return out
if mode == "find": hits = sorted((k, name) for name, (k, _g, _i) in parts.items() if text.lower() in name.lower()) for kind, name in hits: print(f" {kind:<28} {name}") print(f"\n{len(hits)} part(s) in {stem}") sys.exit()
if text not in parts: sys.exit(f'No part named exactly "{text}". Use: python talent_graph.py {hero} find "<word>"')kind, guid, index = parts[text]by_guid = {g: name for name, (_k, g, _i) in parts.items()}labels = [lab for _n, (_k, _g, i) in parts.items() for g, lab in references(i) if g == guid]labels = [lab for lab in labels if lab.endswith("\\" + text)] or labelsprint(f"{text}\n type: {kind}")print(f" label: {labels[0] if labels else '(nothing points at it yet)'}")print("\nUSES (what this part points at):")for g, label in references(index): # The game follows the hidden identity, not the label text, and a rewire # changes only the identity. So name the part the identity really leads to. if g not in by_guid: print(f" {label} (in another file)") continue print(f" {by_guid[g]} ({parts[by_guid[g]][0]})") note = "" if label.endswith("\\" + by_guid[g]) else " <- old text, changed by a rewire" print(f" written as: {label}{note}")print("\nUSED BY (parts that point at this one):")seen = Falsefor name, (_k, _g, i) in sorted(parts.items()): for g, _label in references(i): if g == guid: print(f" {name}") seen = True breakif not seen: print(" (nothing in this file)")The hero name is the first spelling from the
Talent name lookup (SunWukong, Snow_Queen, …).
Find parts
Section titled “Find parts”find lists every part whose name contains a word:
python talent_graph.py SunWukong find "Dash Attack" Anim Clip Skill Dash Attack Clip Skill Controller Skill Controller Dash Attack State Skill Dash Attack Tester Basic Attack Is Skill Dash Attack Condition Value Selector Skill Dash Attack Damage Multiplier Value Selector ...27 part(s) in Hero_SunWukongA good first step for any talent is to find the Search word from the lookup
page.
Look at one part
Section titled “Look at one part”show takes an exact part name, copied from find:
python talent_graph.py SunWukong show "Basic Attack Clip Selector Step 1"Basic Attack Clip Selector Step 1 type: Selector label: [Component Selector] Hero_SunWukong\Ability Basic\Basic Attack Clip Selector Step 1
USES (what this part points at): Skill Dash Attack Clip (Anim Clip) written as: [Anim Clip] Hero_SunWukong\Skill Dash Attack\Skill Dash Attack Clip Basic Attack Is Skill Dash Attack Condition (Tester) written as: [Cpnt Value Tester] Hero_SunWukong\Skill Dash Attack\Basic Attack Is Skill Dash Attack Condition Basic Attack Clip 1 (Anim Clip) written as: [Anim Clip] Hero_SunWukong\Ability Basic\Basic Attack Clip 1
USED BY (parts that point at this one): State Basic Attack Step 1| Line | Meaning |
|---|---|
label: |
How other parts write this part’s name. Copy it when a recipe asks for a part’s label. |
USES |
What this part points at, and where each link really goes. |
written as: |
The label text stored inside this part. Recipes that change a link inside a part match this text. |
USED BY |
Which parts point at this one. |
Following USES and USED BY from part to part is how you work out a chain like
the Stick Twirl diagram above. Start at the talent’s Skill Controller, then
show its State, and keep going.
Check your own mod
Section titled “Check your own mod”After rsmm apply, add --mod <your-mod-id> to see the changed file instead of
the original:
python talent_graph.py SunWukong show "Basic Attack Clip Selector Step 1" --mod twirl-finisherIf a link now goes where you wanted, your rewire worked in the file. A link whose
label text is out of date gets a <- old text, changed by a rewire note, which
is normal.
Recipe 1: Make a talent play a different attack
Section titled “Recipe 1: Make a talent play a different attack”Goal: Stick Twirl’s spinning attack becomes the 4th hit of Wukong’s normal combo, with that hit’s animation, damage, effects and sound.
How: in the chain above, the Selector points at Skill Dash Attack Clip.
Point that link at Basic Attack Clip 4 instead. An Anim Clip carries its hits,
effects and sounds with it, so the whole attack moves.
[[content]]kind = "talent"id = "twirl_plays_hit_four"hero = "SunWukong"file = "Hero_SunWukong.entity"rewires = [ { trigger = '[Anim Clip] Hero_SunWukong\Skill Dash Attack\Skill Dash Attack Clip', action = '[Anim Clip] Hero_SunWukong\Ability Basic\Basic Attack Clip 4', exact = true, within = "Basic Attack Clip Selector Step 1", within_span = 600 },]What each part of the rewire means:
| Field | Meaning |
|---|---|
trigger |
The link to change: its written as: text from the inspector. |
action |
Where it should point now: the target’s label:, or just its name. |
exact = true |
Match the whole label, not just part of it. |
within = "..." |
Only change the link inside this part. |
within_span = 600 |
How far past the part’s name to search, in bytes. A few hundred covers a small part. |
Why exact and within? A rewire changes the first link in the file
whose label matches. In this file the Stick Twirl clip is used by only one part,
so here they are a safety net. Many labels are used by several parts, though, and
without within you change whichever comes first, which is often not the one you
mean. Add both whenever you target a link inside one specific part. Recipe 3
shows what goes wrong without them.
The talent’s gate stays: only the link after “if yes” changed. Without Stick Twirl, your attacks behave exactly as before.
Recipe 2: Add an “if this, else that” rule
Section titled “Recipe 2: Add an “if this, else that” rule”Goal: after a dash, Wukong’s attack becomes Celestial Pillar’s slam if you also own Celestial Pillar, and the 4th combo hit if you do not.
The problem: no part says “Pillar owned ? slam : hit 4”, so there is nothing to rewire to.
The trick: find a part that already has the right shape, copy it, and change what the copy points at. The copy is called a clone.
Wukong’s Basic Attack Combo Selector has exactly that shape. Check it:
python talent_graph.py SunWukong show "Basic Attack Combo Selector"USES (what this part points at): Skill Attack Finisher Combo (Attack Combo) Skill Attack Finisher (State) Basic Attack Combo (Attack Combo)In words: “if the State Skill Attack Finisher (Celestial Pillar owned) is on,
pick Skill Attack Finisher Combo, otherwise pick Basic Attack Combo.” We copy
it, swap its two results for the two attacks we want, and point Stick Twirl at the
copy:
[[content]]kind = "talent"id = "twirl_pillar_or_hit_four"hero = "SunWukong"file = "Hero_SunWukong.entity"
clone_nodes = [ { source = "Basic Attack Combo Selector", name = "Twirl Finisher Clip Selector", retarget = [ { from = '[Dt Attack Combo] Hero_SunWukong\Skill Attack Finisher\Skill Attack Finisher Combo', to = '[Anim Clip] Hero_SunWukong\Skill Attack Finisher\Skill Attack Finisher Clip' }, { from = '[Dt Attack Combo] Hero_SunWukong\Ability Basic\Basic Attack Combo', to = '[Anim Clip] Hero_SunWukong\Ability Basic\Basic Attack Clip 4' }, ] },]
rewires = [ { trigger = '[Anim Clip] Hero_SunWukong\Skill Dash Attack\Skill Dash Attack Clip', action = "Twirl Finisher Clip Selector", exact = true, within = "Basic Attack Clip Selector Step 1", within_span = 600 },]| Field | Meaning |
|---|---|
source |
The part to copy. |
name |
A new, unused name for the copy. |
retarget |
Links to change inside the copy. from is the written as: text inside the source part; to is the new target’s label:. |
A clone on its own does nothing, because nothing points at it yet. The rewires
entry connects it: action is the copy’s name. Clones are always built first,
so a rewire in the same block can use them.
The tested part is the Pillar branch: with both talents owned, the dash attack became the Pillar slam. The “no Pillar” branch uses the same file change as Recipe 1 but has not been played on its own.
Recipe 3: Make a talent switch on a different effect
Section titled “Recipe 3: Make a talent switch on a different effect”Goal: Scarlet (Red) has a finished POWER finisher in her files, but no talent ever switches it on. We make Short Wick switch it on instead of its own effect.
First look at both controllers:
python talent_graph.py Red show "Skill Controller Secondary Quick Bombs"python talent_graph.py Red show "Skill Primary Finisher Finisher Damage Multiplier Selector"Short Wick’s controller switches on the State Skill Secondary Quick Bombs. The
finisher’s damage Value Selector points at Skill Controller Primary Finisher, a
controller nobody can ever own, three times, once for each rarity condition. So we
make two changes:
[[content]]kind = "talent"id = "short_wick_runs_finisher"hero = "Red"file = "Hero_Red.entity"rewires = [ # 1. The effect: Short Wick switches on the finisher's State. { trigger = '[State] Hero_Red\Skill Secondary Quick Bombs\Skill Secondary Quick Bombs', action = '[State] Hero_Red\Skill Primary Finisher\Skill Primary Finisher', exact = true, within = "Skill Controller Secondary Quick Bombs", within_span = 400 },
# 2. The damage: the finisher reads its rarity from Short Wick. { trigger = "Skill Controller Primary Finisher", action = "Skill Controller Secondary Quick Bombs", all = true },]Two new things here:
all = truechanges every matching link, not just the first. A Value Selector points at its controller once per rarity, and changing only one link leaves the other rarities reading the old controller.- Why the first rewire needs
exactandwithin: the textSkill Secondary Quick Bombsalso appears inside the label of Short Wick’s cooldown Modifier, which comes earlier in the file. An earlier version of this mod matched that shorter text withoutexactandwithin, and changed the Modifier instead of the controller’s State link. Nothing reported an error.
Pair it with a new name and description from the beginner tutorial, and
optionally flatten the damage with union_patches.
Tested so far: the damage change reached the game. Whether POWER now ends with the finisher attack has not been confirmed yet.
Recipe 4: Add a stat bonus, with a new number on the card
Section titled “Recipe 4: Add a stat bonus, with a new number on the card”Goal: Sun Wukong’s Fiery Dragon also reduces his TRAIT cooldown: −10 % Common, −15 % Rare, −20 % Epic, −25 % Legendary. The card shows the number.
The short way: add_stats
Section titled “The short way: add_stats”One field does all of the wiring below. In the editor, it is the + Add a stat button under a talent card.
[[content]]kind = "talent"id = "fiery_dragon_cooldown"hero = "SunWukong"add_stats = [ { talent = "Trait Fire", stat = "CD reduce trait", values = [0.10, 0.15, 0.20, 0.25] },]talentis the card’s row name (Skill Controller Trait Fire→Trait Fire).statis any stat from the stat list: the same names the item editor offers.valuesis one number for every rarity, or four (Common, Rare, Epic, Legendary). The number is in the stat’s own unit:0.25on a cooldown stat is −25 %.- The card gets a new number slot, the next
{N}after the ones it has. Write it in the description with askillblock, for example• #TRAIT@ cooldown &-{1}%~.percent = falseshows the number as it is, without multiplying by 100.
add_stats is not quite the hand-built version below. It adds the new modifier to
the talent’s own State instead of swapping that State for a copy, so talents whose
State already does something keep doing it. The parts are the proven ones; this
exact arrangement has not been played yet.
Ultimates cannot take a stat this way: they have no State that marks them as owned.
Talents two heroes share. Romeo and Juliet build their shared talents (Love
Shield, Ignite, …) in one file both inherit, Hero_Juliet/Hero_Romeo_Juliet_Common;
each hero’s own file only overrides the card. add_stats and include find the
talent there and write that file, so the change reaches both heroes. during
and after then take the abilities that file builds (SPECIAL and TRAIT).
rebuild is refused for these for now: the hero’s own card would keep
pointing at the old effect.
Tested in game Only while an ability is in use: add during = "DEFENSE" (or ATTACK, POWER,
SPECIAL, TRAIT, DASH) to an entry, e.g. “During #DEFENSE@, #ATTACK@ damage
&+0%~”. The bonus then reads a copy of a selector the game ships (“ability in use
→ the number, else 0”). The ability is found through the hero’s controller for that
button, never by name. A hero whose ability only signals its start (Merlin’s POWER,
DEFENSE and TRAIT) cannot use during for it; the editor leaves those out.
Tested in game For a while after an ability: add after = "DEFENSE", seconds = 3
to an entry, e.g. “For #3@ second(s) after using #DEFENSE@, #ATTACK@ damage
&+0%~”. Each use of the ability restarts the 3 seconds. It is built the way
Beowulf’s Defense Damage Aura builds its window: using the ability fires an
event that switches a new, empty State off and on again, that State runs a timer,
and the timer switches the State off when the time is up. (A timer’s State link only
ends that State; it never switches it on.) The bonus is then “during” that State. Because it only needs the moment the
ability starts, after also works for abilities that only signal their start
(Merlin). In the editor, pick For a while after … under When.
add_stats = [ { talent = "Trait Fire", stat = "Attack power", values = 0.3, after = "DEFENSE", seconds = 3 },]Tested in game At most once every N seconds: add cooldown = 8 to an
after entry and a use only restarts the window when the last restart was at least
8 seconds ago; uses in between do nothing. This is how Romeo’s Love Shield limits
itself (“every 3 s max”), and the gate is a copy of his: the ability fires a tester
that passes only while a new Cooldown State is off, and that State then restarts the
window and switches itself off after cooldown seconds. In the editor, fill in At
most once every … second(s) under the “Lasts” box; leave it empty for every use.
add_stats = [ { talent = "Trait Fire", stat = "Attack power", values = 0.3, after = "DEFENSE", seconds = 3, cooldown = 8 },]Tested in game Your next ability after another: add next = "ATTACK" to an
after entry and the bonus ends once ATTACK has been used, like Beowulf’s
Retaliation (“your next ATTACK within … after DEFENSE”). The window is the same
after window; ATTACK’s in-use State ends it as it finishes, so the bonus covers that
whole attack and is gone after it. next takes an ability whose use lasts (the same
ones during takes) and must differ from after. In the editor, pick it under
Used up by. next and cooldown can be combined.
add_stats = [ { talent = "Trait Fire", stat = "Attack power", values = 0.5, after = "DEFENSE", seconds = 4, next = "ATTACK" },]Stats that land on enemies are refused. add_stats builds a modifier on the
hero itself, so a stat the game only ever puts on whoever is hit (Ignite, Bleed,
Chilled, Vulnerable, Weak, Marked, Rooted, …) would debuff your own hero. The list
is read from the game’s files; the editor shows those stats greyed out.
Give a talent another talent’s effect: include
Section titled “Give a talent another talent’s effect: include”Owning one talent can also switch on another of the same hero’s talents. What a
talent does hangs off its State, so include keeps the other talent’s State on
while this one is owned, and everything that State does or unlocks runs. In the
editor, it is Also gives the effect of on a card.
[[content]]kind = "talent"id = "shapeshifter_wick"hero = "Red"include = [{ talent = "Trait Active", from = "Secondary Quick Bombs" }]This gives Scarlet’s Shapeshifter the bomb from Short Wick that explodes as
soon as it hits the ground: that behaviour is a switch wired to Short Wick’s State,
which the bomb reads. You get all of the other talent, not a piece of it: here
Short Wick’s faster SPECIAL recharge comes too. Numbers that scale with the other
card’s rarity use their Common value, because that card is not owned. Mention the
effect in the card’s text with a skill block.
From another hero
Section titled “From another hero”Add hero to take a talent from another hero. Its effect is copied into your
hero’s file, and its numbers follow your card’s rarity:
[[content]]kind = "talent"id = "virtuoso_focus"hero = "Piper"include = [{ talent = "Attack Move Speed", from = "Power Hold", hero = "SunWukong" }]Owning Piper’s Virtuoso now also gives Wukong’s Focused Strikes bonus.
Most talents cannot move. They work inside their hero’s abilities: Aladdin’s Defense Tornado is a number his DEFENSE tornado reads, and Piper has no such tornado. RSMM refuses those and says why. These 9 can be given to any hero:
| Hero | Talent (card) | from |
|---|---|---|
| Aladdin | Master Thief | Passive Extra MO |
| Aladdin | Jinniya’s Gift | Trait Extra Wish |
| Carmilla | Bat Master | Defense Master |
| Juliet | Wedding Gifts | Passive Bonus Object |
| Melusine | Soothing Presence | Passive Heal In Combat |
| Melusine | Dance Step | Trait Dash |
| Merlin | Astrology | Passive Gain Reroll High Talent |
| Piper | Virtuoso | Attack Move Speed |
| Sun Wukong | Focused Strikes | Power Hold |
In the editor, they are under Other heroes in the Also gives the effect of list.
Make an ability bigger: scale
Section titled “Make an ability bigger: scale”Range is not a stat: each ability works out its own size in its own files. scale
grows one of those numbers (a radius, a length, a width) while the talent is
owned, by 1 + amount, so 0.3 is +30%:
[[content]]kind = "talent"id = "rising_tide"hero = "Melusine"rebuild = ["Trait Dash"]scale = [ { talent = "Trait Dash", node = "Primary Ability Radius Operation", file = "Hero_Melusine_Power_Caster_Model", values = [0.2, 0.3, 0.4, 0.5] },]node is the number to grow and file the hero file it is in, which is needed
only when the name is in more than one. The number can sit in an entity the hero
spawns (Melusine’s POWER caster, Piper’s projectile), as long as that entity
already reads values from the hero. The card gets a {N} for the amount. Only the
hit area is sure to change; an ability’s visual effect may keep its old size.
In the editor, this is Make an ability bigger in the custom-talent builder: it lists each hero’s sizes by ability.
Make hits put a status on enemies: on_hit
Section titled “Make hits put a status on enemies: on_hit”While the talent is owned, one of the hero’s hits also puts a status on whoever it hits: Chilled, Ignite, Bleed, Vulnerable, Weak or Rooted.
[[content]]kind = "talent"id = "fire_tide"hero = "Melusine"
[[content.on_hit]]talent = "Attack Speed"damage = "Primary Ability Damage"status = "Ignite"damage is one of the hero’s damage records, the thing a hit deals: an ability
can have more than one (Melusine’s POWER has a normal and a geyser hit), so add
an entry for each. file narrows it to one entity file when the name repeats.
The status is copied from a hit in the game that already applies it, with its
own duration and strength. Mention it in the card’s text with a skill block.
In the editor, this is Hits apply a status in the custom-talent builder: it lists each hero’s hits by ability.
Rebuild a talent from scratch: rebuild
Section titled “Rebuild a talent from scratch: rebuild”The number of talents a hero has is fixed, so a new talent replaces one you pick.
rebuild turns that talent’s own effect off and keeps only its card slot; the
stats you add are then all it does. In the editor, press Build a custom talent
on the Talents tab (or Replace with a custom talent on a card): pick the talent
to replace, name it, and add building blocks or start from an example. An idea the
blocks can’t build yet (“POWER spawns a tornado”) can be sent from the same screen
as a Talent idea on GitHub; that is how new blocks get made.
[[content]]kind = "talent"id = "keen_eye"hero = "Beowulf"rebuild = ["Attack Fire Cone"]add_stats = [ { talent = "Attack Fire Cone", stat = "Crit chance primary", values = [0.05, 0.10, 0.15, 0.20] },]
[[content]]kind = "skill"id = "keen_eye_card"hero = "Beowulf"source = "Attack Fire Cone"name = "Keen Eye"description = "• #POWER@ Crit Chance &+{0}%~"How it works: the talent’s controller is pointed at a new, empty State, so the
State everything the talent did hangs off never switches on. The card’s old
numbers are removed too, which is why the first added stat is {0}. Give the card
new text: without a skill block it keeps the old wording.
The long way: four clones
Section titled “The long way: four clones”This recipe needs four clones, because a stat bonus is a small chain of its own:
flowchart LR
C["Skill Controller Trait Fire"] -->|switches on| S["Skill Trait Fire With Cooldown<br/><i>new State</i>"]
S -->|child| M["Skill Trait Fire Cooldown Reduction Modifier<br/><i>new Modifier: TRAIT cooldown</i>"]
M -->|amount| V["Skill Trait Fire Cooldown Reduction Selector<br/><i>new Value Selector</i>"]
V -->|rarity of| C
C -->|card| F["Skill String Desc Trait Fire With Cooldown<br/><i>new String Format, 2 numbers</i>"]
Where the pieces come from
Section titled “Where the pieces come from”Look at what exists:
python talent_graph.py SunWukong show "Skill Trait Fire"python talent_graph.py SunWukong show "Skill Power Hold"python talent_graph.py SunWukong show "Skill String Desc Dash Sprint"- Fiery Dragon’s State
Skill Trait Firehas no children (itsUSESlist is empty), so there is nowhere to hang a Modifier. - Wukong’s Power Hold State has exactly one child, a Modifier, which reads its amount from a Value Selector. That is the shape we need, so we copy Power Hold’s State and Modifier.
- The amount: copy Fiery Dragon’s own damage Value Selector. It already reads Fiery Dragon’s rarity, so we only replace its numbers.
- The card: Fiery Dragon’s String Format fills one gap, and we need two. Dash Sprint’s has two, so we copy that.
Which stat a Modifier changes
Section titled “Which stat a Modifier changes”A copied Modifier changes the same stat as the Modifier it was copied from. Power
Hold’s changes attack power, not TRAIT cooldown. modifier_stat takes the stat from any
other shipped Modifier instead, even another hero’s:
| Stat | Copy it from (hero, node) |
|---|---|
| POWER cooldown | Snow_Queen, Skill Trait Abilities Cooldown Primary CD Modifier |
| SPECIAL cooldown | Red, Skill Secondary Quick Bombs CD Reduction Modifier |
| DEFENSE cooldown | Snow_Queen, Skill Trait Abilities Cooldown Defensive CD Modifier |
| TRAIT cooldown | Beowulf, Skill Trait Quest Complete CD Reduction Modifier |
To look for other stats, find "Modifier" on any hero and read the names.
The mod
Section titled “The mod”[[content]]kind = "talent"id = "trait_fire_cooldown_wiring"hero = "SunWukong"file = "Hero_SunWukong.entity"
clone_nodes = [ # The amount: Fiery Dragon's own per-rarity selector, numbers replaced below. { source = "Skill Trait Fire Damage Multiplier Selector", name = "Skill Trait Fire Cooldown Reduction Selector" },
# The Modifier: Power Hold's, reading the new amount, changing TRAIT cooldown. { source = "Skill Power Hold AP Modifier", name = "Skill Trait Fire Cooldown Reduction Modifier", modifier_stat = { hero = "Beowulf", node = "Skill Trait Quest Complete CD Reduction Modifier" }, retarget = [ { from = '[Value Selector] Hero_SunWukong\Skill Power Hold\Skill Power Hold AP Selector', to = '[Value Selector] Hero_SunWukong\Skill Trait Fire\Skill Trait Fire Cooldown Reduction Selector' }, ] },
# The State: Power Hold's, holding the new Modifier as its child. { source = "Skill Power Hold", name = "Skill Trait Fire With Cooldown", retarget = [ { from = '[Modifier] Hero_SunWukong\Skill Power Hold\Skill Power Hold AP Modifier', to = '[Modifier] Hero_SunWukong\Skill Trait Fire\Skill Trait Fire Cooldown Reduction Modifier' }, ] },
# The card: Dash Sprint's two-number format. {0} = cooldown, {1} = damage. { source = "Skill String Desc Dash Sprint", name = "Skill String Desc Trait Fire With Cooldown", retarget = [ { from = '[Value Selector] Hero_SunWukong\Skill Dash Sprint\Skill Dash Sprint Move Speed Increase Selector', to = '[Value Selector] Hero_SunWukong\Skill Trait Fire\Skill Trait Fire Cooldown Reduction Selector' }, { from = '[Value] Hero_SunWukong\Skill Dash Sprint\Skill Dash Sprint Max Duration', to = '[Multi values operations] Hero_SunWukong\Skill Trait Fire\Skill Trait Fire Supposed Damage Operation' }, ], rename = [ { from = "Skill_Dash_Sprint_Desc", to = "Skill_Trait_Fire_Desc" }, ] },]
# 0.25 means -25 %, the same scale Beowulf's Draconic Binds uses.union_patches = [ { label = "Skill Trait Fire Cooldown Reduction Selector", index = 1, old = 3.85, new = 0.25 }, { label = "Skill Trait Fire Cooldown Reduction Selector", index = 3, old = 3.3, new = 0.20 }, { label = "Skill Trait Fire Cooldown Reduction Selector", index = 5, old = 2.75, new = 0.15 }, { label = "Skill Trait Fire Cooldown Reduction Selector", index = 7, old = 2.2, new = 0.10 },]
rewires = [ # Everything that used Fiery Dragon's State now uses the new one. { trigger = '[State] Hero_SunWukong\Skill Trait Fire\Skill Trait Fire', action = "Skill Trait Fire With Cooldown", exact = true, all = true }, # The card reads its numbers from the new two-number format. { trigger = '[String Format] Hero_SunWukong\Skills\Skill String Desc Trait Fire', action = "Skill String Desc Trait Fire With Cooldown", exact = true },]
[[content]]kind = "skill"id = "trait_fire_cooldown_text"hero = "Sun_Wukong"mode = "relabel"source = "Skill_Trait_Fire"description = "• Adds an explosion of fire to the activation effects of $YANG STANCE*, dealing &{1}~ area damage and inflicting enemies with #IGNITE@\n• #TRAIT@ cooldown &-{0}%~"Three details worth copying into your own work:
renamereplaces a whole piece of text inside the copy. A String Format names the text entry it fills in, so the copy must name Fiery Dragon’s description, not Dash Sprint’s.all = trueon the State rewire moves both the controller’s link and the Tester that adds the fire explosion. If you moved only the controller, the explosion would stop working.- The
oldvalues inunion_patchesare the numbers copied along with the Value Selector, which were the damage numbers. The new values are percentages written as fractions.
Recipe 5: Build an “every N attacks” effect
Section titled “Recipe 5: Build an “every N attacks” effect”Goal: Piper’s Horde also summons 3/4/5 rats that attack at once, every 16 basic attacks.
This is the largest recipe. It reuses one idea many times: find a machine that already does something similar, clone it, and give the copy its own inputs and outputs.
Piper’s Ghost Notes already counts notes and does something every N of them:
Event Attack Shoot Active (every note Piper plays) -> Ghost Notes Notify Tester (only if Ghost Notes is owned) -> SKILL_ATTACK_GHOST_NOTES_COUNTER_INC Event SenderSkill Attack Ghost Notes Counter (listens for that signal, counts to the Requirement Selector, then fires the proc)The mod builds a second copy of that machine for Horde:
| Clone | Copied from | Changed |
|---|---|---|
Horde Attacks Required Selector |
Ghost Notes’ threshold | Reads Horde’s rarity. All four numbers set to 16. |
Horde Attacks Counter |
Ghost Notes’ counter | Fires the rat proc. Listens for its own signal, HORDE_ATTACKS_COUNTER_INC. |
HORDE_ATTACKS_COUNTER_INC Event Sender |
Ghost Notes’ sender | Sends the new signal. |
Horde Attacks State |
Ghost Notes’ State | Holds the new counter. Horde’s controller points here. |
Horde Basic Attack Shoot, Horde Basic Attack Notify |
Piper’s shot events | Only the basic attack sends the new signal. |
Horde Rats Count Selector, Horde Rats Proc, Horde Rats Count Switch |
Invasive Blast’s spawn | Spawns Horde’s own number of rats, which attack at once. |
Skill String Desc Horde Rats |
Sound Barrier’s 5-number card format | Shows the new numbers on Horde’s card. |
The one new rule this recipe teaches:
Tested so far: the cloned counter and the made-up signal name worked in the game, and rats spawned. The final version, with rats counted only from basic attacks and the corrected card numbers, has not been played yet.
Show the full Horde mod
[mod]id = "piper-horde"name = "Piper: Horde of Rats"version = "0.1.0"author = "YourName"description = "Horde also summons 3 / 4 / 5 rats that attack at once, every 16 basic attacks."license = "MIT"sdk_version = ">=3.0,<4"enabled = trueexperimental = truetags = ["talents", "piper"]multiplayer_scope = "deterministic-shared"
[[content]]kind = "talent"id = "horde_rats_wiring"hero = "Piper"file = "Hero_Piper.entity"
clone_nodes = [ # -- the rat count ---------------------------------------------------------- { source = "Skill Defense Spawn Pets Max Count Selector", name = "Horde Rats Count Selector", retarget = [ { from = '[Dt Skill Controller] Hero_Piper\Skills\Skill Controller Defense Spawn Pets', to = '[Dt Skill Controller] Hero_Piper\Skills\Skill Controller Trait More Controllable Pets' }, ] }, { source = "Event Skill Attack Ghost Notes Notify Default", name = "Horde Rats Proc", retarget = [ { from = '[Named Event Sender] Hero_Piper\Skill Attack Ghost Notes\SKILL_ATTACK_GHOST_NOTES_COUNTER_INC Event Sender', to = '[State] Hero_Piper\Skill Defense Spawn Pets\Event Skill Defense Spawn Pets Proc' }, ] }, { source = "Trait Ability Max Controllable Pets Selector", name = "Horde Rats Count Switch", retarget = [ { from = '[State] Hero_Piper\Skill Trait More Controllable Pets\Skill Trait More Controllable Pets', to = '[State] Hero_Piper\Skill Trait More Controllable Pets\Horde Rats Proc' }, { from = '[Value Operation] Hero_Piper\Skill Trait More Controllable Pets\Skill Trait More Controllable Pets Count Operation', to = '[Value Selector] Hero_Piper\Skill Trait More Controllable Pets\Horde Rats Count Selector' }, { from = '[Value] Hero_Piper\Ability Trait\Trait Ability Max Controllable Pets Default Amount', to = '[Value Selector] Hero_Piper\Skill Defense Spawn Pets\Skill Defense Spawn Pets Max Count Selector' }, ] },
# -- the counter and its state ---------------------------------------------- { source = "Skill Attack Ghost Notes Requirement Selector", name = "Horde Attacks Required Selector", retarget = [ { from = '[Dt Skill Controller] Hero_Piper\Skills\Skill Controller Attack Ghost Notes', to = '[Dt Skill Controller] Hero_Piper\Skills\Skill Controller Trait More Controllable Pets' }, ] }, { source = "Skill Attack Ghost Notes Counter", name = "Horde Attacks Counter", retarget = [ { from = '[State] Hero_Piper\Skill Attack Ghost Notes\Event Skill Attack Ghost Notes Proc', to = '[State] Hero_Piper\Skill Trait More Controllable Pets\Horde Rats Proc' }, { from = '[Value Selector] Hero_Piper\Skill Attack Ghost Notes\Skill Attack Ghost Notes Requirement Selector', to = '[Value Selector] Hero_Piper\Skill Trait More Controllable Pets\Horde Attacks Required Selector' }, ], rename = [ { from = "SKILL_ATTACK_GHOST_NOTES_COUNTER_INC", to = "HORDE_ATTACKS_COUNTER_INC" }, { from = "SKILL_ATTACK_GHOST_NOTES_COUNTER_DEC", to = "HORDE_ATTACKS_COUNTER_DEC" }, ] }, { source = "Skill Attack Ghost Notes", name = "Horde Attacks State", retarget = [ { from = '[Counter] Hero_Piper\Skill Attack Ghost Notes\Skill Attack Ghost Notes Counter', to = '[Counter] Hero_Piper\Skill Trait More Controllable Pets\Horde Attacks Counter' }, { from = '[Entity 3d Node] Hero_Piper\Skill Attack Ghost Notes\Skill Attack Ghost Notes 3d Node Left', to = '[Cpnt Value Tester] Hero_Piper\Skill Trait More Controllable Pets\Skill Trait More Controllable Pets Legendary Tester' }, ] },
# -- the basic-attack-only signal ------------------------------------------- { source = "SKILL_ATTACK_GHOST_NOTES_COUNTER_INC Event Sender", name = "HORDE_ATTACKS_COUNTER_INC Event Sender", rename = [ { from = "SKILL_ATTACK_GHOST_NOTES_COUNTER_INC", to = "HORDE_ATTACKS_COUNTER_INC" }, ] }, { source = "Event Primary Ability Shoot", name = "Horde Basic Attack Notify", retarget = [ { from = '[State] Hero_Piper\Ability Common\Event Attack Shoot Active', to = '[Cpnt Value Tester] Hero_Piper\Skill Attack Ghost Notes\Skill Attack Ghost Notes Notify Tester' }, { from = '[Named Event Sender] Hero_Piper\Ability Primary\Primary Ability Increase Shoot Spawn Index Event Sender', to = '[Named Event Sender] Hero_Piper\Skill Trait More Controllable Pets\HORDE_ATTACKS_COUNTER_INC Event Sender' }, ] }, { source = "Event Attack Shoot Active", name = "Horde Basic Attack Shoot", retarget = [ { from = '[Cpnt Value Tester] Hero_Piper\Skill Attack Ghost Notes\Skill Attack Ghost Notes Notify Tester', to = '[State] Hero_Piper\Skill Trait More Controllable Pets\Horde Basic Attack Notify' }, ] },
# -- the card: {0} rats per trigger, {1} attacks needed, {2} rats per group, # {3} rats per group without Horde, {4} extra max rats -------------------- { source = "Skill String Desc Defensive Armor Quest", name = "Skill String Desc Horde Rats", retarget = [ { from = '[Value Selector] Hero_Piper\Skill Defensive Armor Quest\Skill Defensive Armor Quest Objective Count Selector', to = '[Value Selector] Hero_Piper\Skill Trait More Controllable Pets\Horde Rats Count Selector' }, { from = '[Value] Hero_Piper\Skill Defensive Armor Quest\Skill Defensive Armor Quest Armor Per Proc', to = '[Value Selector] Hero_Piper\Skill Trait More Controllable Pets\Horde Attacks Required Selector' }, { from = '[Value] Hero_Piper\Skill Defensive Armor Quest\Skill Defensive Armor Quest Character Impacts Required', to = '[Value Selector] Hero_Piper\Skill Trait More Controllable Pets\Skill Trait More Controllable Pets Per Spawn Amount Selector' }, { from = '[Multi values operations] Hero_Piper\Skill Defensive Armor Quest\Skill Defensive Armor Quest Complete Shield Operation', to = '[Value] Hero_Piper\Ability Trait\Trait Ability Pets Per Spawn Default Amount' }, { from = '[Value] Hero_Piper\Skill Defensive Armor Quest\Skill Defensive Armor Quest Complete Shield Duration', to = '[Value Selector] Hero_Piper\Skill Trait More Controllable Pets\Skill Trait More Controllable Pets Amount Selector' }, ], rename = [ { from = "Skill_Defense_Armor_Quest_Desc", to = "Skill_Trait_More_Controllable_Pets_Desc" }, ] },]
union_patches = [ # Every 16 basic attacks at every rarity. { label = "Horde Attacks Required Selector", index = 1, old = 6, new = 16 }, { label = "Horde Attacks Required Selector", index = 3, old = 7, new = 16 }, { label = "Horde Attacks Required Selector", index = 5, old = 8, new = 16 }, { label = "Horde Attacks Required Selector", index = 7, old = 10, new = 16 }, # 3 rats Common, 4 Rare, 5 Epic and Legendary. { label = "Horde Rats Count Selector", index = 1, old = 7, new = 5 }, { label = "Horde Rats Count Selector", index = 3, old = 6, new = 5 }, { label = "Horde Rats Count Selector", index = 5, old = 5, new = 4 }, { label = "Horde Rats Count Selector", index = 7, old = 4, new = 3 },]
rewires = [ # Invasive Blast's count store reads the switch instead of its own numbers. { trigger = '[Value Selector] Hero_Piper\Skill Defense Spawn Pets\Skill Defense Spawn Pets Max Count Selector', action = "Horde Rats Count Switch", exact = true, within = "Skill Defense Spawn Pets Count Operation Store", within_span = 400 }, # Horde's controller and both rat selectors use the new State. { trigger = '[State] Hero_Piper\Skill Trait More Controllable Pets\Skill Trait More Controllable Pets', action = "Horde Attacks State", exact = true, all = true }, # Horde's card reads the 5-number format. { trigger = '[String Format] Hero_Piper\Skills\Skill String Desc Trait More Controllable Pets', action = "Skill String Desc Horde Rats", exact = true }, # Only the BASIC attack shot goes through Horde's notify. { trigger = '[State] Hero_Piper\Ability Common\Event Attack Shoot Active', action = "Horde Basic Attack Shoot", exact = true, within = "Event Basic Attack Shoot Active", within_span = 300 },]
[[content]]kind = "skill"id = "horde_rats_text"hero = "Piper"mode = "relabel"source = "Skill_Trait_More_Controllable_Pets"description = "• For every #{1}@ notes played with #ATTACK@, summon &{0}~ rats that immediately attack nearby enemies\n• Rats spawn in groups of #{2}@ (instead of {3})\n• Increase maximum controllable rats by &{4}~"Unlock a LOCKED number
Section titled “Unlock a LOCKED number”talent_info.py marks some single numbers LOCKED: the game ignores them and
reads a per-rarity list instead. Usually the right move is to change that list
with union_patches. If you want one fixed number at every rarity instead, add
clear_override = true:
value_patches = [ { label = "Skill Special Marked Value", old = 10.0, new = 12.0, clear_override = true },]This cuts the link to the per-rarity list, so the talent stops scaling with rarity.
All advanced fields
Section titled “All advanced fields”Everything here goes inside a kind = "talent" block. A block that uses
rewires, clone_nodes, union_patches or int_patches needs a file = "..."
line that names exactly one hero file.
rewires
Section titled “rewires”| Field | Required | Meaning |
|---|---|---|
trigger |
yes | The link to change, matched against label text. Copy the written as: text. |
action |
yes | The new target: its label:, or just its name. For a clone, use the clone’s name. |
exact |
no | true matches the whole label. Without it, any label containing the text matches. |
within |
no | Only change links inside the part with this exact name. |
within_span |
no | How many bytes after that name to search. Default 2048. |
all |
no | true changes every match. Default: only the first. |
count |
no | Change this many matches. |
subtest_of |
no | Only change the links a named Tester checks. |
clone_nodes
Section titled “clone_nodes”| Field | Required | Meaning |
|---|---|---|
source |
yes | Exact name of the part to copy. |
name |
yes | A name no part has yet. |
retarget |
no | A list of { from, to }. from is a label written inside the source part; to is the new target’s label. |
rename |
no | A list of { from, to } whole pieces of text replaced inside the copy, such as a signal name or a text entry name. |
modifier_stat |
no | { hero, node }: make a copied Modifier change the same stat as that Modifier. |
Clones are built before every other change in the block, so a rewire in the same block can point at them.
Other fields
Section titled “Other fields”| Field | Meaning |
|---|---|
value_patches |
Single numbers (beginner tutorial). Add clear_override = true to unlock a LOCKED one. |
union_patches |
Per-rarity numbers (beginner tutorial). |
int_patches |
An older way to write whole numbers inside a part. Use union_patches instead. |
add_stats |
Give a talent a stat it does not have, with a number on its card (Recipe 4). |
rebuild |
Turn a talent’s own effect off, keeping its card: the start of a new talent (Recipe 4). |
Rules that save hours
Section titled “Rules that save hours”- Look before you rewire.
showthe part you want to change and the part you want to point at. Most mistakes are pointing at a part with a similar name. - Use
exactandwithinwhenever the label text could also appear inside a longer label. Recipe 3’s Short Wick mistake is the classic case. - Use
all = truefor Value Selectors and States that many parts share. A per-rarity list points at its controller once per rarity. - Shared parts are shared. If two talents use the same part, rewiring that part changes both talents. Clone it first and change the copy, as Recipe 5 does with Invasive Blast’s numbers.
- Clone small logic parts only: Selectors, Value Selectors, States, Testers, Counters, Modifiers, Event Senders, String Formats. Parts that own animations, visual effects or spawners contain pieces RSMM does not copy. Point at those instead of cloning them.
- Card numbers come from a String Format. To show a new number, clone a String
Format with enough gaps, retarget each gap,
renameits text entry to your talent’s_Desc, and rewire the controller’s[String Format]link to the copy. Keep the description’s{0}…{N}in the same order as the gaps. - Check the file, then check the game.
talent_graph.py … --mod <id>proves the link is in the file. Only playing with the talent owned proves it works.
Troubleshooting
Section titled “Troubleshooting”| Message | What it means | How to fix it |
|---|---|---|
no picker reference matching '...' |
No link has that label text. | Copy the written as: text from show, including the [Type] part. |
rewire '...' -> '...': already points there |
The link already goes to that target. | Nothing to do, or you picked the wrong trigger. |
rewires/int_patches need `file` to select exactly one entity file |
The block has no file line. |
Add file = "Hero_<Hero>.entity". |
a component named '...' already exists |
The clone’s name is taken. |
Pick a new name. |
expected exactly one component named '...', found 0 |
The clone’s source is misspelled. |
Copy the exact name from find. |
clone of '...': no reference labelled '...' |
A retarget from is not a link inside the source part. |
show the source part and copy a written as: text. |
It builds, but the game shows no change:
- Check with
talent_graph.py … --mod <id>that the link goes where you meant. - Make sure you own the talent (use the testing file) and are playing that hero.
- Make sure no other mod changes the same hero file.
- Make sure you changed the link the game actually uses. A hero often has similar
parts for different forms, such as Wukong’s
… Conditionand… Condition Yin.USED BYshows which ones are really connected.
