From 749d01663f7b5fae6929b97443ca2fac267a5745 Mon Sep 17 00:00:00 2001
From: Brett Nash
Date: Mon, 15 Sep 2025 22:17:04 -0700
Subject: [PATCH] RolemasterUnified: Token makers and Inventory updates
- First version of token markers
- Sheet options
- Random colours
- Trigger on status change.
- Fix stacking of popups on main page
- Add helper to get total number of bleeds
- Add options for token makers
- Add options for FX (doesn't do much)
- Export to NPC now grabs spell lists.
- Inventory (Stuff)
- Fix Helmet perception penalty trashing other modifiers
- Apply Armor DB
- Removed Armor DB & Perception penalties when you remove items
- Fix for armor losing it's encumberance.
- Add support for "Sets" Armor without a helmet
- Add armor material to edit window
- Correctly support mixed material encumberances (metal != non-metal)
---
.../rolemasterunified.html | 296 +++++++++++++++---
RolemasterUnified_Official/sheet.json | 33 +-
RolemasterUnified_Official/translation.json | 2 +
RolemasterUnified_Official/updates.md | 25 ++
4 files changed, 296 insertions(+), 60 deletions(-)
diff --git a/RolemasterUnified_Official/rolemasterunified.html b/RolemasterUnified_Official/rolemasterunified.html
index ab3f84ef66..1fdfb5a768 100644
--- a/RolemasterUnified_Official/rolemasterunified.html
+++ b/RolemasterUnified_Official/rolemasterunified.html
@@ -3399,7 +3399,7 @@ Lists Selected / 6
-
-
@@ -3457,7 +3457,7 @@ Lists Selected
/ 6
-
+
Actions
@@ -3565,9 +3565,9 @@ Lists Selected / 6
-
+
-
+
Status
@@ -3741,9 +3741,9 @@ Lists Selected / 6
Sleep
-
-
-
+
+
+
Attacks
@@ -3880,12 +3880,12 @@ Lists Selected / 6
-
+
-
+
Defense
@@ -3966,9 +3966,10 @@ Lists Selected / 6
Full Block (4 AP)
-
-
-
+
+
+
+
RRs
@@ -4010,11 +4011,11 @@ Lists Selected / 6
-
+
-
+
-
+
+
+
+
+
Talents
@@ -4053,7 +4057,7 @@ Lists Selected / 6
Add Talent
-
+
@@ -6234,13 +6238,14 @@ Uses (Lvl/Total) / Armor Location
Full
+ Set (All but Helmet)
Vambraces
Greaves
Torso
Helmet
Armor Type (AT)
-
+
1
2
3
@@ -6254,6 +6259,13 @@ Uses (Lvl/Total) / DB Bonus
+ Material
+
+ Cloth
+ Leather
+ Metal
+
+
Number of Attacks
@@ -6529,6 +6541,11 @@ This is not for usual use.
initiative
+
+
+
+Send to Chat
+
@@ -6542,7 +6559,7 @@ Sheet Version:
-Revision 52f9842a63e00550f91634a07b2ecb1db3a83f16
+Revision d1fa0cd182ccb30369359a6669cdec2e5abfc14d
@@ -7663,6 +7680,16 @@ function miscBonusRemove(miscstr, name) {
return JSON.stringify(misc);
}
+// Any item that matches the filter will be remmoebed
+function miscBonusRemoveFilter(miscstr, fn) {
+ if (typeof(miscstr) != 'string' || miscstr == '') {
+ return '';
+ }
+ let misc = JSON.parse(miscstr);
+ misc = misc.filter(fn);
+ return JSON.stringify(misc);
+}
+
function miscBonusTotal(miscstr, prefix = '') {
if (typeof(miscstr) != 'string' || miscstr == '') {
return [0, ""];
@@ -9713,18 +9740,21 @@ function getSubtlePenalty(realm, type, trickery, subtle, voice, hands, enc, meta
// Armor and Transcendance
console.log("Enc and metalenc", enc, metalenc, pentable, pentable.armorenc != pentable.metalenc);
- if (enc || metalenc) {
+ if ((enc || metalenc) && (pentable.armorenc || pentable.metalenc)) {
+ // Enc is all encumberance from armor, Metalenc is jsut hte metal bit.
RMUSkills.uses("transcendence");
let epen = 0;
- if (enc > 0 && pentable.armorenc) {
- epen += enc * pentable.armorenc;
- log += ` -${epen} [${enc} Enc% * ${pentable.armorenc}]`;
- }
- if (pentable.armorenc != pentable.metalenc) {
- // Channeling
- // FIXME: This is wrong for realsm were metal and non-metal are different but not zero
+ if ((pentable.armorenc == pentable.metalenc) || (enc == metalenc)) {
+ // Easy case: Metal and non metal the same - Essence basically
+ // Ignore metal as enc encompases both
+ epen += enc * pentable.metalenc;
+ log += ` -${epen} [${enc} Enc% * ${pentable.metalenc}]`;
+ } else {
+ let encdelta = enc - metalenc;
+ epen += encdelta * pentable.armorenc;
+ log += ` -${epen} [(${enc} - ${metalenc} Non-metal Enc% * ${pentable.armorenc}]`;
epen += metalenc * pentable.metalenc;
- log += ` -${epen} [${enc} Enc% * ${pentable.metalenc} (metal)]`;
+ log += ` -${epen} [${metalenc} Metal Enc% * ${pentable.metalenc}]`;
}
// Now apply transcendance
if (transcend && epen > 0) {
@@ -13734,6 +13764,7 @@ frontpage.updateDB = function () {
console.log("dooing update", ev);
var updates = {};
const db_extra = miscBonusTotal(ev.db_misc || "")[0];
+ console.log("db_extra", db_extra, ev.db_misc)
const dbmiscmod = parseIntDefault(ev.dbmod, 0);
const basedb = +ev.qu * 3 + db_extra + dbmiscmod;
updates.db_base = basedb;
@@ -14218,15 +14249,22 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
let percept = 0;
let rangedpenalty = 0;
let maneuver_penalty = 0;
- let toget = ['base_at_misc', 'perception_misc']
+ let toget = ['base_at_misc', 'perception_misc', 'db_misc'];
ids.forEach(id => {
toget.push(`repeating_stuff_${id}_itemname`);
toget.push(`repeating_stuff_${id}_itemequipped`);
toget.push(`repeating_stuff_${id}_itemsystem`);
});
+
+ let armordb = 0;
+ let armordbname = "None";
getAttrsPending(toget, (stuff) => {
- console.log("Fetch equipment", stuff);
let updates = {};
+ updates.db_misc = miscBonusRemoveFilter(stuff.db_misc, (item) => {
+ return item.name.startsWith("Armor DB Bonus");
+ });
+
+
let atinfo = miscBonusHighest(stuff.base_at_misc || "", 1);
// Set default AT to base_at everywhere.
updates.at_head = updates.at_torso = updates.at_legs =
@@ -14253,10 +14291,12 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
rmuerror("Parsing object JSON", e);
return;
}
+ console.log(json);
// Look for magic fields:
// - AT
// - attack table
if (json.AT) {
+ let db = parseIntDefault(json.DB, 0);
switch (json.Subtype) {
case 'Helmet':
updates.at_head = json.AT;
@@ -14265,6 +14305,10 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
case 'Torso':
updates.at_torso = json.AT;
updates.at_torso_info = name;
+ if (db && db > armordb) {
+ armordb = db;
+ armordbname = name;
+ }
break;
case 'Vambraces':
updates.at_arms = json.AT;
@@ -14273,12 +14317,26 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
case 'Greaves':
updates.at_legs = json.AT;
updates.at_legs_info = name;
- break
+ break;
+ case 'Set':
+ updates.at_torso = json.AT;
+ updates.at_legs = json.AT;
+ updates.at_arms = json.AT;
+ if (db && db > armordb) {
+ armordb = db;
+ armordbname = name;
+ }
+ updates.at_torso_info = updates.at_legs_info = updates.at_arms_info = name;
+ break;
default:
updates.at_head = json.AT;
updates.at_torso = json.AT;
updates.at_legs = json.AT;
updates.at_arms = json.AT;
+ if (db && db > armordb) {
+ armordb = db;
+ armordbname = name;
+ }
updates.at_head_info = updates.at_torso_info = updates.at_legs_info =
updates.at_arms_info = name;
}
@@ -14296,7 +14354,8 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
});
// Adjust due to perception penalty
- updates.perception_misc = miscBonusSet(updates.perception_misc,
+ updates.db_misc = miscBonusSet(stuff.db_misc, `Armor DB Bonus (${armordbname})`, armordb);
+ updates.perception_misc = miscBonusSet(stuff.perception_misc,
"Armor Perception Penalty", percept);
updates.perception_penalty = percept;
updates.ranged_penalty = rangedpenalty;
@@ -14305,6 +14364,7 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
console.log("equipment update", updates);
setAttrsPending(updates, () => {
RMUSkills.updateSingleCategory("Awareness");
+ frontpage.updateDB();
});
});
});
@@ -14563,7 +14623,7 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
"inventoryaddname", "inventoryaddweight", "inventoryaddnotes",
"inventoryaddtype",
"inventoryaddarmorsubtype", "inventoryaddarmorat",
- "inventoryaddarmordbbonus",
+ "inventoryaddarmordbbonus", "inventoryaddarmormaterial",
"inventoryaddshieldattacks", "inventoryaddshielddb",
"inventoryaddweaponmaterialc", "inventoryaddweaponmaterialw",
"inventoryaddweaponmateriali", "inventoryaddweaponmaterialm",
@@ -14594,6 +14654,9 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
if (db != 0) {
system.DB = ia.inventoryaddarmordbbonus;
}
+ // If Armor need to make a %
+ update.weight = `${update.weight}%`;
+ update.itemmaterial = ia.inventoryaddarmormaterial;
} else if (update.type == 'Shield') {
system['Attacks Blocked'] = ia.inventoryaddshieldattacks;
system['DB Bonus'] = ia.inventoryaddshielddb;
@@ -14687,6 +14750,7 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
if (sys.AT) {
update.inventoryaddarmorat = sys.AT;
}
+ update.inventoryaddarmormaterial = item[`${basename}_itemmaterial`];
update.inventoryaddarmordbbonus = parseIntDefault(sys.DB, 0);
}
{//
@@ -14696,6 +14760,7 @@ RMUInventory.itemfields = [ "itemname", "itemweight", "itemcount", "itemsystem",
if (sys['DB Bonus']) {
update.inventoryaddshielddb = sys['DB Bonus'];
}
+ update.inventoryaddarmormaterial = item[`${basename}_itemmaterial`];
}
// Ideally this should be the same as the above function
@@ -16750,6 +16815,7 @@ onCheck("clicked:newcombat", () => {
sendMessage(`${ev.character_name} prepares for battle`);
});
actionsUpdateStatus();
+ updatetokenmarkers();
});
// So this is our per AP stuff
@@ -16763,6 +16829,7 @@ onCheck("clicked:deletebleed", () => {
getAttrs(["character_name"], (x) => {
sendMessage(`${x.character_name} is no longer bleeding`);
setAttrs({ bleeding: 0, bleedlist: []});
+ updatetokenmarkers();
});
});
@@ -16770,6 +16837,7 @@ onCheck("clicked:statusstun75delete", () => {
getAttrs(["character_name"], (x) => {
sendMessage(`${x.character_name} has stun-75 lifted.`);
setAttrs({ statusstun75: 0 }, actionsUpdateStatus);
+ updatetokenmarkers();
});
});
@@ -16777,6 +16845,7 @@ onCheck("clicked:statusstun50delete", () => {
getAttrs(["character_name"], (x) => {
sendMessage(`${x.character_name} has stun-50 lifted.`);
setAttrs({ statusstun50: 0 }, actionsUpdateStatus);
+ updatetokenmarkers();
});
});
@@ -16784,6 +16853,7 @@ onCheck("clicked:statusstun25delete", () => {
getAttrs(["character_name"], (x) => {
sendMessage(`${x.character_name} has stun-25 lifted.`);
setAttrs({ statusstun25: 0 }, actionsUpdateStatus);
+ updatetokenmarkers();
});
});
@@ -16801,7 +16871,7 @@ function handleFreeClicked() {
function handleBleed(currentphase, bleedlist) {
// Bleedlist is probably a string.
if (!bleedlist || typeof(bleedlist) != 'string') {
- return;
+ return 0;
}
// Shift current phase to 1..4
@@ -16823,6 +16893,22 @@ function handleBleed(currentphase, bleedlist) {
return total;
}
+function totalBleed(bleedlist) {
+ // Bleedlist is probably a string.
+ if (!bleedlist || typeof(bleedlist) != 'string') {
+ return 0;
+ }
+
+ const bleeds = JSON.parse(bleedlist);
+ let total = 0;
+
+ for (let bleed of bleeds) {
+ total += bleed.amount;
+ }
+
+ return total;
+}
+
/**
* Generic handler for the status list updates. Called once per phase.
*
@@ -16910,7 +16996,7 @@ function actionsUpdateStatus() {
updates.statuspercent = Math.max(100 + updates.allpenalties, 0);
updates.statuspercent200 = Math.max(200 + updates.allpenalties, 0);
- VrmuSetAttrs(updates)
+ rmuSetAttrs(updates);
});
}
@@ -17020,6 +17106,7 @@ function doTurn() {
VrmuSetAttrs(updates, ()=> {
sendEOTMessage(`${name} completes their turn (Round ${round} / Phase ${relativephase})`);
actionsUpdateStatus();
+ updatetokenmarkers();
});
});
}
@@ -17144,14 +17231,6 @@ onCheck("change:bodydevelopment_bonus change:powerdevelopment_bonus", (x) => {
pp = 0;
}
update.pp_max = pp
- /*
- update.bar1_max = ev.bodydevelopment_bonus;
- update.bar1_value = ev.hp;
- update.bar2_max = ev.powerdevelopment_bonus;
- update.bar2_value = ev.pp;*/
-// if (!ev.hasOwnProperty(hp)) {
-// update.hp = ev.bodydevelopment_bonus;
-// }
setAttrs(update, { silent:true });
});
});
@@ -17162,7 +17241,6 @@ onCheck("change:bodydevelopment_bonus change:endurance_misc change:mentalfocus_b
});
function updateEnduranceBonus() {
- console.log("updateEnduranceBonus");
getAttrs(['bodydevelopment_bonus', 'endurance_misc', 'mentalfocus_bonus'], (ev) => {
let log = "Endurance\n" +
`${F(ev.bodydevelopment_bonus)} Body Development\n`;
@@ -17203,7 +17281,6 @@ onCheck("change:hp", (_) => {
} else {
update.hppenalty = -100;
}
- update.bar1_value = values.hp; // FIXME: this doesn't do much
update.hp_percent = hppercent;
setAttrs(update, { slient: true});
});
@@ -17418,6 +17495,68 @@ function doRollInitiative() {
});
}
+/**/
+function updatetokenmarkers() {
+ console.log("updatetokenmarkers start");
+ getAttrs(['option_usetokenmarkers', 'sheetselect', 'character_name',
+ // Stuns & staggered
+ 'statusstagger', 'statusstun75', 'statusstun50', 'statusstun25',
+ 'bleedlist', 'grapplestatus'], (info) => {
+ console.log("Update token makers", info);
+ if (!info.option_usetokenmarkers || info.option_usetokenmarkers == 'no') {
+ return;
+ }
+ if (info.option_usetokenmarkers == 'pc' && info.sheettype != 'playersheet') {
+ return;
+ }
+ if (info.option_usetokenmarkers == 'npc' && info.sheettype != 'creaturesheet') {
+ return;
+ }
+ let statusstagger = parseIntDefault(info.statusstagger, 0);
+ let statusstun75 = parseIntDefault(info.statusstun75, 0);
+ let statusstun50 = parseIntDefault(info.statusstun50, 0);
+ let statusstun25 = parseIntDefault(info.statusstun25, 0);
+ let grapplestatus = parseIntDefault(info.statusstun25, 0);
+
+ let tokens = []
+
+ let bleeds = totalBleed(info.bleedlist);
+ if (bleeds > 9) bleeds = 9;
+ tokens.push((bleeds > 0) ? `red:${bleeds}` : "!red");
+
+ tokens.push((statusstagger > 0) ? "pink" : "!pink");
+ if (statusstun75 > 0) {
+ let rounds = Math.floor(statusstun75 / 4);
+ tokens.push(`blue:${rounds}`);
+ } else {
+ tokens.push("!blue");
+ }
+
+ if (statusstun50 > 0) {
+ let rounds = Math.floor(statusstun50 / 4);
+ tokens.push(`green:${rounds}`);
+ } else {
+ tokens.push("!green");
+ }
+ if (statusstun25 > 0) {
+ let rounds = Math.floor(statusstun25 / 4);
+ tokens.push(`purple:${rounds}`);
+ } else {
+ tokens.push("!purple");
+ }
+
+ let tstr = tokens.join('|')
+
+ // Join token string. with --set st
+ let message = `!token-mod --ids @{${info.character_name}|character_id} --set statusmarkers|${tstr}`
+ console.log(message)
+ startRoll(message, (t) => finishRoll(t.rollId, t));
+ });
+}
+
+
+
+
@@ -18148,13 +18287,47 @@ function doNpcGeneate() {
});
});
+ // move
+ out['data-Spelllists'] = []
+ addPendingFunction("NPC Gen: Save Spells Base", () => {
+ let spellsToGet = []
+ let prefixs = [];
+ // Messing with the indentation here for sanity
+ getSectionIDsPending("repeating_spelllistspellownbase", (ids1) => {
+ let p1 = ids1.map((id) => "repeating_spelllistspellownbase_" + id);
+ getSectionIDsPending("repeating_spelllistspellopen", (ids2) => {
+ let p2 = ids2.map((id) => "repeating_spelllistspellopen_" + id);
+ getSectionIDsPending("repeating_spelllistspellclosed", (ids3) => {
+ let p3 = ids3.map((id) => "repeating_spelllistspellclosed_" + id);
+ let prefixes = p1.concat(p2, p3);
+ for (prefix of prefixes) {
+ spellsToGet.push(
+ `${prefix}_listname`,
+ `${prefix}_ranks`,
+ `${prefix}_scr`,
+ );
+ }
+ getAttrsPending(spellsToGet, (spells) => {
+ for (prefix of prefixes) {
+ let ranks = parseIntDefault(spells[`${prefix}_ranks`],0);
+ if (!ranks || ranks === 0) { continue; }
+ let name = spells[`${prefix}_listname`];
+ let scr = spells[`${prefix}_scr`];
+ out['data-Spelllists'].push({
+ name: name,
+ scr: scr,
+ ranks: ranks });
+ }
+ });
+
+ }); }); }); // Nest getSectionIds above
+ });
+
+
addPendingFunction("NPC Gen: Save NPC String", () => {
setAttrs({npcstring: JSON.stringify(out)})
});
- // defenses
- // move
- // spells
checkPending();
}
@@ -18495,7 +18668,10 @@ injury.parseInjuryString = function (str) {
}
// Update the status as soon as the injury is applied.
- VrmuSetAttrs(updates, actionsUpdateStatus);
+ VrmuSetAttrs(updates, () => {
+ actionsUpdateStatus();
+ updatetokenmarkers();
+ });
});
}
@@ -19292,6 +19468,22 @@ function dX(x) {
return Math.floor(Math.random() * x) + 1;
}
+
+// Hacky send to chat
+onCheck("clicked:sendtochat", () => {
+ console.log("Send to chat clicked");
+ getAttrs(["chatcommand"], (ev) => {
+ console.log("command", ev);
+ //macroscript = "!token-mod --set statusmarkers|red";
+ startRoll(ev.chatcommand, (results2) => {
+ console.log(results2);
+ // Holding the computed data in an object is a bit cleaner if your rolls get more complex
+ let rollData2 = results2.results;
+ finishRoll(results2.rollId, rollData2);
+ });
+ });
+});
+
diff --git a/RolemasterUnified_Official/sheet.json b/RolemasterUnified_Official/sheet.json
index 42c5d9e983..108737eb5e 100644
--- a/RolemasterUnified_Official/sheet.json
+++ b/RolemasterUnified_Official/sheet.json
@@ -10,14 +10,31 @@
"compendium": "RMU",
"useroptions": [
{
- "attribute": "default_currency",
- "displayname": "Currency for the Game",
- "displaytranslationkey": "defaultcurrency",
- "type": "text",
- "default": "GP(Gold Pieces)=10SP;SP(Silver Pieces)*=10BP;BP(Bronze Pieces)=10CP;CP(Copper Pieces)",
- "description": "Custom currency description",
- "descriptiontranslationkey": "DefaultCurrency"
+ "attribute": "option_usetokenmarkers",
+ "displayname": "Use Token Markeres",
+ "displaytranslationkey": "option_usetokenmarkers",
+ "type": "select",
+ "options": [
+ "no|No token marker updates",
+ "pc|Token Markers on PC sheets",
+ "npc|Token Markers on NPC/Creature sheets",
+ "all|Token Markers on all sheets"
+ ],
+ "default": "no",
+ "description": "Show token makers for character state on the token. Covers stun, bleed and similar states. Requires Token Mod to be installed (and hence a Pro account)."
+ },
+ {
+ "attribute": "option_usefx",
+ "displayname": "Use FX",
+ "displaytranslationkey": "option_usefx",
+ "type": "select",
+ "options": [
+ "no|No FX",
+ "all|All Supported FX"
+ ],
+ "default": "all",
+ "description": "Show FX when available based on the sheets guess of the attack"
}
],
- "version": "1757570905"
+ "version": "1757999780"
}
diff --git a/RolemasterUnified_Official/translation.json b/RolemasterUnified_Official/translation.json
index c7a40725f7..96fe26abae 100644
--- a/RolemasterUnified_Official/translation.json
+++ b/RolemasterUnified_Official/translation.json
@@ -168,6 +168,8 @@
"noprofession": "No Profession",
"open": "Open",
"operation": "Operation",
+ "option_usefx": "Use FX",
+ "option_usetokenmarkers": "Use Token Markers",
"paladin": "Paladin",
"perception": "Perception",
"performanceart": "Performance Art",
diff --git a/RolemasterUnified_Official/updates.md b/RolemasterUnified_Official/updates.md
index bee492884f..a499235c21 100644
--- a/RolemasterUnified_Official/updates.md
+++ b/RolemasterUnified_Official/updates.md
@@ -1,3 +1,28 @@
+# 2025-09-16
+
+- First version of token markers
+ - Sheet options
+ - Random colours
+ - Trigger on status change.
+- Fix stacking of popups on main page
+- Add helper to get total number of bleeds
+- Add options for token makers
+- Add options for FX (doesn't do much)
+- Export to NPC now grabs spell lists.
+- Inventory (Stuff)
+ - Fix Helmet perception penalty trashing other modifiers
+ - Apply Armor DB
+ - Removed Armor DB & Perception penalties when you remove items
+ - Fix for armor losing it's encumberance.
+ - Add support for "Sets" Armor without a helmet
+ - Add armor material to edit window
+ - Correctly support mixed material encumberances (metal != non-metal)
+
+# 2025-09-11
+
+- Try and get the info popups working again.
+- fix spurious --> from the rolltemplates page
+
# 2025-09-09
- Can now roll weapon fumbles direct from the sheet.