First commit

This commit is contained in:
Maulerant 2024-05-20 02:59:52 -07:00
commit b50f08d79b
51 changed files with 2419 additions and 0 deletions

5
CHANGELOG.md Normal file
View File

@ -0,0 +1,5 @@
# CHANGELOG
## 1.2.0
- Add support for Foundry v10

30
LICENSE.txt Normal file
View File

@ -0,0 +1,30 @@
MIT License
Copyright (c) 2020 Asacolips Projects / Foundry Mods
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.
This license does not apply to the compendium content listed in this software's
"packs" directory. See the README for licensing information for the compendium
packs.
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.
IMAGE LICENSES
anvil-impact.png by Lorc under CC BY 3.0 from game-icons.net

82
README.md Normal file
View File

@ -0,0 +1,82 @@
# Shadowrun6 System
![Foundry v11](https://img.shields.io/badge/foundry-v11-green)
This system is a shadowrun-6e-ultimate system that you can use as a starting point for building your own custom systems. It's similar to Simple World-building, but has examples of creating attributes in code rather than dynamically through the UI.
## Usage
There are two ways to get started: using the Shadowrun6 system generator command or manually renaming and updating files.
Regardless of which method you choose, think carefully about your system's name. Your system's package name when submitted to Foundry must be formatted like `alphanumeric-lowercase`, and it must be unique. Check the Foundry systems package list for conflicts before committing to a name!
> **Data Models**
>
> If you would like to use DataModel classes instead of the older template.json configuration, you'll need to use the `npm run generate` command described below and choose to enable them when asked. DataModels are currently an optional feature, and are only availabe in the generator CLI due to that.
### Generator
This system includes a generator CLI in `package.json`. To use it, you must have [node.js](https://nodejs.org) installed, and it's recommended that you install node 20 or later.
> **Python Generator**
>
> If you would rather use Python than node, theres an excellent Python-based generator created by Cussa at https://github.com/Cussa/fvtt-shadowrun-6e-ultimate-initializator. Give it a shot!
Once you have npm installed, you can run the following in your terminal or command prompt:
```bash
npm install
npm run generate
```
Your terminal should prompt you to name your system. Read the instructions carefully, the letter case and special characters in each question matter for correct system generation.
Once the generator completes, it will output your system to `build/<your-system-name>`, where `<your-system-name>` is the package name you supplied during the prompt.
Copy this directory over to your Foundry systems directory and start coding!
### Manual Replacement
Before installing this system, you should rename any files that have `shadowrun-6e-ultimate` in their filename to use whatever machine-safe name your system needs, such as `adnd2e` if you were building a system for 2nd edition Advanced Dungeons & Dragons. In addition, you should search through the files for `shadowrun-6e-ultimate` and `Shadowrun6` and do the same for those, replacing them with appropriate names for your system.
The `name` property in your `system.json` file is your system's package name. This need to be formatted `alphanumeric-lowercase`, and it must also match the foldername you use for your system.
### Vue 3 Shadowrun6
**NOTE: The Vue 3 version is currently outdated and considered an advanced usage of Foundry due to it being a custom renderer. Only try it out if you _really_ like Vue and are feeling dangerous!**
Alternatively, there's another build of this system that supports using Vue 3 components (ES module build target) for character sheet templates.
Head over to the [Vue3Shadowrun6 System](https://gitlab.com/asacolips-projects/foundry-mods/vue3shadowrun-6e-ultimate) repo if you're interested in using Vue!
### Getting Help
Check out the [Official Foundry VTT Discord](https://discord.gg/foundryvtt)! The #system-development channel has helpful pins and is a good place to ask questions about any part of the foundry application.
For more static references, the [Knowledge Base](https://foundryvtt.com/kb/) and [API Documentation](https://foundryvtt.com/api/) provide different levels of detail. For the most detail, you can find the client side code in your foundry installation location. Classes are documented in individual files under `resources/app/client` and `resources/app/common`, and the code is collated into a single file at `resources/app/public/scripts/foundry.js`.
#### Tutorial
For much more information on how to use this system as a starting point for making your own, see the [full tutorial on the Foundry Wiki](https://foundryvtt.wiki/en/development/guides/SD-tutorial)!
Note: Tutorial may be out of date, so look out for the Foundry compatibility badge at the top of each page.
## Sheet Layout
This system includes a handful of helper CSS classes to help you lay out your sheets if you're not comfortable diving into CSS fully. Those are:
- `flexcol`: Included by Foundry itself, this lays out the child elements of whatever element you place this on vertically.
- `flexrow`: Included by Foundry itself, this lays out the child elements of whatever element you place this on horizontally.
- `flex-center`: When used on something that's using flexrow or flexcol, this will center the items and text.
- `flex-between`: When used on something that's using flexrow or flexcol, this will attempt to place space between the items. Similar to "justify" in word processors.
- `flex-group-center`: Add a border, padding, and center all items.
- `flex-group-left`: Add a border, padding, and left align all items.
- `flex-group-right`: Add a border, padding, and right align all items.
- `grid`: When combined with the `grid-Ncol` classes, this will lay out child elements in a grid.
- `grid-Ncol`: Replace `N` with any number from 1-12, such as `grid-3col`. When combined with `grid`, this will layout child elements in a grid with a number of columns equal to the number specified.
## Compiling the CSS
This repo includes both CSS for the theme and SCSS source files. If you're new to CSS, it's probably easier to just work in those files directly and delete the SCSS directory. If you're interested in using a CSS preprocessor to add support for nesting, variables, and more, you can run `npm install` in this directory to install the dependencies for the scss compiler. After that, just run `npm run build` to compile the SCSS and start a process that watches for new changes.
![image](http://mattsmith.in/images/shadowrun-6e-ultimate.png)

BIN
assets/anvil-impact.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,366 @@
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
/* Global styles */
.window-app {
font-family: "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 6/span 6;
grid-template-columns: repeat(6, 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));
}
.grid-start-2 {
grid-column-start: 2;
}
.grid-start-3 {
grid-column-start: 3;
}
.grid-start-4 {
grid-column-start: 4;
}
.grid-start-5 {
grid-column-start: 5;
}
.grid-start-6 {
grid-column-start: 6;
}
.grid-start-7 {
grid-column-start: 7;
}
.grid-start-8 {
grid-column-start: 8;
}
.grid-start-9 {
grid-column-start: 9;
}
.grid-start-10 {
grid-column-start: 10;
}
.grid-start-11 {
grid-column-start: 11;
}
.grid-start-12 {
grid-column-start: 12;
}
.grid-span-2 {
grid-column-end: span 2;
}
.grid-span-3 {
grid-column-end: span 3;
}
.grid-span-4 {
grid-column-end: span 4;
}
.grid-span-5 {
grid-column-end: span 5;
}
.grid-span-6 {
grid-column-end: span 6;
}
.grid-span-7 {
grid-column-end: span 7;
}
.grid-span-8 {
grid-column-end: span 8;
}
.grid-span-9 {
grid-column-end: span 9;
}
.grid-span-10 {
grid-column-end: span 10;
}
.grid-span-11 {
grid-column-end: span 11;
}
.grid-span-12 {
grid-column-end: span 12;
}
.flex-group-center,
.flex-group-left,
.flex-group-right {
justify-content: center;
align-items: center;
text-align: center;
}
.flex-group-left {
justify-content: flex-start;
text-align: left;
}
.flex-group-right {
justify-content: flex-end;
text-align: right;
}
.flexshrink {
flex: 0;
}
.flex-between {
justify-content: space-between;
}
.flexlarge {
flex: 2;
}
.align-left {
justify-content: flex-start;
text-align: left;
}
.align-right {
justify-content: flex-end;
text-align: right;
}
.align-center {
justify-content: center;
text-align: center;
}
/* Styles limited to shadowrun-6e-ultimate sheets */
.shadowrun-6e-ultimate .item-form {
font-family: "Roboto", sans-serif;
}
.shadowrun-6e-ultimate .sheet-header {
flex: 0 auto;
overflow: hidden;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
margin-bottom: 10px;
}
.shadowrun-6e-ultimate .sheet-header .profile-img {
flex: 0 0 100px;
height: 100px;
margin-right: 10px;
}
.shadowrun-6e-ultimate .sheet-header .header-fields {
flex: 1;
}
.shadowrun-6e-ultimate .sheet-header h1.charname {
height: 50px;
padding: 0px;
margin: 5px 0;
border-bottom: 0;
}
.shadowrun-6e-ultimate .sheet-header h1.charname input {
width: 100%;
height: 100%;
margin: 0;
}
.shadowrun-6e-ultimate .sheet-tabs {
flex: 0;
}
.shadowrun-6e-ultimate .sheet-body,
.shadowrun-6e-ultimate .sheet-body .tab,
.shadowrun-6e-ultimate .sheet-body .tab .editor {
height: 100%;
}
.shadowrun-6e-ultimate .tox .tox-editor-container {
background: #fff;
}
.shadowrun-6e-ultimate .tox .tox-edit-area {
padding: 0 8px;
}
.shadowrun-6e-ultimate .resource-label {
font-weight: bold;
}
.shadowrun-6e-ultimate .items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
}
.shadowrun-6e-ultimate .items-header > * {
font-size: 14px;
text-align: center;
}
.shadowrun-6e-ultimate .items-header .item-name {
font-weight: bold;
padding-left: 5px;
text-align: left;
display: flex;
}
.shadowrun-6e-ultimate .items-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: thin;
color: #444;
}
.shadowrun-6e-ultimate .items-list .item-list {
list-style: none;
margin: 0;
padding: 0;
}
.shadowrun-6e-ultimate .items-list .item-name {
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
align-items: center;
display: flex;
}
.shadowrun-6e-ultimate .items-list .item-name h3, .shadowrun-6e-ultimate .items-list .item-name h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
.shadowrun-6e-ultimate .items-list .item-controls {
display: flex;
flex: 0 0 100px;
justify-content: flex-end;
}
.shadowrun-6e-ultimate .items-list .item-controls a {
font-size: 12px;
text-align: center;
margin: 0 6px;
}
.shadowrun-6e-ultimate .items-list .item {
align-items: center;
padding: 0 2px;
border-bottom: 1px solid #c9c7b8;
}
.shadowrun-6e-ultimate .items-list .item:last-child {
border-bottom: none;
}
.shadowrun-6e-ultimate .items-list .item .item-name {
color: #191813;
}
.shadowrun-6e-ultimate .items-list .item .item-name .item-image {
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
.shadowrun-6e-ultimate .items-list .item-prop {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.shadowrun-6e-ultimate .items-list .items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
}
.shadowrun-6e-ultimate .items-list .items-header > * {
font-size: 12px;
text-align: center;
}
.shadowrun-6e-ultimate .items-list .items-header .item-name {
padding-left: 5px;
text-align: left;
}
.shadowrun-6e-ultimate .item-formula {
flex: 0 0 200px;
padding: 0 8px;
}
.shadowrun-6e-ultimate .effects .item .effect-source,
.shadowrun-6e-ultimate .effects .item .effect-duration,
.shadowrun-6e-ultimate .effects .item .effect-controls {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.shadowrun-6e-ultimate .effects .item .effect-controls {
border: none;
}

41
lang/en.json Normal file
View File

@ -0,0 +1,41 @@
{
"SHADOWRUN_6": {
"Ability": {
"Str": { "long": "Strength", "abbr": "str" },
"Con": { "long": "Constitution", "abbr": "con" },
"Dex": { "long": "Dexterity", "abbr": "dex" },
"Int": { "long": "Intelligence", "abbr": "int" },
"Wis": { "long": "Wisdom", "abbr": "wis" },
"Cha": { "long": "Charisma", "abbr": "cha" }
},
"SheetLabels": {
"Actor": "Shadowrun6 Actor Sheet",
"Item": "Shadowrun6 Item Sheet"
},
"Item": {
"Spell": {
"SpellLVL": "Level {level} Spells",
"AddLVL": "Add LVL {level}"
}
},
"Effect": {
"Source": "Source",
"Toggle": "Toggle Effect",
"Temporary": "Temporary Effects",
"Passive": "Passive Effects",
"Inactive": "Inactive Effects"
}
},
"TYPES": {
"Actor": {
"character": "Character",
"npc": "NPC"
},
"Item": {
"item": "Item",
"feature": "Feature",
"spell": "Spell"
}
}
}

View File

0
lib/some-lib/some-lib.min.js vendored Normal file
View File

10
module/data/_module.mjs Normal file
View File

@ -0,0 +1,10 @@
// Export Actors
export {default as Shadowrun6ActorBase} from "./actor-base.mjs";
export {default as Shadowrun6Character} from "./character.mjs";
export {default as Shadowrun6NPC} from "./npc.mjs";
// Export Items
export {default as Shadowrun6ItemBase} from "./item-base.mjs";
export {default as Shadowrun6Item} from "./item.mjs";
export {default as Shadowrun6Feature} from "./feature.mjs";
export {default as Shadowrun6Spell} from "./spell.mjs";

View File

@ -0,0 +1,20 @@
export default class Shadowrun6ActorBase extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = {};
schema.health = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 10 })
});
schema.power = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 5, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
});
schema.biography = new fields.StringField({ required: true, blank: true }); // equivalent to passing ({initial: ""}) for StringFields
return schema;
}
}

