Compare commits

...

131 Commits
dev ... master

Author SHA1 Message Date
5337800f43 Merge branch 'master' of https://www.uberwald.me/gitea/uberwald/fvtt-never-stop-blowing-up 2024-12-10 20:45:10 +01:00
f30f6fa1c4 Update release 2024-12-10 20:44:44 +01:00
17bfec8f4e Actualiser readme.md 2024-12-10 20:43:04 +01:00
618fc98a16 Initial rework 2024-12-10 17:22:50 +01:00
2b9361d3bd Initial rework 2024-12-10 17:22:28 +01:00
WinterMyst
9b25cc053a Merge branch 'old-system' into 'master'
Old system

See merge request wintermyst/kidsonbrooms!8
2024-12-03 12:17:47 +00:00
Joscha Maier
40b9d3a053
fix: merge conflict header 2024-12-03 13:15:56 +01:00
Joscha Maier
47206a8695
Merge branch 'master' into old-system 2024-12-03 13:13:47 +01:00
Joscha Maier
015c3e5181
feat: new version number 2024-12-02 19:03:07 +01:00
Joscha Maier
bde6ffca74
fix: schoolbag, strengths and trope question tab editor content block had height of 0px 2024-12-02 19:00:59 +01:00
Joscha Maier
6dc5149c3b
css died 2024-10-07 10:03:34 +02:00
WinterMyst
8f8a66c78a Update package.json 2024-09-27 21:36:49 +00:00
WinterMyst
4af976cac7 Update system.json 2024-09-27 21:36:34 +00:00
WinterMyst
efa4ac6457 Merge branch 'old-system' into 'master'
Fix NPCs

See merge request wintermyst/kidsonbrooms!7
2024-09-27 21:33:09 +00:00
Joscha Maier
c1a962e3b8
fix: magi die lucky break 2024-09-27 23:26:54 +02:00
Joscha Maier
2eac4210e6
fix: NPC sheet not being able to fetch roll data 2024-09-27 23:11:01 +02:00
WinterMyst
5dfdd81d74 Update package.json 2024-09-26 12:09:19 +00:00
WinterMyst
60c31993b0 Update system.json 2024-09-26 12:09:04 +00:00
WinterMyst
1e105cfc41 Merge branch 'old-system' into 'master'
Old system

See merge request wintermyst/kidsonbrooms!6
2024-09-26 12:08:48 +00:00
WinterMyst
f653eb2ef2 Old system 2024-09-26 12:08:48 +00:00
Joscha Maier
fbf7e826d0
fix: wrong links 2024-09-26 14:08:24 +02:00
WinterMyst
beb4322917
chore: correct release note url 2024-09-26 14:00:01 +02:00
WinterMyst
d18c52ac54
chore:update so future releases link to the correct manifest 2024-09-26 13:58:51 +02:00
WinterMyst
21d17ba037
Merge branch 'master' into old-system 2024-09-26 13:56:56 +02:00
WinterMyst
863236d478
JAVASCRIPT 2024-09-26 13:51:27 +02:00
WinterMyst
34238b7dc9
gfd 2024-09-26 13:48:30 +02:00
WinterMyst
ef50b84dba
rg 2024-09-26 13:44:40 +02:00
WinterMyst
6e69dd8681
dfgbhe 2024-09-26 13:42:11 +02:00
WinterMyst
e83c7b8ee0
fff 2024-09-26 13:38:38 +02:00
Joscha Maier
aa7a951ae1
sdfg 2024-09-26 13:25:08 +02:00
Joscha Maier
004143280b
jsjsjsjsjsjs 2024-09-26 12:48:40 +02:00
Joscha Maier
dcdd9af271
fuck js 2024-09-26 12:45:40 +02:00
Joscha Maier
bef8d4bf7e
asd 2024-09-26 12:43:25 +02:00
WinterMyst
0c670b82b6 Merge branch 'old-system' into 'master'
1.1.3

See merge request wintermyst/kidsonbrooms!5
2024-09-26 10:33:53 +00:00
WinterMyst
428b82e388 1.1.3 2024-09-26 10:33:53 +00:00
WinterMyst
917bd6456d Merge branch 'master' into 'old-system'
# Conflicts:
#   system.json
2024-09-26 10:32:49 +00:00
Joscha Maier
943e2c5185
fix: renamed links to sheets 2024-09-26 12:31:51 +02:00
Joscha Maier
c112950a3e
fix: reupload template 2024-09-26 12:28:17 +02:00
Joscha Maier
0ad5baa341
chore update version and readme 2024-09-26 12:25:28 +02:00
WinterMyst
46ecf27fa5 Update package.json 2024-09-26 00:54:23 +00:00
WinterMyst
34878dfd57 Update system.json 2024-09-26 00:53:58 +00:00
WinterMyst
86b3240616 Merge branch 'old-system' into 'master'
1.1.2

