initial commit

This commit is contained in:
Nick Eimers
2020-06-19 20:14:05 -07:00
commit 6d825f5291
34 changed files with 6068 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# IDE
.idea/
.vs/
# Node Modules
node_modules/
npm-debug.log
+7
View File
@@ -0,0 +1,7 @@
# IDE
.idea/
.vs/
# Node Modules
node_modules/
npm-debug.log
+35
View File
@@ -0,0 +1,35 @@
# DeltaGreen System
This system is a Delta Green (standalone) system based on the Agent's Handbook from Arc Dream Publishing.
# Delta Green Community License
Published by arrangement with the Delta Green Partnership. The intellectual property known as Delta Green is a trademark and copyright owned by the Delta Green Partnership, who has licensed its use here. The contents of this system are free for public use, excepting those elements that are components of the Delta Green intellectual property.
# Foundry VTT License
This code uses the Foundry VTT and its API under the terms of the Limited License Agreement for Module Development.
Foundry VTT is a Copyright of Foundry Gaming, LLC.
# Delta Green Foundry System License
The code (HTML, CSS, Javascript, etc.) in this system and any related information are provided under the MIT license, excepting that which comes via Foundry VTT
excepting that which is otherwise covered by the Delta Green Partnership and/or Arc Dream Publishing.
Copyright 2020 N. Eimers
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Licenses, Citations and Shoutouts
System Base Code
This system is built upon on Matt Smith's (AKA Asacolips) excellent Boilerplate System and corresponding tutorial.
https://gitlab.com/asacolips-projects/foundry-mods/foundryvtt-system-tutorial
DIDACT GOTHIC
The font used in many places is Didact Gothic, by Daniel Johnson and Cyreal.
https://fonts.google.com/specimen/Didact+Gothic?query=did#about
It is licensed and used under the Open Font License.
https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
+281
View File
@@ -0,0 +1,281 @@
@import url("https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Didact+Gothic");
/* Global styles */
.window-app {
font-family: "Didact Gothic", "Roboto", sans-serif;
}
.rollable:hover, .rollable:focus {
color: #000;
text-shadow: 0 0 10px red;
cursor: pointer;
}
.grid,
.grid-2col {
display: grid;
grid-column: span 2 / span 2;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin: 10px 0;
padding: 0;
}
.grid-3col {
grid-column: span 3 / span 3;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-4col {
grid-column: span 4 / span 4;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-5col {
grid-column: span 5 / span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-6col {
grid-column: span 5 / span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-7col {
grid-column: span 7 / span 7;
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.grid-8col {
grid-column: span 8 / span 8;
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.grid-9col {
grid-column: span 9 / span 9;
grid-template-columns: repeat(9, minmax(0, 1fr));
}
.grid-10col {
grid-column: span 10 / span 10;
grid-template-columns: repeat(10, minmax(0, 1fr));
}
.grid-11col {
grid-column: span 11 / span 11;
grid-template-columns: repeat(11, minmax(0, 1fr));
}
.grid-12col {
grid-column: span 12 / span 12;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.flex-group-center,
.flex-group-left,
.flex-group-right {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
text-align: center;
padding: 5px;
border: 1px solid #999;
}
.flex-group-left {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
text-align: left;
}
.flex-group-right {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
text-align: right;
}
.flex-center {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.flex-between {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
/* Styles limited to delta green sheets */
.deltagreen {
font-family: "Didact Gothic", "Roboto", sans-serif;
}
.deltagreen .item-form {
font-family: "Didact Gothic", "Roboto", sans-serif;
}
.deltagreen .sheet-header {
-webkit-box-flex: 0;
-ms-flex: 0 0 300px;
flex: 0 0 300px;
overflow: hidden;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
margin-bottom: 10px;
}
.deltagreen .sheet-header .profile-img {
-webkit-box-flex: 0;
-ms-flex: 0 0 100px;
flex: 0 0 100px;
height: 100px;
margin-right: 10px;
}
.deltagreen .sheet-header .header-fields {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.deltagreen .sheet-header h1.charname {
height: 50px;
padding: 0px;
margin: 5px 0;
border-bottom: 0;
}
.deltagreen .sheet-header h1.charname input {
width: 100%;
height: 100%;
margin: 0;
}
.deltagreen .sheet-tabs {
-webkit-box-flex: 0;
-ms-flex: 0;
flex: 0;
}
.deltagreen .sheet-body,
.deltagreen .sheet-body .tab,
.deltagreen .sheet-body .tab .editor {
height: 100%;
}
.deltagreen .tox .tox-editor-container {
background: #fff;
}
.deltagreen .tox .tox-edit-area {
padding: 0 8px;
}
.deltagreen .resource-label {
font-weight: bold;
text-transform: uppercase;
}
.deltagreen .tabs {
height: 40px;
border-top: 1px solid #AAA;
border-bottom: 1px solid #AAA;
}
.deltagreen .tabs .item {
line-height: 40px;
font-weight: bold;
}
.deltagreen .tabs .item.active {
text-decoration: underline;
text-shadow: none;
}
.deltagreen .items-list {
list-style: none;
margin: 7px 0;
padding: 0;
overflow-y: auto;
}
.deltagreen .items-list .item-header {
font-weight: bold;
}
.deltagreen .items-list .item {
height: 30px;
line-height: 24px;
padding: 3px 0;
border-bottom: 1px solid #BBB;
}
.deltagreen .items-list .item .item-image {
-webkit-box-flex: 0;
-ms-flex: 0 0 24px;
flex: 0 0 24px;
margin-right: 5px;
}
.deltagreen .items-list .item img {
display: block;
}
.deltagreen .items-list .item-name {
margin: 0;
}
.deltagreen .items-list .item-controls {
-webkit-box-flex: 0;
-ms-flex: 0 0 86px;
flex: 0 0 86px;
text-align: right;
}
.deltagreen button.btn-tiny{
height: 26px;
margin-left: 5px;
margin-right: 5px;
font-size: 13px;
padding: 0px;
}
span.breaking-point-label{
justify-content: right;
margin-right: 10px;
width: 100px;
}
span.left-aligned-label{
justify-content: left;
margin-left: 10px;
}
span.max-attribute-value-label{
border: 1px solid rgb(122, 121, 113);
border-radius: 3px;
padding-top: 1px;
padding-bottom: 1px;
padding-left: 3px;
padding-right: 3px;
}
+50
View File
@@ -0,0 +1,50 @@
const gulp = require('gulp');
const prefix = require('gulp-autoprefixer');
const sourcemaps = require('gulp-sourcemaps');
const sass = require('gulp-sass');
/* ----------------------------------------- */
/* Compile Sass
/* ----------------------------------------- */
// Small error handler helper function.
function handleError(err) {
console.log(err.toString());
this.emit('end');
}
const SYSTEM_SCSS = ["scss/**/*.scss"];
function compileScss() {
// Configure options for sass output. For example, 'expanded' or 'nested'
let options = {
outputStyle: 'expanded'
};
return gulp.src(SYSTEM_SCSS)
.pipe(
sass(options)
.on('error', handleError)
)
.pipe(prefix({
cascade: false
}))
.pipe(gulp.dest("./css"))
}
const css = gulp.series(compileScss);
/* ----------------------------------------- */
/* Watch Updates
/* ----------------------------------------- */
function watchUpdates() {
gulp.watch(SYSTEM_SCSS, css);
}
/* ----------------------------------------- */
/* Export Tasks
/* ----------------------------------------- */
exports.default = gulp.series(
compileScss,
watchUpdates
);
exports.css = css;
+1
View File
@@ -0,0 +1 @@
{}
View File
View File
+203
View File
@@ -0,0 +1,203 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class DeltaGreenActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["deltagreen", "sheet", "actor"],
template: "systems/deltagreen/templates/actor/actor-sheet.html",
width: 600,
height: 600,
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";
//}
// Prepare items.
if (this.actor.data.type == 'character') {
this._prepareCharacterItems(data);
}
return data;
}
/**
* Organize and classify Items for Character sheets.
*
* @param {Object} actorData The actor to prepare.
*
* @return {undefined}
*/
_prepareCharacterItems(sheetData) {
const actorData = sheetData.actor;
// Initialize containers.
const gear = [];
const features = [];
const spells = {
0: [],
1: [],
2: [],
3: [],
4: [],
5: [],
6: [],
7: [],
8: [],
9: []
};
// Iterate through items, allocating to containers
// let totalWeight = 0;
for (let i of sheetData.items) {
let item = i.data;
i.img = i.img || DEFAULT_TOKEN;
// Append to gear.
if (i.type === 'item') {
gear.push(i);
}
// Append to features.
else if (i.type === 'feature') {
features.push(i);
}
// Append to spells.
else if (i.type === 'spell') {
if (i.data.spellLevel != undefined) {
spells[i.data.spellLevel].push(i);
}
}
}
// Assign and return
actorData.gear = gear;
actorData.features = features;
actorData.spells = spells;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Add Inventory Item
html.find('.item-create').click(this._onItemCreate.bind(this));
// 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));
});
// Rollable abilities.
html.find('.rollable').click(this._onRoll.bind(this));
html.find('#btnResetBreakingPoint').click(this._resetBreakingPoint.bind(this));
// Drag events for macros.
if (this.actor.owner) {
let handler = ev => this._onDragItemStart(ev);
html.find('li.item').each((i, li) => {
if (li.classList.contains("inventory-header")) return;
li.setAttribute("draggable", true);
li.addEventListener("dragstart", handler, false);
});
}
}
/**
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
* @param {Event} event The originating click event
* @private
*/
_onItemCreate(event) {
event.preventDefault();
const header = event.currentTarget;
// Get the type of item to create.
const type = header.dataset.type;
// Grab any data associated with this control.
const data = duplicate(header.dataset);
// Initialize a default name.
const name = `New ${type.capitalize()}`;
// Prepare the item object.
const itemData = {
name: name,
type: type,
data: data
};
// Remove the type from the dataset since it's in the itemData.type prop.
delete itemData.data["type"];
// Finally, create the item!
return this.actor.createOwnedItem(itemData);
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
_onRoll(event) {
event.preventDefault();
const element = event.currentTarget;
const dataset = element.dataset;
if (dataset.roll) {
let roll = new Roll(dataset.roll, this.actor.data.data);
let key = dataset.label ? dataset.label : '';
let label = dataset.label ? `Rolling ${dataset.label.toUpperCase()}` : '';
let targetVal = "";
// check the 'data-target="something" property to determine how to grab the target for the roll
if(dataset.target === "statistic.x5"){
let stat = this.actor.data.data.statistics[key];
targetVal = stat.x5;
label += `, Target: ${targetVal}`;
}
else if(dataset.target === "sanity"){
targetVal = this.actor.data.data.sanity.value;
label += `, Target: ${targetVal}`;
}
roll.roll().toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label
});
}
}
_resetBreakingPoint(event){
//let actorData = this.actor.data;
console.log('Resetting breaking point...');
//console.log(actorData);
this.actor.data.data.sanity.currentBreakingPoint = this.actor.data.data.sanity.value - this.actor.data.data.statistics.pow.value;
this._render();
}
}
+50
View File
@@ -0,0 +1,50 @@
/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class DeltaGreenActor extends Actor {
/**
* Augment the basic actor data with additional dynamic data.
*/
prepareData() {
super.prepareData();
const actorData = this.data;
const data = actorData.data;
const flags = actorData.flags;
// Make separate methods for each Actor type (character, npc, etc.) to keep
// things organized.
if (actorData.type === 'agent') this._prepareAgentData(actorData);
}
/**
* Prepare Agent type specific data
*/
_prepareAgentData(actorData) {
const data = actorData.data;
// Make modifications to data here. For example:
// Loop through ability scores, and add their modifiers to our sheet output.
for (let [key, statistic] of Object.entries(data.statistics)) {
// the x5 is just whatever the raw statistic is x 5 to turn it into a d100 percentile
statistic.x5 = statistic.value * 5;
}
//console.log(data.statistics);
console.log(actorData);
actorData.data.wp.max = actorData.data.statistics.pow.value;
actorData.data.health.max = Math.ceil((actorData.data.statistics.con.value + actorData.data.statistics.str.value) / 2);
// initialize sanity, don't set these afterwards, as they need to be manually edited
if(actorData.data.sanity.value >= 100){
actorData.data.sanity.value = actorData.data.statistics.pow.x5
actorData.data.sanity.currentBreakingPoint = actorData.data.sanity.value - actorData.data.statistics.pow.value;
};
actorData.data.sanity.max = 99 - actorData.data.skills.unnatural.proficiency;
}
}
+103
View File
@@ -0,0 +1,103 @@
// Import Modules
import { DeltaGreenActor } from "./actor/actor.js";
import { DeltaGreenActorSheet } from "./actor/actor-sheet.js";
import { DeltaGreenItem } from "./item/item.js";
import { DeltaGreenItemSheet } from "./item/item-sheet.js";
Hooks.once('init', async function() {
game.deltagreen = {
DeltaGreenActor,
DeltaGreenItem,
rollItemMacro
};
/**
* Set an initiative formula for the system
* @type {String}
*/
CONFIG.Combat.initiative = {
formula: "@statistics.dex",
decimals: 0
};
// Define custom Entity classes
CONFIG.Actor.entityClass = DeltaGreenActor;
CONFIG.Item.entityClass = DeltaGreenItem;
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("deltagreen", DeltaGreenActorSheet, { makeDefault: true });
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("deltagreen", DeltaGreenItemSheet, { makeDefault: true });
// If you need to add Handlebars helpers, here are a few useful examples:
Handlebars.registerHelper('concat', function() {
var outStr = '';
for (var arg in arguments) {
if (typeof arguments[arg] != 'object') {
outStr += arguments[arg];
}
}
return outStr;
});
Handlebars.registerHelper('toLowerCase', function(str) {
return str.toLowerCase();
});
});
Hooks.once("ready", async function() {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => createDeltaGreenMacro(data, slot));
});
/* -------------------------------------------- */
/* Hotbar Macros */
/* -------------------------------------------- */
/**
* Create a Macro from an Item drop.
* Get an existing item macro if one exists, otherwise create a new one.
* @param {Object} data The dropped data
* @param {number} slot The hotbar slot to use
* @returns {Promise}
*/
async function createDeltaGreenMacro(data, slot) {
if (data.type !== "Item") return;
if (!("data" in data)) return ui.notifications.warn("You can only create macro buttons for owned Items");
const item = data.data;
// Create the macro command
const command = `game.deltagreen.rollItemMacro("${item.name}");`;
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
name: item.name,
type: "script",
img: item.img,
command: command,
flags: { "deltagreen.itemMacro": true }
});
}
game.user.assignHotbarMacro(macro, slot);
return false;
}
/**
* Create a Macro from an Item drop.
* Get an existing item macro if one exists, otherwise create a new one.
* @param {string} itemName
* @return {Promise}
*/
function rollItemMacro(itemName) {
const speaker = ChatMessage.getSpeaker();
let actor;
if (speaker.token) actor = game.actors.tokens[speaker.token];
if (!actor) actor = game.actors.get(speaker.actor);
const item = actor ? actor.items.find(i => i.name === itemName) : null;
if (!item) return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
// Trigger the item roll
return item.roll();
}
+58
View File
@@ -0,0 +1,58 @@
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class DeltaGreenItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["deltagreen", "sheet", "item"],
width: 520,
height: 480,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
});
}
/** @override */
get template() {
const path = "systems/deltagreen/templates/item";
// Return a single sheet for all item types.
// return `${path}/item-sheet.html`;
// Alternatively, you could use the following return statement to do a
// unique item sheet by type, like `weapon-sheet.html`.
return `${path}/item-${this.item.data.type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
getData() {
const data = super.getData();
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;
// Roll handlers, click handlers, etc. would go here.
}
}
+37
View File
@@ -0,0 +1,37 @@
/**
* Extend the basic Item with some very simple modifications.
* @extends {Item}
*/
export class DeltaGreenItem extends Item {
/**
* Augment the basic Item data model with additional dynamic data.
*/
prepareData() {
super.prepareData();
// Get the Item's data
const itemData = this.data;
const actorData = this.actor ? this.actor.data : {};
const data = itemData.data;
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
async roll() {
// Basic template rendering data
const token = this.actor.token;
const item = this.data;
const actorData = this.actor ? this.actor.data.data : {};
const itemData = item.data;
let roll = new Roll('d20+@abilities.str.mod', actorData);
let label = `Rolling ${item.name}`;
roll.roll().toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label
});
}
}
+4277
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
{
"name": "deltagreen",
"version": "1.0.0",
"description": "CSS compiler for the DeltaGreen system",
"scripts": {
"build": "gulp",
"compile": "gulp css",
"watch": "gulp",
"gulp": "gulp"
},
"browserslist": [
"last 3 versions"
],
"author": "John Doe",
"license": "MIT",
"private": true,
"dependencies": {
"gulp": "^4.0.2",
"gulp-autoprefixer": "^7.0.1",
"gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^2.6.5"
}
}
+55
View File
@@ -0,0 +1,55 @@
.item-form {
font-family: $font-primary;
}
.sheet-header {
flex: 0 0 210px;
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;
}
h1.charname {
height: 50px;
padding: 0px;
margin: 5px 0;
border-bottom: 0;
input {
width: 100%;
height: 100%;
margin: 0;
}
}
}
.sheet-tabs {
flex: 0;
}
.sheet-body,
.sheet-body .tab,
.sheet-body .tab .editor {
height: 100%;
}
.tox {
.tox-editor-container {
background: $c-white;
}
.tox-edit-area {
padding: 0 8px;
}
}
+35
View File
@@ -0,0 +1,35 @@
.items-list {
list-style: none;
margin: 7px 0;
padding: 0;
overflow-y: auto;
.item-header {
font-weight: bold;
}
.item {
height: 30px;
line-height: 24px;
padding: 3px 0;
border-bottom: 1px solid #BBB;
.item-image {
flex: 0 0 24px;
margin-right: 5px;
}
img {
display: block;
}
}
.item-name {
margin: 0;
}
.item-controls {
flex: 0 0 86px;
text-align: right;
}
}
+4
View File
@@ -0,0 +1,4 @@
.resource-label {
font-weight: bold;
text-transform: uppercase;
}
+34
View File
@@ -0,0 +1,34 @@
// .tabs {
// .item {
// background: rgba(0, 0, 0, 0.125);
// border: 1px solid;
// border-radius: 4px;
// margin: 0 10px 0;
// padding: 8px 4px;
// &:hover,
// &:focus {
// background: rgba(0, 0, 0, 0.125);
// }
// &.active {
// background: transparent;
// }
// }
// }
.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;
}
}
+18
View File
@@ -0,0 +1,18 @@
// Import utilities.
@import 'utils/typography';
@import 'utils/colors';
@import 'utils/mixins';
@import 'utils/variables';
/* Global styles */
@import 'global/window';
@import 'global/grid';
@import 'global/flex';
/* Styles limited to deltagreen sheets */
.deltagreen {
@import 'components/forms';
@import 'components/resource';
@import 'components/tabs';
@import 'components/items';
}
+9
View File
@@ -0,0 +1,9 @@
.flex-center {
align-items: center;
justify-content: center;
text-align: center;
}
.flex-between {
justify-content: space-between;
}
+79
View File
@@ -0,0 +1,79 @@
.grid,
.grid-2col {
display: grid;
grid-column: span 2 / span 2;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin: 10px 0;
padding: 0;
}
.grid-3col {
grid-column: span 3 / span 3;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-4col {
grid-column: span 4 / span 4;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-5col {
grid-column: span 5 / span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-6col {
grid-column: span 5 / span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-7col {
grid-column: span 7 / span 7;
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.grid-8col {
grid-column: span 8 / span 8;
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.grid-9col {
grid-column: span 9 / span 9;
grid-template-columns: repeat(9, minmax(0, 1fr));
}
.grid-10col {
grid-column: span 10 / span 10;
grid-template-columns: repeat(10, minmax(0, 1fr));
}
.grid-11col {
grid-column: span 11 / span 11;
grid-template-columns: repeat(11, minmax(0, 1fr));
}
.grid-12col {
grid-column: span 12 / span 12;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.flex-group-center,
.flex-group-left,
.flex-group-right {
justify-content: center;
align-items: center;
text-align: center;
padding: 5px;
border: 1px solid #999;
}
.flex-group-left {
justify-content: flex-start;
text-align: left;
}
.flex-group-right {
justify-content: flex-end;
text-align: right;
}
+12
View File
@@ -0,0 +1,12 @@
.window-app {
font-family: $font-primary;
}
.rollable {
&:hover,
&:focus {
color: #000;
text-shadow: 0 0 10px red;
cursor: pointer;
}
}
+2
View File
@@ -0,0 +1,2 @@
$c-white: #fff;
$c-black: #000;
+16
View File
@@ -0,0 +1,16 @@
@mixin element-invisible {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
}
@mixin hide {
display: none;
}
+4
View File
@@ -0,0 +1,4 @@
@import url('https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap');
$font-primary: 'Roboto', sans-serif;
$font-secondary: 'Roboto', sans-serif;
+3
View File
@@ -0,0 +1,3 @@
$padding-sm: 5px;
$padding-md: 10px;
$padding-lg: 20px;
+29
View File
@@ -0,0 +1,29 @@
{
"name": "deltagreen",
"title": "Delta Green",
"description": "Delta Green RPG system for FoundryVTT!",
"version": "1.0.3",
"minimumCoreVersion": "0.6.2",
"compatibleCoreVersion": "0.6.2",
"templateVersion": 2,
"author": "Nick Eimers",
"esmodules": ["module/deltagreen.js"],
"styles": ["css/deltagreen.css"],
"scripts": [],
"packs": [],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"gridDistance": 5,
"gridUnits": "ft",
"primaryTokenAttribute": "health",
"secondaryTokenAttribute": "wp",
"url": "",
"manifest": "",
"download": "",
"license": "LICENSE.txt"
}
+350
View File
@@ -0,0 +1,350 @@
{
"Actor": {
"types": [
"agent",
"npc"
],
"templates": {
"base": {
"health": {
"value": 10,
"min": 0,
"max": 10
},
"wp": {
"value": 10
},
"statistics": {
"str": {
"value": 10,
"distinguishing_feature": ""
},
"con": {
"value": 10,
"distinguishing_feature": ""
},
"dex": {
"value": 10,
"distinguishing_feature": ""
},
"int": {
"value": 10,
"distinguishing_feature": ""
},
"pow": {
"value": 10,
"distinguishing_feature": ""
},
"cha": {
"value": 10,
"distinguishing_feature": ""
}
}
},
"human_skills": {
"skills": {
"accounting": {
"label": "Accounting",
"proficiency": 0,
"failure": false
},
"alertness": {
"label": "Alertness",
"proficiency": 20,
"failure": false
},
"anthropology": {
"label": "Anthropology",
"proficiency": 0,
"failure": false
},
"archeology": {
"label": "Archeology",
"proficiency": 0,
"failure": false
},
"art": [
{
"specialization": "Painting/Dance/Sculpture/etc.",
"proficiency": 0,
"failure": false
}
],
"artillery": {
"label": "Artillery",
"proficiency": 0,
"failure": false
},
"athletics": {
"label": "Artillery",
"proficiency": 30,
"failure": false
},
"bureaucracy": {
"label": "Bureaucracy",
"proficiency": 10,
"failure": false
},
"computer_science": {
"label": "Computer Science",
"proficiency": 0,
"failure": false
},
"craft": [
{
"specialization": "Carpentry/Electrical/Etc.",
"proficiency": 0,
"failure": false
}
],
"criminology": {
"label": "Criminology",
"proficiency": 10,
"failure": false
},
"demolitions": {
"label": "Demolitions",
"proficiency": 0,
"failure": false
},
"disguise": {
"label": "Disguise",
"proficiency": 0,
"failure": false
},
"dodge": {
"label": "Dodge",
"proficiency": 30,
"failure": false
},
"drive": {
"label": "Drive",
"proficiency": 20,
"failure": false
},
"firearms": {
"label": "Firearms",
"proficiency": 20,
"failure": false
},
"first_aid": {
"label": "First Aid",
"proficiency": 10,
"failure": false
},
"forensics": {
"label": "Forensics",
"proficiency": 0,
"failure": false
},
"heavy_machiner": {
"label": "Heavy Machinery",
"proficiency": 10,
"failure": false
},
"heavy_weapons": {
"label": "Heavy Weapons",
"proficiency": 0,
"failure": false
},
"history": {
"label": "History",
"proficiency": 10,
"failure": false
},
"humint": {
"label": "HUMINT",
"proficiency": 10,
"failure": false
},
"law": {
"label": "Law",
"proficiency": 0,
"failure": false
},
"medicine": {
"label": "Medicine",
"proficiency": 0,
"failure": false
},
"melee_weapons": {
"label": "Melee Weapons",
"proficiency": 30,
"failure": false
},
"military_science": {
"label": "Military Science",
"specializations": [
{
"label": "Land/Air/Sea/Etc.",
"proficiency": 0,
"failure": false
}
]
},
"navigate": {
"label": "Navigate",
"proficiency": 10,
"failure": false
},
"occult": {
"label": "Occult",
"proficiency": 10,
"failure": false
},
"persuade": {
"label": "Persuade",
"proficiency": 20,
"failure": false
},
"pilot": {
"label": "Pilot",
"specializations": [
{
"label": "Airplane/Small Boat/Helicopter/Etc.",
"proficiency": 0,
"failure": false
}
]
},
"psychotherapy": {
"label": "Psychotherapy",
"proficiency": 10,
"failure": false
},
"ride": {
"label": "Ride",
"proficiency": 10,
"failure": false
},
"science": {
"label": "Science",
"specializations": [
{
"label": "Biology/Chemistry/Physics/Etc.",
"proficiency": 0,
"failure": false
}
]
},
"search": {
"label": "Search",
"proficiency": 20,
"failure": false
},
"sigint": {
"label": "SIGINT",
"proficiency": 0,
"failure": false
},
"stealth": {
"label": "Stealth",
"proficiency": 10,
"failure": false
},
"survival": {
"label": "Survival",
"proficiency": 10,
"failure": false
},
"swim": {
"label": "Swim",
"proficiency": 20,
"failure": false
},
"unarmed_combat": {
"label": "Unarmed Combat",
"proficiency": 40,
"failure": false
},
"unnatural": {
"label": "Unnatural",
"proficiency": 0
},
"foreign_languages": {
"label": "Foreign Languages",
"specializations": [
{
"label": "Spanish/Yiddish/Etc.",
"proficiency": 0,
"failure": false
}
]
},
"other_skills": {
"label": "Other Skills and Training",
"specializations": [
{
"label": "",
"proficiency": 0,
"failure": false
}
]
}
}
}
},
"agent": {
"templates": [
"base",
"human_skills"
],
"sanity": {
"value": 100,
"currentBreakingPoint": 101,
"adaptations": {
"violence": 0,
"helplessness": 0
}
},
"physicalDescription": "",
"biography": {
"profession": "",
"employer": "",
"nationality": "",
"sex": "",
"age": "",
"education": ""
},
"motivations":[
{
"description":"",
"disorder":""
}
]
}
},
"Item": {
"types": [
"weapon",
"equipment",
"ability",
"ritual"
],
"templates": {
"base": {
"description": ""
}
},
"weapon": {
"templates": [
"base"
]
},
"equipment": {
"templates": [
"base"
]
},
"ability": {
"templates": [
"base"
]
},
"ritual": {
"templates": [
"base"
]
}
}
}
+160
View File
@@ -0,0 +1,160 @@
<form class="{{cssClass}} flexcol" autocomplete="off">
{{!-- Sheet Header --}}
<header class="sheet-header">
<img class="profile-img" src="{{actor.img}}" data-edit="img" title="{{actor.name}}" height="150" width="100"/>
<div class="header-fields">
<h1 class="charname"><input name="name" type="text" value="{{actor.name}}" placeholder="Name"/></h1>
{{!-- The grid classes are defined in scss/global/_grid.scss. To use,
use both the "grid" and "grid-Ncol" class where "N" can be any number
from 1 to 12 and will create that number of columns. --}}
<div class="resources grid grid-2col">
{{!-- "flex-group-center" is also defined in the _grid.scss file
and it will add a small amount of padding, a border, and will
center all of its child elements content and text. --}}
<div class="resource flex-group-center">
<label for="data.health.value" class="resource-label">Health</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="data.health.value" value="{{data.health.value}}" data-dtype="Number"/>
<span> / </span>
<span title="Maximum health is the average of STR + CON, rounded UP." class="max-attribute-value-label">{{numberFormat data.health.max decimals=0 sign=false}}</span>
</div>
</div>
<div class="resource flex-group-center">
<label for="data.wp.value" class="resource-label">WP</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="data.wp.value" value="{{data.wp.value}}" data-dtype="Number"/>
<span> / </span>
<span class="max-attribute-value-label">{{numberFormat data.wp.max decimals=0 sign=false}}</span>
</div>
</div>
</div>
<div class="resources">
{{!-- "flex-group-center" is also defined in the _grid.scss file
and it will add a small amount of padding, a border, and will
center all of its child elements content and text. --}}
<div class="resource flex-group-center">
<label for="data.sanity.value" class="resource-label">SANITY</label>
<div class="resource-content flexrow flex-center flex-between">
<input title="Starting sanity is equal to POW x 5." type="text" name="data.sanity.value" value="{{data.sanity.value}}" data-dtype="Number"/>
<span> / </span>
<span title="Maximum sanity is equal to 99 - the Unnatural skill." class="max-attribute-value-label">{{numberFormat data.sanity.max decimals=0 sign=false}}</span>
<!-- <span class="statistic-mod rollable left-aligned-label" data-roll="d100" data-target="sanity" data-label="SAN">(SAN Test)</span> -->
<button class="btn-tiny rollable" data-roll="d100" data-target="sanity" data-label="SAN">TEST</button>
<span> </span>
<span class="breaking-point-label"> Breaking Point: </span>
<!-- <span title="The current breaking point, resent when current sanity falls to or below this value." class="max-attribute-value-label">{{numberFormat data.sanity.currentBreakingPoint decimals=0 sign=false}}</span> -->
<input title="The current breaking point, resent when current sanity falls to or below this value." type="text" name="data.sanity.currentBreakingPoint" value="{{data.sanity.currentBreakingPoint}}" data-dtype="Number"/>
<button id="btnResetBreakingPoint" class="btn-tiny" title="Reset breaking point to be SAN - WP.">Reset</button>
</div>
</div>
</div>
{{!-- The grid classes are defined in scss/global/_grid.scss. To use,
use both the "grid" and "grid-Ncol" class where "N" can be any number
from 1 to 12 and will create that number of columns. --}}
<div class="statistics grid grid-3col">
{{#each data.statistics as |statistic key|}}
<div class="statistic flexrow flex-group-center">
<label for="data.statistics.{{key}}.value" class="resource-label">{{key}}</label>
<input type="text" name="data.statistics.{{key}}.value" value="{{statistic.value}}" data-dtype="Number"/>
<span class="statistic-mod rollable" data-roll="d100" data-target="statistic.x5" data-label="{{key}}">{{numberFormat statistic.x5 decimals=0 sign=false}}</span>
</div>
{{/each}}
</div>
</div>
</header>
{{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="skills">Skills</a>
<a class="item" data-tab="gear">gear</a>
<a class="item" data-tab="physical-description">Description</a>
<a class="item" data-tab="bonds">Bonds</a>
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body">
{{!-- Biography Tab --}}
<div class="tab biography" data-group="primary" data-tab="physical-description">
{{editor content=data.physicalDescription target="data.physicalDescription" button=true owner=owner editable=editable}}
</div>
{{!-- Owned Items Tab --}}
<div class="tab items" data-group="primary" data-tab="items">
<ol class="items-list">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="item-name">Name</div>
<div class="item-controls">
<a class="item-control item-create" title="Create item" data-type="item"><i class="fas fa-plus"></i> Add item</a>
</div>
</li>
{{#each actor.gear as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-image"><img src="{{item.img}}" title="{{item.name}}" width="24" height="24"/></div>
<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>
{{!-- Owned Features Tab --}}
<div class="tab features" data-group="primary" data-tab="features">
<ol class="items-list">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="item-name">Name</div>
<div class="item-controls">
<a class="item-control item-create" title="Create item" data-type="feature"><i class="fas fa-plus"></i> Add item</a>
</div>
</li>
{{#each actor.features as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-image"><img src="{{item.img}}" title="{{item.name}}" width="24" height="24"/></div>
<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>
{{!-- Owned Spells Tab --}}
<div class="tab spells" data-group="primary" data-tab="spells">
<ol class="items-list">
<li class="item flexrow item-header">
<div class="item-image"></div>
<div class="item-name">Name</div>
<div class="item-controls"></div>
</li>
{{#each actor.spells as |spells spellLevel|}}
<li class="item flexrow item-header">
<div class="item-name">Level {{spellLevel}} Spells</div>
<div class="item-controls">
<a class="item-control item-create" title="Create item" data-type="spell" data-spell-level="{{spellLevel}}"><i class="fas fa-plus"></i> Add LVL {{spellLevel}}</a>
</div>
</li>
{{#each spells as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
<div class="item-image"><img src="{{item.img}}" title="{{item.name}}" width="24" height="24"/></div>
<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}}
{{/each}}
</ol>
</div>
</section>
</form>
+28
View File
@@ -0,0 +1,28 @@
<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>
{{!-- 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">
{{!-- As you add new fields, add them in here! --}}
</div>
</section>
</form>
+38
View File
@@ -0,0 +1,38 @@
<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="grid grid-2col">
<div class="resource">
<label class="resource-label">Quantity</label>
<input type="text" name="data.quantity" value="{{data.quantity}}" data-dtype="Number"/>
</div>
<div class="resource">
<label class="resource-label">Weight</label>
<input type="text" name="data.weight" value="{{data.weight}}" data-dtype="Number"/>
</div>
</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">
{{!-- As you add new fields, add them in here! --}}
</div>
</section>
</form>
+28
View File
@@ -0,0 +1,28 @@
<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>
{{!-- 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">
{{!-- As you add new fields, add them in here! --}}
</div>
</section>
</form>
+32
View File
@@ -0,0 +1,32 @@
<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>
{{!-- 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">
{{!-- As you add new fields, add them in here! --}}
<div class="resource">
<label class="resource-label">Spell Level</label>
<input type="text" name="data.spellLevel" value="{{data.spellLevel}}" data-dtype="Number"/>
</div>
</div>
</section>
</form>