54
module/data/character.mjs Normal file
View File

@ -0,0 +1,54 @@
import Shadowrun6ActorBase from "./actor-base.mjs";
export default class Shadowrun6Character extends Shadowrun6ActorBase {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = super.defineSchema();
schema.attributes = new fields.SchemaField({
level: new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1 })
}),
});
// Iterate over ability names and create a new SchemaField for each.
schema.abilities = new fields.SchemaField(Object.keys(CONFIG.SHADOWRUN_6.abilities).reduce((obj, ability) => {
obj[ability] = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }),
mod: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
label: new fields.StringField({ required: true, blank: true })
});
return obj;
}, {}));
return schema;
}
prepareDerivedData() {
// Loop through ability scores, and add their modifiers to our sheet output.
for (const key in this.abilities) {
// Calculate the modifier using d20 rules.
this.abilities[key].mod = Math.floor((this.abilities[key].value - 10) / 2);
// Handle ability label localization.
this.abilities[key].label = game.i18n.localize(CONFIG.SHADOWRUN_6.abilities[key]) ?? key;
}
}
getRollData() {
const data = {};
// Copy the ability scores to the top level, so that rolls can use
// formulas like `@str.mod + 4`.
if (this.abilities) {
for (let [k,v] of Object.entries(this.abilities)) {
data[k] = foundry.utils.deepClone(v);
}
}
data.lvl = this.attributes.level.value;
return data
}
}

