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 @@
- + Shadowrun black and metallic logo

Character

Player

@@ -20,7 +21,7 @@
- +
@@ -224,7 +225,7 @@
+ - d6 = + d6 = + d6
@@ -407,7 +408,7 @@
-
+
@@ -423,9 +424,21 @@ +
- x + + +
@@ -567,11 +580,14 @@ +
- x + + +
@@ -616,11 +632,14 @@ +
- x + + +
@@ -665,6 +684,7 @@ +
@@ -699,7 +719,7 @@
@@ -727,7 +747,7 @@
- +

Condition Monitor

@@ -806,7 +826,7 @@
- +
@@ -827,10 +847,10 @@

Quality

Type

- + - -
+ +
x +
@@ -877,6 +897,7 @@
+
@@ -907,7 +928,7 @@
- x +
@@ -948,7 +969,7 @@
- Exotics are not linked to skills. Use modifier Dice input to set skill dice. + Exotics are not linked to skills. Use Modifier Dice input to set skill dice.
@@ -981,6 +1002,7 @@
+
@@ -988,7 +1010,7 @@
- x +
@@ -999,7 +1021,7 @@
- +
- Exotic weapons are currently not linked to the skills section. Use modifier Dice input to set dicepool. + Exotic weapons are currently not linked to the skills section. Use Modifier Dice input to set dicepool.
@@ -1036,6 +1058,7 @@
+
@@ -1049,7 +1072,7 @@

Armor

Rating

Acid

Cold

Elec.

Fire

Radi.

- x +
@@ -1098,6 +1121,7 @@
+
@@ -1113,7 +1137,7 @@

Augementation

Rating

Essence

- x +
@@ -1132,6 +1156,7 @@
+
@@ -1146,7 +1171,7 @@

Item

Rating

- x +
@@ -1163,6 +1188,7 @@
+
@@ -1193,7 +1219,7 @@
- x +
@@ -1268,11 +1294,12 @@
+
- x +
@@ -1344,11 +1371,12 @@
+
- x +
@@ -1375,12 +1403,13 @@
+
- +

Adept Powers or Other Abilities

@@ -1389,7 +1418,7 @@

Name

Rating

- x +
@@ -1406,6 +1435,7 @@
+
@@ -1561,7 +1591,7 @@

Complex Form

Target

Duration

Fade

- x +
@@ -1609,6 +1639,7 @@
+
@@ -1627,7 +1658,7 @@

Fake IDs / Related Lifestyles / Funds / Licenses

- x +

Fake ID

@@ -1638,12 +1669,14 @@
- - - - - +
+
+ + + +
+
@@ -1657,7 +1690,7 @@

Name

Loyalty

Connection

Favor

- x +
@@ -1678,6 +1711,7 @@
+
@@ -1690,13 +1724,12 @@
- x +
- @@ -1747,7 +1780,9 @@
-

Base

Modifier

Temp

Base

Modifier

Temp

+

Base

Modifier

+

Temp

Base

+

Modifier

Temp

Notes

@@ -1756,25 +1791,66 @@
-

Vehicle

-

Handling

+
+

Vehicle

+
+ +
+
+
+

Handling

+
+ +
+
-

Acceleration

-

Speed

+
+

Acceleration

+
+ +
+
+
+

Speed

+
+ +
+
-

Pilot

-

Body

+
+

Pilot

+
+ +
+
+
+

Body

+
+ +
+
-

Armor

-

Sensor

+
+

Armor

+
+ +
+
+
+

Sensor

+
+ +
+
+
@@ -1793,6 +1869,7 @@
+

Sheet Select


+
+
+

Importer

+ +

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.

+
@@ -2893,7 +2976,7 @@

Programs

- x +

