Upload old System

This commit is contained in:
Joscha Maier 2024-09-22 01:04:15 +02:00
commit 13af30f74b
No known key found for this signature in database
GPG Key ID: 8C4D045D84A30ABA
36 changed files with 1882 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Joscha Maier
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.

25
LICENSE.txt Normal file
View File

@ -0,0 +1,25 @@
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.

423
css/kidsonbrooms.css Normal file
View File

@ -0,0 +1,423 @@
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
/* Global styles */
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
.window-app {
font-family: "Roboto", sans-serif;
}
.rollable:hover, .rollable:focus {
color: #000;
text-shadow: 0 0 10px rgb(146, 0, 225);
cursor: pointer;
}
.grid {
display: grid;
gap: 10px;
margin: 10px 0;
padding: 0;
}
.grid-start-2 {
grid-column-start: 2;
}
.grid-span-2 {
grid-column-end: span 2;
}
.grid-2col {
grid-column: span 2/span 2;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid-start-3 {
grid-column-start: 3;
}
.grid-span-3 {
grid-column-end: span 3;
}
.grid-3col {
grid-column: span 3/span 3;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-start-4 {
grid-column-start: 4;
}
.grid-span-4 {
grid-column-end: span 4;
}
.grid-4col {
grid-column: span 4/span 4;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-start-5 {
grid-column-start: 5;
}
.grid-span-5 {
grid-column-end: span 5;
}
.grid-5col {
grid-column: span 5/span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-start-6 {
grid-column-start: 6;
}
.grid-span-6 {
grid-column-end: span 6;
}
.grid-6col {
grid-column: span 6/span 6;
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.grid-start-7 {
grid-column-start: 7;
}
.grid-span-7 {
grid-column-end: span 7;
}
.grid-7col {
grid-column: span 7/span 7;
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.grid-start-8 {
grid-column-start: 8;
}
.grid-span-8 {
grid-column-end: span 8;
}
.grid-8col {
grid-column: span 8/span 8;
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.grid-start-9 {
grid-column-start: 9;
}
.grid-span-9 {
grid-column-end: span 9;
}
.grid-9col {
grid-column: span 9/span 9;
grid-template-columns: repeat(9, minmax(0, 1fr));
}
.grid-start-10 {
grid-column-start: 10;
}
.grid-span-10 {
grid-column-end: span 10;
}
.grid-10col {
grid-column: span 10/span 10;
grid-template-columns: repeat(10, minmax(0, 1fr));
}
.grid-start-11 {
grid-column-start: 11;
}
.grid-span-11 {
grid-column-end: span 11;
}
.grid-11col {
grid-column: span 11/span 11;
grid-template-columns: repeat(11, minmax(0, 1fr));
}
.grid-start-12 {
grid-column-start: 12;
}
.grid-span-12 {
grid-column-end: span 12;
}
.grid-12col {
grid-column: span 12/span 12;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.flex-group-center {
display: flex;
flex-direction: center;
flex-wrap: center;
justify-content: flex-start;
align-items: stretch;
text-align: center;
}
.flex-group-left {
display: flex;
flex-direction: flex-start;
flex-wrap: center;
justify-content: flex-start;
align-items: stretch;
text-align: left;
}
.flex-group-right {
display: flex;
flex-direction: flex-end;
flex-wrap: center;
justify-content: flex-start;
align-items: stretch;
text-align: right;
}
.flexshrink {
flex: 0;
}
.flex-between {
justify-content: space-between;
}
.flexlarge {
flex: 2;
}
.align-left {
display: flex;
flex-direction: flex-start;
flex-wrap: center;
justify-content: flex-start;
align-items: stretch;
text-align: left;
}
.align-right {
display: flex;
flex-direction: flex-end;
flex-wrap: center;
justify-content: flex-start;
align-items: stretch;
text-align: right;
}
.align-center {
display: flex;
flex-direction: center;
flex-wrap: center;
justify-content: flex-start;
align-items: stretch;
text-align: center;
}
.right-align-input {
flex: 1;
margin-left: auto;
max-width: 260px;
}
.window-app {
font-family: "Roboto", sans-serif;
}
.rollable:hover, .rollable:focus {
color: #000;
text-shadow: 0 0 10px rgb(179, 7, 217);
cursor: pointer;
}
/* Styles limited to kidsonbrooms sheets */
.kids-on-brooms .item-form {
font-family: "Roboto", sans-serif;
}
.kids-on-brooms .sheet-header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: stretch;
flex: 0 auto;
overflow: hidden;
margin-bottom: 10px;
}
.kids-on-brooms .sheet-header .profile-img {
flex: 0 0 100px;
height: 100px;
margin-right: 10px;
}
.kids-on-brooms .sheet-header .header-fields {
flex: 1;
}
.kids-on-brooms .sheet-header h1.charname {
height: 50px;
padding: 0;
margin: 5px 0;
border-bottom: 0;
}
.kids-on-brooms .sheet-header h1.charname input {
width: 100%;
height: 100%;
margin: 0;
}
.kids-on-brooms div.editor-border {
border: 2px solid rgb(81, 81, 81);
border-radius: 10px;
}
.kids-on-brooms .sheet-tabs {
flex: 0;
}
.kids-on-brooms .sheet-body,
.kids-on-brooms .sheet-body .tab,
.kids-on-brooms .sheet-body .tab .editor {
height: 100%;
}
.kids-on-brooms .tox .tox-editor-container {
background: #fff;
}
.kids-on-brooms .tox .tox-edit-area {
padding: 0 8px;
}
.kids-on-brooms .selection-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.kids-on-brooms .resource-label {
font-weight: bold;
}
.kids-on-brooms .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;
}
.kids-on-brooms .items-header > * {
font-size: 14px;
text-align: center;
}
.kids-on-brooms .items-header .item-name {
font-weight: bold;
padding-left: 5px;
text-align: left;
display: flex;
}
.kids-on-brooms .items-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: thin;
color: #444;
}
.kids-on-brooms .items-list .item-list {
list-style: none;
margin: 0;
padding: 0;
}
.kids-on-brooms .items-list .item {
display: flex;
align-items: center;
padding: 0 2px;
border-bottom: 1px solid #c9c7b8;
}
.kids-on-brooms .items-list .item:last-child {
border-bottom: none;
}
.kids-on-brooms .items-list .item .item-name {
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
display: flex;
color: #191813;
}
.kids-on-brooms .items-list .item .item-name h3, .kids-on-brooms .items-list .item .item-name h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
.kids-on-brooms .items-list .item .item-name .item-image {
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
.kids-on-brooms .items-list .item-controls {
display: flex;
flex: 0 0 100px;
justify-content: flex-end;
}
.kids-on-brooms .items-list .item-controls a {
font-size: 12px;
text-align: center;
margin: 0 6px;
}
.kids-on-brooms .items-list .item-prop {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.kids-on-brooms .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;
}
.kids-on-brooms .items-list .items-header > * {
font-size: 12px;
text-align: center;
}
.kids-on-brooms .items-list .items-header .item-name {
padding-left: 5px;
text-align: left;
}
.kids-on-brooms .item-formula {
flex: 0 0 200px;
padding: 0 8px;
}
.kids-on-brooms .effects .item .effect-source,
.kids-on-brooms .effects .item .effect-duration,
.kids-on-brooms .effects .item .effect-controls {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.kids-on-brooms .effects .item .effect-controls {
border: none;
}
.kids-on-brooms .kids-on-brooms input:focus,
.kids-on-brooms .kids-on-brooms textarea:focus,
.kids-on-brooms .kids-on-brooms select:focus {
outline: none;
border-color: #8102dd;
}
/*# sourceMappingURL=kidsonbrooms.css.map */

1
css/kidsonbrooms.css.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../scss/kidsonbrooms.scss","../scss/global/_base.scss","../scss/global/_window.scss","../scss/utils/_typography.scss","../scss/global/_grid.scss","../scss/global/_flex.scss","../scss/utils/_mixins.scss","../scss/utils/_variables.scss","../scss/components/_forms.scss","../scss/utils/_colors.scss","../scss/components/_resource.scss","../scss/components/_items.scss","../scss/components/_effects.scss"],"names":[],"mappings":"AACQ;AASR;ACTQ;ACDR;EACE,aCDa;;;ADKb;EAEE;EACA;EACA;;;AERJ;EACE;EACA;EACA;EACA;;;AAKA;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;ACrBJ;ECkBE;EACA,gBDlBiB;ECmBjB,WDnByB;ECoBzB,iBAJkD;EAKlD,aALsE;EDftE;;;AAGF;ECaE;EACA,gBDbiB;ECcjB,WDd6B;ECe7B,iBAJkD;EAKlD,aALsE;EDVtE;;;AAGF;ECQE;EACA,gBDRiB;ECSjB,WDT2B;ECU3B,iBAJkD;EAKlD,aALsE;EDLtE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;ECVE;EACA,gBDUiB;ECTjB,WDS6B;ECR7B,iBAJkD;EAKlD,aALsE;EDatE;;;AAGF;ECfE;EACA,gBDeiB;ECdjB,WDc2B;ECb3B,iBAJkD;EAKlD,aALsE;EDkBtE;;;AAGF;ECpBE;EACA,gBDoBiB;ECnBjB,WDmByB;EClBzB,iBAJkD;EAKlD,aALsE;EDuBtE;;;AAIF;EACE;EACA;EACA;;;AJ7CF;EACE,aMEW;;;ANEX;EAEE;EACA;EACA;;;ADIJ;AQhBA;EACE,aLDa;;AKIf;EFeE;EACA,gBEfiB;EFgBjB,WEhBsB;EFiBtB,iBEjB4B;EFkB5B,aALsE;EEZtE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKN;EACE;EACA;;AAGF;EACE;;AAGF;AAAA;AAAA;EAGE;;AAIA;EACE,YCnDM;;ADsDR;EACE;;AAKJ;EACE;EACA;EACA;EACA;;AEhEF;EACE;;ACAF;EACE;EACA;EACA;EACA;EACA;EACA,QJHc;EIId;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,OFxBM;;AE2BN;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OFvDG;;AEyDH;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAMN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,QJrGc;EIsGd;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;;ACzHA;AAAA;AAAA;EAGE;EACA;EACA;EACA;;AAGF;EACE;;AAKJ;AAAA;AAAA;EAGE;EACA,cHVmB","file":"kidsonbrooms.css"}

1
css/test.css.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../scss/kidsonbrooms.scss","../scss/global/_base.scss","../scss/global/_window.scss","../scss/utils/_typography.scss","../scss/global/_grid.scss","../scss/global/_flex.scss","../scss/utils/_mixins.scss","../scss/utils/_variables.scss","../scss/components/_forms.scss","../scss/utils/_colors.scss","../scss/components/_resource.scss","../scss/components/_items.scss","../scss/components/_effects.scss"],"names":[],"mappings":"AACQ;AASR;ACTQ;ACDR;EACE,aCDa;;;ADKb;EAEE;EACA;EACA;;;AERJ;EACE;EACA;EACA;EACA;;;AAKA;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;AAZF;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;;;ACrBJ;ECkBE;EACA,gBDlBiB;ECmBjB,WDnByB;ECoBzB,iBAJkD;EAKlD,aALsE;EDftE;;;AAGF;ECaE;EACA,gBDbiB;ECcjB,WDd6B;ECe7B,iBAJkD;EAKlD,aALsE;EDVtE;;;AAGF;ECQE;EACA,gBDRiB;ECSjB,WDT2B;ECU3B,iBAJkD;EAKlD,aALsE;EDLtE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;ECVE;EACA,gBDUiB;ECTjB,WDS6B;ECR7B,iBAJkD;EAKlD,aALsE;EDatE;;;AAGF;ECfE;EACA,gBDeiB;ECdjB,WDc2B;ECb3B,iBAJkD;EAKlD,aALsE;EDkBtE;;;AAGF;ECpBE;EACA,gBDoBiB;ECnBjB,WDmByB;EClBzB,iBAJkD;EAKlD,aALsE;EDuBtE;;;AJtCF;EACE,aMEW;;;ANEX;EAEE;EACA;EACA;;;ADIJ;AQhBA;EACE,aLDa;;AKIf;EFeE;EACA,gBEfiB;EFgBjB,WEhBsB;EFiBtB,iBEjB4B;EFkB5B,aALsE;EEZtE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKN;EACE;EACA;;AAGF;EACE;;AAGF;AAAA;AAAA;EAGE;;AAIA;EACE,YCnDM;;ADsDR;EACE;;AEvDJ;EACE;;ACAF;EACE;EACA;EACA;EACA;EACA;EACA,QJHc;EIId;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,OFxBM;;AE2BN;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OFvDG;;AEyDH;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAMN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,QJrGc;EIsGd;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;;ACzHA;AAAA;AAAA;EAGE;EACA;EACA;EACA;;AAGF;EACE;;AAKJ;AAAA;AAAA;EAGE;EACA,cHVmB","file":"test.css"}

9
lang/en.json Normal file
View File

@ -0,0 +1,9 @@
{
"KIDSONBROOMS.EffectCreate": "Create Effect",
"KIDSONBROOMS.EffectToggle": "Toggle Effect",
"KIDSONBROOMS.EffectEdit": "Edit Effect",
"KIDSONBROOMS.EffectDelete": "Delete Effect",
"KIDSONBROOMS.Add": "Add"
}

View File

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

View File

@ -0,0 +1,59 @@
/**
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class KidsOnBroomsActor extends Actor {
/**
* Override getRollData() that's supplied to rolls.
*/
getRollData() {
let data = { ...this.system };
// Wand bonuses
data.wandBonus = {
wood: this._getWandBonus(this.system.wand.wood),
core: this._getWandBonus(this.system.wand.core)
};
return data;
}
_getWandBonus(type) {
const bonuses = {
"Wisteria": { stat: "brains", bonus: 1 },
"Hawthorn": { stat: "brains", bonus: 1 },
"Pine": { stat: "brawn", bonus: 1 },
"Oak": { stat: "brawn", bonus: 1 },
"Crabapple": { stat: "fight", bonus: 1 },
"Dogwood": { stat: "fight", bonus: 1 },
"Birch": { stat: "flight", bonus: 1 },
"Bamboo": { stat: "flight", bonus: 1 },
"Ironwood": { stat: "grit", bonus: 1 },
"Maple": { stat: "grit", bonus: 1 },
"Lilac": { stat: "charm", bonus: 1 },
"Cherry": { stat: "charm", bonus: 1 },
"Parchment": { stat: "brains", bonus: 1 },
"Phoenix Feather": { stat: "brains", bonus: 1 },
"Owl Feather": { stat: "brains", bonus: 1 },
"Gorilla Fur": { stat: "brawn", bonus: 1 },
"Ogres Fingernail": { stat: "brawn", bonus: 1 },
"Hippos Tooth": { stat: "brawn", bonus: 1 },
"Dragons Heartstring": { stat: "fight", bonus: 1 },
"Wolfs Tooth": { stat: "fight", bonus: 1 },
"Elks Antler": { stat: "fight", bonus: 1 },
"Hawks Feather": { stat: "flight", bonus: 1 },
"Bats Bone": { stat: "flight", bonus: 1 },
"Changelings Hair": { stat: "charm", bonus: 1 },
"Gold": { stat: "charm", bonus: 1 },
"Mirror": { stat: "charm", bonus: 1 },
"Steel": { stat: "grit", bonus: 1 },
"Diamond": { stat: "grit", bonus: 1 },
"Lions Mane": { stat: "grit", bonus: 1 }
};
return bonuses[type] || { stat: "", bonus: 0 };
}
}

View File

@ -0,0 +1,7 @@
export const KIDSONBROOMS = {};
// Define constants here, such as:
KIDSONBROOMS.foobar = {
'bas': 'KIDSONBROOMS.bas',
'bar': 'KIDSONBROOMS.bar'
};

View File

@ -0,0 +1,15 @@
/**
* 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/kids-on-brooms/templates/actor/parts/actor-features.html",
"systems/kids-on-brooms/templates/actor/parts/actor-adversity.html",
"systems/kids-on-brooms/templates/actor/parts/actor-stats.html",
"systems/kids-on-brooms/templates/actor/parts/actor-npc-stats.html",
]);
};

333
module/kidsonbrooms.mjs Normal file
View File

@ -0,0 +1,333 @@
// Import document classes.
import { KidsOnBroomsActor } from "./documents/actor.mjs";
// Import sheet classes.
import { KidsOnBroomsActorSheet } from "./sheets/actor-sheet.mjs";
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
import { KIDSONBROOMS } from "./helpers/config.mjs";
/* -------------------------------------------- */
/* Init Hook */
/* -------------------------------------------- */
Hooks.once('init', async function() {
// Register the helper
Handlebars.registerHelper('capitalizeFirst', function(string) {
if (typeof string === 'string') {
return string.charAt(0).toUpperCase() + string.slice(1);
}
return '';
});
// Add utility classes and functions to the global game object so that they're more easily
// accessible in global contexts.
game.kidsonbrooms = {
KidsOnBroomsActor,
_onTakeAdversityToken: _onTakeAdversityToken, // Add the function to the global object
_onSpendAdversityTokens: _onSpendAdversityTokens // Add the function to the global object
};
// Add custom constants for configuration.
CONFIG.KIDSONBROOMS = KIDSONBROOMS;
/**
* Set an initiative formula for the system
* @type {String}
*/
CONFIG.Combat.initiative = {
formula: "1d20",
decimals: 2
};
// Define custom Document classes
CONFIG.Actor.documentClass = KidsOnBroomsActor;
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("kids-on-brooms", KidsOnBroomsActorSheet, { makeDefault: true });
//If there is a new chat message that is a roll we add the adversity token controls
Hooks.on("renderChatMessage", (message, html, messageData) => {
const adversityControls = html.find('.adversity-controls');
if (adversityControls.length > 0) {
const messageToEdit = adversityControls.data("roll-id");
// Bind event listeners for the controls
adversityControls.find(".take-adversity").off("click").click((event) => {
const actorId = event.currentTarget.dataset.actorId;
const actor = game.actors.get(actorId);
// Check if the current user owns the actor - They can not claim if they are not
if (!actor.testUserPermission(game.user, "owner")) {
ui.notifications.warn("You don't own this character and cannot take adversity tokens.");
return;
}
// Check if the token has already been claimed -- Contigency if the button somehow activates again
if (message.getFlag("kids-on-brooms", "tokenClaimed")) {
ui.notifications.warn("This adversity token has already been claimed.");
return;
}
_onTakeAdversityToken(event, actor);
if (game.user.isGM) {
let tokenControls = game.messages.get(message.id);
console.log(tokenControls);
// Update the chat message content with the button disabled and text changed
const updatedContent = tokenControls.content.replace(
`<button class="take-adversity" data-actor-id="${actor.id}">Take Adversity Token</button>`,
`<button class="take-adversity" data-actor-id="${actor.id}" disabled>Token claimed</button>`
);
console.log("Removing Button");
// Update the message content
tokenControls.update({ content: updatedContent });
// Set the flag on the chat message to indicate that the token has been claimed
tokenControls.setFlag("kids-on-brooms", "tokenClaimed", true);
} else {
// Emit a socket request to update the message to show that the token has been claimed
game.socket.emit('system.kids-on-brooms', {
action: "takeToken",
messageID: message.id,
actorID: actor.id,
});
}
console.log("Send socket message for taking a token");
});
adversityControls.find(".spend-adversity").off("click").click((event) => {
//This entails a lot more, so I offloaded it to a new function
_onSpendAdversityTokens(event, messageToEdit);
});
}
});
// Preload Handlebars templates.
return preloadHandlebarsTemplates();
});
/***
* This handles the incoming socket requests.
* If a player wants to spend tokens on another players roll the gm has to approve first
* if a player wants to claim a token we will update the message since they do not have the permissions
*/
Hooks.once('ready', function() {
game.socket.on('system.kids-on-brooms', async (data) => {
console.log("Socket data received:", data);
if (data.action === "spendTokens") {
console.log(`Request to spend tokens: ${data.tokensToSpend} tokens for ${data.rollActorId} by ${data.spendingActorId}`);
// Only handle the request if the GM is logged in
if (!game.user.isGM) {
console.log("Not GM, ignoring the token spend request.");
return;
}
// The actor who made the roll
const rollActor = game.actors.get(data.rollActorId);
// The actor who is spending tokens
const spendingActor = game.actors.get(data.spendingActorId);
//If these for some reason do not exist
if (!rollActor || !spendingActor) {
console.warn("Actor not found:", data.rollActorId, data.spendingActorId);
return;
}
// Create a confirmation dialog for the GM
new Dialog({
title: "Approve Adversity Token Spending?",
content: `<p>${spendingActor.name} wants to spend ${data.tokenCost} adversity tokens on ${rollActor.name}'s roll to increase it by ${data.tokensToSpend}. Approve?</p>`,
buttons: {
yes: {
label: "Yes",
callback: async () => {
const currentTokens = spendingActor.system.adversityTokens || 0;
// Update the spending actor's adversity token count
await spendingActor.update({ "system.adversityTokens": currentTokens - data.tokenCost });
// Modify the roll message with the new total
await _updateRollMessage(data.rollMessageId, data.tokensToSpend, false);
console.log(`${spendingActor.name} spent ${data.tokensToSpend} tokens, updated roll total to ${roll.cumulativeTotal}`);
ui.notifications.info(`${spendingActor.name} successfully spent ${data.tokensToSpend} tokens.`);
}
},
no: {
label: "No",
callback: () => {
ui.notifications.info(`The GM denied ${spendingActor.name}'s request to spend tokens.`);
}
}
},
default: "yes"
}).render(true);
} else if (data.action === "takeToken") {
// Only handle the request if the GM is logged in
if (!game.user.isGM) {
console.log("Not GM, ignoring the token spend request.");
return;
}
let tokenControls = game.messages.get(data.messageID);
console.log(tokenControls);
// Update the chat message content with the button disabled and text changed
const updatedContent = tokenControls.content.replace(
`<button class="take-adversity" data-actor-id="${data.actorID}">Take Adversity Token</button>`,
`<button class="take-adversity" data-actor-id="${data.actorID}" disabled>Token claimed</button>`
);
console.log("Removing Button");
// Update the message content
tokenControls.update({ content: updatedContent });
// Set the flag on the chat message to indicate that the token has been claimed
tokenControls.setFlag("kids-on-brooms", "tokenClaimed", true);
}
});
});
/***
* This function adds the adversity token to the actor that made the roll and logs it
*
* @param {Event} e - The button click event
* @param {Actor} actor - The actor object that made the roll
*/
async function _onTakeAdversityToken(e, actor) {
e.preventDefault();
// Get the chat message ID (assuming it's stored in the dataset)
const messageId = e.currentTarget.closest('.message').dataset.messageId;
const message = game.messages.get(messageId);
// Add an adversity token to the actor
const currentTokens = actor.system.adversityTokens || 0;
await actor.update({ "system.adversityTokens": currentTokens + 1 });
// Notify the user
ui.notifications.info(`You gained 1 adversity token.`);
console.log(`Gave one adversity token to ${actor.id}`)
}
/***
* This function allows players to spend tokens to change a roll. This will automatically be calculated in their sheet
*
*/
async function _onSpendAdversityTokens(e, rollMessageId) {
e.preventDefault();
// The actor who made the roll
const rollActorId = e.currentTarget.dataset.actorId;
const rollActor = game.actors.get(rollActorId); //technically redundant since it is also done in the main hook, but perfomance is good enuff
// Get the actor of the player who is spending tokens
const spendingPlayerActor = game.actors.get(game.user.character?.id || game.actors.filter(actor => actor.testUserPermission(game.user, "owner"))[0]?.id);
if (!spendingPlayerActor) {
ui.notifications.warn("You don't control any actors.");
return;
}
//Get the tokens to be spend from the input field
const tokenInput = $(e.currentTarget).closest('.adversity-controls').find('.token-input').val();
const tokensToSpend = parseInt(tokenInput, 10);
if (isNaN(tokensToSpend) || tokensToSpend <= 0) {
ui.notifications.warn("Please enter a valid number of tokens.");
return;
}
let tokenCost = tokensToSpend;
// If the player spending tokens is not the owner of the actor who rolled, they spend double
//(note, this is a rule of mine, I have disabled it by default)
if ((!spendingPlayerActor.testUserPermission(game.user, "owner") || spendingPlayerActor.id !== rollActorId) && false) {
tokenCost = tokensToSpend * 2;
}
// Ensure the spending actor has enough adversity tokens
if (spendingPlayerActor.system.adversityTokens < tokenCost) {
ui.notifications.warn(`You do not have enough adversity tokens.`);
return;
}
// Check if the player owns the actor who made the roll
if (spendingPlayerActor.id === rollActorId) {
// The player owns the actor, so they can spend tokens directly without GM approval
const currentTokens = spendingPlayerActor.system.adversityTokens || 0;
// Deduct the tokens from the player
await spendingPlayerActor.update({ "system.adversityTokens": currentTokens - tokenCost });
// Modify the roll message with the new total
await _updateRollMessage(rollMessageId, tokensToSpend, true);
} else {
// The player does not own the actor, so request GM approval to spend the tokens
console.log(`Requesting to spend ${tokensToSpend} tokens for ${rollActor.name} by ${spendingPlayerActor.name} (cost: ${tokenCost})`);
// Emit a socket request to spend tokens
game.socket.emit('system.kids-on-brooms', {
action: "spendTokens",
rollActorId: rollActorId,
spendingActorId: spendingPlayerActor.id, // Send the player's actor who is spending the tokens
tokensToSpend: tokensToSpend,
tokenCost: tokenCost,
rollMessageId: rollMessageId // Pass message ID to update the roll result
});
ui.notifications.info(`Requested to spend ${tokenCost} tokens for ${rollActor.name}`);
}
}
// Helper function to send a new message with the updated roll result
async function _updateRollMessage(rollMessageId, tokensToSpend, isPlayerOfActor) {
const message = game.messages.get(rollMessageId);
if (!message) {
console.error("Message not found with ID:", rollMessageId);
return;
}
// Retrieve current tokens spent from flags, or initialize to 0 if not found
let cumulativeTokensSpent = message.getFlag("kids-on-brooms", "tokensSpent") || 0;
let newTotal = message.getFlag("kids-on-brooms", "newRollTotal") || message.rolls[0].total;
/*if(isPlayerOfActor)
{
// Add the new tokens to the cumulative total
cumulativeTokensSpent += tokensToSpend;
} else {
cumulativeTokensSpent += 2*tokensToSpend;
}*/
cumulativeTokensSpent += tokensToSpend;
newTotal += tokensToSpend;
await message.setFlag("kids-on-brooms", "newRollTotal", newTotal);
// Update the message's flags to store the cumulative tokens spent
await message.setFlag("kids-on-brooms", "tokensSpent", cumulativeTokensSpent);
let newContent = "";
if(cumulativeTokensSpent === 1)
{
newContent = `You have now spent ${cumulativeTokensSpent} token. The new roll total is ${newTotal}.`;
} else {
newContent = `You have now spent ${cumulativeTokensSpent} tokens. The new roll total is ${newTotal}.`;
}
// Create a new chat message to display the updated total
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: message.speaker.actor }),
content: newContent,
type: CONST.CHAT_MESSAGE_STYLES.OTHER,
});
}

View File

@ -0,0 +1,147 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class KidsOnBroomsActorSheet extends ActorSheet {
/** @override */
static get defaultOptions()
{
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["kids-on-brooms", "sheet", "actor"],
width: 800,
height: 800,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "features" }]
});
}
/** @override */
get template()
{
console.log("template", this.actor)
return `systems/kids-on-brooms/templates/actor/actor-${this.actor.type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
/** @override */
async getData()
{
// Retrieve the data structure from the base sheet.
const context = super.getData();
// Use a safe clone of the actor data for further operations.
const actorData = this.document.toObject(false);
// Add the actor's data to context.data for easier access, as well as flags.
context.system = actorData.system;
context.flags = actorData.flags;
// Add roll data for TinyMCE editors.
context.rollData = context.actor.getRollData();
// Add roll data for TinyMCE editors.
context.rollData = context.actor.getRollData();
console.log(context);
return context;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html)
{
super.activateListeners(html);
// -------------------------------------------------------------
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
// Rollable abilities.
html.find('.rollable').click(this._onRoll.bind(this));
//If the user changes their wand material save that
html.find('select[name="system.wand.wood"]').change(event => {
const value = event.target.value;
this.actor.update({ "system.wand.wood": value });
});
html.find('select[name="system.wand.core"]').change(event => {
const value = event.target.value;
this.actor.update({ "system.wand.core": value });
});
}
/**
* Handle clickable rolls.
* @param {Event} event The originating click event
* @private
*/
async _onRoll(e) {
e.preventDefault();
const element = e.currentTarget;
const dataset = element.dataset;
// Handle rolls that supply the formula directly
if (dataset.roll) {
let label = dataset.label ? `${dataset.label}` : '';
// Get the roll data and include wand bonuses
let rollData = this.actor.getRollData();
let totalBonus = 0;
console.log(dataset.roll);
// Apply wood bonus if it matches the stat being rolled for
if (rollData.wandBonus.wood.stat === dataset.key) {
totalBonus += rollData.wandBonus.wood.bonus;
}
// Apply core bonus if it matches the stat being rolled for AND it's different from the wood bonus
if (rollData.wandBonus.core.stat === dataset.key && rollData.wandBonus.core.stat !== rollData.wandBonus.wood.stat) {
totalBonus += rollData.wandBonus.core.bonus;
}
let rollFormula = dataset.roll + `+${totalBonus}`;
let roll = new Roll(rollFormula, rollData);
console.log(rollFormula);
console.log(rollData);
// Send the roll message to chat
const rollMessage = await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
flavor: label,
rollMode: game.settings.get('core', 'rollMode'),
})
// Now send the follow-up message with the adversity controls
await this._sendAdversityControlsMessage(this.actor.id, rollMessage.id);
return roll;
}
}
//This just sends the buttons for the adversity token system
async _sendAdversityControlsMessage(actorId, rollMessageId) {
// Create the content for the adversity controls
const adversityControlsHtml = this._createAdversityControls(actorId, rollMessageId);
// Send the adversity controls as a follow-up message
const controlMessage = await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
content: adversityControlsHtml,
});
return controlMessage;
}
// Create HTML content for adversity controls
_createAdversityControls(actorId, rollMessageId) {
return `
<div class="adversity-controls" data-roll-id="${rollMessageId}">
<button class="take-adversity" data-actor-id="${actorId}">Take Adversity Token</button>
<input type="number" class="token-input" value="1" min="1" />
<button class="spend-adversity" data-actor-id="${actorId}">Spend Adversity Tokens</button>
</div>
`;
}
}

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "kids-on-brooms",
"version": "2.0.0",
"description": "CSS compiler for the Kids On Brooms system",
"scripts": {
"build": "gulp build",
"compile": "gulp css",
"watch": "gulp",
"gulp": "gulp"
},
"browserslist": [
"last 5 versions"
],
"author": "Josiah Bradbury, Joscha Maier",
"license": "MIT",
"private": true,
"dependencies": {
"gulp": "^5",
"gulp-autoprefixer": "^8",
"gulp-sass": "^5",
"gulp-sourcemaps": "^3",
"kids-on-brooms": "file:"
},
"devDependencies": {
"sass": "^1.77.8"
}
}

1
readme.md Normal file
View File

@ -0,0 +1 @@
The Kids On Brooms System Implemented in FoundryVTT, reupload from https://github.com/Singularity-Lathe-VTT/kids-on-brooms

View File

@ -0,0 +1,22 @@
.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;
}
}
// _effects.scss
.kids-on-brooms input:focus,
.kids-on-brooms textarea:focus,
.kids-on-brooms select:focus {
outline: none;
border-color: $focus-border-color;
}

View File

@ -0,0 +1,66 @@
.item-form {
font-family: $font-primary;
}
.sheet-header {
@include flexbox(row, wrap, flex-start); // Use a mixin for flexbox
flex: 0 auto;
overflow: hidden;
margin-bottom: 10px;
.profile-img {
flex: 0 0 100px;
height: 100px;
margin-right: 10px;
}
.header-fields {
flex: 1;
}
h1.charname {
height: 50px;
padding: 0;
margin: 5px 0;
border-bottom: 0;
input {
width: 100%;
height: 100%;
margin: 0;
}
}
}
div.editor-border {
border: 2px solid $primary-border-color; // Replace the hardcoded color with a variable
border-radius: 10px;
}
.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;
}
}
// Flexbox container for each row
.selection-row {
display: flex;
justify-content: space-between; // Ensures label stays left and input moves to the right
align-items: center; // Vertically center the label and input
margin-bottom: 10px; // Optional: Add some space between rows
}

124
scss/components/_items.scss Normal file
View File

@ -0,0 +1,124 @@
// 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;
}
}
// 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;
}
// Individual Item
.item {
display: flex;
align-items: center;
padding: 0 2px; // Align with the header border
border-bottom: 1px solid $c-faint;
&:last-child {
border-bottom: none;
}
// Item name and image
.item-name {
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
display: flex;
color: $c-dark;
h3, h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
.item-image {
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
}
}
// Control Buttons
.item-controls {
display: flex;
flex: 0 0 100px;
justify-content: flex-end;
a {
font-size: 12px;
text-align: center;
margin: 0 6px;
}
}
// Item Properties (like stats or details)
.item-prop {
text-align: center;
border-left: 1px solid $c-faint;
border-right: 1px solid $c-faint;
font-size: 12px;
}
}
// Section Header inside Items List
.items-list .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;
}
}
// Optional item formula block
.item-formula {
flex: 0 0 200px;
padding: 0 8px;
}

View File

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

15
scss/global/_base.scss Normal file
View File

@ -0,0 +1,15 @@
// _base.scss
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
.window-app {
font-family: $font-stack;
}
.rollable {
&:hover,
&:focus {
color: #000;
text-shadow: $hover-text-shadow;
cursor: pointer;
}
}

50
scss/global/_flex.scss Normal file
View File

@ -0,0 +1,50 @@
// Flexbox Utility Classes
.flex-group-center {
@include flexbox(center, center);
text-align: center;
}
.flex-group-left {
@include flexbox(flex-start, center);
text-align: left;
}
.flex-group-right {
@include flexbox(flex-end, center);
text-align: right;
}
.flexshrink {
flex: 0;
}
.flex-between {
justify-content: space-between;
}
.flexlarge {
flex: 2;
}
// Alignment Utility Classes
.align-left {
@include flexbox(flex-start, center);
text-align: left;
}
.align-right {
@include flexbox(flex-end, center);
text-align: right;
}
.align-center {
@include flexbox(center, center);
text-align: center;
}
// Only apply the right alignment to specific inputs with this class
.right-align-input {
flex: 1;
margin-left: auto; // Push the input to the far right
max-width: 260px; // Optional: Control the width of the input field
}

25
scss/global/_grid.scss Normal file
View File

@ -0,0 +1,25 @@
// _grid.scss
.grid {
display: grid;
gap: 10px;
margin: 10px 0;
padding: 0;
}
@for $i from 2 through 12 {
// Create grid-start-* classes for offsets
.grid-start-#{$i} {
grid-column-start: #{$i};
}
// Create grid-span-* classes for column spans
.grid-span-#{$i} {
grid-column-end: span #{$i};
}
// Create grid-*col classes for grid columns
.grid-#{$i}col {
grid-column: span #{$i} / span #{$i};
grid-template-columns: repeat(#{$i}, minmax(0, 1fr));
}
}

12
scss/global/_window.scss Normal file
View File

@ -0,0 +1,12 @@
.window-app {
font-family: $font-primary;
}
.rollable {
&:hover,
&:focus {
color: #000;
text-shadow: 0 0 10px rgb(146, 0, 225);
cursor: pointer;
}
}

23
scss/kidsonbrooms.scss Normal file
View File

@ -0,0 +1,23 @@
// 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');
// This is the font used for the book, will not buy it but for refrence https://www.myfonts.com/collections/dreadful-font-aiyari?queryId=undefined&index=universal_search_data&objectIDs=5368854002
// This is the font used for text https://www.myfonts.com/products/lapidary-333-lapidary-333-434881?queryId=undefined&index=universal_search_data&objectIDs=5468003002
// Import utilities.
@import 'utils/variables';
@import 'utils/typography';
@import 'utils/colors';
@import 'utils/mixins';
/* Global styles */
@import 'global/window';
@import 'global/grid';
@import 'global/flex';
@import 'global/base';
/* Styles limited to kidsonbrooms sheets */
.kids-on-brooms {
@import 'components/forms';
@import 'components/resource';
@import 'components/items';
@import 'components/effects';
}

13
scss/utils/_colors.scss Normal file
View File

@ -0,0 +1,13 @@
$c-white: #fff;
$c-black: #000;
$c-dark: #191813;
$c-faint: #c9c7b8;
$c-beige: #b5b3a4;
$c-tan: #444;
$primary-border-color: rgb(81, 81, 81);
$focus-border-color: #8102dd;
$hover-text-shadow: 0 0 10px rgb(179, 7, 217);
$border-color: #ccc;

25
scss/utils/_mixins.scss Normal file
View File

@ -0,0 +1,25 @@
@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;
}
// Update the mixin to accept 3 arguments: direction, wrap, and justify
@mixin flexbox($direction, $wrap: nowrap, $justify: flex-start, $align: stretch) {
display: flex;
flex-direction: $direction;
flex-wrap: $wrap;
justify-content: $justify;
align-items: $align;
}

View File

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

View File

@ -0,0 +1,10 @@
$padding-sm: 5px;
$padding-md: 10px;
$padding-lg: 20px;
$border-groove: 2px groove #eeede0;
$font-stack: "Roboto", sans-serif;
$padding: 10px;
$border-radius: 5px;
$flex-align: center;

24
system.json Normal file
View File

@ -0,0 +1,24 @@
{
"id": "kids-on-brooms",
"title": "Kids on Brooms System",
"description": "The Kids on Brooms system for FoundryVTT! - Deprecated",
"version": "0.0.1",
"compatibility": {
"minimum": 12,
"verified": 12
},
"authors": [{
"name": "Josiah Bradbury, Joscha Maier"
}],
"esmodules": ["module/kidsonbrooms.mjs"],
"styles": ["css/kidsonbrooms.css"],
"socket": true,
"grid": {
"distance": 5,
"units": "ft"
},
"primaryTokenAttribute": "system.adversityTokens",
"url": "https://github.com/josmaier/KidsOnBroomsFoundryVTT",
"manifest": "https://github.com/josmaier/KidsOnBroomsFoundryVTT/blob/2fb5d0a11d1c9d78945c2508d5aa2cc7daf297e0/system.json",
"download": "https://github.com/josmaier/KidsOnBroomsFoundryVTT/blob/2fb5d0a11d1c9d78945c2508d5aa2cc7daf297e0/kids-on-brooms.zip"
}

68
template.json Normal file
View File

@ -0,0 +1,68 @@
{
"Actor": {
"types": ["character", "npc"],
"templates": {
"base": {
"stats": {
"fight": {
"value": "d4",
"stat": 0,
"magic": 0
},
"flight": {
"value": "d4",
"stat": 0,
"magic": 0
},
"brains": {
"value": "d4",
"stat": 0,
"magic": 0
},
"brawn": {
"value": "d4",
"stat": 0,
"magic": 0
},
"charm": {
"value": "d4",
"stat": 0,
"magic": 0
},
"grit": {
"value": "d4",
"stat": 0,
"magic": 0
}
},
"description": ""
}
},
"character": {
"templates": ["base"],
"trope": "",
"age": "",
"pronouns": "",
"fear": "",
"motivation": "",
"grade":"",
"broom": {
"name": "",
"look": "",
"mechanicalbenifit": ""
},
"wand": {
"wood": "",
"core": ""
},
"animalfamiliar":"",
"schoolbag": "",
"adversityTokens": 0,
"tropequestions": "",
"strengths": ""
},
"npc": {
"templates": ["base"]
}
}
}

View File

@ -0,0 +1,63 @@
<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>
<div class="resources grid">
<div class="resource flex-group-center">
<label for="system.trope" class="resource-label">Trope</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.trope" value="{{system.trope}}" data-dtype="String"/>
</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="schoolbag">School Bag</a>
<a class="item" data-tab="strengths">Strengths</a>
<a class="item" data-tab="trope">Trope Questions</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">
<section class="main grid-span-2">
{{> "systems/kids-on-brooms/templates/actor/parts/actor-features.html"}}
{{> "systems/kids-on-brooms/templates/actor/parts/actor-adversity.html"}}
</section>
<aside class="sidebar">
{{> "systems/kids-on-brooms/templates/actor/parts/actor-stats.html"}}
</aside>
</section>
</div>
{{!-- Schoolbag Tab --}}
<div class="tab features editor-border" data-group="primary" data-tab="schoolbag">
{{editor schoolbag target="system.schoolbag" engine="prosemirror" button=false collaborate=false editable=true}}
</div>
{{!-- Strengths Tab --}}
<div class="tab features editor-border" data-group="primary" data-tab="strengths">
{{editor strengths target="system.strengths" engine="prosemirror" button=false collaborate=false editable=true}}
</div>
{{!-- Trope Questions Tab --}}
<div class="tab features editor-border" data-group="primary" data-tab="trope">
{{editor tropequestions target="system.tropequestions" engine="prosemirror" button=false collaborate=false editable=true}}
</div>
</section>
</form>

View File

@ -0,0 +1,35 @@
<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>
<div class="resources grid">
<div class="resource flex-group-center">
<label for="system.trope" class="resource-label">Trope</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="system.trope" value="{{system.trope}}" data-dtype="String"/>
</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>
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body">
{{!-- Owned Features Tab --}}
<div class="tab features" data-group="primary" data-tab="features">
{{> "systems/kids-on-brooms/templates/actor/parts/actor-npc-stats.html"}}
</div>
</section>
</form>

View File

@ -0,0 +1,11 @@
<fieldset>
<legend>Adversity Tokens</legend>
<div class="resource flexcol" >
<label for="system.adversity" class="resource-label">
Begin the game with 3
adversity tokens. Add 1
each time you fail a roll.
</label>
<input type="number" name="system.adversityTokens" value="{{system.adversityTokens}}" data-dtype="Number"/>
</div>
</fieldset>

View File

@ -0,0 +1,156 @@
<section class="grid grid-3col">
<fieldset class="resource grid-span-3 flexcol">
<div class="resource flexrow">
<label for="system.age" class="resource-label">Age</label>
<input type="text" name="system.age" value="{{system.age}}" data-dtype="String"/>
</div>
<div class="resource grid-span-2 flexrow">
<label for="system.pronouns" class="resource-label">Pronouns</label>
<input type="text" name="system.pronouns" value="{{system.pronouns}}" data-dtype="String"/>
</div>
<div class="resource grid-span-3 flexrow">
<label for="system.fear" class="resource-label">Fear</label>
<input type="text" name="system.fear" value="{{system.fear}}" data-dtype="String"/>
</div>
<div class="resource grid-span-3 flexrow" >
<label for="system.motivation" class="resource-label">Motivation</label>
<input type="text" name="system.motivation" value="{{system.motivation}}" data-dtype="String"/>
</div>
<div class="resource grid-span-3 flexrow" >
<label for="system.description" class="resource-label">Description</label>
<input type="text" name="system.description" value="{{system.description}}" data-dtype="String"/>
</div>
<div class="resource grid-span-3 flexrow">
<label for="system.grade" class="resource-label">Grade</label>
<input type="text" name="system.grade" value="{{system.grade}}" data-dtype="String"/>
</div>
</fieldset>
<fieldset class="resource grid-span-3 flexcol">
<legend>Your Broom</legend>
<!-- Broom Name Input with Dropdown -->
<div class="resource flexrow">
<label for="broom-name" class="resource-label">Name</label>
<input list="broomOptions" id="broom-name" name="system.broom.name"
value="{{system.broom.name}}" data-dtype="String" placeholder="Select or Enter Broom Name"
oninput="updateBroomDetails()" onblur="updateBroomDetails()">
<datalist id="broomOptions">
<option value="The Blocker's Broom" data-look="Defensive" data-mechanical="Gain the Guardian Strength"></option>
<option value="Bolting 4000" data-look="Fast" data-mechanical="+1 to Flight checks"></option>
<option value="The Bruiser" data-look="Intense" data-mechanical="+1 to Fight checks"></option>
<option value="Cunning Captains Cruiser" data-look="Natural Leader" data-mechanical="Treat Snap Decisions as Planned Actions unless facing fear"></option>
<option value="Daredevils Duster" data-look="Flashy" data-mechanical="+3 to Charm checks when performing a stunt"></option>
<option value="The Daring Dodger 3000" data-look="Ambitious" data-mechanical="Each Adversity Token adds +2 to your roll instead of +1"></option>
<option value="Heartwoods Helper" data-look="Outgoing" data-mechanical="Each successful check grants an ally one Adversity Token"></option>
<option value="Mapmakers Friend" data-look="Level-Headed" data-mechanical="Cannot get lost if you know the area"></option>
<option value="The Masterminds Sweeper" data-look="Confident" data-mechanical="+1 to Brains checks"></option>
<option value="The Strong Sweep 2500" data-look="Strong" data-mechanical="+1 to Brawn checks"></option>
<option value="The Suave Sweeper" data-look="Trustworthy" data-mechanical="+1 to Charm checks"></option>
<option value="The Tough Break" data-look="Tough" data-mechanical="+1 to Grit checks"></option>
<option value="Valiance 2400" data-look="Brave" data-mechanical="May ignore your fears"></option>
<option value="Weasels Whisk" data-look="Sneaky" data-mechanical="Gain the Unassuming Strength"></option>
</datalist>
</div>
<!-- Broom Look -->
<div class="resource flexrow">
<label for="broom-look" class="resource-label">Look</label>
<input type="text" id="broom-look" name="system.broom.look"
value="{{system.broom.look}}" data-dtype="String"/>
</div>
<!-- Mechanical Benefit as Textarea -->
<div class="resource flexrow">
<label for="broom-mechanical" class="resource-label">Mechanical Benefit</label>
<textarea id="broom-mechanical" name="system.broom.mechanicalbenefit"
data-dtype="String" rows="3" style="resize:none;"></textarea>
</div>
</fieldset>
<script>
function updateBroomDetails() {
// Use a short delay to allow browser to properly handle the datalist input
setTimeout(function() {
const broomNameInput = document.getElementById("broom-name").value.trim();
const broomOptions = document.querySelectorAll("#broomOptions option");
let selectedLook = "";
let selectedMechanical = "";
// Loop through the datalist options to find a matching broom name
broomOptions.forEach(option => {
if (option.value.toLowerCase() === broomNameInput.toLowerCase()) {
selectedLook = option.getAttribute("data-look");
selectedMechanical = option.getAttribute("data-mechanical");
}
});
// Update the look and mechanical benefit fields if a predefined broom is selected
document.getElementById("broom-look").value = selectedLook || "";
document.getElementById("broom-mechanical").value = selectedMechanical || "";
}, 100); // Delay of 100 milliseconds
}
</script>
<fieldset class="resource grid-span-3 flexcol">
<legend>Wand Selection</legend>
<!-- Wood Selection -->
<div class="resource-flexrow">
<label for="system.wand.wood" class="resource-label">Wood Type</label>
<input list="WoodOptions" id="wandWoodChoice" name="system.wand.wood" value="{{system.wand.wood}}" placeholder="Select Wood type"
oninput="updateWandWoodDetails()" onblur="updateWandWoodDetails()">
<datalist id="WoodOptions">
<option value="">Select Wood</option>
<option value="Wisteria">(Brains)</option>
<option value="Hawthorn">(Brains)</option>
<option value="Pine">(Brawn)</option>
<option value="Oak">(Brawn)</option>
<option value="Crabapple">(Fight)</option>
<option value="Dogwood">(Fight)</option>
<option value="Birch">(Flight)</option>
<option value="Bamboo">(Flight)</option>
<option value="Ironwood">(Grit)</option>
<option value="Maple">(Grit)</option>
<option value="Lilac">(Charm)</option>
<option value="Cherry">(Charm)</option>
</datalist>
</div>
<!-- Core Selection -->
<div class="resource-flexrow">
<label for="system.wand.core" class="resource-label">Core Type</label>
<input list="CoreOptions" id="wandCoreChoice" name="system.wand.core" value="{{system.wand.core}}" placeholder="Select Core type"
oninput="updateWandCoreDetails()" onblur="updateWandCoreDetails()">
<datalist id="CoreOptions">
<option value="">Select Core</option>
<option value="Parchment">(Brains)</option>
<option value="Phoenix Feather">(Brains)</option>
<option value="Owl Feather">(Brains)</option>
<option value="Gorilla Fur">(Brawn)</option>
<option value="Ogres Fingernail">(Brawn)</option>
<option value="Hippos Tooth">(Brawn)</option>
<option value="Dragons Heartstring">(Fight)</option>
<option value="Wolfs Tooth">(Fight)</option>
<option value="Elks Antler">(Fight)</option>
<option value="Hawks Feather">(Flight)</option>
<option value="Bats Bone">(Flight)</option>
<option value="Changelings Hair">(Charm)</option>
<option value="Gold">(Charm)</option>
<option value="Mirror">(Charm)</option>
<option value="Steel">(Grit)</option>
<option value="Diamond">(Grit)</option>
<option value="Lions Mane">(Grit)</option>
</datalist>
</div>
</fieldset>
<fieldset class="resource grid-span-3 flexcol">
<legend>Animal Familiar</legend>
<div class="resource grid-span-3 flexrow">
<label for="system.animalfamiliar" class="resource-label">Animal Familiar</label>
<input type="text" name="system.animalfamiliar" value="{{system.animalfamiliar}}" data-dtype="String"/>
</div>
</fieldset>
</section>

View File

@ -0,0 +1,29 @@
<section class="flexcol">
{{#each system.stats as |stat key|}}
<Fieldset class="grid grid-5col">
<legend>{{key}}</legend>
<select name="system.stats.{{key}}.value">
{{#select stat.value}}
<option value="d20">d20</option>
<option value="d12">d12</option>
<option value="d10">d10</option>
<option value="d8">d8</option>
<option value="d6">d6</option>
<option value="d4">d4</option>
{{/select}}
</select>
<div class="flexrow grid-span-4">
<Fieldset class="flexrow">
<legend>Stat</legend>
<span class="ability-mod rollable" data-roll="{{stat.value}}+{{stat.stat}}" data-label="Stat Roll for {{key}}"><i class="fas fa-dice-d20"></i></span>
<input type="text" name="system.stats.{{key}}.stat" value="{{stat.stat}}" data-dtype="String"/>
</Fieldset>
<Fieldset class="flexrow">
<legend>Magic</legend>
<span class="ability-mod rollable" data-roll="{{stat.value}}+{{stat.magic}}" data-label="Magic Roll for {{key}}"><i class="fas fa-dice-d20"></i></span>
<input type="text" name="system.stats.{{key}}.magic" value="{{stat.magic}}" data-dtype="String"/>
</Fieldset>
</div>
</Fieldset>
{{/each}}
</section>

View File

@ -0,0 +1,37 @@
<section class="flexcol">
{{#each system.stats as |stat key|}}
<Fieldset class="flexrow">
<legend>{{capitalizeFirst key}}</legend>
<div class="flexrow flex-group-center">
<!-- Die type dropdown -->
<select name="system.stats.{{key}}.value">
<option value="d20" {{#if (eq stat.value 'd20')}}selected{{/if}}>Superb</option>
<option value="d12" {{#if (eq stat.value 'd12')}}selected{{/if}}>Impressive</option>
<option value="d10" {{#if (eq stat.value 'd10')}}selected{{/if}}>Above Average</option>
<option value="d8" {{#if (eq stat.value 'd8')}}selected{{/if}}>Below Average</option>
<option value="d6" {{#if (eq stat.value 'd6')}}selected{{/if}}>Bad</option>
<option value="d4" {{#if (eq stat.value 'd4')}}selected{{/if}}>Terrible</option>
</select>
<!-- Stat rolling and input -->
<Fieldset class="flexrow flex-group-center">
<legend>Stat</legend>
<span class="ability-mod rollable" data-roll="1{{stat.value}}x+{{stat.stat}}" data-label="Stat Roll for {{key}}" data-key="{{key}}">
<i class="fas fa-dice-d20"></i>
</span>
<input type="number" name="system.stats.{{key}}.stat" value="{{stat.stat}}" data-dtype="Number"/>
</Fieldset>
<!-- Magic rolling and input -->
<Fieldset class="flexrow flex-group-center">
<legend>Magic</legend>
<span class="ability-mod rollable" data-roll="1{{stat.value}}x+1d4+{{stat.stat}}" data-label="Magic Roll for {{key}}" data-key="{{key}}">
<i class="fas fa-dice-d20"></i>
</span>
</Fieldset>
</div>
</Fieldset>
{{/each}}
</section>