System for playing Rolemaster Standard System in Foundry Virtual Tabletop. Based on the original repository by Cynicide.
- Foundry VTT v13+
- socketlib (required module)
- Install socketlib from the module directory.
- Install RMSS as a system from the manifest URL or by copying the folder to
Data/systems/rmss.
- Character sheet (PC): stats, armor, weapons, skills, spells, inventory, experience.
- NPC sheet and creature sheet with attacks and attack tables.
- Races and professions as draggable items.
- Level-up system with skill development.
- Skill categories with configurable progression.
- Skills with ranks, category bonus, and designations (Occupational, Everyman, Restricted).
- Skill maneuvers with Static Maneuver table and unified penalties (hits taken, bleeding, stunned, wounds).
- Spell lists (open, closed, base) organized by realm (Essence, Channeling, Mentalism and Arcane).
- Force (F): Basic Spell Attack Table, RR per target, chat with targets and RR to beat.
- Base Elemental (BE): attack tables (bola, fire_ball, fire_bolt, etc.), attack confirmation to GM.
- Directed Elemental (DE): bolt tables, directed spell skill, flow similar to BE.
- Static Maneuver (E, P, U, I): static maneuver table.
- Casting options (hands, voice, preparation) and automatic penalties.
- Spell failure table on fumbles.
- Manual fumbles: the RMSS Effects popup (token HUD skull button) has a Fumble tab, open to every user, to roll a weapon fumble (pick one of the character's weapons or a weapon type) or a spell failure (spell family, severity, casting modifiers) on purpose, rolling the dice or typing the result. The card goes to the chat.
- Per-spell macros with access to
spellContext(RR and targets) for Force spells.
- Attack tables by weapon (melee, missile, bolt, ball).
- Critical resolution (slash, krush, puncture, heat, cold, electricity, etc.).
- Weapon Slaying property (multi-tag, e.g. "orc slayer"): when a weapon's Slaying tag(s) match a target npc/creature's Creature tags, the critical confirmation dialog auto-selects the Superlarge critical table and the Slaying column — regardless of the target's own Critical Table setting (Normal/Large/Superlarge).
- Automatic effects: Stunned, Bleeding, Penalty.
- Attack confirmation to GM (socket) for player attacks.
- Parry: a combatant reserves part of their OB to parry (combat tracker button, or
game.rmss.openParryDialog(actor)), always 1 OB = 1 DB. One-handed weapons can reserve up to 100 % of the OB; two-handed and pole weapons up to 50 %; creature attacks have their own limit (100 or 50 %); being Stunned caps it at 50 % too (the caps don't stack). The reserved OB comes off the combatant's attacks, and the defense is added automatically to the one frontal melee attack they suffer next (never missiles, thrown weapons or spells). The reservation is a standing stance: it keeps costing that OB every round and is re-armed when the combatant's turn starts, until they change or release it (a player can't lower or release it until they have attacked once with it on; the GM always can). So nobody forgets it, the owner gets a whispered card with a "Change or release" button when their turn starts, and the token carries a "Parry +N" shield icon (dimmed once the defense is spent). A "must parry" critical (Pwith rounds and an optional value) creates its own "Parry" effect: while any is active the victim must keep at least half their OB reserved (house rule: the manual only asks for 1 point, which makes the effect meaningless; it can't be released), and the values add up as a penalty on all their attacks; each effect expires on its own. - Parry safety switch (system setting "Parry rules"): Automatic (default), Manual (the reservation, tracker badge, token icon and cards work as a record, but the attack dialog is not pre-filled and "must parry" criticals force nothing and add no penalty) or Off (no parry features, and shields count regardless of facing, as before).
- Shield: its DB bonus only counts against frontal attacks; the attack confirmation takes it out of the defender's DB for flank and rear attacks (and follows the facing selector live).
- Initiative and combat tracker.
- Movement in combat: each combatant spends their remaining Movement Rate as the token moves, using the route cost Foundry measures. Draw a Region with the Increase Movement Cost behavior (set the multiplier for walking) over rough terrain and only the stretches that cross it cost more: 15 ft of open ground plus 10 ft at x2 spends 35 ft. A move that costs more than what is left is refused; pushes, teleports and undoing a move cost nothing.
- Weapons, armor, transport, herbs/poisons.
- Special ammunition (items tagged
arrow/bolt/bullet): besides the attack bonus, ammo can carry an extra critical (e.g. fire arrows), Increased Critical, bleeding, and the magical/holy/unholy/slaying properties, applied to the shot made with it. If the ammo has a macro it replaces the bow's macro for that shot; either macro now runs once the shot is confirmed (and receivesweapon,ammo,targetandtargetsbesides the usual variables). - Containers with capacity.
- Currency system (mithril, platinum, gold, silver, etc.).
- Languages: English and Spanish.
- Unit tests (Jest) with GitHub Actions pipeline.
- Embedded macros in items with integrated editor.
game.rmss exposes a small API for macros and scene triggers to talk to the system without
reaching into internal classes.
For traps and hazards driven by scene triggers (native Region Behavior "Execute Script",
Monk's Active Tiles, a macro...), game.rmss exposes two functions that reuse the normal
combat resolution (GM confirmation, chat cards, etc.) instead of duplicating it:
-
game.rmss.triggerTrapAttack(trapActorId, target, weaponItemId?)— full attack: rolls on the attack table and resolves the critical as usual (Slaying included).trapActorIdis a hidden npc/creature actor representing the trap; its attack is whatever it has equipped (aweaponitem) or, for acreatureactor, its naturalcreature_attack(no equipped flag needed).targetis the token that triggered the trap (a Token/TokenDocument, or its id as a string). PassweaponItemIdto pick a specific attack when the trap actor has more than one.// Region Behavior "Execute Script" (or Monk's Active Tiles), token = the token that entered await game.rmss.triggerTrapAttack("<trapActorId>", token);
-
game.rmss.triggerTrapCritical(target, { severity, critType, damage, modifier, trapActorId })— fires a critical directly, with no attack roll: rolls 1d100 on the given critical table and applies it (hit points, chat card, effects), the same as the GM's manual "Effects" button on the token HUD.severityis a letter A–E,critTypeany code fromCONFIG.rmss.criticalDictionary(S,K,P,U,G,T,heat,cold, ...). Does not support the large-creature subtypes (magic/mithril/holy/slaying) — usetriggerTrapAttackfor those.await game.rmss.triggerTrapCritical(token, { severity: "C", critType: "P", damage: 15 });
Both resolve the triggering token's actor and skip already-defeated targets; look up the
token variable your trigger module provides (e.g. Monk's Active Tiles exposes it as token
and inside args[0].token).
General-purpose helpers meant for macros (hotbar buttons, item macros, etc.):
-
game.rmss.getActorSex(actor)— normalized sex ("male"/"female"/"other") for any actor type (character, npc, creature). -
game.rmss.castSpellFromHotbar(actorId, itemId)— cast a spell from a hotbar macro (routes to the Force/BE/DE/instant service based on the spell, same as casting it from the character sheet). Use this instead of callingspell.use()directly so targeting context is set up correctly for Force spells.await game.rmss.castSpellFromHotbar(actor.id, "<spellItemId>");
-
game.rmss.createProfession(professionKey, options?)— create a profession item from the built-in profession data (options.openSheetto open its sheet after creating it). Returns the createdItem, ornullifprofessionKeyisn't recognized.await game.rmss.createProfession("Cleric");
-
game.rmss.createProfessionDialog()— same as above, but shows a dialog to pick the profession first. -
game.rmss.applySpellHealHits(options)— heal hit points on 1–maxTargetstargeted tokens as GM (for healing-spell macros).await game.rmss.applySpellHealHits({ amountPerTarget: 20, maxTargets: 1 });
-
game.rmss.findAmmoStacksOnActor(actorId, ammoTag)— ammo item stacks on an actor matching a tag fromCONFIG.rmss.ammunition_types(e.g."arrow") with quantity > 0. -
game.rmss.findAmmoStacksForWeapon(actorId, weaponItemId)— same, but resolved from a specific missile weapon's ownsystem.ammoTypeinstead of a tag you pick yourself. -
game.rmss.rollResistance({ target, attackerLevel, defenderLevel?, modifier? })— rolls a Resistance Roll againsttarget(a Token/TokenDocument, or its id) and returns{ success, finalRoll, rrTarget }immediately, so a spell/item macro can branch on the outcome.defenderLeveldefaults to the target's own actor level if omitted. Posts the same RR result card to chat as the Token HUD's Effects popup.for (const target of Array.from(game.user.targets)) { const { success } = await game.rmss.rollResistance({ target, attackerLevel: actor.system.attributes.level.value }); if (success) { /* target resisted */ } else { /* effect takes hold */ } }
-
game.rmss.scheduleDelayedAction({ combat?, roundsFromNow?, atRound?, command, context? })— schedulescommand(a JS code string, same as an item macro) to run automatically once combat reaches a future round — for spells with a casting delay (e.g. a 2-round summon: place a marker on round 1, the creature appears on its own when round 2 begins). Stored on the Combat document, so it survives a reload/disconnect and is cleaned up when the encounter ends.contextis a plain-data object passed intocommandas thecontextargument;game/canvas/ui/etc. are available as usual globals. Returns the pending action's id — pass it togame.rmss.cancelDelayedAction(id)if the caster gets interrupted first.await game.rmss.scheduleDelayedAction({ roundsFromNow: 1, context: { x: token.x, y: token.y, elementalActorId: "<id>" }, command: ` const elemental = game.actors.get(context.elementalActorId); const tokenDoc = await elemental.getTokenDocument({ x: context.x, y: context.y }); await canvas.scene.createEmbeddedDocuments("Token", [tokenDoc.toObject()]); ` });
The system can run a world macro when something happens in combat, without knowing anything about animations or modules. Set the macro's UUID (right-click the macro > Copy UUID) in the system settings; empty means nothing runs.
| Event | Setting | When |
|---|---|---|
| Missed melee attack | macroOnMeleeMiss |
A melee attack (not missile) deals no damage and no critical |
The macro runs on the GM's client with actor, token (attacker), target (defender), targets, weapon and event. These names are function parameters, so don't redeclare them with const/let (use typeof target !== "undefined" to fall back to game.user.targets when the macro is also run by hand).
npm install
npm test # Run tests
npm run test:watch- Cynicide — Original project
- Ruben Rey
- Marcos Sanchez