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
-
+

Statistics

y @@ -3442,13 +3442,13 @@ Lists Selected / 6
-
+
-
+
Power LevelProfessionRaceCultureLevelEPFate PointsRealmReligionAgeSkinEyesHairGenderBuildHeightWeightyWeight allowance CarriedLoad (%)Max PaceEnc PenaltyyBase Move (BMR) SizeRecovery MultiplierFeats of StrengthRoutineEasyLightMediumHardVery HardExt. HardSheer FollyAbsurdNigh Impossible -
+
@@ -3457,7 +3457,7 @@ Lists Selected / 6
-
+

Actions

@@ -3565,9 +3565,9 @@ Lists Selected / 6 -
+
-
+

Status

@@ -3741,9 +3741,9 @@ Lists Selected / 6 -
- -
+
+ +

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 -
+
-
+

Favorite Skills

@@ -4036,8 +4037,11 @@ Lists Selected / 6
-
-
+
+ +
+ +

Talents

@@ -4053,7 +4057,7 @@ Lists Selected / 6 -
+
@@ -6234,13 +6238,14 @@ Uses (Lvl/Total) / Armor Location Armor Type (AT) - @@ -6254,6 +6259,13 @@ Uses (Lvl/Total) / DB Bonus + Material + + Number of Attacks @@ -6529,6 +6541,11 @@ This is not for usual use.

+
+ + + +
@@ -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.