See merge request wintermyst/kidsonbrooms!4
2024-09-26 00:50:44 +00:00
WinterMyst
9f9b093d71 1.1.2 2024-09-26 00:50:43 +00:00
WinterMyst
b9b014d59f fix: typo 2024-09-26 00:48:12 +00:00
WinterMyst
a514ad4367 fix: no compendium folder 2024-09-26 00:46:34 +00:00
WinterMyst
1ec482a98f chore: fix name 2024-09-26 00:35:05 +00:00
josch
cc49017b3f d 2024-09-24 19:37:22 +02:00
josch
e928c253eb c 2024-09-24 19:35:44 +02:00
josch
f81fcb6111 b 2024-09-24 19:34:17 +02:00
josch
de8b7ec38a a 2024-09-24 19:31:35 +02:00
josch
36a506b338 update depencies 2024-09-24 19:29:36 +02:00
josch
2294addb8a fix typo 2024-09-24 19:25:17 +02:00
josch
6fe30953cc a 2024-09-24 19:23:45 +02:00
josch
90f4d79e48 Fix error 2024-09-24 19:22:58 +02:00
josch
aff0d5ffbe Updated release workflow 2024-09-24 19:21:44 +02:00
WinterMyst
cf66b7e128 Update .gitlab-ci.yml file 2024-09-23 20:53:27 +00:00
GitLab CI
928739218d Update .zip with new version 2024-09-23 20:50:39 +00:00
WinterMyst
520ebaeed0 Update .gitlab-ci.yml file 2024-09-23 20:46:41 +00:00
GitLab CI
b5c01caff2 Update .zip with new version 2024-09-23 20:45:43 +00:00
WinterMyst
f6efcd73e2 Update .gitlab-ci.yml file 2024-09-23 20:42:04 +00:00
GitLab CI
6ec32e0962 Update .zip with new version 2024-09-23 20:40:30 +00:00
WinterMyst
79ca616ad7 Update .gitlab-ci.yml file 2024-09-23 20:35:44 +00:00
WinterMyst
1e1fa46ee0 Update system.json 2024-09-23 20:33:59 +00:00
WinterMyst
f15950e9cc Update system.json 2024-09-23 20:26:33 +00:00
WinterMyst
283af9a050 Update system.json 2024-09-23 20:26:07 +00:00
WinterMyst
282ec95c0c Update system.json 2024-09-23 20:25:35 +00:00
WinterMyst
fed0ba9ca1 Update system.json 2024-09-23 20:25:20 +00:00
WinterMyst
79b6a0ea13 Update system.json 2024-09-23 20:24:51 +00:00
WinterMyst
4fca58bf33 Update system.json 2024-09-23 20:23:51 +00:00
WinterMyst
0b8bfb53bc Upload New File 2024-09-23 20:21:57 +00:00
WinterMyst
9dcc1fffe1 Update .gitlab-ci.yml file 2024-09-23 20:15:53 +00:00
WinterMyst
1d3711c0af Update .gitlab-ci.yml file 2024-09-23 20:10:05 +00:00
WinterMyst
a1b62e0eb0 Update .gitlab-ci.yml file 2024-09-23 16:15:08 +00:00
WinterMyst
9ecc7d5573 Update .gitlab-ci.yml file 2024-09-23 16:09:50 +00:00
WinterMyst
9bd1ae3602 Update .gitlab-ci.yml file 2024-09-23 16:04:14 +00:00
GitLab CI
00ab7937b0 Update system.json with release URL 2024-09-23 16:02:48 +00:00
WinterMyst
b52239cdac Update .gitlab-ci.yml file 2024-09-23 16:00:47 +00:00
WinterMyst
41f7aa4426 Update .gitlab-ci.yml file 2024-09-23 15:58:07 +00:00
WinterMyst
873b98f578 Update .gitlab-ci.yml file 2024-09-23 15:48:33 +00:00
WinterMyst
e1097fffc1 Update .gitlab-ci.yml file 2024-09-23 15:44:12 +00:00
GitLab CI
1619c9fb01 Update system.json with release URL 2024-09-22 18:14:43 +00:00
WinterMyst
65521c2531 Update .gitlab-ci.yml file 2024-09-22 18:12:03 +00:00
WinterMyst
67e0692439 Update .gitlab-ci.yml file 2024-09-22 17:33:15 +00:00
WinterMyst
903082c3ca g 2024-09-22 19:32:23 +02:00
WinterMyst
e5bd48a204 n 2024-09-22 19:28:08 +02:00
WinterMyst
e0f617f205 i 2024-09-22 19:25:21 +02:00
WinterMyst
7a5918da87 r 2024-09-22 19:20:10 +02:00
WinterMyst
7ebcd5452e Update .gitlab-ci.yml file 2024-09-22 17:16:04 +00:00
WinterMyst
070aa208ac r 2024-09-22 18:56:14 +02:00
WinterMyst
2ee59e85e4 e 2024-09-22 18:55:51 +02:00
WinterMyst
5f4ab69c2c f 2024-09-22 18:54:50 +02:00
WinterMyst
5f86e986cd f 2024-09-22 18:51:01 +02:00
WinterMyst
7e601ee9a9 u 2024-09-22 18:34:23 +02:00
WinterMyst
438455c3c7 s 2024-09-22 18:31:43 +02:00
WinterMyst
a4b9db93fc update ci 2024-09-22 18:26:14 +02:00
WinterMyst
8829860d0f chore: I am trying 2024-09-22 18:21:03 +02:00
WinterMyst
3b45bcadde Update .gitlab-ci.yml file 2024-09-22 15:13:26 +00:00
WinterMyst
4c687785c7 Update .gitlab-ci.yml file 2024-09-22 15:07:13 +00:00
WinterMyst
91bbd00b33 Update .gitlab-ci.yml file 2024-09-22 14:59:31 +00:00
WinterMyst
99f47525a6 Update .gitlab-ci.yml file 2024-09-22 14:56:10 +00:00
WinterMyst
2f7023c930 Update .gitlab-ci.yml file 2024-09-22 14:52:45 +00:00
WinterMyst
94af26f3d3 Update .gitlab-ci.yml file 2024-09-22 14:52:24 +00:00
WinterMyst
528290f27b Update .gitlab-ci.yml file 2024-09-22 14:50:02 +00:00
WinterMyst
3ed0cf596c Update .gitlab-ci.yml file 2024-09-22 14:46:57 +00:00
WinterMyst
673e67dc54 Update .gitlab-ci.yml file 2024-09-22 14:41:32 +00:00
WinterMyst
ec6d9a881e chore:fix my merge mistake 2024-09-22 16:28:59 +02:00
WinterMyst
e3d83ce4bd Merge branch 'old-system' 2024-09-22 16:27:12 +02:00
WinterMyst
c19f2f2650 Commit all changes 2024-09-22 16:26:38 +02:00
WinterMyst
00e9219a25 chore:updated Gitignore 2024-09-22 16:23:15 +02:00
WinterMyst
f289ed6b7a Merge branch 'old-system' into 'master'
Old system

