- Adds Turfs
- Adds TODO
- Adds BladesHelpers static class with helpers
This commit is contained in:
Peter Varaksin 2020-04-24 18:30:51 +03:00
parent afa763c99f
commit 88779566d7
10 changed files with 487 additions and 134 deletions

10
TODO Normal file
View file

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

View file

@ -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);
}
});
}
}
/* -------------------------------------------- */
}

92
module/blades-helpers.js Normal file
View file

@ -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;
}
}

View file

@ -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) : '';
});
});

115
module/crew-sheet.js Normal file
View file

@ -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);
}
}
/* -------------------------------------------- */
}

View file

@ -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;
}
/* -------------------------------------------- */
}

View file

@ -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 */

View file

@ -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": []
}
}
}
}
}

10
templates/crew-sheet.html Normal file
View file

@ -0,0 +1,10 @@
<form class="{{cssClass}} actor-sheet" autocomplete="off">
<div id="name-alias" class="section">
<div id="name">
<label for="character-name">Name</label>
<input type="text" id="character-name" name="name" value="{{actor.name}}">
</div>
</div>
</form>

View file

@ -0,0 +1,41 @@
<form class="{{cssClass}}" autocomplete="off">
<header class="sheet-header">
<img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}"/>
<div class="header-fields">
<h1 class="charname"><input name="name" type="text" value="{{item.name}}" placeholder="Name"/></h1>
</div>
</header>
<div id="turf-list">
<div class="turf-row">
{{#each data.turfs as |turf id|}}
<div class="turf-block {{#if turf.value}}turf-selected{{/if}}">
{{#each turf.connects as |connect|}}
<div class="connector {{connect}}"></div>
{{/each}}
{{#noteq id "8"}}
<input class="name-box" type="checkbox" name="data.turfs.{{id}}.value" {{checked turf.value}}>
{{/noteq}}
{{#multiboxes turf.connects}}
<input type="checkbox" name="data.turfs.{{id}}.connects" value="left">
<input type="checkbox" name="data.turfs.{{id}}.connects" value="top">
<input type="checkbox" name="data.turfs.{{id}}.connects" value="right">
<input type="checkbox" name="data.turfs.{{id}}.connects" value="bottom">
{{/multiboxes}}
<input type="text" name="data.turfs.{{id}}.name" value="{{turf.name}}">
<textarea name="data.turfs.{{id}}.description">{{turf.description}}</textarea>
</div>
{{#eq id "5"}}
</div><div class="turf-row">
{{/eq}}
{{#eq id "10"}}
</div><div class="turf-row">
{{/eq}}
{{/each}}
</div>
</div>
</form>