3
module/data/feature.mjs Normal file
View File

@ -0,0 +1,3 @@
import Shadowrun6ItemBase from "./item-base.mjs";
export default class Shadowrun6Feature extends Shadowrun6ItemBase {}

11
module/data/item-base.mjs Normal file
View File

@ -0,0 +1,11 @@
export default class Shadowrun6ItemBase extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
schema.description = new fields.StringField({ required: true, blank: true });
return schema;
}
}

31
module/data/item.mjs Normal file
View File

@ -0,0 +1,31 @@
import Shadowrun6ItemBase from "./item-base.mjs";
export default class Shadowrun6Item extends Shadowrun6ItemBase {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = super.defineSchema();
schema.quantity = new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 });
schema.weight = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0 });
// Break down roll formula into three independent fields
schema.roll = new fields.SchemaField({
diceNum: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
diceSize: new fields.StringField({ initial: "d20" }),
diceBonus: new fields.StringField({ initial: "+@str.mod+ceil(@lvl / 2)" })
})
schema.formula = new fields.StringField({ blank: true });
return schema;
}
prepareDerivedData() {
// Build the formula dynamically using string interpolation
const roll = this.roll;
this.formula = `${roll.diceNum}${roll.diceSize}${roll.diceBonus}`
}
}

19
module/data/npc.mjs Normal file
View File

@ -0,0 +1,19 @@
import Shadowrun6ActorBase from "./actor-base.mjs";
export default class Shadowrun6NPC extends Shadowrun6ActorBase {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const schema = super.defineSchema();
schema.cr = new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 });
schema.xp = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 });
return schema
}
prepareDerivedData() {
this.xp = this.cr * this.cr * 100;
}
}

13
module/data/spell.mjs Normal file
View File

@ -0,0 +1,13 @@
import Shadowrun6ItemBase from "./item-base.mjs";
export default class Shadowrun6Spell extends Shadowrun6ItemBase {
static defineSchema() {
const fields = foundry.data.fields;
const schema = super.defineSchema();
schema.spellLevel = new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 1, max: 9 });
return schema;
}
}

View File

@ -0,0 +1,45 @@
/**
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class Shadowrun6Actor extends Actor {
/** @override */
prepareData() {
// Prepare data for the actor. Calling the super version of this executes
// the following, in order: data reset (to clear active effects),
// prepareBaseData(), prepareEmbeddedDocuments() (including active effects),
// prepareDerivedData().
super.prepareData();
}
/** @override */
prepareBaseData() {
// Data modifications in this step occur before processing embedded
// documents or derived data.
}
/**
* @override
* Augment the actor source data with additional dynamic data that isn't
* handled by the actor's DataModel. Data calculated in this step should be
* available both inside and outside of character sheets (such as if an actor
* is queried and has a roll executed directly from it).
*/
prepareDerivedData() {
const actorData = this;
const flags = actorData.flags.shadowrun6eultimate || {};
}
/**
*
* @override
* Augment the actor's default getRollData() method by appending the data object
* generated by the its DataModel's getRollData(), or null. This polymorphic
* approach is useful when you have actors & items that share a parent Document,
* but have slightly different data preparation needs.
*/
getRollData() {
return { ...super.getRollData(), ...this.system.getRollData?.() ?? null };
}
}

71
module/documents/item.mjs Normal file
View File

