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