See merge request wintermyst/kidsonbrooms!3
2024-09-22 14:19:29 +00:00
WinterMyst
10b10efd27 Delete kids-on-brooms.zip 2024-09-22 14:18:28 +00:00
WinterMyst
8d22451a38 Delete LICENSE.txt 2024-09-22 14:18:19 +00:00
WinterMyst
4b4a0ffe97 Merge branch 'old-system' 2024-09-22 16:15:58 +02:00
WinterMyst
6bf4ece8cf Update .gitlab-ci.yml file 2024-09-22 14:11:43 +00:00
WinterMyst
c69daf7d63 Update .gitlab-ci.yml file 2024-09-22 14:11:31 +00:00
WinterMyst
8af2a3b50d fix: forgot to decode key 2024-09-22 16:05:15 +02:00
WinterMyst
8c7eec7706 chore: updated CI/CD 2024-09-22 15:58:18 +02:00
WinterMyst
f90e6899f3 chore:updated CI 2024-09-22 15:57:37 +02:00
WinterMyst
0dabdc9e89
chore:updated system.json 2024-09-22 15:45:39 +02:00
Winter_Myst
5a748abc5d sometimes it breaks 2024-09-22 02:26:56 +02:00
Winter_Myst
615834e7be core: update to new sass compile function 2024-09-22 02:09:42 +02:00
Winter_Myst
b85efb57d1 fix: npm sec concern 2024-09-22 02:07:57 +02:00
Winter_Myst
4c038a5857 chore: forgot to change name of export 2024-09-22 02:04:28 +02:00
Winter_Myst
ceee105072 chore: update depencies 2024-09-22 02:01:01 +02:00
Winter_Myst
8154f3aa6b Merge branch 'old-system' of https://gitlab.com/wintermyst/kids-on-brooms into old-system 2024-09-22 01:56:44 +02:00
Winter_Myst
ad9e7ec7ab fix: curl not installed??? 2024-09-22 01:56:42 +02:00
Winter_Myst
ff65331abd Merge branch 'old-system' of https://gitlab.com/wintermyst/kids-on-brooms into old-system 2024-09-22 01:55:02 +02:00
Winter_Myst
c2a56997b6 fix: update gulpfile 2024-09-22 01:54:53 +02:00
WinterMyst
97c5360dab Update .gitlab-ci.yml file 2024-09-21 23:48:59 +00:00
Winter_Myst
7fc334ab80 feat: ci/cd 2024-09-22 01:45:32 +02:00
Joscha Maier
b5f97a31e4
Old Repo 2024-09-22 01:07:02 +02:00
36 changed files with 2495 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# IDE
.idea/
.vs/
# Node Modules
node_modules/
package-lock.json

84
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,84 @@
image: ubuntu:latest
stages:
- build
- release
variables:
MANIFEST: "system.json"
ZIPFILE: "kidsonbrooms.zip"
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/${CI_COMMIT_TAG}"
MANIFEST_RELEASE_URL: "${PACKAGE_REGISTRY_URL}/${MANIFEST}"
ZIPFILE_RELEASE_URL: "${PACKAGE_REGISTRY_URL}/${ZIPFILE}"
MANIFEST_PERMALINK_URL: "https://gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/-/releases/${CI_COMMIT_TAG}/downloads/${MANIFEST}"
ZIPFILE_PERMALINK_URL: "https://gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}/-/releases/${CI_COMMIT_TAG}/downloads/${ZIPFILE}"
dry_run: true
# Build job
build:
stage: build
before_script:
# Install Node.js v21.x manually
- apt-get update && apt-get install -y curl
- curl -fsSL https://deb.nodesource.com/setup_21.x | bash -
- apt-get install -y nodejs
- node -v # Verify the correct Node.js version
# Install Gulp globally
- npm install --global gulp-cli
- gulp --version # Verify Gulp is installed
script:
- npm install
- gulp build
artifacts:
paths:
- kidsonbrooms.zip
- system.json
- packs/
only:
- branches
# Release job
release:
stage: release
rules:
- if: $CI_COMMIT_TAG
variables:
dry_run: "false"
before_script:
# Install Node.js v21.x manually
- apt-get update && apt-get install -y curl
- curl -fsSL https://deb.nodesource.com/setup_21.x | bash -
- apt-get install -y nodejs
- node -v # Verify the correct Node.js version
# Install Gulp globally
- npm install --global gulp-cli
- gulp --version # Verify Gulp is installed
script:
- npm install
- gulp release
# Create GitLab release
create-release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
needs:
- job: release
rules:
- if: $CI_COMMIT_TAG
script:
- echo "Creating GitLab release for $CI_COMMIT_TAG"
release:
name: "$CI_COMMIT_TAG"
tag_name: "$CI_COMMIT_TAG"
description: "Release $CI_COMMIT_TAG of $CI_PROJECT_NAME."
assets:
links:
- name: "$MANIFEST"
url: "${MANIFEST_RELEASE_URL}"
filepath: "/${MANIFEST}"
- name: "$ZIPFILE"
url: "${ZIPFILE_RELEASE_URL}"
filepath: "/${ZIPFILE}"