@ -0,0 +1,71 @@
/**
* Extend the basic Item with some very simple modifications.
* @extends {Item}
*/
export class Shadowrun6Item extends Item {
/**
* Augment the basic Item data model with additional dynamic data.
*/
prepareData() {
// As with the actor class, items are documents that can have their data
// preparation methods overridden (such as prepareBaseData()).
super.prepareData();
}
/**
* Prepare a data object which defines the data schema used by dice roll commands against this Item
* @override
*/
getRollData() {
// Starts off by populating the roll data with `this.system`
const rollData = { ...super.getRollData() };
// Quit early if there's no parent actor
if (!this.actor) return rollData;
// If present, add the actor's roll data
rollData.actor = this.actor.getRollData();
return rollData;
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
async roll() {
const item = this;
// Initialize chat data.
const speaker = ChatMessage.getSpeaker({ actor: this.actor });
const rollMode = game.settings.get('core', 'rollMode');
const label = `[${item.type}] ${item.name}`;
// If there's no roll data, send a chat message.
if (!this.system.formula) {
ChatMessage.create({
speaker: speaker,
rollMode: rollMode,
flavor: label,
content: item.system.description ?? '',
});
}
// Otherwise, create a roll and send a chat message from it.
else {
// Retrieve roll data.
const rollData = this.getRollData();
// Invoke the roll and submit it to chat.
const roll = new Roll(rollData.formula, rollData.actor);
// If you need to store the value first, uncomment the next line.
// const result = await roll.evaluate();
roll.toMessage({
speaker: speaker,
rollMode: rollMode,
flavor: label,
});
return roll;
}
}
}

23
module/helpers/config.mjs Normal file
View File

@ -0,0 +1,23 @@
export const SHADOWRUN_6 = {};
/**
* The set of Ability Scores used within the system.
* @type {Object}
*/
SHADOWRUN_6.abilities = {
str: 'SHADOWRUN_6.Ability.Str.long',
dex: 'SHADOWRUN_6.Ability.Dex.long',
con: 'SHADOWRUN_6.Ability.Con.long',
int: 'SHADOWRUN_6.Ability.Int.long',
wis: 'SHADOWRUN_6.Ability.Wis.long',
cha: 'SHADOWRUN_6.Ability.Cha.long',
};
SHADOWRUN_6.abilityAbbreviations = {
str: 'SHADOWRUN_6.Ability.Str.abbr',
dex: 'SHADOWRUN_6.Ability.Dex.abbr',
con: 'SHADOWRUN_6.Ability.Con.abbr',
int: 'SHADOWRUN_6.Ability.Int.abbr',
wis: 'SHADOWRUN_6.Ability.Wis.abbr',
cha: 'SHADOWRUN_6.Ability.Cha.abbr',
};

View File

@ -0,0 +1,68 @@
/**
* Manage Active Effect instances through an Actor or Item Sheet via effect control buttons.
* @param {MouseEvent} event The left-click event on the effect control
* @param {Actor|Item} owner The owning document which manages this effect
*/
export function onManageActiveEffect(event, owner) {
event.preventDefault();
const a = event.currentTarget;
const li = a.closest('li');
const effect = li.dataset.effectId
? owner.effects.get(li.dataset.effectId)
: null;
switch (a.dataset.action) {
case 'create':
return owner.createEmbeddedDocuments('ActiveEffect', [
{
name: game.i18n.format('DOCUMENT.New', {
type: game.i18n.localize('DOCUMENT.ActiveEffect'),
}),
icon: 'icons/svg/aura.svg',
origin: owner.uuid,
'duration.rounds':
li.dataset.effectType === 'temporary' ? 1 : undefined,
disabled: li.dataset.effectType === 'inactive',
},
]);
case 'edit':
return effect.sheet.render(true);
case 'delete':
return effect.delete();
case 'toggle':
return effect.update({ disabled: !effect.disabled });
}
}
/**
* Prepare the data structure for Active Effects which are currently embedded in an Actor or Item.
* @param {ActiveEffect[]} effects A collection or generator of Active Effect documents to prepare sheet data for
* @return {object} Data for rendering
*/
export function prepareActiveEffectCategories(effects) {
// Define effect header categories
const categories = {
temporary: {
type: 'temporary',
label: game.i18n.localize('SHADOWRUN_6.Effect.Temporary'),
effects: [],
},
passive: {
type: 'passive',
label: game.i18n.localize('SHADOWRUN_6.Effect.Passive'),
effects: [],
},
inactive: {
type: 'inactive',
label: game.i18n.localize('SHADOWRUN_6.Effect.Inactive'),
effects: [],
},
};
// Iterate over active effects, classifying them into categories
for (let e of effects) {
if (e.disabled) categories.inactive.effects.push(e);
else if (e.isTemporary) categories.temporary.effects.push(e);
else categories.passive.effects.push(e);
}
return categories;
}

View File

@ -0,0 +1,16 @@
/**
* Define a set of template paths to pre-load
* Pre-loaded templates are compiled and cached for fast access when rendering
* @return {Promise}
*/
export const preloadHandlebarsTemplates = async function () {
return loadTemplates([
// Actor partials.
'systems/shadowrun-6e-ultimate/templates/actor/parts/actor-features.hbs',
'systems/shadowrun-6e-ultimate/templates/actor/parts/actor-items.hbs',
'systems/shadowrun-6e-ultimate/templates/actor/parts/actor-spells.hbs',
'systems/shadowrun-6e-ultimate/templates/actor/parts/actor-effects.hbs',
// Item partials
'systems/shadowrun-6e-ultimate/templates/item/parts/item-effects.hbs',
]);
};

View File

@ -0,0 +1,158 @@
// Import document classes.
import { Shadowrun6Actor } from './documents/actor.mjs';
import { Shadowrun6Item } from './documents/item.mjs';
// Import sheet classes.
import { Shadowrun6ActorSheet } from './sheets/actor-sheet.mjs';
import { Shadowrun6ItemSheet } from './sheets/item-sheet.mjs';
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from './helpers/templates.mjs';
import { SHADOWRUN_6 } from './helpers/config.mjs';
// Import DataModel classes
import * as models from './data/_module.mjs';
/* -------------------------------------------- */
/* Init Hook */
/* -------------------------------------------- */
Hooks.once('init', function () {
// Add utility classes to the global game object so that they're more easily
// accessible in global contexts.
game.shadowrun6eultimate = {
Shadowrun6Actor,
Shadowrun6Item,
rollItemMacro,
};
// Add custom constants for configuration.
CONFIG.SHADOWRUN_6 = SHADOWRUN_6;
/**
* Set an initiative formula for the system
* @type {String}
*/
CONFIG.Combat.initiative = {
formula: '1d20 + @abilities.dex.mod',
decimals: 2,
};
// Define custom Document and DataModel classes
CONFIG.Actor.documentClass = Shadowrun6Actor;
// Note that you don't need to declare a DataModel
// for the base actor/item classes - they are included
// with the Character/NPC as part of super.defineSchema()
CONFIG.Actor.dataModels = {
character: models.Shadowrun6Character,
npc: models.Shadowrun6NPC
}
CONFIG.Item.documentClass = Shadowrun6Item;
CONFIG.Item.dataModels = {
item: models.Shadowrun6Item,
feature: models.Shadowrun6Feature,
spell: models.Shadowrun6Spell
}
// Active Effects are never copied to the Actor,
// but will still apply to the Actor from within the Item
// if the transfer property on the Active Effect is true.
CONFIG.ActiveEffect.legacyTransferral = false;
// Register sheet application classes
Actors.unregisterSheet('core', ActorSheet);
Actors.registerSheet('shadowrun-6e-ultimate', Shadowrun6ActorSheet, {
makeDefault: true,
label: 'SHADOWRUN_6.SheetLabels.Actor',
});
Items.unregisterSheet('core', ItemSheet);
Items.registerSheet('shadowrun-6e-ultimate', Shadowrun6ItemSheet, {
makeDefault: true,
label: 'SHADOWRUN_6.SheetLabels.Item',
});
// Preload Handlebars templates.
return preloadHandlebarsTemplates();
});
/* -------------------------------------------- */
/* Handlebars Helpers */
/* -------------------------------------------- */
// If you need to add Handlebars helpers, here is a useful example:
Handlebars.registerHelper('toLowerCase', function (str) {
return str.toLowerCase();
});
/* -------------------------------------------- */
/* Ready Hook */
/* -------------------------------------------- */
Hooks.once('ready', 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) => createItemMacro(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 createItemMacro(data, slot) {
// First, determine if this is a valid owned item.
if (data.type !== 'Item') return;
if (!data.uuid.includes('Actor.') && !data.uuid.includes('Token.')) {
return ui.notifications.warn(
'You can only create macro buttons for owned Items'
);
}
// If it is, retrieve it based on the uuid.
const item = await Item.fromDropData(data);
// Create the macro command using the uuid.
const command = `game.shadowrun6eultimate.rollItemMacro("${data.uuid}");`;
let macro = game.macros.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: { 'shadowrun-6e-ultimate.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} itemUuid
*/
function rollItemMacro(itemUuid) {
// Reconstruct the drop data so that we can load the item.
const dropData = {
type: 'Item',
uuid: itemUuid,
};
// Load the item from the uuid.
Item.fromDropData(dropData).then((item) => {
// Determine if the item loaded and if it's an owned item.
if (!item || !item.parent) {
const itemName = item?.name ?? itemUuid;
return ui.notifications.warn(
`Could not find item ${itemName}. You may need to delete and recreate this macro.`
);
}
// Trigger the item roll
item.roll();
});
}

View File

@ -0,0 +1,246 @@
import {
onManageActiveEffect,
prepareActiveEffectCategories,
} from '../helpers/effects.mjs';
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class Shadowrun6ActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['shadowrun-6e-ultimate', 'sheet', 'actor'],
width: 600,
height: 600,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'features',
},
],
});
}
/** @override */
get template() {
return `systems/shadowrun-6e-ultimate/templates/actor/actor-${this.actor.type}-sheet.hbs`;
}
/* -------------------------------------------- */
/** @override */
getData() {
// Retrieve the data structure from the base sheet. You can inspect or log
// the context variable to see the structure, but some key properties for
// sheets are the actor object, the data object, whether or not it's
// editable, the items array, and the effects array.
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const actorData = context.data;
// Add the actor's data to context.data for easier access, as well as flags.
context.system = actorData.system;
context.flags = actorData.flags;
// Prepare character data and items.
if (actorData.type == 'character') {
this._prepareItems(context);
this._prepareCharacterData(context);
}
// Prepare NPC data and items.
if (actorData.type == 'npc') {
this._prepareItems(context);
}
// Add roll data for TinyMCE editors.
context.rollData = context.actor.getRollData();
// Prepare active effects
context.effects = prepareActiveEffectCategories(
// A generator that returns all effects stored on the actor
// as well as any items
this.actor.allApplicableEffects()
);
return context;
}
/**
* Organize and classify Items for Character sheets.
*
* @param {Object} actorData The actor to prepare.
*
* @return {undefined}
*/
_prepareCharacterData(context) {
// Handle ability scores.
// for (let [k, v] of Object.entries(context.system.abilities)) {
// v.label = game.i18n.localize(CONFIG.SHADOWRUN_6.abilities[k]) ?? k;
// }
}
/**
* Organize and classify Items for Character sheets.
*
* @param {Object} actorData The actor to prepare.
*
* @return {undefined}
*/
_prepareItems(context) {
// Initialize containers.
const gear = [];
const features = [];
const spells = {
0: [],
1: [],
2: [],
3: [],
4: [],
5: [],
6: [],
7: [],
8: [],
9: [],
};
// Iterate through items, allocating to containers
for (let i of context.items) {
i.img = i.img || Item.DEFAULT_ICON;
// 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.system.spellLevel != undefined) {
spells[i.system.spellLevel].push(i);
}
}
}
// Assign and return
context.gear = gear;
context.features = features;
context.spells = spells;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Render the item sheet for viewing/editing prior to the editable check.
html.on('click', '.item-edit', (ev) => {
const li = $(ev.currentTarget).parents('.item');
const item = this.actor.items.get(li.data('itemId'));
item.sheet.render(true);
});
// -------------------------------------------------------------
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
// Add Inventory Item
html.on('click', '.item-create', this._onItemCreate.bind(this));
// Delete Inventory Item
html.on('click', '.item-delete', (ev) => {
const li = $(ev.currentTarget).parents('.item');
const item = this.actor.items.get(li.data('itemId'));
item.delete();
li.slideUp(200, () => this.render(false));
});
// Active Effect management
html.on('click', '.effect-control', (ev) => {
const row = ev.currentTarget.closest('li');
const document =
row.dataset.parentId === this.actor.id
? this.actor
: this.actor.items.get(row.dataset.parentId);
onManageActiveEffect(ev, document);
});
// Rollable abilities.
html.on('click', '.rollable', this._onRoll.bind(this));
// Drag events for macros.
if (this.actor.isOwner) {
let handler = (ev) => this._onDragStart(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
*/
async _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,
system: data,
};
// Remove the type from the dataset since it's in the itemData.type prop.
delete itemData.system['type'];
// Finally, create the item!
return await Item.create(itemData, { parent: this.actor });
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
_onRoll(event) {
event.preventDefault();
const element = event.currentTarget;
const dataset = element.dataset;
// Handle item rolls.
if (dataset.rollType) {
if (dataset.rollType == 'item') {
const itemId = element.closest('.item').dataset.itemId;
const item = this.actor.items.get(itemId);
if (item) return item.roll();
}
}
// Handle rolls that supply the formula directly.
if (dataset.roll) {
let label = dataset.label ? `[ability] ${dataset.label}` : '';
let roll = new Roll(dataset.roll, this.actor.getRollData());
roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
});
return roll;
}
}
}

