diff --git a/TODO b/TODO new file mode 100644 index 0000000..ab0a4aa --- /dev/null +++ b/TODO @@ -0,0 +1,10 @@ +- Add upgrade points count for Crew and Character!!! +- Add values to classes default attributes +- Add friends-and-foes +- Add XP gain cheetsheet for each class +- Add Class items to Compendium +- Add brush strokes if needed for styling (https://www.onlygfx.com/55-dry-brush-stroke-png-transparent/) +- Add City map as Compendium or separate button +- Add die calculator +- Add Crew Template shortcut +- Add Crew XP cheetsheet \ No newline at end of file diff --git a/module/actor-sheet.js b/module/actor-sheet.js index 74f5e7b..22cb24a 100644 --- a/module/actor-sheet.js +++ b/module/actor-sheet.js @@ -91,68 +91,9 @@ export class BladesActorSheet extends ActorSheet { return this.object.update(formData); } - /** @override */ + /** override */ _getFormData(form) { - const FD = new FormData(form); - const dtypes = {}; - const editorTargets = Object.keys(this.editors); - - // Always include checkboxes - for ( let el of form.elements ) { - if ( !el.name ) continue; - - // Handle Radio groups - if ( form[el.name] instanceof RadioNodeList ) { - - const inputs = Array.from(form[el.name]); - if ( inputs.every(i => i.disabled) ) FD.delete(k); - - let values = ""; - let type = "Checkboxes"; - values = inputs.map(i => i.checked ? i.value : false).filter(i => i); - - FD.set(el.name, JSON.stringify(values)); - dtypes[el.name] = 'Radio'; - } - - // Remove disabled elements - else if ( el.disabled ) FD.delete(el.name); - - // Checkboxes - else if ( el.type == "checkbox" ) { - FD.set(el.name, el.checked || false); - dtypes[el.name] = "Boolean"; - } - - // Include dataset dtype - else if ( el.dataset.dtype ) dtypes[el.name] = el.dataset.dtype; - } - - // Process editable images - for ( let img of form.querySelectorAll('img[data-edit]') ) { - if ( img.getAttribute("disabled") ) continue; - let basePath = window.location.origin+"/"; - if ( ROUTE_PREFIX ) basePath += ROUTE_PREFIX+"/"; - FD.set(img.dataset.edit, img.src.replace(basePath, "")); - } - - // Process editable divs (excluding MCE editors) - for ( let div of form.querySelectorAll('div[data-edit]') ) { - if ( div.getAttribute("disabled") ) continue; - else if ( editorTargets.includes(div.dataset.edit) ) continue; - FD.set(div.dataset.edit, div.innerHTML.trim()); - } - - // Handle MCE editors - Object.values(this.editors).forEach(ed => { - if ( ed.mce ) { - FD.delete(ed.mce.id); - if ( ed.changed ) FD.set(ed.target, ed.mce.getContent()); - } - }); - - // Record target data types for casting - FD._dtypes = dtypes; + const FD = BladesHelpers.getFormDataHelper(form, this.editors); return FD; } @@ -189,7 +130,8 @@ export class BladesActorSheet extends ActorSheet { } if (item) { - this._removeDuplicatedItemType(item.data.type); + const actor = this.actor; + BladesHelpers.removeDuplicatedItemType(item.data.type, actor); } // Call parent on drop logic @@ -199,25 +141,4 @@ export class BladesActorSheet extends ActorSheet { } /* -------------------------------------------- */ - - /** - * Removes a duplicate item type from charlist. - * - * @param {string} item_type - */ - _removeDuplicatedItemType(item_type) { - - const actor = this.actor; - let distinct_types = ["class", "heritage", "background", "vice"]; - - if (distinct_types.indexOf(item_type) >= 0) { - actor.items.forEach(i => { - if (i.data.type === item_type) { - actor.deleteOwnedItem(i.id); - } - }); - } - } - - /* -------------------------------------------- */ } diff --git a/module/blades-helpers.js b/module/blades-helpers.js new file mode 100644 index 0000000..a91c103 --- /dev/null +++ b/module/blades-helpers.js @@ -0,0 +1,92 @@ +export class BladesHelpers { + + /** + * Removes a duplicate item type from charlist. + * + * @param {string} item_type + * @param {Actor} actor + */ + static removeDuplicatedItemType(item_type, actor) { + + let distinct_types = ["class", "heritage", "background", "vice", "crew_type"]; + + if (distinct_types.indexOf(item_type) >= 0) { + actor.items.forEach(i => { + if (i.data.type === item_type) { + actor.deleteOwnedItem(i.id); + } + }); + } + } + + /** + * _getFormData() override helper. + * @param {*} form + */ + static getFormDataHelper(form, editors) { + + const FD = new FormData(form); + const dtypes = {}; + const editorTargets = Object.keys(editors); + + // Always include checkboxes + for ( let el of form.elements ) { + if ( !el.name ) continue; + + // Handle Radio groups + if ( form[el.name] instanceof RadioNodeList ) { + + const inputs = Array.from(form[el.name]); + if ( inputs.every(i => i.disabled) ) FD.delete(k); + + let values = ""; + let type = "Checkboxes"; + values = inputs.map(i => i.checked ? i.value : false).filter(i => i); + + FD.set(el.name, JSON.stringify(values)); + dtypes[el.name] = 'Radio'; + } + + // Remove disabled elements + else if ( el.disabled ) FD.delete(el.name); + + // Checkboxes + else if ( el.type == "checkbox" ) { + FD.set(el.name, el.checked || false); + dtypes[el.name] = "Boolean"; + } + + // Include dataset dtype + else if ( el.dataset.dtype ) dtypes[el.name] = el.dataset.dtype; + } + + // Process editable images + for ( let img of form.querySelectorAll('img[data-edit]') ) { + if ( img.getAttribute("disabled") ) continue; + let basePath = window.location.origin+"/"; + if ( ROUTE_PREFIX ) basePath += ROUTE_PREFIX+"/"; + FD.set(img.dataset.edit, img.src.replace(basePath, "")); + } + + // Process editable divs (excluding MCE editors) + for ( let div of form.querySelectorAll('div[data-edit]') ) { + if ( div.getAttribute("disabled") ) continue; + else if ( editorTargets.includes(div.dataset.edit) ) continue; + FD.set(div.dataset.edit, div.innerHTML.trim()); + } + + // Handle MCE editors + Object.values(editors).forEach(ed => { + if ( ed.mce ) { + FD.delete(ed.mce.id); + if ( ed.changed ) FD.set(ed.target, ed.mce.getContent()); + } + }); + + // Record target data types for casting + FD._dtypes = dtypes; + return FD; + + } + +} diff --git a/module/blades.js b/module/blades.js index 4aab67a..df1d401 100644 --- a/module/blades.js +++ b/module/blades.js @@ -6,14 +6,17 @@ // Import Modules import { preloadHandlebarsTemplates } from "./templates.js"; +import { BladesHelpers } from "./blades-helpers.js"; import { BladesItem } from "./item.js"; import { BladesItemSheet } from "./item-sheet.js"; import { BladesActorSheet } from "./actor-sheet.js"; +import { BladesCrewSheet } from "./crew-sheet.js"; + +window.BladesHelpers = BladesHelpers; /* -------------------------------------------- */ /* Foundry VTT Initialization */ /* -------------------------------------------- */ - Hooks.once("init", async function() { console.log(`Initializing Blades In the Dark System`); @@ -26,6 +29,7 @@ Hooks.once("init", async function() { // Register sheet application classes Actors.unregisterSheet("core", ActorSheet); Actors.registerSheet("blades", BladesActorSheet, { types: ["character"], makeDefault: true }); + Actors.registerSheet("blades", BladesCrewSheet, { types: ["crew"], makeDefault: true }); Items.unregisterSheet("core", ItemSheet); Items.registerSheet("blades", BladesItemSheet, {makeDefault: true}); preloadHandlebarsTemplates(); @@ -63,4 +67,9 @@ Hooks.once("init", async function() { return (a === b) ? options.fn(this) : ''; }); + // NotEquals handlebar. + Handlebars.registerHelper('noteq', function (a, b, options) { + return (a !== b) ? options.fn(this) : ''; + }); + }); diff --git a/module/crew-sheet.js b/module/crew-sheet.js new file mode 100644 index 0000000..b65d591 --- /dev/null +++ b/module/crew-sheet.js @@ -0,0 +1,115 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ +export class BladesCrewSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ["blades-in-the-dark", "sheet", "actor"], + template: "systems/blades-in-the-dark/templates/crew-sheet.html", + width: 700, + height: 970 + }); + } + + /* -------------------------------------------- */ + + /** @override */ + getData() { + const data = super.getData(); + + // Calculate Load + let loadout = 0; + data.items.forEach(i => {loadout += (i.type === "item") ? parseInt(i.data.load) : 0}); + data.data.loadout = loadout; + console.log("DATA"); + console.log(data); + return data; + } + + /* -------------------------------------------- */ + + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) return; + + // Update Inventory Item + html.find('.item-body').click(ev => { + const element = $(ev.currentTarget).parents(".item"); + const item = this.actor.getOwnedItem(element.data("itemId")); + item.sheet.render(true); + }); + + // Delete Inventory Item + html.find('.item-delete').click(ev => { + const element = $(ev.currentTarget).parents(".item"); + this.actor.deleteOwnedItem(element.data("itemId")); + element.slideUp(200, () => this.render(false)); + }); + } + + /* -------------------------------------------- */ + + /** @override */ + _updateObject(event, formData) { + + // Update the Actor + return this.object.update(formData); + } + + /** override */ + _getFormData(form) { + const FD = BladesHelpers.getFormDataHelper(form, this.editors); + return FD; + } + + /* -------------------------------------------- */ + + /** @override */ + async _onDrop (event) { + + event.preventDefault(); + + // Get dropped data + let data; + let item; + try { + data = JSON.parse(event.dataTransfer.getData('text/plain')); + } catch (err) { + return false; + } + + // Add only Items. + if (data.type === "Item") { + + // Import from Compendium + if (data.pack) { + const pack = game.packs.find(p => p.collection === data.pack); + await pack.getEntity(data.id).then(ent => { + item = ent; + }); + } + // Get from Items list. + else { + // Class must be distinct. + item = game.items.get(data.id); + } + + if (item) { + const actor = this.actor; + BladesHelpers._removeDuplicatedItemType(item.data.type, actor); + } + + // Call parent on drop logic + return super._onDrop(event); + } + + } + + /* -------------------------------------------- */ +} diff --git a/module/item-sheet.js b/module/item-sheet.js index 83d1851..c5b9a81 100644 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -8,8 +8,8 @@ export class BladesItemSheet extends ItemSheet { static get defaultOptions() { return mergeObject(super.defaultOptions, { classes: ["blades-in-the-dark", "sheet", "item"], - width: 520, - height: 480, + width: 900, + height: 900, tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}] }); } @@ -82,4 +82,12 @@ export class BladesItemSheet extends ItemSheet { } } /* -------------------------------------------- */ + + /** override */ + _getFormData(form) { + const FD = BladesHelpers.getFormDataHelper(form, this.editors); + return FD; + } + + /* -------------------------------------------- */ } diff --git a/styles/blades.css b/styles/blades.css index 518712c..e78623a 100644 --- a/styles/blades.css +++ b/styles/blades.css @@ -450,5 +450,77 @@ * .coins.coins-stashed { width: 190px; } +* #turf-list { + display: flex; + flex-direction: column; +} +* #turf-list .turf-row { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +* #turf-list .turf-row *:first-child { + margin-left: 0px; +} +* #turf-list .turf-row *:last-child { + margin-right: 0px; +} +* #turf-list .turf-row .turf-block { + width: 120px; + height: 100px; + background-color: lightgray; + position: relative; + margin: 20px; + flex-grow: initial; +} +* #turf-list .turf-row .turf-block .connector { + position: absolute; + display: block; + background-color: red; +} +* #turf-list .turf-row .turf-block .connector.right, * #turf-list .turf-row .turf-block .connector.left { + width: 40px; + height: 20px; + top: 40px; +} +* #turf-list .turf-row .turf-block .connector.left { + left: -40px; +} +* #turf-list .turf-row .turf-block .connector.right { + left: 120px; +} +* #turf-list .turf-row .turf-block .connector.top, * #turf-list .turf-row .turf-block .connector.bottom { + height: 40px; + width: 20px; + left: 50px; +} +* #turf-list .turf-row .turf-block .connector.top { + top: -40px; +} +* #turf-list .turf-row .turf-block .connector.bottom { + top: 100px; +} +* #turf-list .turf-row .turf-block.turf-selected { + background-color: gray; +} +* #turf-list .turf-row .turf-block textarea { + border: none; + background: none; + resize: none; + width: 120px; +} +* #turf-list .turf-row .turf-block input[type=text] { + border: none; + border-radius: 0px; +} +* #turf-list .turf-row .turf-block input[type=checkbox] { + transform: scale(1); +} +* #turf-list .turf-row .turf-block .name-box { + position: absolute; + top: 10px; + left: 10px; + margin: 0px; +} /*# sourceMappingURL=style.css.map */ diff --git a/template.json b/template.json index 4f76306..9f2e72b 100644 --- a/template.json +++ b/template.json @@ -60,58 +60,32 @@ "sway": [0] } } - }, - "classes": { - "cutter": { - "skills": { - "skirmish": 2, - "command": 1 - } - }, - "hound": { - "skills": { - "hunt": 2, - "survey": 1 - } - }, - "leech": { - "skills": { - "tinker": 2, - "wreck": 1 - } - }, - "lurk": { - "skills": { - "finesse": 1, - "prowl": 2 - } - }, - "slide": { - "skills": { - "consort": 1, - "sway": 2 - } - }, - "spider": { - "skills": { - "study": 1, - "consort": 2 - } - }, - "whisper": { - "skills": { - "study": 1, - "attune": 2 - } - } } + }, + "crew": { + "name": "", + "reputation": [0], + "lair": "", + "tier": [0], + "deity": "", + "hold": [], + "experience": [0], + "hold_types": ["weak", "strong"], + "coins": [0], + "vault": [0], + "turfs": [8] } }, "Item": { - "types": ["item", "class", "ability", "heritage", "background", "vice"], + "types": ["item", "class", "ability", "heritage", "background", "vice", "crew_upgrade", "cohort", "crew_type"], "templates": { "default": { "description": "" + }, + "ability": { + "description": "", + "class": "", + "price": 1 } }, "item": { @@ -138,9 +112,10 @@ "class_items": [] }, "ability": { - "templates": ["default"], - "class": "", - "price": 1 + "templates": ["ability"] + }, + "crew_upgrade": { + "templates": ["ability"] }, "heritage": { "templates": ["default"] @@ -150,6 +125,106 @@ }, "vice": { "templates": ["default"] + }, + "cohort": { + "templates": ["default"], + "type": [], + "cohort_types": ["gang", "expert"] + }, + "crew_type": { + "description": "", + "turfs": { + "1": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "2": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "3": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "4": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "5": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "6": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "7": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "8": { + "name": "Lair", + "value": 1, + "description": "", + "connects": [] + }, + "9": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "10": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "11": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "12": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "13": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "14": { + "name": "", + "value": "", + "description": "", + "connects": [] + }, + "15": { + "name": "", + "value": "", + "description": "", + "connects": [] + } + } } } } diff --git a/templates/crew-sheet.html b/templates/crew-sheet.html new file mode 100644 index 0000000..632a5e2 --- /dev/null +++ b/templates/crew-sheet.html @@ -0,0 +1,10 @@ +
+ +
+
+ + +
+
+ +
diff --git a/templates/items/crew_type.html b/templates/items/crew_type.html new file mode 100644 index 0000000..3cb044f --- /dev/null +++ b/templates/items/crew_type.html @@ -0,0 +1,41 @@ +
+
+ +
+

+
+
+ + + +
+
+ {{#each data.turfs as |turf id|}} +
+ {{#each turf.connects as |connect|}} +
+ {{/each}} + {{#noteq id "8"}} + + {{/noteq}} + + {{#multiboxes turf.connects}} + + + + + {{/multiboxes}} + + + +
+ {{#eq id "5"}} +
+ {{/eq}} + {{#eq id "10"}} +
+ {{/eq}} + {{/each}} +
+
+