@@ -2908,13 +2991,21 @@
+
- + @@ -2949,7 +3040,6 @@ - @@ -3172,15 +3262,27 @@ }); }); - on("clicked:repeating_quality:quality", (eventinfo) => { - const source = (eventinfo.sourceAttribute).slice(0, -8); + ["active", "knowledge", "language"].forEach((attr) => { + on(`clicked:repeating_${attr}:skill`, (eventinfo) => { + settingsToggle(eventinfo); + }); + }); - getAttrs([`${source}_flag_quality`], (v) => { + ["quality", "martial", "items", "range", "melee", "armor", "spell", "preps", "ritual", "powers", "forms", "vehicle", "augementations", "lifestyle", "contacts", "programs"].forEach((attr) => { + on(`clicked:repeating_${attr}:${attr}`, (eventinfo) => { + settingsToggle(eventinfo); + }); + }); + + const settingsToggle = (eventinfo) => { + const split = eventinfo.sourceAttribute.split("_"); + const source = `${split[0]}_${split[1]}_${split[2]}`; + getAttrs([`${source}_flag`], (v) => { setAttrs({ - [`${source}_flag_quality`]: (v[`${source}_flag_quality`] === "settings") ? "display" : "settings" + [`${source}_flag`]: (v[`${source}_flag`] === "settings") ? "display" : "settings" }); }); - }); + }; on("clicked:whisper", (eventinfo) => { getAttrs([`gm_toggle`], (v) => { @@ -3204,7 +3306,7 @@ [`core`, `arms`, `augs`, `gear`, `magic`, `matrix`, `social`, `vehicle`, `options`].forEach(attr => { on(`clicked:tab_${attr}`, () => { - console.log(`c% The button clicked was tab_${attr} update tab to ${attr}`); + console.log(`%c The button clicked was tab_${attr} update tab to ${attr}`, "color:purple;"); setAttrs({tab: attr}); }); }); @@ -3483,7 +3585,7 @@ //Convert physical into the physical damage track boxes and reverse ['physical', 'stun'].forEach(attr => { - on(`change:${attr} change:${attr}_damage`, function(eventinfo) { + on(`change:${attr} change:${attr}_damage`, (eventinfo) => { getAttrs([`${attr}`, `${attr}_damage`, `${attr}_max`], (v) => { const stat = parseInt(v[`${attr}`]) || 0, dam = parseInt(v[`${attr}_damage`]) || 0, max = parseInt(v[`${attr}_max`]) || 0, source = eventinfo.sourceAttribute; const wou = (source === `${attr}`) ? max - stat : max - dam; @@ -3593,7 +3695,7 @@ update["matrix_mod"] = tot; update["matrix_dice"] = dice; - (mat != 0) ? update["display_matrix_mod"] = `${bas} (${tot})` : tot; + update["display_matrix_mod"] = (mat != 0) ? `${bas} (${tot})` : tot; setAttrs(update); }); @@ -3780,7 +3882,6 @@ //REFACTORED Skills ['repeating_active','repeating_knowledge','repeating_language'].forEach(field => { on(`change:${field}`, (eventinfo) => { - console.table(eventinfo); const source = eventinfo.sourceAttribute; const type = eventinfo.sourceType; @@ -3968,22 +4069,6 @@ }); }); - //Repeating IDs & Lifestyles Section Display - on("change:repeating_lifestyle", () => { - const lifestyle = ["repeating_lifestyle_name", "repeating_lifestyle_lifestyle", "repeating_lifestyle_funds", "repeating_lifestyle_licenses"]; - getAttrs(lifestyle, (v) => { - let string = ""; - lifestyle.forEach((variable) => { - const value = - (v[`${variable}`].length > 0 && v[`${variable}`].length <= 14) ? v[`${variable}`] : - (v[`${variable}`].length > 14) ? v[`${variable}`].slice(0,14) + "..." : - ""; - string += (lifestyle.indexOf(variable) === 0) ? `${value}` : ` / ${value}`; - }); - setAttrs({repeating_lifestyle_display: string}); - }); - }); - //Repeating contacts for PC sheet on("change:repeating_contacts", () => { getAttrs(["repeating_contacts_name"], (v) => { @@ -4003,20 +4088,6 @@ }); }); - //Vehicle displays for PCs - on("change:repeating_vehicle:name", () =>{ - getAttrs(["repeating_vehicle_name", "repeating_vehicle_display_name"], (v) => { - const name = (v.repeating_vehicle_name); - const dis = (v.repeating_vehicle_display_name); - let update = {}; - - let displayName = ((name).length > 0 && (name).length <= 24) ? name : ((name).length > 24 ) ? (name).slice(0, 24) + "..." : " "; - if (dis != displayName) {update["repeating_vehicle_display_name"] = displayName}; - - setAttrs(update); - }); - }); - ['acceleration', 'armor', 'body', 'data_processing', 'handling', 'pilot', 'sensor','speed'].forEach(attr => { // Attach listener on(`change:repeating_vehicle:${attr}_base change:repeating_vehicle:${attr}_modifier change:repeating_vehicle:${attr}_temp`, () => { @@ -4033,9 +4104,984 @@ setAttrs({ [attr]: stat, [`repeating_vehicle_display_${attr}`]: bon === 0 ? bas : `${bas} (${stat})` - }); + }, {silent: true}); }); }); }); + +// CHARACTERMANCER IMPORTER + on("clicked:import_launch", (eventinfo) => { + startCharactermancer("importer"); + }); + + on("page:importer", () => { + setAttrs({builder: "Chummer"}); + setCharmancerText({ + "directions" : chummerDirections + }); + }); + + on("clicked:import", () => { + const mancerData = getCharmancerData(); + const mancerValues = mancerData["importer"].values; + let applyEnabled = true; + let setText = {}; + + // Verify the user entered a JSON in the textarea. If not, provide user feedabck. + if (mancerValues.jsonData) { + const parsedData = JSON.parse(mancerValues.jsonData) || false; + setText[`import-feedback`] = ""; + // There is a value in the textarea! Check to see if its a valid JSON. Provide users feeadback + if (parsedData) { + setText[`import-feedback`] += `

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 = `
    +
  1. In Chummer mark your character as created
  2. +
  3. Go to File -> Export -> Export JSON
  4. +
  5. Copy the JSON in the file
  6. +
  7. Paste it into the textfield below
  8. +
  9. Click the Import button
  10. +
  11. Review the data. Use the checkbox Show Hidden Inputs to fix data.
  12. +
  13. Click Apply to overwrite information on the character sheet
  14. +
`; + + const herolabDirections = ` +
    +
  1. Download the file "HLtoRoll20Export.hl" from: https://drive.google.com/file/d/0B3E3jkp51s8aRmpTU1NMRjRIVkk/view
  2. +
  3. Double click the file. Hero Lab will import it automatically now.
  4. +
  5. Load a character
  6. +
  7. Go to File/Save Custom Output and select the "Roll20 Export"
  8. +
  9. Save the file. It should open automatically in your browser
  10. +
  11. Follow the directions.
  12. +
`; + + on("change:builder", (eventinfo) => { + let setText = {}; + setText[`directions`] = (eventinfo.newValue === "Chummer") ? chummerDirections : herolabDirections; + setCharmancerText(setText); + }); + + const clean = () => { + ['active', 'knowledge', 'language', 'quality', 'martial', + 'range', 'melee', 'weapon', 'armor', 'augementations', + 'items', 'NPCspell', 'spell', 'rituals', 'preps', 'powers', + 'contacts', 'vehicle', 'lifestyle', "programs", "forms"].forEach(group => { + getSectionIDs(`repeating_${group}`, (ids) => { + ids.forEach((id) => { + removeRepeatingRow(`repeating_${group}_${id}`); + }); + }); + }); + }; + + //USEFUL FUNCTIONS + const capitialize = (string) => { + return string.charAt(0).toUpperCase() + string.slice(1); + }; + + const addFeedback = (attribute, value) => { + return `

${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(); + }); + }); + + + + +
+

Shadowrun Fifth Edition Character Importer

+

Setup

+

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.

+

Pick Character Builder

+
+ + +
+
+

Enter JSON Here

+ + + +
+ +

Results

+ +

Enter a JSON string in the text area above.

+
+

Character data to be updated

+

Import button has not been pressed yet.

+

Character data not to be updated

+

Import button has not been pressed yet.

+
+

Show Hidden Inputs

+ + + +
+ +

Complete

+

After reviewing the data above, click Apply. This will overwrite data on the sheet where applicable.

+

Chummer Notes

+ +
+ +
+ +
+ +
+
+
+ + +
+
+

Importing your Shadowrun character...overwriting previous data

+ emerald productions, llc +
+
+
diff --git a/Shadowrun5thEdition/ShadowrunLogo.png b/Shadowrun5thEdition/ShadowrunLogo.png deleted file mode 100644 index 0773340e97..0000000000 Binary files a/Shadowrun5thEdition/ShadowrunLogo.png and /dev/null differ diff --git a/Shadowrun5thEdition/gun.png b/Shadowrun5thEdition/gun.png deleted file mode 100644 index f1566799ae..0000000000 Binary files a/Shadowrun5thEdition/gun.png and /dev/null differ diff --git a/Shadowrun5thEdition/images/EProd_Title_Green.jpg b/Shadowrun5thEdition/images/EProd_Title_Green.jpg new file mode 100644 index 0000000000..5cef8d391e Binary files /dev/null and b/Shadowrun5thEdition/images/EProd_Title_Green.jpg differ diff --git a/Shadowrun5thEdition/translation.json b/Shadowrun5thEdition/translation.json index 77baa8f7b9..ebb98e8b6b 100644 --- a/Shadowrun5thEdition/translation.json +++ b/Shadowrun5thEdition/translation.json @@ -375,5 +375,13 @@ "whisper": "Whisper", "whisperdescription": "Set the default for rolling in public or to gm.", "willpower": "Willpower", - "wounds": "Wounds" + "wounds": "Wounds", + "cancel": "Cancel", + "next": "Next", + "back": "Back", + "import": "Import", + "importer": "Importer", + "importecharacter": "Import Character", + "apply": "Apply", + "importertext": "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." } \ No newline at end of file