View File

@ -0,0 +1,77 @@
import {
onManageActiveEffect,
prepareActiveEffectCategories,
} from '../helpers/effects.mjs';
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class Shadowrun6ItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ['shadowrun-6e-ultimate', 'sheet', 'item'],
width: 520,
height: 480,
tabs: [
{
navSelector: '.sheet-tabs',
contentSelector: '.sheet-body',
initial: 'description',
},
],
});
}
/** @override */
get template() {
const path = 'systems/shadowrun-6e-ultimate/templates/item';
// Return a single sheet for all item types.
// return `${path}/item-sheet.hbs`;
// Alternatively, you could use the following return statement to do a
// unique item sheet by type, like `weapon-sheet.hbs`.
return `${path}/item-${this.item.type}-sheet.hbs`;
}
/* -------------------------------------------- */
/** @override */
getData() {
// Retrieve base data structure.
const context = super.getData();
// Use a safe clone of the item data for further operations.
const itemData = context.data;
// Retrieve the roll data for TinyMCE editors.
context.rollData = this.item.getRollData();
// Add the item's data to context.data for easier access, as well as flags.
context.system = itemData.system;
context.flags = itemData.flags;
// Prepare active effects for easier access
context.effects = prepareActiveEffectCategories(this.item.effects);
return context;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
// Roll handlers, click handlers, etc. would go here.
// Active Effect management
html.on('click', '.effect-control', (ev) =>
onManageActiveEffect(ev, this.item)
);
}
}

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "shadowrun-6e-ultimate",
"version": "2.0.0",
"description": "CSS compiler for the Shadowrun6 system",
"scripts": {
"build": "sass src/scss/shadowrun-6e-ultimate.scss css/shadowrun-6e-ultimate.css --style=expanded --no-source-map",
"watch": "sass src/scss/shadowrun-6e-ultimate.scss css/shadowrun-6e-ultimate.css --style=expanded --source-map --watch"
},
"browserslist": [
"last 3 versions"
],
"author": "Asacolips",
"license": "MIT",
"private": true,
"devDependencies": {
"sass": "^1.53.0"
}
}

