diff --git a/Shadowrun5thEdition/Blackbackground.jpg b/Shadowrun5thEdition/Blackbackground.jpg deleted file mode 100644 index 1cb8c34f05..0000000000 Binary files a/Shadowrun5thEdition/Blackbackground.jpg and /dev/null differ diff --git a/Shadowrun5thEdition/Shadowrun5thEdition.css b/Shadowrun5thEdition/Shadowrun5thEdition.css index 8b18581e86..8b609232ed 100644 --- a/Shadowrun5thEdition/Shadowrun5thEdition.css +++ b/Shadowrun5thEdition/Shadowrun5thEdition.css @@ -60,7 +60,8 @@ vertical-align: middle;} input::placeholder { - color: #b8b7b6;} + color: #b8b7b6; + } input[type="checkbox"], input[type="radio"], @@ -119,7 +120,12 @@ .charsheet { width: 850px;} - .sheet-red {color: #610b0d;} + .sheet-red {color: #610b0d;} + + button.sheet-settings-button { + height: 5%; + margin-left: 1%; + } /********************************************************************************************************/ /********************************* PC & NPC SHEET SHOW & HIDE SETTINGS ***********************************/ @@ -294,7 +300,7 @@ .sheet-row-long {border-bottom: 1px solid #000;} .sheet-settings-row {margin-top: 5px;} - .sheet-settings-row select.sheet-skill-select {width: 30%;} + .sheet-settings-row select.sheet-skill-select {width: 29%;} /***** DECORATIVE BOX THAT ADDS THE RED OUTLINE ON PC SHEET *****/ .sheet-border-box { @@ -539,7 +545,7 @@ margin-left : 0px; padding-right: 2px; text-align : right; - width : 35%;} + width : 30%;} .sheet-attr-col input[type="number"] { border-radius: 0px; @@ -589,7 +595,7 @@ height : 22px; margin-bottom : 4px; vertical-align: top; - width : 60%;} + width : 65%;} .sheet-attr-und .sheet-display {text-align: center;} @@ -794,7 +800,7 @@ border : none; border-bottom: 1px solid #333; border-radius: 0px; - width : 35px;} + width : 8%;} .sheet-repeat-box .sheet-settings input[type="text"]:first-child { text-align: left;} @@ -821,17 +827,12 @@ font-weight: bolder; height : 20px; padding : 0px; - width : 15%;} + width : 14%;} .sheet-repeat-box option { padding : 2px; font-weight: bold;} - /***** OLD REPEATING SECTION to be Deprecated *****/ - .sheet-repeat-box .sheet-settings, - .sheet-repeat-box .sheet-display { - margin-left: -8px;} - /***** COLLAPSABLE DIVS *****/ .sheet-collapsable { padding-left: 1.5em; @@ -858,9 +859,19 @@ width : 95%; height : 100%;} - .sheet-collapsable .sheet-desc {display: none;} + .sheet-collapsable .sheet-desc, + .sheet-collapsable .sheet-desc-lifestyle { + display: none; + } - .sheet-display-flag:checked ~ .sheet-collapsable .sheet-desc {display: inline;} + .sheet-display-flag:checked ~ .sheet-collapsable .sheet-desc { + display: inline; + } + + .sheet-display-flag:checked ~ .sheet-collapsable .sheet-desc-lifestyle { + display: inline-block; + width: 100%; + } .sheet-arms input.sheet-display-flag, .sheet-arms input.sheet-display-flag + span, @@ -869,10 +880,19 @@ .sheet-matrix input.sheet-display-flag, .sheet-matrix input.sheet-display-flag + span { height : 12px; - left : 95.5%; position: absolute; top : 2px; - width : 12px;} + width : 12px; + } + + .sheet-arms input.sheet-display-flag, + .sheet-arms input.sheet-display-flag + span, + .sheet-spell-box input.sheet-display-flag, + .sheet-spell-box input.sheet-display-flag + span, + .sheet-matrix input.sheet-display-flag, + .sheet-matrix input.sheet-display-flag + span { + left: 93%; + } .sheet-arms input.sheet-display-flag + span, .sheet-spell-box input.sheet-display-flag + span, @@ -928,9 +948,14 @@ select.sheet-attr-select[name="attr_limit"] { left : 82%;} - .sheet-attr-select option:nth-of-type(1n+5) {color: #610b0d;} + .sheet-attr-select option:nth-of-type(1n+5), + .sheet-limit-select option:nth-of-type(1n+5) {color: #610b0d;} .sheet-attr-select option:nth-of-type(1n+9) {color: #7a2593;} + .sheet-limit-select { + margin-left: 4%; + } + /***** SKILLS REPEATING SECTION *****/ .sheet-skills-row .sheet-settings-row div { width: 70%;} @@ -963,7 +988,8 @@ font-weight : 700; margin-bottom: -3px;} - .sheet-skills-row .sheet-settings-row input[type="text"] {width: 48%;} + .sheet-skills-row .sheet-settings-row input[type="text"] { + width: 45%;} /***** SKILLS DISPLAY *****/ .sheet-skill-box .sheet-name { @@ -983,11 +1009,15 @@ .sheet-tab-switch[value="range"] ~ .sheet-weapon-range, .sheet-tab-switch[value="melee"] ~ .sheet-weapon-melee, .sheet-arms .sheet-display { - display: inline-block;} + display: inline-block;%; + } + + .sheet-arms .sheet-display { + margin-left: -1%; + } /***** WEAPON SETTINGS *****/ .sheet-weapon-box select {width: 10%;} - .sheet-weapon-box .sheet-settings-row {margin-top: 5px;} .sheet-arms input[type="text"], @@ -997,14 +1027,24 @@ width : 8%;} .sheet-arms input[type="text"]:first-child, - .sheet-arms h3:first-child, .sheet-arms .sheet-display .sheet-display-row:first-child { text-align : left; - width : 38%; - margin-right: 1%;} + width : 36%; + margin-right: 1%; + } - .sheet-arms h3:first-child { - margin-right: 2%;} + .sheet-arms h3:first-child { + text-align: left; + margin-right: 2%; + } + + .sheet-arms h3[data-i18n="weapon"] { + width: 34%; + } + + .sheet-arms h3[data-i18n="armor"] { + width: 38%; + } .sheet-weapon-range h3 { margin-right: 3px;} @@ -1012,16 +1052,12 @@ .sheet-weapon-melee h3 { margin-right: 10px;} - .sheet-weapon-range h3:first-child, - .sheet-weapon-melee h3:first-child { - margin-right: -1%} - .sheet-weapon-melee h3:first-child, .sheet-weapon-melee input[type="text"]:first-child { width: 40%;} .sheet-arms .sheet-display-row:nth-of-type(2) { - width: 7%;} + width: 12%;} .sheet-weapon-range h3:nth-child(2), .sheet-weapon-range h3:nth-child(5) { @@ -1031,7 +1067,7 @@ .sheet-weapon-melee .sheet-display-row { margin-right: 5px;} - .sheet-weapon-box label + input[type="text"] {width: 28%;} + .sheet-weapon-box label + input[type="text"] {width: 27%;} .sheet-exotic-warning { display : none; @@ -1043,12 +1079,14 @@ /***** ARMOR *****/ .sheet-armor-box h3 {margin-right: 5.5px;} - .sheet-armor-box button:nth-child(1n+2) { - height: 25px; - width : 25px;} - /***** ARMOR SETTINGS *****/ - .sheet-armor-box.sheet-settings input {margin-right: 1px;} + .sheet-armor-box .sheet-settings input[name="attr_name"] { + width: 40%; + } + + .sheet-armor-box .sheet-settings input[type="number"] { + margin-right: 0.75%; + } /***** ARMOR DISPLAY *****/ .sheet-armor-box .sheet-display .sheet-display-row {margin-right: 3px;} @@ -1074,7 +1112,6 @@ width : 82%;} .sheet-quality .sheet-display-row:nth-of-type(1) { - margin-left: 2%; width: 80%;} .sheet-quality .sheet-display-row:nth-of-type(2), @@ -1119,11 +1156,6 @@ margin : 0px 0px 0px 0px;} /***** LIFESTYLE DISPLAY *****/ - .sheet-lifestyle .sheet-display span { - display : inline-block; - font-family: sans-serif; - width : 24%;} - .sheet-lifestyle .sheet-display-flag:checked ~ span:nth-of-type(1), .sheet-lifestyle .sheet-display-flag ~ span:nth-of-type(1n+2) { display: none;} @@ -1131,7 +1163,7 @@ .sheet-lifestyle .sheet-display-flag:checked ~ span:nth-of-type(1n+2) {display: inherit;} /***** CONTACTS *****/ - .sheet-contacts h3:nth-of-type(1) {margin-right: 53%;} + .sheet-contacts h3:nth-of-type(1) {margin-right: 50%;} .sheet-contacts h3:nth-of-type(2), .sheet-contacts h3:nth-of-type(3) { @@ -1176,7 +1208,7 @@ width: 85%;} /***** AUGMENTATIONS (AUGS) *****/ - .sheet-aug-box h3:nth-of-type(1) {margin-right: 58%;} + .sheet-aug-box h3:nth-of-type(1) {margin-right: 55%;} .sheet-aug-box h3:nth-of-type(2) {margin-right: 3%;} /***** AUGMENTATIONS SETTINGS *****/ @@ -1206,7 +1238,7 @@ .sheet-powers-box .sheet-display .sheet-display-row:nth-of-type(1) { margin-right: 1%;} - .sheet-spell-box h3 {width: 15%;} + .sheet-spell-box h3 {width: 13%;} .sheet-powers-box h3, .sheet-powers-box input[type="number"], @@ -1426,10 +1458,10 @@ /***** COMPLEX FORMS *****/ .sheet-form-box h3 { margin-right: 1%; - width : 18%;} + width : 16%;} .sheet-form-box h3:nth-of-type(1) { - width: 50%;} + width: 48%;} .sheet-form-box h3:nth-of-type(4) { width: 10%;} @@ -1455,9 +1487,9 @@ .sheet-form-box .sheet-display .sheet-display-row { margin-right: 1%; text-align : left; - width : 14%;} + width : 16%;} - .sheet-form-box .sheet-display-row:nth-of-type(1) {width: 50%;} + .sheet-form-box .sheet-display-row:nth-of-type(1) {width: 48%;} .sheet-form-box .sheet-display .sheet-display-row:nth-of-type(4) {width: 10%;} .sheet-form-box .sheet-display .sheet-display-row:nth-of-type(5) {width: 7%;} @@ -1468,26 +1500,26 @@ width : 100%;} .sheet-vehicle-box input[type="text"] { - width: 45%;} + width: 44%;} .sheet-vehicle-box label { - width: 18%;} + width: 20%;} .sheet-vehicle-box .sheet-row-long:first-child label { width : 12%;} .sheet-vehicle-box label:nth-child(1n+4) { text-align: right; - width : 23%;} + width : 21%;} /***** VEHICLE SETTING *****/ .sheet-vehicle-box .sheet-settings h3 {margin-right: 2%;} .sheet-vehicle-box .sheet-settings h3:nth-child(1) { - margin-left: 20%; + margin-left: 18%; margin-top : -5px;} - .sheet-vehicle-box .sheet-settings h3:nth-child(4) {margin-left: 23.5%;} + .sheet-vehicle-box .sheet-settings h3:nth-child(4) {margin-left: 22%;} .sheet-vehicle-box .sheet-settings h3:nth-child(6) {margin-right: 0px;} .sheet-vehicle-box .sheet-settings input[type="text"], @@ -1500,13 +1532,17 @@ width : 49%;} .sheet-vehicle-box .sheet-display-row h3 { - width: 32%;} + width: 35%;} .sheet-vehicle-box .sheet-display-row div { display : inline-block; font-weight: normal; text-align : center; - width : 65%;} + } + + .sheet-vehicle-box .sheet-display-row div.sheet-vehicle-stat { + width: 60%; + } .sheet-vehicle-box .sheet-collapsable {padding-left: 0.2em;} @@ -1521,7 +1557,25 @@ .sheet-op-col {margin-bottom: 5px;} - .sheet-op-col h2 {text-decoration: underline;} + .sheet-op-col h2 { + text-decoration: underline; + width: 100%; + } + + .sheet-op-col button[name="act_import_launch"] { + border: 2px solid black; + background: #333; + color: white; + font-weight: bold; + width: auto; + height: auto; + padding: 2% 4% + } + + .sheet-op-col p { + margin-top: 2%; + margin-right: 5%; + } .sheet-op-col hr { border : 0.5px solid #1c1c1c; @@ -2234,6 +2288,17 @@ text-align : left; width : 700px;} + div.sheet-footer div { + display: inline-block; + width: 90%; + } + + div.sheet-footer img { + float: right; + vertical-align: top; + width: 5%; + } + /********************************************************************************************************/ /**************************************** REPEATING SECTIONS *******************************************/ /********************************************************************************************************/ @@ -2362,6 +2427,180 @@ .sheet-rolltemplate-rolls span.sheet-template-desc .inlinerollresult {font-size: 14px;} +/********************************************************************************************************/ +/******************************* Charactermancer ****************************************/ +/******************************************************************************************************/ + div.sheet-mancer-box { + border: 2px solid #610b0d; + background-color: #e7e6e5; + margin: 1% 2%; + padding: 1% 2%; + } + + div.sheet-mancer-box input[type="radio"], + div.sheet-mancer-box input[type="checkbox"] { + opacity: 1; + height: 1.5em; + width: 1.5em; + } + + div.sheet-mancer-box input[type="text"] { + background-color: white; + border: 1px solid #333; + width: 100%; + } + + div.sheet-mancer-box label { + display: inline-block; + } + + div.sheet-mancer-box div.sheet-pick-builder label { + margin: 2% 2% 1% 0%; + width: 15%; + } + + div.sheet-mancer-box label { + display: inline-block; + margin: 2% 2%; + } + + div.sheet-mancer-box h1 { + text-decoration: underline; + } + + div.sheet-mancer-box label > h4 { + display: inline; + } + + div.sheet-mancer-box textarea { + overflow: scroll; + min-height: 5em; + width: 100%; + } + + div.sheet-mancer-box hr { + margin-top: 3%; + margin-bottom: 3%; + } + + div.sheet-mancer-box button, + div.sheet-bottombar button { + border-radius : 2px; + font-weight : bolder; + height : 2em; + padding : 5px; + text-transform : capitalize; + background-color: #333; + box-shadow : inset 0px 0px 3px 1px #7c7c7c; + color : #fff + } + + div.sheet-mancer-box button:not(disabled):hover, + div.sheet-mancer-box button:not(disabled):focus, + button[name="act_import_launch"]:hover, + button[name="act_import_launch"]:focus { + text-shadow: 1px 1px 2px black, 0 0 25px red, 0 0 5px #e31715; + text-transform : capitalize; + border: 0.1em dotted white; + } + + div.sheet-mancer-box button[type="action"], + div.sheet-mancer-box button[type="finish"] { + font-size: 1.5em; + width : 20%; + } + + div.sheet-mancer-box button[type="finish"]:disabled { + color: rgba(255, 255, 255, 0.2); + cursor: not-allowed; + } + + div.sheet-mancer-box button[type="cancel"] { + margin-top: 3%; + float: right; + font-size: 1em; + } + + div.sheet-mancer-box .sheet-feedback { + color: #000e3b; + } + + div.sheet-mancer-box .sheet-warning { + color: #610b0d; + } + + div.sheet-mancer-box .sheet-results, + div.sheet-mancer-box .sheet-error { + border: 1px solid #333; + box-shadow: inset 0px 0px 3px 1px #7c7c7c; + height: 30em; + margin-top: 2%; + overflow: scroll; + padding: 1%; + } + + div.sheet-mancer-box .sheet-results { + border: 1px solid #000e3b; + margin-right: 3%; + background: rgba(136, 221, 4, 0.1); + } + + div.sheet-mancer-box .sheet-error { + border: 1px solid #610b0d; + background: rgba(204, 7, 1, 0.11); + } + + div.sheet-result-grid { + display: grid; + grid-template-columns: 1fr 1fr; + } + + div.sheet-result-grid h4:nth-of-type(1) { + grid-column: 1 / 1; + grid-row: 1 / 1; + } + + div.sheet-result-grid h4:nth-of-type(2) { + grid-column: 2 / 2; + grid-row: 1 / 1; + } + + div.sheet-results { + grid-column: 1 / 1; + grid-row: 2 / 2; + } + + div.sheet-ignored { + grid-column: 2 / 2; + grid-row: 2 / 2; + } + + div.sheet-hidden, + div.sheet-hidden-skills { + display: none; + } + + h4.sheet-hidden-header { + display: inline; + } + + input[name="comp_hidden"]:checked + div.sheet-hidden, + input[name="comp_hidden"]:checked + div.sheet-hidden-skills { + display: block; + } + + div.sheet-hidden label { + display: inline-block; + width: 30%; + margin: 1% 1%; + } + + div.sheet-hidden div { + display: inline-block; + font-weight: bold; + margin-right: 2%; + width: 40% + } /********************************************************************************************************/ /******************************* FireFox *******************************************/ diff --git a/Shadowrun5thEdition/Shadowrun5thEdition.html b/Shadowrun5thEdition/Shadowrun5thEdition.html index ae09675348..d384af9782 100644 --- a/Shadowrun5thEdition/Shadowrun5thEdition.html +++ b/Shadowrun5thEdition/Shadowrun5thEdition.html @@ -1,3 +1,4 @@ + @@ -8,7 +9,7 @@
+
You can import a character with a JSON from Chummer. The game owner must set a Compendium in the Game Settings. It does not matter which one you pick. Charactermancer software simply requires one be picked.
+A valid JSON was imported for ${mancerValues.builder}.
` + // Send the parsed data to the selected importer or let users know something went wrong + if (mancerValues.builder && mancerValues.builder === "Chummer") { + if (parsedData.characters && parsedData.characters.character) { + importChummer(parsedData.characters.character); + } else { + setText[`import-feedback`] += `Parsed JSON is missing character data.
`; + applyEnabled = false + } + } else if (mancerValues.builder && mancerValues.builder === "Hero Lab") { + importHeroLab(); + setText[`import-feedback`] += `Coming Soon....
`; + } else { + setText[`import-feedback`] += `A character builder was not selected. Try changing your selection then change it back if needed.
`; + applyEnabled = false + } + } else { + setText[`import-feedback`] += `The text imported is not a valid JSON. Verify the JSON is formatted correctly. Check the format at, JSONLint.com.
`; + applyEnabled = false + }; + } else { + setText[`import-feedback`] = `No information was entered in the JSON text area.
`; + applyEnabled = false + }; + + let buttonStatus = (applyEnabled) ? "" : "disabled"; + setText[`finish-button`] = ``; + + setCharmancerText(setText); + }); + + const chummerDirections = `${capitialize(attribute)}: ${value}
` + }; + + const alphabatizeKeys = (unsortedJSON) => { + let alphabeticalKeys = {}; + Object.keys(unsortedJSON).sort().forEach((key) => { + alphabeticalKeys[key] = unsortedJSON[key]; + }); + return alphabeticalKeys + }; + + const getValInParen = (value) => { + return value.split("(")[1].split(")")[0]; + }; + + const getArray = (array) => { + return Array.isArray(array) ? array : [ array ]; + }; + + const getSplitNum = (value) => { + return value.split("/")[0]; + } + + //IMPORTERS + const importHeroLab = (character) => { + console.log(`%c HERO LAB IMPORTER:`, "color: purple; font-weight:bold"); + }; + + const importChummer = (character) => { + //Match Chummer JSON keys with their sheet attribute. + //Chummer name is on the left, Roll20 sheet atribute on right. + const jsonKeys = { + "name": "name" + ,"adept": "flag_special" + ,"age" : "age" + ,"alias": "character_name" + ,"armor": "armor" + ,"calculatednotoriety": "notoriety" + ,"calculatedpublicawareness": "public_awareness" + ,"calculatedstreetcred": "street_cred" + ,"cmoverflow": "overflow" + ,"composure" : "composure" + ,"height": "height" + ,"initdice": "initiative_dice" + ,"initvalue": "initiative_mod" + ,"judgeintentions" : "judge_intentions" + ,"karma": "karma" + ,"liftandcarry": "lift_carry" + ,"limitmental": "mental_limit" + ,"limitphysical": "physical_limit" + ,"limitsocial": "social_limit" + ,"magician": "flag_special" + ,"memory": "memory" + ,"nuyen":"primary_nuyen" + ,"physicalcm": "physical_max" + //,"physicalcmfilled": "physical_damage" //RESULTING IN INFINATE LOOP + ,"playername": "player_name" + ,"sex": "sex" + ,"stuncm": "stun_max" + //,"stuncmfilled": "stun_damage" + ,"technomancer": "flag_special" + ,"totaless": "essence" + ,"totalkarma": "total_karma" + ,"weight": "weight" + ,"indirectdefenseresist": "defense" + }; + + const attributeKeys = { + "BOD": "body" + ,"AGI": "agility" + ,"REA": "reaction" + ,"STR": "strength" + ,"CHA": "charisma" + ,"INT": "intuition" + ,"LOG": "logic" + ,"WIL": "willpower" + ,"EDG": "edge" + ,"MAG": "magic" + ,"RES": "resonance" + }; + + const processChummer = new Promise((resolve, reject) => { + console.log(`%c CHUMMER IMPORTER`, "color: purple; font-weight:bold"); + const sortedKeys = alphabatizeKeys(jsonKeys); + let setText = { + "error": "", + "results": "", + "ignored": "" + }; + let update = {}; + + //MATRIX + const setCyberdeck = (cyberdeck) => { + update["cyberdeck"] = cyberdeck.name; + update["device_rating"] = cyberdeck.devicerating; + update["attribute_1"] = cyberdeck.attack; + update["attribute_2"] = cyberdeck.sleaze; + update["attribute_3"] = cyberdeck.dataprocessing; + update["attribute_4"] = cyberdeck.firewall; + update["matrix_con"] = 0; + } + + // PROCESS TOP LEVEL KEYS + Object.keys(sortedKeys).forEach((key) => { + try { + const charSheetAttr = jsonKeys[key]; + const chummerValue = character[key]; + if ((key === "adept" || key === "magician" || key === "technomancer")) { + if (chummerValue.toUpperCase() === "TRUE") { + update[`${charSheetAttr}`] = (key === "technomancer") ? "resonance" : "magic"; + } else if (!update.hasOwnProperty(`${charSheetAttr}`)) { // if we don't have the property set already otherwise keys get overriden + update[`${charSheetAttr}`] = "mundane"; + }; + } else if (key === "totaless") { + const essence = chummerValue.includes(",") ? chummerValue.replace(",", ".") : chummerValue; + update[`${charSheetAttr}`] = parseFloat(essence) || 6; + } else if (chummerValue && chummerValue != null) { + update[`${charSheetAttr}`] = chummerValue; + } else { + setText["error"] += addFeedback(charSheetAttr, chummerValue); + }; + + const textUpdate = (update[`${charSheetAttr}`]) ? "results" : "ignored"; + const feedbackText = addFeedback(jsonKeys[key].replace("_"," "), update[`${charSheetAttr}`]); + (setText[`results`].includes(feedbackText) || setText[`ignored`].includes(feedbackText)) ? false : setText[`${textUpdate}`] += feedbackText; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(key), error); + }; + }); + + // PROCESS METATYPE + try { + update[`metatype`] = character.metatype; + if (character.metavariant) { + update[`metatype`] += ` (${character.metavariant})` + } + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(character.metatype), error); + }; + + // PROCESS ATTRIBUTES + const attributes = character.attributes[1].attribute; + let attributeTotals = { + "essence": update["essence"] + }; // USED FOR LIMITS BELOW + attributes.forEach((object) => { + try { + const charSheetAttr = attributeKeys[object.name]; + if (charSheetAttr === "edge") { + update[`${charSheetAttr}`] = object.base; + } else { + update[`${charSheetAttr}_base`] = object.base; + update[`${charSheetAttr}_modifier`] = object.total - object.base; + }; + + // USED FOR LIMITS BELOW + attributeTotals[`${charSheetAttr}`] = parseInt(object.total) || 0; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(object), error); + }; + }); + + //PROCESS LIMITS + const limitKeys = { + "limitmental": "mental_limit" + ,"limitphysical": "physical_limit" + ,"limitsocial": "social_limit" + }; + + Object.keys(limitKeys).forEach((key) => { + //CALCULATE THE BASE FOR LIMIT + const limitCalculate = (attrsArray) => { + return Math.ceil(((attrsArray[0] + attrsArray[1] + (attrsArray[2] * 2))/3)) + }; + + try { + let attrs = []; + if (key === "limitmental") { + attrs.push(attributeTotals["intuition"], attributeTotals["willpower"], attributeTotals["logic"]); + } else if (key === "limitphysical") { + attrs.push(attributeTotals["body"], attributeTotals["reaction"], attributeTotals["strength"]); + } else { + attrs.push(attributeTotals["essence"], attributeTotals["willpower"], attributeTotals["charisma"]); + }; + + //SET LIMIT MODIFIER EQUAL TO THE TOTAL IN CHUMMER - THE CALCULATED BASE LIMIT + update[`${limitKeys[key]}_modifier`] = parseInt(character[key]) - limitCalculate(attrs); + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(`${key}`), error); + }; + }); + + // LOOP THROUGHT QUALITIES + if (character.qualities != null && character.qualities.quality != null) { + const qualitiesList = getArray(character.qualities.quality); + qualitiesList.forEach((quality) => { + try { + const id = generateRowID(); + update[`repeating_quality_${id}_flag`] = "display"; + update[`repeating_quality_${id}_quality`] = quality.name; + update[`repeating_quality_${id}_type`] = (quality.qualitytype === "Positive") ? "P" : "N"; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(quality), error); + }; + }); + } + + // LOOP THROUGH SKILLS & GENERATE REPEATING FIELDS + if (character.skills != null && character.skills.skill != null) { + const skillsList = getArray(character.skills.skill); + skillsList.forEach((skill) => { + try { + const rating = parseInt(skill.rating) || 0; + if (rating > 0) { + const id = generateRowID(); + const type = + (skill.knowledge.toUpperCase() === "TRUE" && skill.islanguage.toUpperCase() === "TRUE") ? "language" : + (skill.knowledge.toUpperCase() === "TRUE") ? "knowledge" : + "active"; + const exotic = skill.exotic.toUpperCase(); + const section = `repeating_${type}_${id}` + // Exotic Weapons need their name in Specialization + + update[`${section}_flag`] = "display"; + update[`${section}_skill`] = + (skill.name === "Exotic Melee Weapon") ? "Exotic Melee" : + (skill.name === "Exotic Ranged Weapon") ? "Exotic Range" : + skill.name; + update[`${section}_display`] = + (skill.name === "Exotic Melee Weapon") ? "Exotic Melee" : + (skill.name === "Exotic Ranged Weapon") ? "Exotic Range" : + skill.name; + update[`${section}_rating`] = skill.rating; + update[`${section}_rating_modifier`] = skill.ratingmod; + + const skilltotal = parseInt(skill.rating) + parseInt(skill.ratingmod); + update[`${section}_dicepool`] = skilltotal; + + //ACTIVE SKILLS HAVE THEIR TOTAL SAVED IN A HIDDEN ATTRIBUTE + if (type === "active") { + update[skill.name.toLowerCase().replace(/ /g, "")] = skilltotal; + }; + + //This needs to be verified with multiple specializations + if (skill.skillspecializations && skill.skillspecializations != null) { + const chummerSpecialization = skill.skillspecializations.skillspecialization.name; + const specialization = (chummerSpecialization.includes("[")) ? chummerSpecialization.slice(1, -1) : chummerSpecialization; + update[`${section}_specialization`] = specialization; + } else if (exotic === "TRUE") { + update[`${section}_specialization`] = skill.spec; + }; + + //CREATE THE DISPLAYS FOR SKILLS + if (update[`${section}_specialization`]) { + update[`${section}_display_specialization`] = `(${update[`${section}_specialization`]})`; + }; + + //DISPLAY FOR RTG + update[`${section}_display_rating`] = (skill.ratingmod != 0) ? `${skill.rating} (${skilltotal})` : `${skill.rating}`; + + const attribute = skill.attribute; + update[`${section}_attribute`] = `@{${attributeKeys[attribute]}}`; + update[`${section}_display_attribute`] = attributeKeys[attribute]; + + update[`${section}_limit`] = + (attribute === "AGI" || attribute === "BOD" || attribute === "STR" || attribute === "REA") ? `@{physical_limit}` : + (attribute === "WIL" || attribute === "LOG" || attribute === "INT") ? `@{mental_limit}` : + (attribute === "CHA") ? `@{social_limit}` : "none"; + }; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(skill), error); + }; + }); + } + + //MOVEMENT + try { + const movement = character.movementwalk.split(",")[0].split("/"); + update[`walk`] = movement[0]; + update[`run`] = movement[1]; + } catch (error) { + setText["error"] += addFeedback("movement", error); + }; + + //MARTIAL ARTS + if (character.martialarts != null && character.martialarts.martialart != null) { + const martialartsList = getArray(character.martialarts.martialart); + martialartsList.forEach((art) => { + const techniqueList = getArray(art.martialarttechniques.martialarttechnique); + techniqueList.forEach((tech) => { + try { + const id = generateRowID(); + update[`repeating_martial_${id}_flag`] = "display"; + update[`repeating_martial_${id}_technique`] = tech.name; + update[`repeating_martial_${id}_style`] = art.name; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(art), error); + }; + }); + }); + }; + + //WEAPON KEYS + const weaponKeys = { + "name": "weapon" + ,"damage": "damage" + ,"ap": "ap" + ,"dicepool": "dicepool" + ,"skill": "skill" + ,"reach": "reach" + ,"rc": "recoil" + ,"mode": "mode_options" + ,"accuracy": "acc" + }; + + //WEAPONS + if (character.weapons != null && character.weapons.weapon != null) { + const weaponsList = getArray(character.weapons.weapon); + weaponsList.forEach((weapon) => { + try { + const id = generateRowID(); + const section = (weapon.type.toUpperCase() === "MELEE") ? `repeating_melee_${id}` : `repeating_range_${id}`; + const npcWeapon = `repeating_weapon_${id}`; + + const category = weapon.category.toUpperCase(); + Object.keys(weaponKeys).forEach((key) => { + if (weapon[`${key}`] && weapon[`${key}`] != null) { + update[`${section}_${weaponKeys[key]}`] = + (key != "damage" && weapon[`${key}`].includes("(")) ? getValInParen(weapon[`${key}`]) : + (key === "skill") ? `@{${weapon[`${key}`].toLowerCase().replace(" ", "")}}` : + weapon[`${key}`]; + } else if (key === "skill" && category.includes("EXOTIC")) { + update[`${section}_skill`] = 0; + }; + }); + + //EXOTIC WEAPONS NEED SPECIAL HANDLING + if (category.includes("EXOTIC")) { + update[`${section}_dicepool_modifier`] = weapon.dicepool; + }; + + //RANGED WEAPONS NEED CLIP & RANGES + if (weapon.type.toUpperCase() === "RANGED") { + if (weapon.clips != null && weapon.clips.clip != null) { + const clips = Array.isArray(weapon.clips.clip) ? weapon.clips.clip : [ weapon.clips.clip ]; + update[`${section}_ammo`] = clips[0].count; + } else if (weapon.name.includes("Grenade")) { + const gearsList = getArray(character.gears.gear); + gearsList.forEach((gear) => { + if (weapon.name === gear.name) { + update[`${section}_ammo`] = gear.qty || 1; + }; + }); + } else if (weapon.ammo) { + update[`${section}_ammo`] = weapon.ammo; + }; + + //RANGE + const rangeList = weapon.ranges; + let range = ""; + Object.keys(rangeList).forEach((key) => { + if (key != "name" && rangeList[key] != null) { + const rangeNumber = rangeList[key].split("-")[1]; + range += (key === "short") ? `${rangeNumber}` : `/${rangeNumber}`; + }; + }); + update[`${section}_range`] = range; + }; + + //ACCESSORIES + if (weapon.accessories) { + let accessories = ""; + const accessoriesList = getArray(weapon.accessories.accessory); + accessoriesList.forEach((accessory) => { + accessories += `${accessory["name"]}, ` + }); + + update[`${section}_notes`] = accessories; + }; + + // TOGGLE THE SETTINGS OFF + update[`${section}_flag`] = "display"; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(weapon), error); + }; + }); + } + + //ARMOR + if (character.armors != null && character.armors.armor != null) { + const armorsList = getArray(character.armors.armor); + armorsList.forEach((armor) => { + try { + const id = generateRowID(); + if (armor.category != "Clothing") { + const section = `repeating_armor_${id}`; + let armorMods = ""; + let armorGear = ""; + + // ARMOR NAME + update[`${section}_name`] = armor.name; + + // ARMOR sometimes has a bonus + update[`${section}_rating`] = (armor.armor.includes("/+")) ? armor.armor.split("/+")[0] : armor.armor; + + // ARMOR MODS + if (armor.armormods && armor.armormods != null) { + const modsList = getArray(armor.armormods.armormod); + modsList.forEach((modification) => { + armorMods += `${modification.name}, `; + + if (modification.name === "Nonconductivity") { + update[`${section}_electrical_modifier`] = modification.rating; + } else if (modification.name === "Thermal Damping" || modification === "Insulation") { + update[`${section}_cold_modifier`] = modification.rating; + } else if (modification.name === "Radiation Shielding") { + update[`${section}_radiation_modifier`] = modification.rating; + } else if (modification.name === "Chemical Protectio") { + update[`${section}_acid_modifier`] = modification.rating; + } else if (modification.name === "Fire Resistance") { + update[`${section}_fire_modifier`] = modification.rating; + }; + }); + }; + + // ARMOR GEAR + if (armor.gears && armor.gears != null) { + const gearList = getArray(armor.gears.gear); + gearList.forEach((gear) => { + if (gear.children) { + const childrenList = getArray(gear.children.gear); + childrenList.forEach((child) => { + armorGear += `${gear.name} (${child.name}), `; + }); + } else { + armorGear += `${gear.name}, `; + }; + }); + }; + + // HIDE SETTINGS + update[`${section}_flag`] = "display"; + + update[`${section}_notes`] = + (armorMods && armorGear) ? `Modification: ${armorMods} Gear: ${armorGear}` : + (armorMods) ? `Modification: ${armorMods}` : + (armorGear) ? `Gear: ${armorGear}` : ""; + } else { + // Chummer puts Clothing in with armor + const section = `repeating_items_${id}`; + update[`${section}_item`] = armor.name; + update[`${section}_flag`] = "display"; + }; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(armor), error); + } + }); + } + + //AUGMENTATIONS + if (character.cyberwares != null && character.cyberwares.cyberware != null) { + const cyberwaresList = getArray(character.cyberwares.cyberware); + cyberwaresList.forEach((ware) => { + const id = generateRowID(); + try { + const section = `repeating_augementations_${id}`; + update[`${section}_flag`] = "display"; + update[`${section}_augmentation`] = ware.name; + update[`${section}_rating`] = ware.rating; + update[`${section}_essence`] = ware.ess; + update[`${section}_notes`] = `Grade: ${ware.grade}`; + + if (ware.gears != null && ware.gears.gear != null) { + const wareGearList = getArray(ware.gears.gear); + wareGearList.forEach((gear) => { + if (gear.category.toLowerCase().includes("cyberdeck")) { + setCyberdeck(gear); + } + }); + } + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(ware.name), error); + } + }); + }; + + console.log(character); + + // GEAR + if (character.gears != null && character.gears.gear != null) { + const gearsList = getArray(character.gears.gear); + gearsList.forEach((gear) => { + const id = generateRowID(); + try { + if (gear.name.toLowerCase() === "fake sin") { + const section = `repeating_lifestyle_${id}`; + update[`${section}_name`] = `${gear.extra} (rating ${gear.rating})`; + update[`${section}_lifestyle`] = (gear.location != null) ? `${gear.location},` : ""; + update[`${section}_flag`] = "display"; + + update[`${section}_licenses`] = ""; + if (gear.children && gear.children.gear != null) { + const childrenList = getArray(gear.children.gear); + childrenList.forEach((child) => { + update[`${section}_licenses`] += `${child.extra} (rating ${child.rating}), `; + }); + } + } else if (gear.category.toLowerCase().includes("cyberdeck")) { + setCyberdeck(gear); + } else if (gear.category.toLowerCase().includes("programs")) { + const section = `repeating_programs_${id}`; + update[`${section}_program`] = gear.name; + update[`${section}_flag`] = "display"; + } else { + const section = `repeating_items_${id}`; + let gearNotes = ""; + update[`${section}_item`] = gear.name; + update[`${section}_flag`] = "display"; + update[`${section}_rating`] = (gear.rating > 0) ? gear.rating : ""; + + //NAME + update[`${section}_item`] += (gear.name.includes("Ammo:")) ? ` (${gear.extra}) ` : ""; + update[`${section}_item`] += (gear.qty > 1) ?` x${gear.qty}` : ""; + + //NOTES + gearNotes += (gear.location != null) ? `Location: ${gear.location},` : ""; + gearNotes += (gear.qty != null) ? ` Qty: ${gear.qty},` : ""; + gearNotes += (gear.extra != null) ? ` Extra: ${gear.extra},` : ""; + + if (gear.children && gear.children != null) { + const childrenList = getArray(gear.children.gear); + childrenList.forEach((child) => { + gearNotes += `${child.name}, `; + }); + }; + + update[`${section}_notes`] = gearNotes; + } + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(gear.name), error); + }; + }); + }; + + //SPELL KEYS + const spellKeys = { + "category": "category" + ,"name": "name" + ,"range": "range" + }; + + // SPELLS + if (character.spells != null && character.spells.spell != null) { + const spellsList = getArray(character.spells.spell); + spellsList.forEach((spell) => { + try { + const id = generateRowID(); + const type = (spell.alchemy.toUpperCase() === "TRUE") ? "preps" : "spell"; + const section = `repeating_${type}_${id}`; + + Object.keys(spellKeys).forEach((key) => { + if (spell[`${key}`] && spell[`${key}`] != null) { + update[`${section}_${spellKeys[key]}`] = spell[`${key}`]; + }; + }); + + // FLAG + update[`${section}_flag`] = "display"; + + // TYPE + update[`${section}_type`] = (spell.type === "P") ? "Physical" : "Mana"; + + // DRAIN + update[`${section}_drain`] = spell.dv.replace("F", ""); + + // SET COMBAT TYPE + if (spell.category.toUpperCase() === "COMBAT") { + const combatType = spell.descriptors.toUpperCase(); + update[`${section}_combat_type`] = + (combatType.includes("DIRECT")) ? "Direct, Force " : + (combatType.includes("INDIRECT")) ? "Indirect, Force ?{Force|}, AP -" : + "Force "; + update[`${section}_damage`] = spell.damage; + } + + // DURATION + const duration = spell.duration.toUpperCase(); + update[`${section}_duration`] = + (duration === "S") ? "Sustained" : + (duration === "I") ? "Instant" : + (duration === "P") ? "Permanent" : + "Special"; + + // NOTES + if (spell.descriptors != null) { + update[`${section}_notes`] = spell.descriptors; + } + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(spell.name), error); + } + }); + } + + // POWERS + if (character.powers != null && character.powers.power != null) { + const powersList = getArray(character.powers.power); + powersList.forEach((power) => { + try { + const id = generateRowID(); + const section = `repeating_powers_${id}`; + + update[`${section}_name`] = (power.fullname != null) ? power.fullname : power.name; + update[`${section}_rating`] = power.rating; + + if (power.totalpoints != null) { + update[`${section}_notes`] = `Total Points: ${power.totalpoints}.`; + } + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(power.name), error); + } + }); + } + + // LIFESTYLES + if (character.lifestyles != null && character.lifestyles.lifestyle != null) { + const lifestyleList = getArray(character.lifestyles.lifestyle); + lifestyleList.forEach((lifestyle) => { + try { + const id = generateRowID(); + const section = `repeating_lifestyle_${id}`; + + update[`${section}_name`] = lifestyle.name; + update[`${section}_flag`] = "display"; + + if (lifestyle.qualities != null && lifestyle.qualities.quality) { + const lqualitiesList = getArray(lifestyle.qualities.quality); + let lifestyleQualities = ""; + lqualitiesList.forEach((quality) => { + try { + lifestyleQualities += (quality.fullname != null) ? quality.fullname : quality.name; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(quality.name), error); + } + }); + + update[`${section}_lifestyle`] = lifestyleQualities; + } + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(lifestyle.name), error); + } + }); + } + + // CONTACTS + if (character.contacts != null && character.contacts.contact != null) { + const contactList = getArray(character.contacts.contact); + contactList.forEach((contact) => { + try { + const id = generateRowID(); + const section = `repeating_contacts_${id}`; + let contactNotes = ""; + + update[`${section}_name`] = (contact.role != null) ? `${contact.name} (${contact.role})` : `${contact.name}`; + update[`${section}_loyalty`] = contact.loyalty; + update[`${section}_connection`] = contact.connection; + update[`${section}_flag`] = "display"; + + ["metatype", "sex", "age", "preferredpayment", "hobbiesvice", "personallife"].forEach((detail) => { + if (contact[`${detail}`] != null) { + contactNotes += `${contact[`${detail}`]}. ` + } + }); + + update[`${section}_notes`] = contactNotes; + + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(contact.name), error); + } + }); + } + + // VEHICLE KEYS + const vehicleKeys = { + "handling": "handling_base" + ,"accel": "acceleration_base" + ,"speed": "speed_base" + ,"body": "body_base" + ,"armor": "armor_base" + ,"sensor": "sensor_base" + ,"pilot": "pilot_base" + }; + + // VEHICLES + /* if (character.vehicles != null && character.vehicles.vehicle != null) { + const vehicleList = getArray(character.vehicles.vehicle); + vehicleList.forEach((vehicle) => { + try { + const id = generateRowID(); + const section = `repeating_vehicle_${id}`; + let vehicleNotes = ""; + + update[`${section}_name`] = vehicle.name; + update[`${section}_flag`] = "display"; + Object.keys(vehicleKeys).forEach((key) => { + if (vehicle[`${key}`] && vehicle[`${key}`] != null) { + update[`${section}_${vehicleKeys[`${key}`]}`] = + (vehicle[`${key}`].includes("/")) ? getSplitNum(vehicle[`${key}`]) : + vehicle[`${key}`]; + }; + }); + + ["attack", "sleaze", "dataprocessing", "firewall", "devicerating"].forEach((matrixAttr) => { + vehicleNotes += (matrixAttr === "devicerating") ? `Device Rating (${vehicle[`${matrixAttr}`]}) ` : + (matrixAttr === "firewall") ? `${vehicle[`${matrixAttr}`]} ` : `${vehicle[`${matrixAttr}`]}/`; + }); + + if (vehicle.mods != null && vehicle.mods.mod != null) { + const vehicleModsList = getArray(vehicle.mods.mod); + vehicleNotes += "Mods: " + vehicleModsList.forEach((mod) => { + vehicleNotes += (mod.fullname != null) ? `${mod.fullname}, ` : `${mod.name}, `; + }); + } + + if (vehicle.gears != null && vehicle.gears.gear != null) { + const vehicleGearList = getArray(vehicle.gears.gear); + vehicleNotes += "Gear: " + vehicleGearList.forEach((gear) => { + if (gear.children != null && gear.children.gear != null) { + const vehicleChildrenList = getArray(gear.children.gear); + let vehicleChildrenNotes = ""; + vehicleChildrenList.forEach((child) => { + vehicleChildrenNotes += `${child.name}, `; + }); + vehicleNotes += `${gear.name} (${vehicleChildrenNotes}), `; + } else { + vehicleNotes += `${gear.name}, `; + }; + }); + } + + update[`${section}_notes`] = vehicleNotes; + } catch (error) { + setText["error"] += addFeedback(JSON.stringify(vehicle.name), error); + } + }); + } + + */ + + setCharmancerText(setText); + resolve(update); + + console.log(update); + }).then((update) => { + let setText = { + "hidden": "" + }; + + //GENERATE INPUTS TO SAVE THE DATA + Object.keys(update).forEach((key) => { + const label = (key.includes("_")) ? key.replace("_", " ") : key; + const input = ``; + setText[`hidden`] += (setText[`hidden`].includes(input)) ? "" : input; + + const feedbackLabel = + (key.includes("repeating")) ? key.split("_")[3] : + (key.includes("_")) ? key.replace("_", " ") : key; + (feedbackLabel === "flag" || feedbackLabel === "display") ? false : setText["results"] += addFeedback(feedbackLabel, update[key]); + }); + + setAttrs(update); + + return setText; + }).then((result) => { + setCharmancerText(result); + }); + }; + + // APPLY Changes + on("mancerfinish:apply", () => { + const mancerData = getCharmancerData(); + const mancerValues = mancerData["importer"].values; + + Object.keys(mancerValues).forEach((key) => { + if (key == "builder" || key == "jsonData" || key == "hidden") { + delete mancerValues[key]; + }; + }); + + clean(); + setAttrs(mancerValues, () => { + console.log(`%c APPLY IMPORTER:`, "color: blue; font-weight:bold"); + console.log(mancerValues); + deleteCharmancerData(["importer"]); + finishCharactermancer(); + }); + }); + + + +This will import character JSON data generated from Chummer 5. This is an experimental feature which is not 100% completed. It will save you a bit of time when bringing a character to Roll20.
+Enter a JSON string in the text area above.
Import button has not been pressed yet.
Import button has not been pressed yet.
After reviewing the data above, click Apply. This will overwrite data on the sheet where applicable.
+
+