10
.npmignore Normal file
View File

@ -0,0 +1,10 @@
# IDE
.idea/
.vs/
# Node Modules
node_modules/
npm-debug.log
# Foundry
*.lock

View File

@ -0,0 +1,548 @@
@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;
}
.window-app .window-content > * {
flex:0;
}
.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: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: center;
flex-direction: center;
-ms-flex-wrap: center;
flex-wrap: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
text-align: center;
}
.flex-group-left {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: flex-start;
flex-direction: flex-start;
-ms-flex-wrap: center;
flex-wrap: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
text-align: left;
}
.flex-group-right {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: flex-end;
flex-direction: flex-end;
-ms-flex-wrap: center;
flex-wrap: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
text-align: right;
}
.flexshrink {
-webkit-box-flex: 0;
-ms-flex: 0;
flex: 0;
}
.flex-between {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.flexlarge {
-webkit-box-flex: 2;
-ms-flex: 2;
flex: 2;
}
.align-left {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: flex-start;
flex-direction: flex-start;
-ms-flex-wrap: center;
flex-wrap: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
text-align: left;
}
.align-right {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: flex-end;
flex-direction: flex-end;
-ms-flex-wrap: center;
flex-wrap: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
text-align: right;
}
.align-center {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: center;
flex-direction: center;
-ms-flex-wrap: center;
flex-wrap: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
text-align: center;
}
.right-align-input {
-webkit-box-flex: 1;
-ms-flex: 1;
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;
}
.editor-container {
min-height: 200px; /* Adjust this value as needed */
}
/* Styles limited to kidsonbrooms sheets */
.fvtt-never-stop-blowing-up .item-form {
font-family: "Roboto", sans-serif;
}
.fvtt-never-stop-blowing-up .sheet-header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-flex: 0;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
overflow: hidden;
margin-bottom: 10px;
height: 110px;
}
.fvtt-never-stop-blowing-up .sheet-header .profile-img {
-webkit-box-flex: 0;
-ms-flex: 0 0 100px;
flex: 0 0 100px;
height: 100px;
margin-right: 10px;
}
.fvtt-never-stop-blowing-up .sheet-header .header-fields {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.fvtt-never-stop-blowing-up .sheet-header h1.charname {
height: 50px;
padding: 0;
margin: 5px 0;
border-bottom: 0;
}
.fvtt-never-stop-blowing-up .sheet-header h1.charname input {
width: 100%;
height: 100%;
margin: 0;
}
.fvtt-never-stop-blowing-up div.editor-border {
border: 2px solid rgb(81, 81, 81);
border-radius: 10px;
}
.fvtt-never-stop-blowing-up .sheet-tabs {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.fvtt-never-stop-blowing-up .sheet-body,
.fvtt-never-stop-blowing-up .sheet-body .tab,
.fvtt-never-stop-blowing-up .sheet-body .tab .editor {
height: 100%;
}
.fvtt-never-stop-blowing-up .tox .tox-editor-container {
background: #fff;
}
.fvtt-never-stop-blowing-up .tox .tox-edit-area {
padding: 0 8px;
}
.fvtt-never-stop-blowing-up .selection-row {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 10px;
}
.fvtt-never-stop-blowing-up .resource-label {
font-weight: bold;
}
.fvtt-never-stop-blowing-up .items-header {
height: 28px;
margin: 2px 0;
padding: 0;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
}
.fvtt-never-stop-blowing-up .items-header > * {
font-size: 14px;
text-align: center;
}
.fvtt-never-stop-blowing-up .items-header .item-name {
font-weight: bold;
padding-left: 5px;
text-align: left;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.fvtt-never-stop-blowing-up .items-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
scrollbar-width: thin;
color: #444;
}
.fvtt-never-stop-blowing-up .items-list .item-list {
list-style: none;
margin: 0;
padding: 0;
}
.fvtt-never-stop-blowing-up .items-list .item {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 0 2px;
border-bottom: 1px solid #c9c7b8;
}
.fvtt-never-stop-blowing-up .items-list .item:last-child {
border-bottom: none;
}
.fvtt-never-stop-blowing-up .items-list .item .item-name {
-webkit-box-flex: 2;
-ms-flex: 2;
flex: 2;
margin: 0;
overflow: hidden;
font-size: 13px;
text-align: left;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
color: #191813;
}
.fvtt-never-stop-blowing-up .items-list .item .item-name h3, .fvtt-never-stop-blowing-up .items-list .item .item-name h4 {
margin: 0;
white-space: nowrap;
overflow-x: hidden;
}
.fvtt-never-stop-blowing-up .items-list .item .item-name .item-image {
-webkit-box-flex: 0;
-ms-flex: 0 0 30px;
flex: 0 0 30px;
height: 30px;
background-size: 30px;
border: none;
margin-right: 5px;
}
.fvtt-never-stop-blowing-up .items-list .item-controls {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 0;
-ms-flex: 0 0 100px;
flex: 0 0 100px;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.fvtt-never-stop-blowing-up .items-list .item-controls a {
font-size: 12px;
text-align: center;
margin: 0 6px;
}
.fvtt-never-stop-blowing-up .items-list .item-prop {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.fvtt-never-stop-blowing-up .items-list .items-header {
height: 28px;
margin: 2px 0;
padding: 0;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background: rgba(0, 0, 0, 0.05);
border: 2px groove #eeede0;
font-weight: bold;
}
.fvtt-never-stop-blowing-up .items-list .items-header > * {
font-size: 12px;
text-align: center;
}
.fvtt-never-stop-blowing-up .items-list .items-header .item-name {
padding-left: 5px;
text-align: left;
}
.fvtt-never-stop-blowing-up .item-formula {
-webkit-box-flex: 0;
-ms-flex: 0 0 200px;
flex: 0 0 200px;
padding: 0 8px;
}
.fvtt-never-stop-blowing-up .effects .item .effect-source,
.fvtt-never-stop-blowing-up .effects .item .effect-duration,
.fvtt-never-stop-blowing-up .effects .item .effect-controls {
text-align: center;
border-left: 1px solid #c9c7b8;
border-right: 1px solid #c9c7b8;
font-size: 12px;
}
.fvtt-never-stop-blowing-up .effects .item .effect-controls {
border: none;
}
.fvtt-never-stop-blowing-up .fvtt-never-stop-blowing-up input:focus,
.fvtt-never-stop-blowing-up .fvtt-never-stop-blowing-up textarea:focus,
.fvtt-never-stop-blowing-up .fvtt-never-stop-blowing-up select:focus {
outline: none;
border-color: #8102dd;
}

338
gulpfile.js Normal file
View File

@ -0,0 +1,338 @@
const gulp = require('gulp');
const prefix = require('gulp-autoprefixer');
const sourcemaps = require('gulp-sourcemaps');
const sass = require('gulp-sass')(require('sass'));
const zip = require('gulp-zip');
const fs = require('fs');
const fetch = require('node-fetch');
const replace = require('gulp-replace');
const FormData = require('form-data');
/* ----------------------------------------- */
/* Export Tasks
/* ----------------------------------------- */
exports.default = gulp.series(
compileScss,
watchUpdates
);
exports.build = gulp.series(
compileScss,
checkVersion,
ensureOutputDirExists,
packageCompendiums,
updateSystemJson,
zipRelease
);
exports.compile = gulp.series(
compileScss,
ensureOutputDirExists,
packageCompendiums,
);
exports.release = gulp.series(
exports.build,
uploadToPackageRegistry,
publishToFoundry
);
/* ----------------------------------------- */
/* Compile Sass
/* ----------------------------------------- */
// Small error handler helper function.
function handleError(err) {
console.log(err.toString());
this.emit('end');
}
const SYSTEM_SCSS = ["scss/**/*.scss"];
function compileScss() {
// Configure options for sass output. For example, 'expanded' or 'nested'
let options = {
outputStyle: 'expanded'
};
return gulp.src(SYSTEM_SCSS)
.pipe(
sass(options)
.on('error', handleError)
)
.pipe(prefix({
cascade: false
}))
.pipe(gulp.dest("./css"))
}
/* ----------------------------------------- */
/* Watch Updates
/* ----------------------------------------- */
function watchUpdates() {
gulp.watch(SYSTEM_SCSS, compileScss);
}
/* ----------------------------------------- */
/* Zip Release
/* ----------------------------------------- */
function zipRelease() {
return gulp.src([
'./**/*',
'!./node_modules/**',
'!./.git/**',
'!./.gitignore',
'!./gulpfile.js',
'!./package-lock.json',
'!./package.json',
'!./scss/**/*',
'!./.github/**/*',
'!./.gitlab-ci.yml',
'!./README.md',
'!./compendiums/**/*',
'!./*.zip'
], { base: '.' })
.pipe(zip('kidsonbrooms.zip'))
.pipe(gulp.dest('.'));
}
/* ----------------------------------------- */
/* Version Check
/* ----------------------------------------- */
function checkVersion(done) {
const Manifest = JSON.parse(fs.readFileSync('system.json'));
const manifestVersion = Manifest.version;
const gitTag = process.env.CI_COMMIT_TAG;
if (gitTag && manifestVersion !== gitTag) {
console.error(`Version mismatch between tag (${gitTag}) and manifest (${manifestVersion})!`);
process.exit(1);
} else {
console.log(`Version check passed: ${manifestVersion}`);
done();
}
}
/* ----------------------------------------- */
/* Bundle Compendium
/* ----------------------------------------- */
const { exec } = require('child_process');
function packageCompendiums(done) {
const packsDir = './compendiums'; // Adjust to your compendium source directory
const outputDir = './packs';
const moduleId = 'kidsonbrooms'; // Replace with your actual module ID
// Read all subdirectories in the packsDir
if (!fs.existsSync(packsDir)) {
console.log(`Compendium directory ${packsDir} does not exist. Skipping packaging.`);
done();
return;
}
// Read all files and directories in the packsDir
fs.readdir(packsDir, (err, files) => {
if (err) {
console.error(`Error reading directory ${packsDir}: ${err}`);
process.exit(1);
}
// Filter to get only directories
const folders = files.filter(file => {
const fullPath = path.join(packsDir, file);
return fs.statSync(fullPath).isDirectory();
});
if (folders.length === 0) {
console.log(`No compendium folders found in ${packsDir}. Skipping packaging.`);
done();
return;
}
let completed = 0;
folders.forEach(folder => {
const packName = folder; // Use the folder name as the pack name
const inputPath = path.join(packsDir, folder);
const command = `npx fvtt package pack --type System --id ${moduleId} -n "${packName}" --in "${inputPath}" --out "${outputDir}" --yaml`;
exec(command, (err, stdout, stderr) => {
if (err) {
console.error(`Error packaging compendium ${packName}:\n${stderr}`);
process.exit(1);
} else {
console.log(`Compendium ${packName} packaged successfully.`);
console.log(stdout);
completed++;
// When all compendiums have been processed, call done()
if (completed === folders.length) {
done();
}
}
});
});
});
}
/* ----------------------------------------- */
/* Ensure Output Directory Exists
/* ----------------------------------------- */
function ensureOutputDirExists() {
const outputDir = './packs';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
return Promise.resolve();
}
/* ----------------------------------------- */
/* Upload to Package Registry
/* ----------------------------------------- */
async function uploadToPackageRegistry(done) {
const manifestFile = 'system.json';
const zipFile = process.env.ZIPFILE || 'kidsonbrooms.zip';
const packageRegistryUrl = process.env.PACKAGE_REGISTRY_URL;
const ciJobToken = process.env.CI_JOB_TOKEN;
if (!packageRegistryUrl || !ciJobToken) {
console.error('PACKAGE_REGISTRY_URL or CI_JOB_TOKEN is not defined.');
process.exit(1);
}
try {
// Upload manifest file
const manifestUploadUrl = `${packageRegistryUrl}/${manifestFile}`;
console.log(`Uploading ${manifestFile} to ${manifestUploadUrl}`);
let response = await fetch(manifestUploadUrl, {
method: 'PUT',
headers: {
'JOB-TOKEN': ciJobToken,
'Content-Type': 'application/octet-stream',
},
body: fs.createReadStream(manifestFile),
});
if (response.ok) {
console.log(`Uploaded ${manifestFile} successfully.`);
} else {
console.error(`Failed to upload ${manifestFile}: ${response.statusText}`);
process.exit(1);
}
// Upload zip file
const zipUploadUrl = `${packageRegistryUrl}/${zipFile}`;
console.log(`Uploading ${zipFile} to ${zipUploadUrl}`);
response = await fetch(zipUploadUrl, {
method: 'PUT',
headers: {
'JOB-TOKEN': ciJobToken,
'Content-Type': 'application/octet-stream',
},
body: fs.createReadStream(zipFile),
});
if (response.ok) {
console.log(`Uploaded ${zipFile} successfully.`);
} else {
console.error(`Failed to upload ${zipFile}: ${response.statusText}`);
process.exit(1);
}
done();
} catch (error) {
console.error(`Error uploading files: ${error.message}`);
process.exit(1);
}
}
/* ----------------------------------------- */
/* Publish to FoundryVTT
/* ----------------------------------------- */
async function publishToFoundry(done) {
const moduleManifestPath = 'system.json';
const moduleManifest = JSON.parse(fs.readFileSync(moduleManifestPath));
const id = moduleManifest.name;
const version = moduleManifest.version;
const compMin = moduleManifest.compatibility.minimum;
const compVer = moduleManifest.compatibility.verified;
const compMax = moduleManifest.compatibility.maximum;
const manifest = process.env.MANIFEST_PERMALINK_URL || `https://gitlab.com/${process.env.CI_PROJECT_NAMESPACE}/${process.env.CI_PROJECT_NAME}/-/releases/${process.env.CI_COMMIT_TAG}/downloads/${moduleManifestPath}`;
const notes = `https://gitlab.com/${process.env.CI_PROJECT_NAMESPACE}/${process.env.CI_PROJECT_NAME}/-/releases/${process.env.CI_COMMIT_TAG}`;
const dryRun = process.env.dry_run === 'true';
const authToken = process.env.FOUNDRY_API_KEY;
if (!authToken) {
console.error('Foundry VTT API authentication token (FOUNDRY_API_KEY) is not defined.');
process.exit(1);
}
// Construct the payload
const payload = {
id: "kidsonbrooms",
release: {
version: version,
manifest: manifest,
notes: notes,
compatibility: {
minimum: compMin,
verified: compVer,
maximum: compMax,
},
},
};
if (dryRun) {
payload['dry-run'] = true;
}
// Send the POST request to Foundry VTT API
const response = await fetch('https://api.foundryvtt.com/_api/packages/release_version', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: authToken,
},
body: JSON.stringify(payload),
});
const responseData = await response.text();
if (responseData.includes('success')) {
console.log('Successfully published to Foundry VTT:');
console.log(JSON.stringify(responseData, null, 2));
done();
} else {
console.error('Failed to publish to Foundry VTT:');
console.error(JSON.stringify(responseData, null, 2));
process.exit(1);
}
}
/* ----------------------------------------- */
/* Update systen.json with Download URL
/* ----------------------------------------- */
function updateSystemJson(done) {
const ManifestPath = 'system.json';
const Manifest = JSON.parse(fs.readFileSync(ManifestPath));
const zipUrl = process.env.ZIPFILE_RELEASE_URL || 'https://gitlab.com/wintermyst/kidsonbrooms/-/raw/master/kidsonbrooms.zip?inline=false';
Manifest.download = zipUrl;
fs.writeFileSync(ManifestPath, JSON.stringify(Manifest, null, 2));
console.log(`Updated module.json with download URL: ${zipUrl}`);
done();
}

9
lang/en.json Normal file
View File

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

View File

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

View File

@ -0,0 +1,65 @@
/**
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class NeverStopBlowingUpActor extends Actor {
/**
* Override getRollData() that's supplied to rolls.
*/
getRollDataPC() {
let data = { ...this.system };
// Wand bonuses
data.wandBonus = {
wood: this._getWandBonus(this.system.wand.wood),
core: this._getWandBonus(this.system.wand.core)
};
return data;
}
getRollDataNPC() {
let data = { ...this.system};
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 NEVERSTOPBLOWINGUP = {};
// Define constants here, such as:
NEVERSTOPBLOWINGUP.foobar = {
'bas': 'NEVERSTOPBLOWINGUP.bas',
'bar': 'NEVERSTOPBLOWINGUP.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/fvtt-never-stop-blowing-up/templates/actor/parts/actor-features.html",
"systems/fvtt-never-stop-blowing-up/templates/actor/parts/actor-adversity.html",
"systems/fvtt-never-stop-blowing-up/templates/actor/parts/actor-stats.html",
"systems/fvtt-never-stop-blowing-up/templates/actor/parts/actor-npc-stats.html",
]);
};

View File

@ -0,0 +1,332 @@
// Import document classes.
import { NeverStopBlowingUpActor } from "./documents/actor.mjs";
// Import sheet classes.
import { NeverStopBlowingUpActorSheet } from "./sheets/actor-sheet.mjs";
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
import { NEVERSTOPBLOWINGUP } 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 = {
NeverStopBlowingUpActor,
_onTakeAdversityToken: _onTakeAdversityToken, // Add the function to the global object
_onSpendAdversityTokens: _onSpendAdversityTokens // Add the function to the global object
};
// Add custom constants for configuration.
CONFIG.NEVERSTOPBLOWINGUP = NEVERSTOPBLOWINGUP;
/**
* Set an initiative formula for the system
* @type {String}
*/
CONFIG.Combat.initiative = {
formula: "1d20",
decimals: 2
};
// Define custom Document classes
CONFIG.Actor.documentClass = NeverStopBlowingUpActor;
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("fvtt-never-stop-blowing-up", NeverStopBlowingUpActorSheet, { 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("kidsonbrooms", "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("fvtt-never-stop-blowing-up", "tokenClaimed", true);
} else {
// Emit a socket request to update the message to show that the token has been claimed
game.socket.emit('system.fvtt-never-stop-blowing-up', {
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.fvtt-never-stop-blowing-up', 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("fvtt-never-stop-blowing-up", "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.fvtt-never-stop-blowing-up', {
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("fvtt-never-stop-blowing-up", "tokensSpent") || 0;
let newTotal = message.getFlag("fvtt-never-stop-blowing-up", "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("fvtt-never-stop-blowing-up", "newRollTotal", newTotal);
// Update the message's flags to store the cumulative tokens spent
await message.setFlag("fvtt-never-stop-blowing-up", "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,156 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
export class NeverStopBlowingUpActorSheet extends ActorSheet {
/** @override */
static get defaultOptions()
{
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-never-stop-blowing-up", "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/fvtt-never-stop-blowing-up/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();
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;
if(this.actor.type == "character") {
rollData = this.actor.getRollDataPC();
} else if (this.actor.type == "npc") {
rollData = this.actor.getRollDataNPC();
} else {
console.log("ERROR: UNKNOWN AUTHOR TYPE");
return;
}
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>
`;
}
}

31
package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "kidsonbrooms",
"version": "1.1.5",
"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": "Joscha Maier",
"license": "MIT",
"private": true,
"dependencies": {
"form-data": "^4.0.0",
"gulp": "^5",
"gulp-autoprefixer": "^8",
"gulp-replace": "^1.1.4",
"gulp-sass": "^5",
"gulp-sourcemaps": "^2.6.5",
"gulp-zip": "^5.0.1",
"kidsonbrooms": "file:",
"node-fetch": "^2.7.0"
},
"devDependencies": {
"sass": "^1.79.1"
}
}

2
readme.md Normal file
View File

@ -0,0 +1,2 @@
The Never Stop Blowing Up System Implemented in FoundryVTT

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,71 @@
.item-form {
font-family: $font-primary;
}
.sheet-header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start; // Use a mixin for flexbox
flex: 0 1 auto;
overflow: hidden;
margin-bottom: 10px;
height: 110px;
.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: 1;
}
.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;
}
}

27
scss/kidsonbrooms.scss Normal file
View File

@ -0,0 +1,27 @@
// 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';
.editor-container {
min-height: 200px; /* Adjust this value as needed */
}
/* 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;

26
system.json Normal file
View File

@ -0,0 +1,26 @@
{
"id": "fvtt-never-stop-blowing-up",
"title": "Never Stop Blowing Up",
"description": "The Never Stop Blowing Up system for FoundryVTT!",
"version": "12.0.0",
"compatibility": {
"minimum": 12,
"verified": 12
},
"authors": [{
"name": "Joscha Maier"
},{
"name": "LeRatierBretonnien"
}],
"esmodules": ["module/never-stop-blowing-up.mjs"],
"styles": ["css/never-stop-blowing-up.css"],
"socket": true,
"grid": {
"distance": 5,
"units": "ft"
},
"primaryTokenAttribute": "system.adversityTokens",
"url": "https://www.uberwald.me/gitea/uberwald/fvtt-never-stop-blowing-up",
"manifest": "https://www.uberwald.me/gitea/uberwald/fvtt-never-stop-blowing-up/raw/branch/master/system.json",
"download": "https://www.uberwald.me/gitea/uberwald/fvtt-never-stop-blowing-up/archive/12.0.0.zip"
}

103
template.json Normal file
View File

@ -0,0 +1,103 @@
{
"Actor": {
"types": ["character", "npc"],
"templates": {
"base": {
"stats": {
"stat1": {
"name": "fight",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat2": {
"name": "flight",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat3": {
"name": "brains",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat4": {
"name": "brawn",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat5": {
"name": "charm",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat6": {
"name": "grit",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat7": {
"name": "N/A",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat8": {
"name": "N/A",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat9": {
"name": "N/A",
"value": "d4",
"stat": 0,
"magic": 0
},
"stat10": {
"name": "N/A",
"value": "d4",
"stat": 0,
"magic": 0
}
},
"description": ""
}
},
"character": {
"templates": ["base"],
"wounds": {
"minor": {"m1": false, "m2": false, "m3": false},
"moderate": {"m1": false, "m2": false},
"mortal": {"m1": false}
},
"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,66 @@
<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">Class</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">Inventory</a>
<a class="item" data-tab="strengths">Abilities</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/fvtt-never-stop-blowing-up/templates/actor/parts/actor-features.html"}}
{{> "systems/fvtt-never-stop-blowing-up/templates/actor/parts/actor-adversity.html"}}
</section>
<aside class="sidebar">
{{> "systems/fvtt-never-stop-blowing-up/templates/actor/parts/actor-stats.html"}}
</aside>
</section>
</div>
<div class="tab schoolbag" data-group="primary" data-tab="schoolbag">
{{!-- 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>
</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/fvtt-never-stop-blowing-up/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,183 @@
<section class="grid grid-3col">
<fieldset class="resource grid-span-3 flexcol">
<div class="resource grid-span-3 flexrow">
<label for="system.grade" class="resource-label">Species</label>
<input type="text" name="system.grade" value="{{system.grade}}" data-dtype="String" />
</div>
<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>
</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>
-->
<!--
<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>
<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>
<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>
<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>
-->
<fieldset class="resource grid-span-3 flexcol">
<legend>Wounds</legend>
<div class="flexrow">
<span>Minor</span>
{{#each system.wounds.minor as |minorWound key|}}
<input type="checkbox" name="system.wounds.minor.{{key}}" {{checked minorWound}} />
{{/each}}
</div>
<div class="flexrow">
<span>Moderate</span>
{{#each system.wounds.moderate as |minorWound key|}}
<input type="checkbox" name="system.wounds.moderate.{{key}}" {{checked minorWound}} />
{{/each}}
</div>
<div class="flexrow">
<span>Mortal</span>
{{#each system.wounds.mortal as |minorWound key|}}
<input type="checkbox" name="system.wounds.mortal.{{key}}" {{checked minorWound}} />
{{/each}}
</div>
</fieldset>
</section>

View File

@ -0,0 +1,31 @@
<section class="flexcol">
{{#each system.stats as |stat key|}}
<Fieldset class="grid grid-5col">
<legend>{{stat.name}}</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><input type="text" value="{{capitalizeFirst stat.name}}" name="system.stats.{{key}}.name"></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}}>d20</option>
<option value="d12" {{#if (eq stat.value 'd12')}}selected{{/if}}>d12</option>
<option value="d10" {{#if (eq stat.value 'd10')}}selected{{/if}}>d10</option>
<option value="d8" {{#if (eq stat.value 'd8')}}selected{{/if}}>d8</option>
<option value="d6" {{#if (eq stat.value 'd6')}}selected{{/if}}>d6</option>
<option value="d4" {{#if (eq stat.value 'd4')}}selected{{/if}}>d4</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+1d4x+{{stat.stat}}" data-label="Magic Roll for {{key}}" data-key="{{key}}">
<i class="fas fa-dice-d20"></i>
</span>
</Fieldset>-->
</div>
</Fieldset>
{{/each}}
</section>