RolemasterUnified Official: Custom Race support

Thursday updates?  A very weird Tuesday.  Lets go with that.

- Custom Race support (Beta)
- Remove the old Race name translations.  Unused.
This commit is contained in:
Brett Nash
2025-07-16 07:46:44 -07:00
parent 758cd1ca92
commit 663ee59f9b
4 changed files with 210 additions and 43 deletions
@@ -358,7 +358,7 @@ in the game in "Superior".</p>
<charmancer class="sheet-charmancer-race">
<div>
<h2>Race and Culture</h2>
<h2>Race and Culture</h2>
<div>
@@ -368,6 +368,7 @@ in the game in "Superior".</p>
<select name='comp_race' required="required">
<option value>Select...</option>
<option value='custom~custom'>Custom</option>
</select>
<hr>
@@ -391,13 +392,35 @@ in the game in "Superior".</p>
<div class='cm_info'>
<div class="compendium-page race-info" accept='Rules:Race'></div>
</div>
<div class='choice custom-race-upload'>
<textarea name='comp_customraceuploaddata'></textarea>
<button type='action' name='act_uploadrace' class='fancybutton'>Upload</button>
Message: <span class='uploadracemessage'></span>
<p>Format for custom races is a JSON file, as documented
<a href='https://github.com/nashidau/RMU-Interchange/blob/main/Race.md'
target='_blank'>https://github.com/nashidau/RMU-Interchange/blob/main/Race.md</a>
</p>
</div>
<div style='display:none'>
<input type='text' name='comp_customrace'>
</div>
</div>
</div>
<button class="fancybutton" type="submit" value="profession">Next (Profession)</button>
<span class="choice race-no-save">
<button class="fancybutton disabled" type="action" value="noaction">Next (Profession)</button>
</span>
<span class="choice race-save">
<button class="fancybutton" type="submit" value="profession">Next (Profession)</button>
</span>
</div>
</charmancer>
@@ -6247,7 +6270,7 @@ Sheet Version: <span name='attr_version'></span>
<br>
Revision 1ab67984127d8e78c5d286b8fc001471553df408
Revision b0bf9cb69bab879cb92fc6c7b3349b8e34285e1e
<hr>
@@ -7742,6 +7765,7 @@ let RMURaces = {};
RMURaces.raceSet = function(raceName, racedata) {
console.log("Starting setting race");
console.log(racedata)
const rstr = `Racial (${raceName})`;
// need to get:
@@ -7789,7 +7813,7 @@ let RMURaces = {};
// Talents
{
const talents = racedata['data-RacialTalents'];
const talents = racedata['data-RacialTalents'] || racedata['data-Talents'];
for (const talent in talents) {
RMUTalents.addByName(talents[talent].Talent,
talents[talent].Tier || 1,
@@ -11762,18 +11786,177 @@ onCheck("page:race", () => {
setCharmancerOptions("culture", "Category:Culture", cmdata?.race?.values?.culture);
});
/* Select of the Race page.
Need to handle custom selection.
*/
onCheck("mancerchange:race", (ev) => {
if (!ev.newValue) {
// Someone selected 'select...'. Sigh
showChoices(['race-no-save']);
hideChoices(['race-no-save']);
}
if (ev.newValue == 'custom~custom') {
showChoices(['custom-race-upload']);
validateRaceProgress();
return;
}
hideChoices(['custom-race-upload']);
// Get compendium page so it gets saved for later
getCompendiumPage(ev.newValue, (pdata) => { } );
changeCompendiumPage('race-info', ev.newValue);
validateRaceProgress();
});
onCheck("mancerchange:culture", (ev) => {
// Get compendium page so it gets saved for later
getCompendiumPage(ev.newValue, (pdata) => { } );
changeCompendiumPage('race-info', ev.newValue);
validateRaceProgress();
});
onCheck("mancerchange:gender", () => {
validateRaceProgress();
});
// Validate if the
function validateRaceProgress() {
const cmdata = getCharmancerData();
console.log(cmdata);
const racepage = cmdata.race.values;
// We can move on if
// - Race selected
// - Culture selected
// - Gender selected
// If Race is custom, then we need to check we have a validated race
let canProgress = true;
if (!racepage.gender) {
canProgress = false;
} else if (racepage.gender != 'M' && racepage.gender != 'F') {
canProgress = false;
}
// How about culture
if (!racepage.culture) {
canProgress = false;
}
// So we need a race or a custom rave
if (!racepage.race) {
canProgress = false;
} else if (racepage.race == 'custom~custom') {
if (!racepage.customrace) {
canProgress = false;
}
}
if (canProgress) {
hideChoices(["race-no-save"]);
showChoices(["race-save"]);
} else {
showChoices(["race-no-save"]);
hideChoices(["race-save"]);
}
}
onCheck("clicked:uploadrace", () => {
const cmdata = getCharmancerData();
const str = cmdata.race.values.customraceuploaddata;
cmdata.race.data.customrace = false;
let race = {};
try {
race = JSON.parse(str);
} catch (e) {
setCharmancerText({"uploadracemessage": `Error parsing json string: ${e.message}`});
console.log(str);
rmuerror({"uploadracemessage": `Error parsing json string: ${e.message}`});
return;
}
// So check for the fields we need
let result = validaterace(race)
console.log("parse race", result);
if (result.valid !== true) {
setCharmancerText({"uploadracemessage": result.message});
return;
}
console.log("Seeting succss");
setCharmancerText({"uploadracemessage": `${race.name} updated successfully`});
// Save a copy as the string
setAttrs({customrace: str});
setCharmancerText({"uploadracemessage": "Upload successful" });
validateRaceProgress();
return;
});
const racerequiredfields = [
{ name: "Name", type: "string" },
{ name: "content", type: "string" },
{ name: "Bonus DP", type: "number" },
{ group: "data-health", name: "Base Hits", type: "number" },
{ group: "data-health", name: "Recovery Mult", type: "string" },
{ group: "data-health", name: "Endurance", type: "number" },
{ group: "data-rrs", name: "Mentalism", type: "number", optional: true },
{ group: "data-rrs", name: "Physical", type: "number", optional: true },
{ group: "data-rrs", name: "Fear", type: "number", optional: true },
{ group: "data-rrs", name: "Essence", type: "number", optional: true },
{ group: "data-rrs", name: "Channeling", type: "number", optional: true },
{ group: "data-stats", name: "Reasoning", type: "number", optional: true },
{ group: "data-stats", name: "Agility", type: "number", optional: true },
{ group: "data-stats", name: "Strength", type: "number", optional: true },
{ group: "data-stats", name: "Memory", type: "number", optional: true },
{ group: "data-stats", name: "Self Discipline", type: "number", optional: true },
{ group: "data-stats", name: "Empathy", type: "number", optional: true },
{ group: "data-stats", name: "Intuition", type: "number", optional: true },
{ group: "data-stats", name: "Quickness", type: "number", optional: true },
{ group: "data-stats", name: "Presence", type: "number", optional: true },
{ group: "data-stats", name: "Constitution", type: "number", optional: true },
{ group: "data-size", name: "Stride", type: "number" },
{ group: "data-size", name: "Height (M)", type: "number" },
{ group: "data-size", name: "Height (F)", type: "number" },
{ group: "data-size", name: "Weight (M)", type: "number" },
{ group: "data-size", name: "Weight (F)", type: "number" },
{ group: "data-size", name: "Size", type: "string" },
{ group: "data-size", name: "Variance", type: "number" },
];
function validaterace(prof) {
const emsg = function(msg) { return { valid: false, message: msg }; };
for (test of racerequiredfields) {
let block = prof
if (test.group) {
block = prof[test.group];
}
if (block[test.name] != null) {
// It's here... chaeck type
if (typeof(block[test.name]) == test.type) {
// Great
} else {
return emsg(`Type of ${test.name} wrong. Expect ${test.type}, ` +
`got ${typeof(block[test.name])} (Value: ${block[test.name]}`);
}
} else if (test.optional) {
// Optional, don't care
} else {
return emsg(`Missing field ${test.name}`);
}
}
return { valid: true };
}
onCheck("page:profession", () => {
const cmdata = getCharmancerData();
@@ -11801,10 +11984,13 @@ onCheck("mancerchange:profession", (ev) => {
getCompendiumPage(ev.newValue);
showChoices(["profession-save"]);
hideChoices(["custom-profession-upload", "profession-no-save"]);
});
function validateprofession(prof) {
return true;
}
@@ -12523,12 +12709,19 @@ onCheck("mancerfinish:newcharacter", (ev) => {
// Race
addPendingFunction("CMFinish: race data", () => {
console.log("Set race data");
console.log("Set race data", data);
showChoices(["message-save-race"]);
const race = cleanCompendiumName(data.race.values.race)
setAttrsPending({race: race, critreduction: 0});
RMURaces.raceSet(race, data.race.data.race);
if (data.race.values.race == 'custom~custom') {
console.log(data.race.values.customrace);
const racedata = JSON.parse(data.race.values.customrace);
console.log("parsed", racedata);
setAttrs({race: racedata.Name + " (Custom)", critreduction: 0});
RMURaces.raceSet(racedata.Name, racedata);
} else {
const racename = cleanCompendiumName(data.race.values.race)
setAttrs({race: racename, critreduction: 0});
RMURaces.raceSet(race, data.race.data.race);
}
})
addPendingFunction("CMFinish: Generate Height and Weight", () => {
+1 -1
View File
@@ -19,5 +19,5 @@
"descriptiontranslationkey": "DefaultCurrency"
}
],
"version": "1752123949"
"version": "1752677122"
}
@@ -31,7 +31,6 @@
"architecture": "Architecture",
"astronomy": "Astronomy",
"attunement": "Attunement",
"avinarc": "Avinarc",
"awareness": "Awareness",
"bard": "Bard",
"base": "Base",
@@ -82,13 +81,8 @@
"drawingpainting": "Drawing/Painting",
"druid": "Druid",
"duping": "Duping",
"dwarf": "Dwarf",
"elemental": "Elemental",
"elementalbolts": "Elemental Bolts",
"elffair": "Elf, fair",
"elfgrey": "Elf grey",
"elfhigh": "Elf, high",
"elfwood": "Elf, wood",
"em": "Em",
"em/pr": "em/pr",
"empathy": "Empathy",
@@ -102,31 +96,18 @@
"flying": "Flying",
"footwork": "Footwork",
"fortitude": "Fortitude",
"gnoll": "Gnoll",
"gnome": "Gnome",
"goblin": "Goblin",
"grace": "Grace",
"gratar": "Gratar",
"greaterblade": "Greater Blade",
"greaterchain": "Greater Chain",
"greaterhafter": "Greater Hafted",
"gymnastic": "Gymnastic",
"hafted": "Hafted",
"halfelf": "Half-Elf",
"halfling": "Halfling",
"healer": "Healer",
"healing": "Healing",
"herbalism": "Herbalism",
"historiclore": "Historic Lore",
"hobgoblin": "Hobgoblin",
"horn": "Horn",
"humancave": "Human, Cave",
"humancommon": "Human Common",
"humanhigh": "Human High",
"humanmixed": "Human, mixed",
"hurling": "Hurling",
"hvasstonn": "Hvasstonn",
"idiyva": "Idiyva",
"illusionaryattacks": "Illusionary Attacks",
"illusioncrafting": "Illusion Crafting",
"illusionist": "Illusionist",
@@ -138,7 +119,6 @@
"intimidation": "Intimidation",
"intuition": "Intuition",
"jumping": "Jumping",
"kobold": "Kobold",
"laborer": "Laborer",
"language": "Language",
"layhealer": "Lay Healer",
@@ -182,20 +162,13 @@
"mystic": "Mystic",
"navigation": "Navigation",
"noprofession": "No Profession",
"nycamerith": "Nycamerith",
"open": "Open",
"operation": "Operation",
"orcgreater": "Orc, greater",
"orcgrey": "Orc, grey",
"orclesser": "Orc, lesser",
"orcscrug": "Orc, scrug",
"orcvard": "Orc, vard",
"paladin": "Paladin",
"perception": "Perception",
"performanceart": "Performance Art",
"physical": "Physical",
"piloting": "Piloting",
"plynos": "Plynos",
"poisonmastery": "Poison Mastery",
"polearm": "Pole Arm",
"powerdevelopment": "Power Development",
@@ -231,16 +204,13 @@
"scholar": "Scholar",
"science": "Science",
"sd": "SD",
"seakral": "Sea-kral",
"selfdiscipline": "Self Discipline",
"service": "Service",
"shield": "Shield",
"shieldgroup": "Shields",
"sibbicai": "Sibbicai",
"sling": "Sling",
"social": "Social",
"socialawareness": "Social Awareness",
"sohleugir": "Sohleugir",
"sorcerer": "Sorcerer",
"spellcasting": "Spellcasting",
"spellclosed": "Closed",
@@ -252,7 +222,6 @@
"spellotherbaseownrealm": "Other Base (Own Realm)",
"spellownbase": "Base",
"spelltrickery": "Spell Trickery",
"sstoiisslythi": "Sstoi'isslythi",
"st": "St",
"stagemagic": "Stage Magic",
"stalking": "Stalking",
@@ -290,11 +259,9 @@
"transcendence": "Transcendence",
"traps": "Traps",
"trickery": "Trickery",
"troll": "Troll",
"unarmed": "Unarmed",
"unarmedgroup": "Unarmed Attacks",
"vocation": "Vocation",
"vulfen": "Vulfen",
"warriormonk": "Warrior Monk",
"weighttraining": "Weight-training",
"woodcraft": "Woodcraft",
+7
View File
@@ -1,3 +1,10 @@
# 2025-07-17
Thursday updates? A very weird Tuesday
- Remove the old Race name translations. Unused.
- Custom Race support.
# 2025-07-10
The Thursday little bug edition