From c88a40be4a380bae4141eb79018782572f2751e4 Mon Sep 17 00:00:00 2001 From: Brett Nash Date: Mon, 3 Feb 2025 22:16:29 -0800 Subject: [PATCH] RolemasterUnified Official: Updates for creatures. So this is a change in the way the system parses creatures and attacks. Now we parse (don't implement all; but parse) all attacks in the system. - Creatures: Validate attacks for all listed attacks. - Add attack types for all known attacks - Correctly adjust attack size - Change display of attack size for creatures. - Add meditation to character mancer skills. --- .../rolemasterunified.css | 25 +- .../rolemasterunified.html | 404 +++++++++++------- RolemasterUnified_Official/sheet.json | 2 +- RolemasterUnified_Official/updates.md | 9 +- 4 files changed, 280 insertions(+), 160 deletions(-) diff --git a/RolemasterUnified_Official/rolemasterunified.css b/RolemasterUnified_Official/rolemasterunified.css index 4fa5485c28..30ed73bbb3 100644 --- a/RolemasterUnified_Official/rolemasterunified.css +++ b/RolemasterUnified_Official/rolemasterunified.css @@ -83,6 +83,9 @@ h3 { height: 1em; } +.textnowrap { + white-space:nowrap +} .creaturesheet { @@ -215,6 +218,7 @@ input:not(:checked).toggle + .toggler { display: none; } column-count: auto; column-width: 18em; column-gap: 1em; + padding-bottom: 10em; } @@ -381,9 +385,9 @@ input:not(:checked).toggle + .toggler { display: none; } border-radius: 255px 15px 225px 15px/15px 225px 15px 255px; text-align: left; - background-size: 20px 20px; - background-image: linear-gradient(to right, rgba(137,207,240,1.0) 1px, transparent 1px), - linear-gradient(to bottom, rgba(137,207,240,1.0) 1px, white 1px); + background-image: linear-gradient(to bottom, var(--fancycomplimentlight), var(--fancycompliment)); + /* background-image: linear-gradient(to right, rgba(137,207,240,1.0) 1px, transparent 1px), + linear-gradient(to bottom, rgba(137,207,240,1.0) 1px, white 1px);*/ } /*Popup on the right */ @@ -400,9 +404,7 @@ input:not(:checked).toggle + .toggler { display: none; } width: fit-content; border-radius: 255px 15px 225px 15px/15px 225px 15px 255px; - background-size: 20px 20px; - background-image: linear-gradient(to right, rgba(137,207,240,1.0) 1px, transparent 1px), - linear-gradient(to bottom, rgba(137,207,240,1.0) 1px, white 1px); + background-image: linear-gradient(to bottom, var(--fancycomplimentlight), var(--fancycompliment)); } .icon:hover + .stat_info, @@ -1215,14 +1217,15 @@ div.attacks { .creaturestats { - width: 8em; - display: grid; - grid-template-columns: 1fr 1fr; - float: left; - padding: 0.5em; border-radius: 6px; margin: 1em; box-shadow: rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.07) 0 2px 2px, rgba(0, 0, 0, 0.07) 0 4px 4px, rgba(0, 0, 0, 0.07) 0 6px 6px; ; background-image: linear-gradient(to bottom, var(--fancycomplimentlight), var(--fancycompliment)); + display: grid; +} + +.creaturegrid5 { + display: grid; + grid-template-columns: repeat(5, 1fr); } .creaturedefenses { diff --git a/RolemasterUnified_Official/rolemasterunified.html b/RolemasterUnified_Official/rolemasterunified.html index 4033833580..df46cd62f5 100644 --- a/RolemasterUnified_Official/rolemasterunified.html +++ b/RolemasterUnified_Official/rolemasterunified.html @@ -978,6 +978,8 @@ Lists Selected / 6
To translatte
+
To translatte
+
To translatte
To translatte
@@ -2121,16 +2123,10 @@ Lists Selected / 6
Stats - Ag X -Co X -Em X -In X -Me X -Pr X -Qu X -Re X -SD X -St X + +
+ AgCoEmInMe00000PrQuReSDSt00000 +
@@ -2243,13 +2239,20 @@ Lists Selected / 6
Attacks +
- + + + + + + - +
@@ -2428,18 +2431,28 @@ Lists Selected / 6
Skills
- - , + + + +
+
+ Talents +
+ + +
+
+
Injuries @@ -2458,8 +2471,9 @@ Lists Selected / 6 Spells
- + @@ -3147,8 +3161,8 @@ Lists Selected / 6

Skills

-
-
+
+
ag + em
@@ -4576,8 +4590,11 @@ Lists Selected / 6
-
+
+ + +
@@ -5378,7 +5395,7 @@ Sheet Version:
-Revision 745ecfc9098ac3471d2a7ad477c002fc177ff7d9 +Revision d70a48ea56503a8bde45a7168ab22d1b5d000ffc
@@ -12846,7 +12863,10 @@ function sizeToNumeric(code) { case 'IX': return 9; case 'X': return 10; case "Mi": return 1; + case "MI": return 1; case "Di": return 2; + case "DI": return 2; + case "D": return 2; case "T": return 3; case "S": return 4; case "M": return 5; @@ -12855,6 +12875,7 @@ function sizeToNumeric(code) { case "H": return 8; case "G": return 9; case "E": return 10; +// case "O: } rmuerror("Unknown size: ", code); return 5; @@ -14412,6 +14433,7 @@ creature = {} creature.getLongSize = function(shortname) { switch (shortname) { case "Mi": return "Miniscule"; + case "MI": return "Miniscule"; case "Di": return "Diminutive"; case "D": return "Diminutive"; case "T": return "Tiny"; @@ -14422,96 +14444,105 @@ creature.getLongSize = function(shortname) { case "H": return "Huge"; case "G": return "Gigantic"; case "E": return "Enormous"; + case "I": return "Immense"; + case "O": return "Behomoth"; + case "V": return "Leviathan"; } console.log("Unknown size ", shortname); - return "" + return "??" +} + +creature.getLongDescriptionFromNumber = function(numeric) { + switch (numeric) { + case 1: return "Miniscule"; + case 2: return "Diminutive"; + case 3: return "Tiny"; + case 4: return "Small"; + case 5: return "Medium"; + case 6: return "Big"; + case 7: return "Large"; + case 8: return "Huge"; + case 9: return "Gigantic"; + case 10: return "Enormous"; + case 11: return "Immense"; + case 12: return "Behomoth"; + case 13: return "Leviathan"; + } + console.log("Unknown size ", numeric); + return "??" } // Sorted alphabetically // Weapon (we) and ranged weapon are special (rw) // Require table and size for each. -creature.weaponcodes = { - be: { table: "Beak", size: 0, type: "melee" }, - bi: { table: "Bite", size: 0, type: "melee" }, - cl: { table: "Claw", size: 0, type: "melee" }, - cr: { table: "Crush", size: 0, type: "melee" }, - gr: { table: "Grapple", size: 0, type: "melee" }, - ho: { table: "Horn", size: 0, type: "melee" }, - ra: { table: "Ram", size: 0, type: "melee" }, - sw: { table: "Unarmed Sweeps", size: 0, type: "melee" }, - un: { table: "Unarmed Strike", size: 0, type: "melee" }, - si: { table: "Stinger", size: 0, type: "melee" }, - tr: { table: "Trample", size: 0, type: "melee" }, - // FXIME: Hacky - we: { table: "Arming Sword", size: 0, type: "melee" }, - da: { table: "Dagger", size: 0, type: "melee" }, - rp: { table: "Rapier", size: 0, type: "melee" }, - as: { table: "Arming Sword", size: 0, type: "melee" }, - bs: { table: "Broadsword", size: 0, type: "melee" }, - sc: { table: "Scimitar", size: 0, type: "melee" }, - fa: { table: "Falchion", size: 0, type: "melee" }, - ma: { table: "Mace", size: 0, type: "melee" }, - cu: { table: "Club", size: 0, type: "melee" }, - lc: { table: "Club", size: 1, type: "melee" }, - sb: { table: "Bow, Short", size: 0, type: "ranged" }, - lb: { table: "Bow, Long", size: 0, type: "ranged", }, - ss: { table: "Arming Sword", size: -1, type: "melee" }, - ls: { table: "Arming Sword", size: 1, type: "melee" }, - fa: { table: "Falchion", size: 0, type: "melee" }, +creature.weaponcodes = + { + "be": { "table": "Beak", "size": 0, "type": "melee" }, + "bi": { "table": "Bite", "size": 0, "type": "melee" }, + "bx": { "display": "Battle Axe", "size": 0, "type": "melee", "table": "Battle Axe" }, + "cba": { "table": "Ball, Cold", "display": "Cold Ball", "size": 0, "type": "ranged" }, + "cl": { "table": "Claw", "size": 0, "type": "melee" }, + "cr": { "table": "Crush", "size": 0, "type": "melee" }, + "ebo": { "table": "Bolt, Earth", "size": 0, "type": "directed", "display" : "Earth Ball" }, + "fba": { "table": "Ball, Fire", "size": 0, "type": "ball", "display": "Fire ball" }, + "fbo": { "table": "Bolt, Fire", "size": 0, "type": "directed", "display": "Fire Bolt" }, + "gr": { "table": "Grapple", "size": 0, "type": "melee" }, + "ho": { "table": "Horn", "size": 0, "type": "melee" }, + "ibo": { "display": "Ice bolt", "table": "Bolt, Ice", "size": 0, "type": "ranged" }, + "lba": { "display": "Lightning Ball", "table": "Ball, Lightning", "size": 0, "type": "ball" }, + "lbo": { "table": "Bolt, Lightning", "size": 0, "type": "ranged", "display": "Ligtning Bolt" }, + "px": { "display": "Pole Axe", "size" : 1, "table": "War Hammer", "type": "melee" }, + "qs": { "display": "Quarterstaff", "table": "Fighting Stick", "size": 1, "type": "melee" }, + "ra": { "table": "Ram", "size": 0, "type": "melee" }, + "rt" : { "display": "Thrown Rock", "type": "ranged", "size": 0, "table": "Rock" }, + "ro" : { "display": "Thrown Rock", "type": "ranged", "size": 0, "table": "Rock" }, + "sp": { "display": "Spear", "size":0, "table": "Spear", "type" : "melee" }, + "sw": { "table": "Unarmed Sweeps", "size": 0, "type": "melee", "display": "Sweep" }, + "ta": { "table": "Battle Axe", "size": -1, "type" : "ranged", "display": "Thrown Hand Axe" }, + "td": { "table": "Dagger", "size": 0, "type" : "ranged", "display": "Thrown Dagger" }, + "tc": { "table": "Club", "size": 1, "type" : "melee", "display": "Two Handed Club" }, + "ts": { "table": "Club", "size": 1, "type" : "melee", "display": "Two Handed Club" }, + "tp": { "display": "Thrown Spear", "size":0, "table": "Spear", "type" : "ranged" }, + "tx": { "display": "Two Handed Axe", "size": 1, "type": "melee", "table": "Battle Axe" }, + "wh": { "table": "War Hammer", "size": 0, "type": "ranged", "display": "Thrown War Hammer" }, + "un": { "table": "Unarmed Strike", "size": 0, "type": "melee" }, + "wbo": { "table": "Bolt, Water", "size": 0, "type": "ranged", "display": "Water Bolt" }, + "tw": { "table": "War Hammer", "size": 0, "type": "melee", "display": "War Hammer" }, + + "si": { "table": "Stinger", "size": 0, "type": "melee" }, + "tr": { "table": "Trample", "size": 0, "type": "melee" }, + "da": { "table": "Dagger", "size": 0, "type": "melee" }, + "rp": { "table": "Rapier", "size": 0, "type": "melee" }, + "as": { "table": "Arming Sword", "size": 0, "type": "melee" }, + "bs": { "table": "Broadsword", "size": 0, "type": "melee" }, + "sc": { "table": "Scimitar", "size": 0, "type": "melee" }, + "fa": { "table": "Falchion", "size": 0, "type": "melee" }, + "ma": { "table": "Mace", "size": 0, "type": "melee" }, + "cu": { "table": "Club", "size": 0, "type": "melee" }, + "sb": { "table": "Bow, Short", "size": 0, "type": "ranged" }, + "lb": { "table": "Bow, Long", "size": 0, "type": "ranged" }, + "ss": { "display": "Short Sword", "table": "Arming Sword", "size": -1, "type": "melee" }, + "ls": { "table": "Arming Sword", "size": 1, "type": "melee" }, + "wi": { "table": "Whip", "size": 0, "type": "melee" }, + + "lc": { "table": "Crossbow", "size": 0, "type": "ranged", "display": "Light Crossbox" }, + "hc": { "table": "Crossbow", "size": 1, "type": "ranged", "display": "Heavy Crossbow" }, + "hx": { "table": "Crossbow", "size": -1, "type": "ranged", "display" : "Hand Crossbow" }, + + + "we": { "table": "Arming Sword", "size": 0, "type": "melee" }, + "any": { "table": "Arming Sword", "size": 0, "type": "melee" }, + "all": { "table": "Arming Sword", "size": 0, "type": "melee" }, + "rw": { "table": "Arming Sword", "size": 0, "type": "melee" } +} + + - lc: { table: "Crossbow", size: 0, type: "ranged" }, - hc: { table: "Crossbow", size: 1, type: "ranged" }, - hx: { table: "Crossbow", size: -1, type: "ranged" }, - -/* -Two-handed Sword ts -Two-handed Club tc -Shield sh -Quarterstaff qs -War Hammer wh -War Mattock wm -Hand Axe hx -Battle Axe bx -Two-handed Axe tx -Flail fl -Heavy Flail hf -Whip wi -Spear sp -Halberd ha -Pole Axe px -Sling sl -Bola bo -Javelin ja -Dagger (Thrown) td -Hand Axe (Thrown) ta -Rock, one handed ro -Rock, Two handed rt -Rock, medium rm -Rock, large rl -Rock, huge rh -Spear (Thrown) tp -War Hammer (Thrown) tw -Elemental bolt elb -Blast bolt bbo -Earth bolt ebo -Fire Bolt fbo -Ice Bolt ibo -Water Bolt wbo -Lightning Bolt lbo -Blast ball bba -Cold Ball cba -Earth Ball -Shard ball sba -Fire Ball fba -Lightning Ball lba -Tongue wi -*/ -}; /* if the data comes from roll20 sub arrays may be json, or they may be strings. This is obviously stupid... but still. - So take a look, it's string, parse it as JSON. If an object, + So take a look, it's string, parse im as JSON. If an object, just return directly. */ creature.parseJSONMaybe = function(jdata, def) { @@ -14524,6 +14555,101 @@ creature.parseJSONMaybe = function(jdata, def) { } } +/** + * Returns an Ovject or nul + * Nil if if does not match. + * Object has: + * - OB: The OB natch + * - size: Size of the attack as a Letter. + * - sizen: Size of the attack as number + * - type: Attack type: two or three letter code or "any" + * - nextound: true (or not present) if attack has one to follow next round + * - bonus: Attack happens on a bonus on critical + * - crit: Additional Crit type / Not implemented + * - extra: Currently just anything else + */ +creature.parseAttackString = function(string) { + // The > sometimes is turned in > + string = string.replace(">", ">") + + // 1.OB 2.Size 3.sizen 4.Attack type 5 > 6 >> 7. extra + let match = string.match(/^(-?\d+)([A-Za][Ii]?)\((\d*)\)([a-z]*)(?:\[(\w*)])?(>)?(≫)?(?:\(.*\))?$/); + if (match) { + return { + OB: match[1], + size: match[2], + sizen: parseInt(match[3]), + type: match[4], + nextround: match[5] ? true : false, + bonus: match[6] ? true : false, + extra: match[7] + } + } + + //--> 28G(9)any 2 melee(2WF) + // 1,OB 2, Size 3.n 4. any melee ?? >/>> + match = string.match(/^(-?\d+)([A-Za][Ii]?)(\(\d*\))(?:any|all).*melee(?:\(.*\))?$/); + if (match) { + return { + OB: match[1], + size: match[2], + sizen: parseInt(match[3]), + type: match[4], + nextround: false, + bonus: false, + extra: "" + } + } + + return null; +} + +/* */ +creature.parseAttack = function(attack, cdata, updates) { + const info = creature.parseAttackString(attack.attack); + if (!info) { + rmuerror("Unable to match attack", attack.attack); + return; + } + const ob = info.OB; + const size = info.size; + const type = info.type; + console.log("Attack", info, ob, size, type); + const rowid = generateRowID(); + const prefix = `repeating_creatureattack_${rowid}`; + + // + const wc = creature.weaponcodes[type]; + let attacksize = info.sizen + wc.size; + { + updates[`${prefix}_attacksize`] = attacksize; + updates[`${prefix}_attacksizedisplay`] = creature.getLongDescriptionFromNumber(attacksize); + } + + updates[`${prefix}_attack`] = attack.attack; + updates[`${prefix}_ob`] = ob; + updates[`${prefix}_attackersize`] = cdata.size; + updates[`${prefix}_attackname`] = wc.display || wc.table; + + let table = wc.table; + getCompendiumPage(`AttackTable:${table}`, cdata => { + let nu = {}; + // FIXME: Handle errors in size slookup gracefully. + nu[`${prefix}_attackbasefumble`] = 1; // FIXMEitems.attackaddfumble; + nu[`${prefix}_attacktable`] = table; + nu[`${prefix}_attackstr`] = 100; + nu[`${prefix}_attacksource`] = 'creature'; + nu[`${prefix}_attackskill`] = ob; + nu[`${prefix}_attackri`] = 10; + + nu[`${prefix}_attackmin`] = cdata.data.min.match(/\d+/)[0] || 0; + nu[`${prefix}_attackmax`] = cdata.data.max.match(/\d+/)[0] || 0; + nu[`${prefix}_attackdata`] = cdata.data['data-attack'] || ""; + VrmuSetAttrs(nu); + }); + + updates[`${prefix}_type`] = type; +} creature.newCreature = function(name, jdata) { @@ -14595,45 +14721,7 @@ creature.newCreature = function(name, jdata) { } console.log(attacks); for (attack of attacks) { - // FIXME: Not dealign with additiona criiticals here - const info = attack.attack.match(/(-?\d+)([A-Za-z])\(\d*\)([a-z]*)/) - const ob = info[1]; - const size = info[2]; - const type = info[3]; - console.log("Attack", info, ob, size, type); - const rowid = generateRowID(); - const prefix = `repeating_creatureattack_${rowid}`; - - updates[`${prefix}_attack`] = attack.attack; - updates[`${prefix}_ob`] = ob; - updates[`${prefix}_attacksize`] = size; - updates[`${prefix}_attackersize`] = cdata.size; - - if (creature.weaponcodes[type]) { - console.log("Look up", creature.weaponcodes[type]); - const wc = creature.weaponcodes[type]; - let table = wc.table; - let sizemode = wc.sizemod; - getCompendiumPage(`AttackTable:${table}`, cdata => { - let nu = {}; - // FIXME: Handle errors in size slookup gracefully. - nu[`${prefix}_attackname`] = creature.getLongSize(size) + " " + table; - nu[`${prefix}_attacksize`] = size + wc.sizemod; - nu[`${prefix}_attackbasefumble`] = 1; // FIXMEitems.attackaddfumble; - nu[`${prefix}_attacktable`] = table; - nu[`${prefix}_attackstr`] = 100; - nu[`${prefix}_attacksource`] = 'creature'; - nu[`${prefix}_attackskill`] = ob; - nu[`${prefix}_attackri`] = 10; - - nu[`${prefix}_attackmin`] = cdata.data.min.match(/\d+/)[0] || 0; - nu[`${prefix}_attackmax`] = cdata.data.max.match(/\d+/)[0] || 0; - nu[`${prefix}_attackdata`] = cdata.data['data-attack'] || ""; - VrmuSetAttrs(nu); - }); - } - - updates[`${prefix}_type`] = type; + creature.parseAttack(attack, cdata, updates) } const moves = creature.parseJSONMaybe(cdata['data-move'], []); @@ -14657,14 +14745,38 @@ creature.newCreature = function(name, jdata) { updates[`${prefix}_bonus`] = bonus; } + { + const talents = creature.parseJSONMaybe(cdata['data-talents'], []) + let lastgroup = 'Elvis'; + for (const {talent, group} of talents) { + const rowid = generateRowID(); + const prefix = `repeating_creaturetalent_${rowid}`; + if (lastgroup != group) { + updates[`${prefix}_group`] = group; + lastgroup = group; + } + updates[`${prefix}_talent`] = talent; + } + } + // "data-skills": [ { "skill": "Running", "bonus": 43 } ], // "data-spellLists": [ { "list": "Concussions Ways ", "ranks": "1" } ] - const spells = creature.parseJSONMaybe(cdata['data-spellLists'], []) - for (const { list, ranks } of spells) { + const spells = creature.parseJSONMaybe(cdata['data-Spelllists'], []) + for (const { list, ranks, scr } of spells) { const rowid = generateRowID(); const prefix = `repeating_creaturespell_${rowid}`; + let cranks = ranks; updates[`${prefix}_name`] = list; - updates[`${prefix}_ranks`] = ranks; + // Ranks may be expressed as level or Own Level + if (ranks == "Own lvl") { + cranks = updates.level + } else { + cranks = ranks; + } + updates[`${prefix}_ranks`] = cranks; + // If we have an SCR mod, use it; else just ranks + // FIXME: Should get realm + updates[`${prefix}_scr`] = scr || cranks; } updates.togglenpcupload = 0; diff --git a/RolemasterUnified_Official/sheet.json b/RolemasterUnified_Official/sheet.json index c1695d87e2..849638d36f 100644 --- a/RolemasterUnified_Official/sheet.json +++ b/RolemasterUnified_Official/sheet.json @@ -8,5 +8,5 @@ "legacy": false, "printable": true, "compendium": "RMU", - "version": "1737436958" + "version": "1738649443" } diff --git a/RolemasterUnified_Official/updates.md b/RolemasterUnified_Official/updates.md index e4ea00f198..2e04cfe3da 100644 --- a/RolemasterUnified_Official/updates.md +++ b/RolemasterUnified_Official/updates.md @@ -1,11 +1,16 @@ +# 2025-2-4 + +- Creatures: Validate attacks for all listed attacks. +- Correctly adjust attack size +- Change display of attack size for creatures. +- Add meditation to attack skills. + # 2025-1-21 - Creature: Handle Level correctly on dropped creatures. - Dark Mode updates - closer to useful - Creature layout updates. - - # 2025-1-14 The wait... it's already halfway through January update!