Push initial structure
This commit is contained in:
parent
81c79376af
commit
8f67397096
130
module/actor-sheet.js
Normal file
130
module/actor-sheet.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* Extend the basic ActorSheet with some very simple modifications
|
||||||
|
* @extends {ActorSheet}
|
||||||
|
*/
|
||||||
|
export class SimpleActorSheet extends ActorSheet {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
classes: ["worldbuilding", "sheet", "actor"],
|
||||||
|
template: "systems/worldbuilding/templates/actor-sheet.html",
|
||||||
|
width: 600,
|
||||||
|
height: 600,
|
||||||
|
tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}],
|
||||||
|
dragDrop: [{dragSelector: ".item-list .item", dropSelector: null}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getData() {
|
||||||
|
const data = super.getData();
|
||||||
|
data.dtypes = ["String", "Number", "Boolean"];
|
||||||
|
for ( let attr of Object.values(data.data.attributes) ) {
|
||||||
|
attr.isCheckbox = attr.dtype === "Boolean";
|
||||||
|
}
|
||||||
|
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-edit').click(ev => {
|
||||||
|
const li = $(ev.currentTarget).parents(".item");
|
||||||
|
const item = this.actor.getOwnedItem(li.data("itemId"));
|
||||||
|
item.sheet.render(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete Inventory Item
|
||||||
|
html.find('.item-delete').click(ev => {
|
||||||
|
const li = $(ev.currentTarget).parents(".item");
|
||||||
|
this.actor.deleteOwnedItem(li.data("itemId"));
|
||||||
|
li.slideUp(200, () => this.render(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add or Remove Attribute
|
||||||
|
html.find(".attributes").on("click", ".attribute-control", this._onClickAttributeControl.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
setPosition(options={}) {
|
||||||
|
const position = super.setPosition(options);
|
||||||
|
const sheetBody = this.element.find(".sheet-body");
|
||||||
|
const bodyHeight = position.height - 192;
|
||||||
|
sheetBody.css("height", bodyHeight);
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for click events on an attribute control to modify the composition of attributes in the sheet
|
||||||
|
* @param {MouseEvent} event The originating left click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onClickAttributeControl(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const a = event.currentTarget;
|
||||||
|
const action = a.dataset.action;
|
||||||
|
const attrs = this.object.data.data.attributes;
|
||||||
|
const form = this.form;
|
||||||
|
|
||||||
|
// Add new attribute
|
||||||
|
if ( action === "create" ) {
|
||||||
|
const nk = Object.keys(attrs).length + 1;
|
||||||
|
let newKey = document.createElement("div");
|
||||||
|
newKey.innerHTML = `<input type="text" name="data.attributes.attr${nk}.key" value="attr${nk}"/>`;
|
||||||
|
newKey = newKey.children[0];
|
||||||
|
form.appendChild(newKey);
|
||||||
|
await this._onSubmit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing attribute
|
||||||
|
else if ( action === "delete" ) {
|
||||||
|
const li = a.closest(".attribute");
|
||||||
|
li.parentElement.removeChild(li);
|
||||||
|
await this._onSubmit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_updateObject(event, formData) {
|
||||||
|
|
||||||
|
// Handle the free-form attributes list
|
||||||
|
const formAttrs = expandObject(formData).data.attributes || {};
|
||||||
|
const attributes = Object.values(formAttrs).reduce((obj, v) => {
|
||||||
|
let k = v["key"].trim();
|
||||||
|
if ( /[\s\.]/.test(k) ) return ui.notifications.error("Attribute keys may not contain spaces or periods");
|
||||||
|
delete v["key"];
|
||||||
|
obj[k] = v;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Remove attributes which are no longer used
|
||||||
|
for ( let k of Object.keys(this.object.data.data.attributes) ) {
|
||||||
|
if ( !attributes.hasOwnProperty(k) ) attributes[`-=${k}`] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-combine formData
|
||||||
|
formData = Object.entries(formData).filter(e => !e[0].startsWith("data.attributes")).reduce((obj, e) => {
|
||||||
|
obj[e[0]] = e[1];
|
||||||
|
return obj;
|
||||||
|
}, {_id: this.object._id, "data.attributes": attributes});
|
||||||
|
|
||||||
|
// Update the Actor
|
||||||
|
return this.object.update(formData);
|
||||||
|
}
|
||||||
|
}
|
35
module/actor.js
Normal file
35
module/actor.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
|
||||||
|
* @extends {Actor}
|
||||||
|
*/
|
||||||
|
export class SimpleActor extends Actor {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getRollData() {
|
||||||
|
const data = super.getRollData();
|
||||||
|
const shorthand = game.settings.get("worldbuilding", "macroShorthand");
|
||||||
|
|
||||||
|
// Re-map all attributes onto the base roll data
|
||||||
|
if ( !!shorthand ) {
|
||||||
|
for ( let [k, v] of Object.entries(data.attributes) ) {
|
||||||
|
if ( !(k in data) ) data[k] = v.value;
|
||||||
|
}
|
||||||
|
delete data.attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map all items data using their slugified names
|
||||||
|
data.items = this.data.items.reduce((obj, i) => {
|
||||||
|
let key = i.name.slugify({strict: true});
|
||||||
|
let itemData = duplicate(i.data);
|
||||||
|
if ( !!shorthand ) {
|
||||||
|
for ( let [k, v] of Object.entries(itemData.attributes) ) {
|
||||||
|
if ( !(k in itemData) ) itemData[k] = v.value;
|
||||||
|
}
|
||||||
|
delete itemData["attributes"];
|
||||||
|
}
|
||||||
|
obj[key] = itemData;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
115
module/item-sheet.js
Normal file
115
module/item-sheet.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* Extend the basic ItemSheet with some very simple modifications
|
||||||
|
* @extends {ItemSheet}
|
||||||
|
*/
|
||||||
|
export class SimpleItemSheet extends ItemSheet {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
classes: ["worldbuilding", "sheet", "item"],
|
||||||
|
template: "systems/worldbuilding/templates/item-sheet.html",
|
||||||
|
width: 520,
|
||||||
|
height: 480,
|
||||||
|
tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getData() {
|
||||||
|
const data = super.getData();
|
||||||
|
data.dtypes = ["String", "Number", "Boolean"];
|
||||||
|
for ( let attr of Object.values(data.data.attributes) ) {
|
||||||
|
attr.isCheckbox = attr.dtype === "Boolean";
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
setPosition(options={}) {
|
||||||
|
const position = super.setPosition(options);
|
||||||
|
const sheetBody = this.element.find(".sheet-body");
|
||||||
|
const bodyHeight = position.height - 192;
|
||||||
|
sheetBody.css("height", bodyHeight);
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
|
||||||
|
// Everything below here is only needed if the sheet is editable
|
||||||
|
if (!this.options.editable) return;
|
||||||
|
|
||||||
|
// Add or Remove Attribute
|
||||||
|
html.find(".attributes").on("click", ".attribute-control", this._onClickAttributeControl.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for click events on an attribute control to modify the composition of attributes in the sheet
|
||||||
|
* @param {MouseEvent} event The originating left click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onClickAttributeControl(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const a = event.currentTarget;
|
||||||
|
const action = a.dataset.action;
|
||||||
|
const attrs = this.object.data.data.attributes;
|
||||||
|
const form = this.form;
|
||||||
|
|
||||||
|
// Add new attribute
|
||||||
|
if ( action === "create" ) {
|
||||||
|
const nk = Object.keys(attrs).length + 1;
|
||||||
|
let newKey = document.createElement("div");
|
||||||
|
newKey.innerHTML = `<input type="text" name="data.attributes.attr${nk}.key" value="attr${nk}"/>`;
|
||||||
|
newKey = newKey.children[0];
|
||||||
|
form.appendChild(newKey);
|
||||||
|
await this._onSubmit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing attribute
|
||||||
|
else if ( action === "delete" ) {
|
||||||
|
const li = a.closest(".attribute");
|
||||||
|
li.parentElement.removeChild(li);
|
||||||
|
await this._onSubmit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_updateObject(event, formData) {
|
||||||
|
|
||||||
|
// Handle the free-form attributes list
|
||||||
|
const formAttrs = expandObject(formData).data.attributes || {};
|
||||||
|
const attributes = Object.values(formAttrs).reduce((obj, v) => {
|
||||||
|
let k = v["key"].trim();
|
||||||
|
if ( /[\s\.]/.test(k) ) return ui.notifications.error("Attribute keys may not contain spaces or periods");
|
||||||
|
delete v["key"];
|
||||||
|
obj[k] = v;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Remove attributes which are no longer used
|
||||||
|
for ( let k of Object.keys(this.object.data.data.attributes) ) {
|
||||||
|
if ( !attributes.hasOwnProperty(k) ) attributes[`-=${k}`] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-combine formData
|
||||||
|
formData = Object.entries(formData).filter(e => !e[0].startsWith("data.attributes")).reduce((obj, e) => {
|
||||||
|
obj[e[0]] = e[1];
|
||||||
|
return obj;
|
||||||
|
}, {_id: this.object._id, "data.attributes": attributes});
|
||||||
|
|
||||||
|
// Update the Item
|
||||||
|
return this.object.update(formData);
|
||||||
|
}
|
||||||
|
}
|
46
module/simple.js
Normal file
46
module/simple.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* A simple and flexible system for world-building using an arbitrary collection of character and item attributes
|
||||||
|
* Author: Atropos
|
||||||
|
* Software License: GNU GPLv3
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Import Modules
|
||||||
|
import { SimpleActor } from "./actor.js";
|
||||||
|
import { SimpleItemSheet } from "./item-sheet.js";
|
||||||
|
import { SimpleActorSheet } from "./actor-sheet.js";
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Foundry VTT Initialization */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
Hooks.once("init", async function() {
|
||||||
|
console.log(`Initializing Simple Worldbuilding System`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an initiative formula for the system
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
CONFIG.Combat.initiative = {
|
||||||
|
formula: "1d20",
|
||||||
|
decimals: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define custom Entity classes
|
||||||
|
CONFIG.Actor.entityClass = SimpleActor;
|
||||||
|
|
||||||
|
// Register sheet application classes
|
||||||
|
Actors.unregisterSheet("core", ActorSheet);
|
||||||
|
Actors.registerSheet("dnd5e", SimpleActorSheet, { makeDefault: true });
|
||||||
|
Items.unregisterSheet("core", ItemSheet);
|
||||||
|
Items.registerSheet("dnd5e", SimpleItemSheet, {makeDefault: true});
|
||||||
|
|
||||||
|
// Register system settings
|
||||||
|
game.settings.register("worldbuilding", "macroShorthand", {
|
||||||
|
name: "Shortened Macro Syntax",
|
||||||
|
hint: "Enable a shortened macro syntax which allows referencing attributes directly, for example @str instead of @attributes.str.value. Disable this setting if you need the ability to reference the full attribute model, for example @attributes.str.label.",
|
||||||
|
scope: "world",
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
config: true
|
||||||
|
});
|
||||||
|
});
|
139
styles/simple.css
Normal file
139
styles/simple.css
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
.worldbuilding {
|
||||||
|
/* Sheet Tabs */
|
||||||
|
/* Items List */
|
||||||
|
/* Attributes */
|
||||||
|
}
|
||||||
|
.worldbuilding .window-content {
|
||||||
|
height: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
.worldbuilding .sheet-header {
|
||||||
|
height: 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.worldbuilding .sheet-header .profile-img {
|
||||||
|
flex: 0 0 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.worldbuilding .sheet-header .header-fields {
|
||||||
|
flex: 1;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
.worldbuilding .sheet-header h1.charname {
|
||||||
|
height: 50px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
.worldbuilding .sheet-header h1.charname input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.worldbuilding .resource {
|
||||||
|
width: 50%;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 10px;
|
||||||
|
float: left;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.worldbuilding .resource input {
|
||||||
|
width: 100px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
.worldbuilding .tabs {
|
||||||
|
height: 40px;
|
||||||
|
border-top: 1px solid #AAA;
|
||||||
|
border-bottom: 1px solid #AAA;
|
||||||
|
}
|
||||||
|
.worldbuilding .tabs .item {
|
||||||
|
line-height: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.worldbuilding .tabs .item.active {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.worldbuilding .sheet-body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.worldbuilding .sheet-body .tab {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.worldbuilding .editor,
|
||||||
|
.worldbuilding .editor-content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.worldbuilding .item-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 7px 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.worldbuilding .item-list .item {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 24px;
|
||||||
|
padding: 3px 0;
|
||||||
|
border-bottom: 1px solid #BBB;
|
||||||
|
}
|
||||||
|
.worldbuilding .item-list .item img {
|
||||||
|
flex: 0 0 24px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.worldbuilding .item-list .item-name {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.worldbuilding .item-list .item-controls {
|
||||||
|
flex: 0 0 36px;
|
||||||
|
}
|
||||||
|
.worldbuilding .attributes-header {
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px 0;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1px solid #AAA;
|
||||||
|
border-radius: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.worldbuilding .attributes-header .attribute-label {
|
||||||
|
flex: 1.5;
|
||||||
|
}
|
||||||
|
.worldbuilding .attributes-header .attribute-control {
|
||||||
|
flex: 0 0 20px;
|
||||||
|
}
|
||||||
|
.worldbuilding .attributes-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.worldbuilding .attributes-list li > * {
|
||||||
|
margin: 0 3px;
|
||||||
|
height: 28px;
|
||||||
|
line-height: 24px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
border-bottom: 1px solid #AAA;
|
||||||
|
}
|
||||||
|
.worldbuilding .attributes-list a.attribute-control {
|
||||||
|
flex: 0 0 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 28px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.worldbuilding.sheet.actor {
|
||||||
|
min-width: 560px;
|
||||||
|
min-height: 420px;
|
||||||
|
}
|
||||||
|
.worldbuilding.sheet.item {
|
||||||
|
min-width: 460px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
162
styles/simple.less
Normal file
162
styles/simple.less
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
.worldbuilding {
|
||||||
|
.window-content {
|
||||||
|
height: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header {
|
||||||
|
height: 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.profile-img {
|
||||||
|
flex: 0 0 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields {
|
||||||
|
flex: 1;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.charname {
|
||||||
|
height: 50px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
width: 50%;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 10px;
|
||||||
|
float: left;
|
||||||
|
text-align: center;
|
||||||
|
input {
|
||||||
|
width: 100px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sheet Tabs */
|
||||||
|
.tabs {
|
||||||
|
height: 40px;
|
||||||
|
border-top: 1px solid #AAA;
|
||||||
|
border-bottom: 1px solid #AAA;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
line-height: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.active {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-body {
|
||||||
|
overflow: hidden;
|
||||||
|
.tab {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor, .editor-content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Items List */
|
||||||
|
.item-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 7px 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 24px;
|
||||||
|
padding: 3px 0;
|
||||||
|
border-bottom: 1px solid #BBB;
|
||||||
|
|
||||||
|
img {
|
||||||
|
flex: 0 0 24px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-controls {
|
||||||
|
flex: 0 0 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attributes */
|
||||||
|
.attributes-header {
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px 0;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1px solid #AAA;
|
||||||
|
border-radius: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.attribute-label {
|
||||||
|
flex: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-control {
|
||||||
|
flex: 0 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attributes-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li > * {
|
||||||
|
margin: 0 3px;
|
||||||
|
height: 28px;
|
||||||
|
line-height: 24px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
border-bottom: 1px solid #AAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.attribute-control {
|
||||||
|
flex: 0 0 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 28px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.worldbuilding.sheet.actor {
|
||||||
|
min-width: 560px;
|
||||||
|
min-height: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.worldbuilding.sheet.item {
|
||||||
|
min-width: 460px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
22
system.json
Normal file
22
system.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "worldbuilding",
|
||||||
|
"title": "Simple World-Building",
|
||||||
|
"description": "A minimalist game system with very simple Actor and Item models to support free-form system agnostic gameplay.",
|
||||||
|
"version": 0.36,
|
||||||
|
"minimumCoreVersion": "0.5.6",
|
||||||
|
"compatibleCoreVersion": "0.5.7",
|
||||||
|
"templateVersion": 2,
|
||||||
|
"author": "Atropos",
|
||||||
|
"esmodules": ["module/simple.js"],
|
||||||
|
"styles": ["styles/simple.css"],
|
||||||
|
"packs": [],
|
||||||
|
"languages": [],
|
||||||
|
"gridDistance": 5,
|
||||||
|
"gridUnits": "ft",
|
||||||
|
"primaryTokenAttribute": "health",
|
||||||
|
"secondaryTokenAttribute": "power",
|
||||||
|
"url": "https://gitlab.com/foundrynet/worldbuilding/",
|
||||||
|
"manifest": "https://gitlab.com/foundrynet/worldbuilding/raw/master/system.json",
|
||||||
|
"download": "https://gitlab.com/foundrynet/worldbuilding/-/archive/release-036/worldbuilding-release-036.zip",
|
||||||
|
"license": "LICENSE.txt"
|
||||||
|
}
|
28
template.json
Normal file
28
template.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"Actor": {
|
||||||
|
"types": ["character"],
|
||||||
|
"character": {
|
||||||
|
"biography": "",
|
||||||
|
"health": {
|
||||||
|
"value": 10,
|
||||||
|
"min": 0,
|
||||||
|
"max": 10
|
||||||
|
},
|
||||||
|
"power": {
|
||||||
|
"value": 5,
|
||||||
|
"min": 0,
|
||||||
|
"max": 5
|
||||||
|
},
|
||||||
|
"attributes": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"types": ["item"],
|
||||||
|
"item": {
|
||||||
|
"description": "",
|
||||||
|
"quantity": 1,
|
||||||
|
"weight": 0,
|
||||||
|
"attributes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
templates/actor-sheet.html
Normal file
86
templates/actor-sheet.html
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<form class="{{cssClass}}" autocomplete="off">
|
||||||
|
|
||||||
|
{{!-- Sheet Header --}}
|
||||||
|
<header class="sheet-header">
|
||||||
|
<img class="profile-img" src="{{actor.img}}" data-edit="img" title="{{actor.name}}" height="100" width="100"/>
|
||||||
|
<div class="header-fields">
|
||||||
|
<h1 class="charname"><input name="name" type="text" value="{{actor.name}}" placeholder="Name"/></h1>
|
||||||
|
<div class="resource">
|
||||||
|
<input type="text" name="data.health.value" value="{{data.health.value}}" data-dtype="Number"/>
|
||||||
|
<span> / </span>
|
||||||
|
<input type="text" name="data.health.max" value="{{data.health.max}}" data-dtype="Number"/>
|
||||||
|
</div>
|
||||||
|
<div class="resource">
|
||||||
|
<input type="text" name="data.power.value" value="{{data.power.value}}" data-dtype="Number"/>
|
||||||
|
<span> / </span>
|
||||||
|
<input type="text" name="data.power.max" value="{{data.power.max}}" data-dtype="Number"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{{!-- Sheet Tab Navigation --}}
|
||||||
|
<nav class="sheet-tabs tabs" data-group="primary">
|
||||||
|
<a class="item" data-tab="description">Description</a>
|
||||||
|
<a class="item" data-tab="items">Items</a>
|
||||||
|
<a class="item" data-tab="attributes">Attributes</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{!-- Sheet Body --}}
|
||||||
|
<section class="sheet-body">
|
||||||
|
|
||||||
|
{{!-- Biography Tab --}}
|
||||||
|
<div class="tab biography" data-group="primary" data-tab="description">
|
||||||
|
{{editor content=data.biography target="data.biography" button=true owner=owner editable=editable}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Owned Items Tab --}}
|
||||||
|
<div class="tab items" data-group="primary" data-tab="items">
|
||||||
|
<ol class="item-list">
|
||||||
|
{{#each actor.items as |item id|}}
|
||||||
|
<li class="item flexrow" data-item-id="{{item._id}}">
|
||||||
|
<img src="{{item.img}}" title="{{item.name}}" width="24" height="24"/>
|
||||||
|
<h4 class="item-name">{{item.name}}</h4>
|
||||||
|
<div class="item-controls">
|
||||||
|
<a class="item-control item-edit" title="Edit Item"><i class="fas fa-edit"></i></a>
|
||||||
|
<a class="item-control item-delete" title="Delete Item"><i class="fas fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Attributes Tab --}}
|
||||||
|
<div class="tab attributes" data-group="primary" data-tab="attributes">
|
||||||
|
<header class="attributes-header flexrow">
|
||||||
|
<span class="attribute-key">Attribute Key</span>
|
||||||
|
<span class="attribute-value">Value</span>
|
||||||
|
<span class="attribute-label">Label</span>
|
||||||
|
<span class="attribute-dtype">Data Type</span>
|
||||||
|
<a class="attribute-control" data-action="create"><i class="fas fa-plus"></i></a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ol class="attributes-list">
|
||||||
|
{{#each data.attributes as |attr key|}}
|
||||||
|
<li class="attribute flexrow" data-attribute="{{key}}">
|
||||||
|
<input class="attribute-key" type="text" name="data.attributes.{{key}}.key" value="{{key}}"/>
|
||||||
|
{{#if attr.isCheckbox}}
|
||||||
|
<label class="attribute-value checkbox"><input type="checkbox" name="data.attributes.{{key}}.value" {{checked attr.value}}/></label>
|
||||||
|
{{else}}
|
||||||
|
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
|
||||||
|
{{/if}}
|
||||||
|
<input class="attribute-label" type="text" name="data.attributes.{{key}}.label" value="{{attr.label}}"/>
|
||||||
|
<select class="attribute-dtype" name="data.attributes.{{key}}.dtype">
|
||||||
|
{{#select attr.dtype}}
|
||||||
|
{{#each ../dtypes as |t|}}
|
||||||
|
<option value="{{t}}">{{t}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{/select}}
|
||||||
|
</select>
|
||||||
|
<a class="attribute-control" data-action="delete"><i class="fas fa-trash"></i></a>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
|
64
templates/item-sheet.html
Normal file
64
templates/item-sheet.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<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 class="resource">
|
||||||
|
<label>Quantity</label>
|
||||||
|
<input type="text" name="data.quantity" value="{{data.quantity}}" data-dtype="Number"/>
|
||||||
|
</div>
|
||||||
|
<div class="resource">
|
||||||
|
<label>Weight</label>
|
||||||
|
<input type="text" name="data.weight" value="{{data.weight}}" data-dtype="Number"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{{!-- Sheet Tab Navigation --}}
|
||||||
|
<nav class="sheet-tabs tabs" data-group="primary">
|
||||||
|
<a class="item" data-tab="description">Description</a>
|
||||||
|
<a class="item" data-tab="attributes">Attributes</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{!-- Sheet Body --}}
|
||||||
|
<section class="sheet-body">
|
||||||
|
|
||||||
|
{{!-- Description Tab --}}
|
||||||
|
<div class="tab" data-group="primary" data-tab="description">
|
||||||
|
{{editor content=data.description target="data.description" button=true owner=owner editable=editable}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Attributes Tab --}}
|
||||||
|
<div class="tab attributes" data-group="primary" data-tab="attributes">
|
||||||
|
<header class="attributes-header flexrow">
|
||||||
|
<span class="attribute-key">Attribute Key</span>
|
||||||
|
<span class="attribute-value">Value</span>
|
||||||
|
<span class="attribute-label">Label</span>
|
||||||
|
<span class="attribute-dtype">Data Type</span>
|
||||||
|
<a class="attribute-control" data-action="create"><i class="fas fa-plus"></i></a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ol class="attributes-list">
|
||||||
|
{{#each data.attributes as |attr key|}}
|
||||||
|
<li class="attribute flexrow" data-attribute="{{key}}">
|
||||||
|
<input class="attribute-key" type="text" name="data.attributes.{{key}}.key" value="{{key}}"/>
|
||||||
|
{{#if attr.isCheckbox}}
|
||||||
|
<label class="attribute-value checkbox"><input type="checkbox" name="data.attributes.{{key}}.value" {{checked attr.value}}/></label>
|
||||||
|
{{else}}
|
||||||
|
<input class="attribute-value" type="text" name="data.attributes.{{key}}.value" value="{{attr.value}}" data-dtype="{{attr.dtype}}"/>
|
||||||
|
{{/if}}
|
||||||
|
<input class="attribute-label" type="text" name="data.attributes.{{key}}.label" value="{{attr.label}}"/>
|
||||||
|
<select class="attribute-dtype" name="data.attributes.{{key}}.dtype">
|
||||||
|
{{#select attr.dtype}}
|
||||||
|
{{#each ../dtypes as |t|}}
|
||||||
|
<option value="{{t}}">{{t}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{/select}}
|
||||||
|
</select>
|
||||||
|
<a class="attribute-control" data-action="delete"><i class="fas fa-trash"></i></a>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</form>
|
Loading…
Reference in New Issue
Block a user