1
packs/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
/** binary

View File

@ -0,0 +1,14 @@
.effects .item {
.effect-source,
.effect-duration,
.effect-controls {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.effect-controls {
border: none;
}
}

View File

@ -0,0 +1,55 @@
.item-form {
font-family: $font-primary;
}
.sheet-header {
flex: 0 auto;
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;
}
}

View File

@ -0,0 +1,121 @@
// Section Header
.items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: $border-groove;
font-weight: bold;
> * {
font-size: 14px;
text-align: center;
}
.item-name {
font-weight: bold;
padding-left: 5px;
text-align: left;
display: flex;
// font-size: 16px;
}
}
// -----------------------------------------
// Items Lists
// -----------------------------------------
.items-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: thin;
color: $c-tan;
// Child lists
.item-list {
list-style: none;
margin: 0;
padding: 0;
}
// Item Name
.item-name {
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
align-items: center;
display: flex;
h3, h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
}
// Control Buttons
.item-controls {
display: flex;
flex: 0 0 100px;
justify-content: flex-end;
a {
font-size: 12px;
text-align: center;
margin: 0 6px;
}
}
// Individual Item
.item {
align-items: center;
padding: 0 2px; // to align with the header border
border-bottom: 1px solid $c-faint;
&:last-child { border-bottom: none; }
.item-name {
color: $c-dark;
.item-image {
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
}
}
.item-prop {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
// Section Header
.items-header {
height: 28px;
margin: 2px 0;
padding: 0;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: $border-groove;
font-weight: bold;
> * {
font-size: 12px;
text-align: center;
}
.item-name {
padding-left: 5px;
text-align: left;
// font-size: 16px;
}
}
}
// Example style for Shadowrun6 (can be removed if not needed).
.item-formula {
flex: 0 0 200px;
padding: 0 8px;
}

View File

@ -0,0 +1,3 @@
.resource-label {
font-weight: bold;
}

View File

@ -0,0 +1,46 @@
// Flexbox.
.flex-group-center,
.flex-group-left,
.flex-group-right {
justify-content: center;
align-items: center;
text-align: center;
}
.flex-group-left {
justify-content: flex-start;
text-align: left;
}
.flex-group-right {
justify-content: flex-end;
text-align: right;
}
.flexshrink {
flex: 0;
}
.flex-between {
justify-content: space-between;
}
.flexlarge {
flex: 2;
}
// Alignment styles.
.align-left {
justify-content: flex-start;
text-align: left;
}
.align-right {
justify-content: flex-end;
text-align: right;
}
.align-center {
justify-content: center;
text-align: center;
}

View File

@ -0,0 +1,85 @@
// Grid.
.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 6 / span 6;
grid-template-columns: repeat(6, 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));
}
// Grid offset.
.grid-start-2 { grid-column-start: 2 }
.grid-start-3 { grid-column-start: 3 }
.grid-start-4 { grid-column-start: 4 }
.grid-start-5 { grid-column-start: 5 }
.grid-start-6 { grid-column-start: 6 }
.grid-start-7 { grid-column-start: 7 }
.grid-start-8 { grid-column-start: 8 }
.grid-start-9 { grid-column-start: 9 }
.grid-start-10 { grid-column-start: 10 }
.grid-start-11 { grid-column-start: 11 }
.grid-start-12 { grid-column-start: 12 }
.grid-span-2 { grid-column-end: span 2 }
.grid-span-3 { grid-column-end: span 3 }
.grid-span-4 { grid-column-end: span 4 }
.grid-span-5 { grid-column-end: span 5 }
.grid-span-6 { grid-column-end: span 6 }
.grid-span-7 { grid-column-end: span 7 }
.grid-span-8 { grid-column-end: span 8 }
.grid-span-9 { grid-column-end: span 9 }
.grid-span-10 { grid-column-end: span 10 }
.grid-span-11 { grid-column-end: span 11 }
.grid-span-12 { grid-column-end: span 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;
}
}

View File

@ -0,0 +1,21 @@
// Add custom fonts by visiting and search https://fonts.google.com
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
// 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 shadowrun-6e-ultimate sheets */
.shadowrun-6e-ultimate {
@import 'components/forms';
@import 'components/resource';
@import 'components/items';
@import 'components/effects';
}

View File

@ -0,0 +1,7 @@
$c-white: #fff;
$c-black: #000;
$c-dark: #191813;
$c-faint: #c9c7b8;
$c-beige: #b5b3a4;
$c-tan: #444;

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

View File

@ -0,0 +1,2 @@
$font-primary: 'Roboto', sans-serif;
$font-secondary: 'Roboto', sans-serif;

View File

@ -0,0 +1,5 @@
$padding-sm: 5px;
$padding-md: 10px;
$padding-lg: 20px;
$border-groove: 2px groove #eeede0;

51
system.json Normal file
View File

@ -0,0 +1,51 @@
{
"id": "shadowrun-6e-ultimate",
"title": "Shadowrun 6 Ultimania",
"description": "The Shadowrun 6 Ultimania system for FoundryVTT!",
"authors": [
{
"name": "Asacolips",
"discord": "asacolips"
},
{
"name": "Lee Talman",
"discord": ".pizzaWizard"
}
],
"url": "Replace this with a link to your public repository",
"license": "LICENSE.txt",
"readme": "README.md",
"bugs": "Replace this with a link to file issues or tickets",
"changelog": "CHANGELOG.md",
"media": [
{
"type": "setup",
"url": "systems/shadowrun-6e-ultimate/assets/anvil-impact.png",
"thumbnail": "systems/shadowrun-6e-ultimate/assets/anvil-impact.png"
}
],
"version": "2.0.0",
"compatibility": {
"minimum": 11,
"verified": "11.315"
},
"esmodules": ["module/shadowrun-6e-ultimate.mjs"],
"styles": ["css/shadowrun-6e-ultimate.css"],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"packs": [],
"packFolders": [],
"socket": false,
"manifest": "Replace this with a link to the /latest release system.json so people can receive updates after they've installed the system",
"download": "This must link to a .zip file of the current version",
"background": "systems/shadowrun-6e-ultimate/assets/anvil-impact.png",
"gridDistance": 5,
"gridUnits": "ft",
"primaryTokenAttribute": "health",
"secondaryTokenAttribute": "power"
}

8
template.json Normal file
View File

@ -0,0 +1,8 @@
{
"Actor": {
"types": ["character", "npc"]
},
"Item": {
"types": ["item", "feature", "spell"]
}
}

View File

@ -0,0 +1,111 @@
<form class="{{cssClass}} {{actor.type}} flexcol" 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>
{{!-- 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-3col">
{{!-- "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="system.health.value" class="resource-label">Health</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.health.value" value="{{system.health.value}}" data-dtype="Number"/>
<span> / </span>
<input type="text" name="system.health.max" value="{{system.health.max}}" data-dtype="Number"/>
</div>
</div>
<div class="resource flex-group-center">
<label for="system.power.value" class="resource-label">Power</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.power.value" value="{{system.power.value}}" data-dtype="Number"/>
<span> / </span>
<input type="text" name="system.power.max" value="{{system.power.max}}" data-dtype="Number"/>
</div>
</div>
<div class="resource flex-group-center">
<label for="system.attributes.level.value" class="resource-label">Level</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.attributes.level.value" value="{{system.attributes.level.value}}" data-dtype="Number"/>
</div>
</div>
</div>
</div>
</header>
{{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" data-group="primary">
{{!-- Default tab is specified in actor-sheet.mjs --}}
<a class="item" data-tab="features">Features</a>
<a class="item" data-tab="description">Description</a>
<a class="item" data-tab="items">Items</a>
<a class="item" data-tab="spells">Spells</a>
<a class="item" data-tab="effects">Effects</a>
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body">
{{!-- Owned Features Tab --}}
<div class="tab features" data-group="primary" data-tab="features">
<section class="grid grid-3col">
<aside class="sidebar">
{{!-- 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="abilities flexcol">
{{#each system.abilities as |ability key|}}
<div class="ability flexrow flex-group-center">
<label for="system.abilities.{{key}}.value" class="resource-label rollable flexlarge align-left" data-roll="d20+@abilities.{{key}}.mod" data-label="{{ability.label}}">{{ability.label}}</label>
<input type="text" name="system.abilities.{{key}}.value" value="{{ability.value}}" data-dtype="Number"/>
<span class="ability-mod rollable" data-roll="d20+@abilities.{{key}}.mod" data-label="{{ability.label}}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
</div>
{{/each}}
</div>
</aside>
{{!-- For the main features list, span the right two columns --}}
<section class="main grid-span-2">
{{!-- This is a Handlebars partial. They're stored in the `/parts` folder next to this sheet, and defined in module/helpers/templates.mjs --}}
{{> "systems/shadowrun-6e-ultimate/templates/actor/parts/actor-features.hbs"}}
</section>
</section>
</div>
{{!-- Biography Tab --}}
<div class="tab biography" data-group="primary" data-tab="description">
{{!-- If you want TinyMCE editors to output inline rolls when rendered, you need to pass the actor's roll data to the rollData property. --}}
{{editor system.biography target="system.biography" rollData=rollData button=true owner=owner editable=editable}}
</div>
{{!-- Owned Items Tab --}}
<div class="tab items" data-group="primary" data-tab="items">
{{> "systems/shadowrun-6e-ultimate/templates/actor/parts/actor-items.hbs"}}
</div>
{{!-- Owned Spells Tab --}}
<div class="tab spells" data-group="primary" data-tab="spells">
{{> "systems/shadowrun-6e-ultimate/templates/actor/parts/actor-spells.hbs"}}
</div>
{{!-- Active Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/shadowrun-6e-ultimate/templates/actor/parts/actor-effects.hbs"}}
</div>
</section>
</form>
{{log system}}

View File

@ -0,0 +1,76 @@
<form class="{{cssClass}} {{actor.type}} flexcol" 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>
{{!-- 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-3col">
{{!-- "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="system.health.value" class="resource-label">Health</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.health.value" value="{{system.health.value}}" data-dtype="Number"/>
<span> / </span>
<input type="text" name="system.health.max" value="{{system.health.max}}" data-dtype="Number"/>
</div>
</div>
<div class="resource flex-group-center">
<label for="system.power.value" class="resource-label">Power</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.power.value" value="{{system.power.value}}" data-dtype="Number"/>
<span> / </span>
<input type="text" name="system.power.max" value="{{system.power.max}}" data-dtype="Number"/>
</div>
</div>
<div class="resource flex-group-center">
<label for="system.cr" class="resource-label">CR / XP</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.cr" value="{{system.cr}}" data-dtype="Number"/>
<span> / </span>
<input type="text" disabled="true" name="system.xp" value="{{system.xp}}" data-dtype="Number"/>
</div>
</div>
</div>
</div>
</header>
{{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" data-group="primary">
{{!-- Default tab is specified in actor-sheet.mjs --}}
<a class="item" data-tab="description">Description</a>
<a class="item" data-tab="items">Items</a>
<a class="item" data-tab="effects">Effects</a>
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body">
{{!-- Biography Tab --}}
<div class="tab biography" data-group="primary" data-tab="description">
{{!-- If you want TinyMCE editors to output inline rolls when rendered, you need to pass the actor's roll data to the rollData property. --}}
{{editor system.biography target="system.biography" rollData=rollData button=true owner=owner editable=editable}}
</div>
{{!-- Owned Items Tab --}}
<div class="tab items" data-group="primary" data-tab="items">
{{> "systems/shadowrun-6e-ultimate/templates/actor/parts/actor-items.hbs"}}
</div>
{{!-- Active Effects Tab --}}
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
{{> "systems/shadowrun-6e-ultimate/templates/actor/parts/actor-effects.hbs"}}
</div>
</section>
</form>

View File

@ -0,0 +1,38 @@
<ol class="items-list effects-list">
{{#each effects as |section sid|}}
<li class="items-header flexrow" data-effect-type="{{section.type}}" data-parent-id="{{@root.actor.id}}" >
<h3 class="item-name effect-name flexrow">{{localize section.label}}</h3>
<div class="effect-source">{{localize "SHADOWRUN_6.Effect.Source"}}</div>
<div class="effect-source">{{localize "EFFECT.TabDuration"}}</div>
<div class="item-controls effect-controls flexrow">
<a class="effect-control" data-action="create" title="{{localize 'DOCUMENT.Create' type="Effect"}}">
<i class="fas fa-plus"></i> {{localize "DOCUMENT.New" type="Effect"}}
</a>
</div>
</li>
<ol class="item-list">
{{#each section.effects as |effect|}}
<li class="item effect flexrow" data-effect-id="{{effect.id}}" data-parent-id="{{effect.parent.id}}">
<div class="item-name effect-name flexrow">
<img class="item-image" src="{{effect.icon}}"/>
<h4>{{effect.name}}</h4>
</div>
<div class="effect-source">{{effect.sourceName}}</div>
<div class="effect-duration">{{effect.duration.label}}</div>
<div class="item-controls effect-controls flexrow">
<a class="effect-control" data-action="toggle" title="{{localize 'SHADOWRUN_6.Effect.Toggle'}}">
<i class="fas {{#if effect.disabled}}fa-check{{else}}fa-times{{/if}}"></i>
</a>
<a class="effect-control" data-action="edit" title="{{localize 'DOCUMENT.Update' type="Effect"}}">
<i class="fas fa-edit"></i>
</a>
<a class="effect-control" data-action="delete" title="{{localize 'DOCUMENT.Delete' type="Effect"}}">
<i class="fas fa-trash"></i>
</a>
</div>
</li>
{{/each}}
</ol>
{{/each}}
</ol>

View File

@ -0,0 +1,46 @@
<ol class='items-list'>
<li class='item flexrow items-header'>
<div class='item-name'>{{localize 'Name'}}</div>
<div class='item-controls'>
<a
class='item-control item-create'
title='Create item'
data-type='feature'
>
<i class='fas fa-plus'></i>
{{localize 'DOCUMENT.New' type='feature'}}
</a>
</div>
</li>
{{#each features as |item id|}}
<li class='item flexrow' data-item-id='{{item._id}}'>
<div class='item-name'>
<div class='item-image'>
<a class='rollable' data-roll-type='item'>
<img
src='{{item.img}}'
title='{{item.name}}'
width='24'
height='24'
/>
</a>
</div>
<h4>{{item.name}}</h4>
</div>
<div class='item-controls'>
<a
class='item-control item-edit'
title='{{localize "DOCUMENT.Edit" type='feature'}}'
>
<i class='fas fa-edit'></i>
</a>
<a
class='item-control item-delete'
title='{{localize "DOCUMENT.Delete" type='feature'}}'
>
<i class='fas fa-trash'></i>
</a>
</div>
</li>
{{/each}}
</ol>

View File

@ -0,0 +1,48 @@
<ol class='items-list'>
<li class='item flexrow items-header'>
<div class='item-name'>{{localize 'Name'}}</div>
<div class='item-formula'>{{localize 'Roll Formula'}}</div>
<div class='item-controls'>
<a
class='item-control item-create'
title='{{localize "DOCUMENT.Create" type='Item'}}'
data-type='item'
>
<i class='fas fa-plus'></i>
{{localize 'DOCUMENT.New' type='item'}}
</a>
</div>
</li>
{{#each gear as |item id|}}
<li class='item flexrow' data-item-id='{{item._id}}'>
<div class='item-name'>
<div class='item-image'>
<a class='rollable' data-roll-type='item'>
<img
src='{{item.img}}'
title='{{item.name}}'
width='24'
height='24'
/>
</a>
</div>
<h4>{{item.name}}</h4>
</div>
<div class='item-formula item-prop'>{{item.system.formula}}</div>
<div class='item-controls'>
<a
class='item-control item-edit'
title='{{localize "DOCUMENT.Update" type='Item'}}'
>
<i class='fas fa-edit'></i>
</a>
<a
class='item-control item-delete'
title='{{localize "DOCUMENT.Delete" type='Item'}}'
>
<i class='fas fa-trash'></i>
</a>
</div>
</li>
{{/each}}
</ol>

View File

@ -0,0 +1,49 @@
<ol class='items-list'>
{{#each spells as |spells spellLevel|}}
<li class='item flexrow items-header'>
<div class='item-name'>
{{localize 'SHADOWRUN_6.Item.Spell.SpellLVL' level=spellLevel}}
</div>
<div class='item-controls'>
<a
class='item-control item-create'
title='{{localize "DOCUMENT.Create" type='Spell'}}'
data-type='spell'
data-spell-level='{{spellLevel}}'
>
<i class='fas fa-plus'></i>
{{localize 'SHADOWRUN_6.Item.Spell.AddLVL' level=spellLevel}}
</a>
</div>
</li>
{{#each spells as |item id|}}
<li class='item flexrow' data-item-id='{{item._id}}'>
<div class='item-name flexrow'>
<div class='item-image'>
<a class='rollable' data-roll-type='item'><img
src='{{item.img}}'
title='{{item.name}}'
width='24'
height='24'
/></a>
</div>
<h4>{{item.name}}</h4>
</div>
<div class='item-controls'>
<a
class='item-control item-edit'
title='{{localize "DOCUMENT.Edit" type='spell'}}'
>
<i class='fas fa-edit'></i>
</a>
<a
class='item-control item-delete'
title='{{localize "DOCUMENT.Delete" type='spell'}}'
>
<i class='fas fa-trash'></i>
</a>
</div>
</li>
{{/each}}
{{/each}}
</ol>

View File

@ -0,0 +1,35 @@
<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>
<a class="item" data-tab="effects">Effects</a>
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body">
{{!-- Description Tab --}}
<div class="tab" data-group="primary" data-tab="description">
{{editor system.description target="system.description" rollData=rollData 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>
{{!-- Effects Tab --}}
<div class="tab effects" data-group="primary" data-tab="effects">
{{> "systems/shadowrun-6e-ultimate/templates/item/parts/item-effects.hbs"}}
</div>
</section>
</form>

View File

@ -0,0 +1,62 @@
<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="system.quantity" value="{{system.quantity}}" data-dtype="Number" />
</div>
<div class="resource">
<label class="resource-label">Weight</label>
<input type="text" name="system.weight" value="{{system.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">
{{!-- To render inline rolls in a TinyMCE editor, you need to pass the parent actor's (if any) roll data to the
rollData prop. --}}
{{editor system.description target="system.description" rollData=rollData 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">Roll Formula:</label>
<span>{{system.formula}}</span>
<div class="grid grid-4col">
<div class="grid-span-1">
<label class="resource-label">Number of Dice</label>
<input type="text" name="system.roll.diceNum" value="{{system.roll.diceNum}}" data-dtype="Number" />
</div>
<div class="grid-span-1">
<label class="resource-label">Die Size</label>
<input type="text" name="system.roll.diceSize" value="{{system.roll.diceSize}}" data-dtype="String" />
</div>
<div class="grid-span-2">
<label class="resource-label">Roll Modifier</label>
<input type="text" name="system.roll.diceBonus" value="{{system.roll.diceBonus}}" data-dtype="String" />
</div>
</div>
</div>
</div>
</section>
</form>
{{log system}}

View File

@ -0,0 +1,30 @@
{{!-- This template is a fallback for when items don't have more specific templates. --}}
{{!-- Generally, you'll want to make more specific templates when possible. --}}
<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 system.description target="system.description" rollData=rollData 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>

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 system.description target="system.description" rollData=rollData 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="system.spellLevel" value="{{system.spellLevel}}" data-dtype="Number"/>
</div>
</div>
</section>
</form>

View File

@ -0,0 +1,38 @@
<ol class="items-list effects-list">
{{#each effects as |section sid|}}
<li class="items-header flexrow" data-effect-type="{{section.type}}">
<h3 class="item-name effect-name flexrow">{{localize section.label}}</h3>
<div class="effect-source">{{localize "SHADOWRUN_6.Effect.Source"}}</div>
<div class="effect-source">{{localize "EFFECT.TabDuration"}}</div>
<div class="item-controls effect-controls flexrow">
<a class="effect-control" data-action="create" title="{{localize 'DOCUMENT.Create' type="Effect"}}">
<i class="fas fa-plus"></i> {{localize "DOCUMENT.New" type="Effect"}}
</a>
</div>
</li>
<ol class="item-list">
{{#each section.effects as |effect|}}
<li class="item effect flexrow" data-effect-id="{{effect.id}}" data-parent-id="{{effect.parent.id}}">
<div class="item-name effect-name flexrow">
<img class="item-image" src="{{effect.icon}}"/>
<h4>{{effect.name}}</h4>
</div>
<div class="effect-source">{{effect.sourceName}}</div>
<div class="effect-duration">{{effect.duration.label}}</div>
<div class="item-controls effect-controls flexrow">
<a class="effect-control" data-action="toggle" title="{{localize 'SHADOWRUN_6.Effect.Toggle'}}">
<i class="fas {{#if effect.disabled}}fa-check{{else}}fa-times{{/if}}"></i>
</a>
<a class="effect-control" data-action="edit" title="{{localize 'DOCUMENT.Update' type="Effect"}}">
<i class="fas fa-edit"></i>
</a>
<a class="effect-control" data-action="delete" title="{{localize 'DOCUMENT.Delete' type="Effect"}}">
<i class="fas fa-trash"></i>
</a>
</div>
</li>
{{/each}}
</ol>
{{/each}}
</ol>