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
51 changed files with 2390 additions and 3204 deletions

399
.gitignore vendored
View File

@ -1,398 +1,7 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
# IDE
.idea/
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Node Modules
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
package-lock.json

View File

@ -1,94 +1,84 @@
image: ubuntu:latest
stages:
- compile
- build
- release
# Compile Job (runs on every commit)
compile:
stage: compile
image: node:14
script:
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 compile
- gulp build
artifacts:
paths:
- kidsonbrooms.zip
- system.json
- packs/
only:
- branches
# Release Job (manually triggered with version)
# Release job
release:
stage: release
image: node:14
rules:
- if: $CI_COMMIT_TAG
variables:
dry_run: "false"
before_script:
- apt-get update && apt-get install -y curl jq
# 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
- npm install
# Set up SSH agent and add private key for pushing to protected branch
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
- gulp --version # Verify Gulp is installed
script:
# Check if VERSION is provided
- if [ -z "$VERSION" ]; then echo "Error: VERSION variable is required." && exit 1; fi
# Run Gulp release task (includes zipping)
- npm install
- gulp release
# Create a release on GitLab
- export RELEASE_RESPONSE=$(curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"name": "Release v'$VERSION'",
"tag_name": "v'$VERSION'",
"description": "Release v'$VERSION'",
"ref": "master",
"assets": {
"links": [
{
"name": "Download kids-on-brooms.zip",
"url": "https://gitlab.com/YOUR_NAMESPACE/YOUR_PROJECT/-/jobs/$CI_JOB_ID/artifacts/download"
}
]
}
}' "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/releases")
# 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}"
# Get the release URL from the response
- export RELEASE_URL=$(echo $RELEASE_RESPONSE | jq -r '.assets.links[0].url')
# Update the system.json file with the release URL
- sed -i "s|\"download\":.*|\"download\": \"$RELEASE_URL\",|" system.json
# Commit the updated system.json and push it to master
- git config --global user.name "GitLab CI"
- git config --global user.email "ci@gitlab.com"
- git add system.json
- git commit -m "Update system.json with release URL"
- git push origin master
# Publish the release to the Foundry API
- curl -X POST https://api.foundryvtt.com/_api/packages/release_version/ \
-H "Authorization: $FOUNDRY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "Your-Package-ID",
"release": {
"version": "'$VERSION'",
"manifest": "https://gitlab.com/wintermyst/kids-on-brooms/-/raw/master/system.json",
"notes": "https://gitlab.com/wintermyst/kids-on-brooms/releases/tag/v'$VERSION'",
"compatibility": {
"minimum": "12.331",
"verified": "12.331",
"maximum": ""
}
}
}'
only:
- master
when: manual
allow_failure: false
artifacts:
paths:
- kids-on-brooms.zip
expire_in: never

10
.npmignore Normal file
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 KiB

21
LICENSE
View File

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

View File

@ -1,2 +0,0 @@
# KidsOnBroomsFoundryVTT
The Kids on Brooms System for Foundry VTT

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

View File

@ -3,7 +3,41 @@ const prefix = require('gulp-autoprefixer');
const sourcemaps = require('gulp-sourcemaps');
const sass = require('gulp-sass')(require('sass'));
const zip = require('gulp-zip');
const { compile } = require('sass');
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
@ -31,28 +65,15 @@ function compileScss() {
}))
.pipe(gulp.dest("./css"))
}
const css = gulp.series(compileScss);
/* ----------------------------------------- */
/* Watch Updates
/* ----------------------------------------- */
function watchUpdates() {
gulp.watch(SYSTEM_SCSS, compileScss());
gulp.watch(SYSTEM_SCSS, compileScss);
}
/* ----------------------------------------- */
/* Export Tasks
/* ----------------------------------------- */
exports.default = gulp.series(
compileScss,
watchUpdates
);
exports.build = gulp.series(
compileScss
);
exports.css = css;
/* ----------------------------------------- */
/* Zip Release
@ -69,12 +90,249 @@ function zipRelease() {
'!./package.json',
'!./scss/**/*',
'!./.github/**/*',
'!./.gitlab-ci.yml',
'!./README.md',
'!./compendiums/**/*',
'!./*.zip'
], { base: '.' })
.pipe(zip('kids-on-brooms.zip'))
.pipe(zip('kidsonbrooms.zip'))
.pipe(gulp.dest('.'));
}
exports.release = gulp.series(
compileScss,
zipRelease
);
/* ----------------------------------------- */
/* 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();
}

View File

@ -1,23 +0,0 @@
import * as models from "./modules/dataModel/_system.mjs";
import * as sheets from "./modules/sheets/_system.mjs";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
const SYSTEM = {
id: "kidsonbrooms",
}
Hooks.once("init", async function() {
console.log("Initialising Kids on Brooms system");
globalThis.kidsonbrooms = game.system;
game.system.CONST = SYSTEM;
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet(SYSTEM.id, "{{sheets.BaseActorSheet}}", {types: ["PlayerCharacter"], makeDefault: true});
sheets.BaseActorSheet.getTemplate();
CONFIG.Actor.dataModels = models.playerCharacterModel;
})

View File

@ -1,30 +1,9 @@
{
"BaseActor": {
"FIELDS": {
"age": "Age",
"pronouns": "Pronouns",
"fear": "Fear",
"grade": "Grade",
"stats": {
"fight": "Fight",
"brains": "Brains",
"charm": "Charm",
"flight": "Flight",
"brawn": "Brawn",
"grit": "Grit"
}
}
},
"PlayerCharacter": {
"FIELDS": {
"description": "Description",
"broom": "Broom",
"wand": "Wand",
"animalFamiliar": "Animal Familiar",
"schoolbag": "Schoolbag",
"strengths": "Strengths",
"adversityTokens": "Adversity Tokens"
}
}
}
"NEVERSTOPBLOWINGUP.EffectCreate": "Create Effect",
"NEVERSTOPBLOWINGUP.EffectToggle": "Toggle Effect",
"NEVERSTOPBLOWINGUP.EffectEdit": "Edit Effect",
"NEVERSTOPBLOWINGUP.EffectDelete": "Delete Effect",
"NEVERSTOPBLOWINGUP.Add": "Add"
}

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

View File

@ -1,4 +0,0 @@
export * from "./itemModel.mjs";
export * from "./dataModel.mjs";
export * from "./playerCharacterModel.mjs";
export * from "./baseActorModel.mjs"

View File

@ -1,31 +0,0 @@
import Stat from "./dataModel.mjs"
/* -------------------------------------------- */
/* Actor base Model */
/* -------------------------------------------- */
const fields = foundry.data.fields;
export default class ActorGeneral extends foundry.abstract.TypeDataModel
{
static defineSchema(){
return {
age: new fields.StringField({required: false}),
pronouns: new fields.StringField({required: false}),
fear: new fields.StringField({required: false}),
grade: new fields.StringField({required: false}),
stats: new fields.SchemaField({
fight: new fields.EmbeddedDataField(Stat, {required: true, nullable: false, default: new Stat()}),
brains: new fields.EmbeddedDataField(Stat, {required: true, nullable: false, default: new Stat()}),
charm: new fields.EmbeddedDataField(Stat, {required: true, nullable: false, default: new Stat()}),
flight: new fields.EmbeddedDataField(Stat, {required: true, nullable: false, default: new Stat()}),
brawn: new fields.EmbeddedDataField(Stat, {required: true, nullable: false, default: new Stat()}),
grit: new fields.EmbeddedDataField(Stat, {required: true, nullable: false, default: new Stat()}),
}),
}
}
static LOCALISATION_PREFIXES = ["BaseActor"];
prepareDerivedData() {
super.prepareDerivedData();
}
}

View File

@ -1,71 +0,0 @@
/* -------------------------------------------- */
/* Base Models */
/* -------------------------------------------- */
const fields = foundry.data.fields;
export default class Stat extends foundry.abstract.DataModel
{
static defineSchema() {
return {
id: new fields.StringField({ required: true, initial: "statID"}),
name: new fields.StringField({ required: true, intial: "Stat"}),
die: new fields.NumberField({ required: true, nullable: false, initial: "d4"}),
modifiers: new fields.ArrayField({ required: true, type: Modifier, default: []}),
modifier: new fields.NumberField({required: true, integer: true, initial: 0})
};
}
}
export class Modifier extends foundry.abstract.DataModel
{
static defineSchema() {
return {
statID: new fields.StringField({ required: true, initial: "statID"}),
name: new fields.StringField({ required: true, initial: "Modifier"}),
value: new fields.NumberField({ required: true, integer: true, initial: 0}),
};
}
Modifier(statID,name,value) {
this.statID = statID;
this.name = name;
this.value = value;
};
}
/* -------------------------------------------- */
/* Effect Models */
/* -------------------------------------------- */
export class Effect extends foundry.abstract.DataModel
{
static defineSchema() {
return {
description: new fields.StringField({ required: true, initial: "A EffectDescription" }),
modifier: new fields.EmbeddedDataField(Modifier, { required: true, nullable: true, default: null}),
};
}
}
export class Flaw extends foundry.abstract.DataModel
{
static defineSchema() {
return {
name: new fields.StringField({ required: true, initial: "Flaw"}),
description: new fields.StringField({ required: true, initial: "A FlawDescription" })
};
}
}
export class Strength extends Effect
{
static defineSchema() {
return {
...super.defineSchema(),
name: new fields.StringField({ required: true, initial: "Strength"})
};
}
}

View File

@ -1,45 +0,0 @@
import Effect from "./dataModel.mjs";
/* -------------------------------------------- */
/* Item Models */
/* -------------------------------------------- */
/* How this will work is when we first load a sheet we load all the items we have and take their effects and apply them to our stats. */
const fields = foundry.data.fields;
export default class KidsOnBroomsItem extends foundry.abstract.TypeDataModel
{
static defineSchema() {
return {
description: new fields.StringField({ required: true, initial: "An KidsOnBroomsItemDescription" }),
effects: new fields.ArrayField({ required: true, type: Effect, default: []}),
quantity: new fields.NumberField({required: true, nullable: false, integer: true, initial: 1, min: 0}),
price: new fields.NumberField({required: true, nullable: false, integer: true, initial: 0, min: 0}),
};
}
KidsOnBroomsItem(name,description,effects) {
this.name = name;
this.description = description;
this.effects = effects;
}
}
export class Wand extends KidsOnBroomsItem
{
static defineSchema() {
return {
wood: new fields.EmbeddedDataField(KidsOnBroomsItem, { required: true, nullable: true, default: null}), //These are just KidsOnBroomsItems!
core: new fields.EmbeddedDataField(KidsOnBroomsItem, { required: true, nullable: true, default: null}),
};
}
}
export class Broom extends KidsOnBroomsItem
{
static defineSchema(){
return {
look: new fields.StringField({required: true, initial: "A broom"}),
mechanicalBenefit: new fields.EmbeddedDataField(Effect, {required: false})
}
}
}

View File

@ -1,48 +0,0 @@
import ActorGeneral from "./baseActorModel.mjs";
import {Wand, Broom} from "./itemModel.mjs"
import KidsOnBroomsItem from "./itemModel.mjs"
/* -------------------------------------------- */
/* PC Model */
/* -------------------------------------------- */
const fields = foundry.data.fields;
export default class PlayerCharacter extends ActorGeneral
{
static defineSchema(){
return {
...super.defineSchema(),
description: new fields.StringField({required: false, intial: "Enter your characters description here."}),
broom: new fields.EmbeddedDataField(Broom, {nullable: true}),
wand: new fields.EmbeddedDataField(Wand, {nullable: true}),
animalFamiliar: new fields.StringField({required: false, initial: "Describe your companion!"}),
schoolbag: new fields.ArrayField({type: KidsOnBroomsItem, default: []}),
strengths: new fields.ArrayField({type: Strength, default: []}),
adversityTokens: new fields.NumberField({required: true, nullable: false, integer: true, initial: 3, min: 0})
}
}
static LOCALISATION_PREFIXES = ["PlayerCharacter"];
prepareBaseData() {
super.prepareBaseData();
let effectsToApply = this.gatherEffects();
effectsToApply.forEach(element => {
console.log(element);
})
}
gatherEffects() {
let effectsToApply = new [];
this.schoolbag.array.forEach(element => {
if(element.effects != []) {
element.effects.forEach(effect => {
effectsToApply.push(effect);
})
}
});
return effectsToApply;
}
}

View File

@ -1,7 +0,0 @@
export default class KidsOnBroomsActor extends Actor {
constructor(data, context) {
super(data, context)
}
}

View File

@ -1 +0,0 @@
export * from "./actor-sheet.mjs"

View File

@ -1,31 +0,0 @@
const {api, sheets} = foundry.applications;
export default class BaseActorSheet extends api.HandlebarsApplicationMixin(sheets.ActorSheetV2) {
/** @override */
static get defaultOptions()
{
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["kidsonbrooms", "sheet", "actor"],
width: 800,
height: 800,
tabs: [{}]
});
}
/** @override */
get template()
{
console.log("template", this.actor)
return `systems/kidsonbrooms/templates/actor/actor-sheet-{$this.actor.type}.html`;
}
async getData()
{
const context = super.getData();
console.log(context);
return context;
}
}

2332
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,31 @@
{
"name": "kidsonbroomsfoundryvtt",
"version": "0.1.0",
"description": "The Kids on Brooms System for Foundry VTT",
"main": "kidsonbroomsfoundryvtt.js",
"name": "kidsonbrooms",
"version": "1.1.5",
"description": "CSS compiler for the Kids On Brooms system",
"scripts": {
"sass": "sass --watch scss/kidsOnBrooms.scss css/kidsOnBrooms.css"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"gulp": "^5.0.0",
"mathjs": "^13.1.1",
"sass": "^1.79.3"
"build": "gulp build",
"compile": "gulp css",
"watch": "gulp",
"gulp": "gulp"
},
"browserslist": [
"last 5 versions"
],
"author": "Joscha Maier",
"license": "MIT",
"private": true,
"dependencies": {
"kidsonbroomsfoundryvtt": "file:"
"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;
}
}

View File

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;

View File

@ -1,51 +1,26 @@
{
"name": "kidsonbrooms",
"title": "Kids on Brooms",
"description": "This is a implementation of the Kids on Brooms system in FoundryVTT.",
"version": "0.1.0",
"compatibility": {
"minimum": 12,
"verified": 12
},
"esmodules": ["kidsonbrooms.mjs"],
"authors": [{
"name": "Joscha Maier",
"url": "https://gitlab.com/wintermyst"
}],
"documentTypes": {
"Actor": {
"playerCharacter": {
}
}
},
"languages": [{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}],
"packs": [],
"packFolders": [],
"socket": true,
"url": "https://gitlab.com/wintermyst/kidsonbrooms",
"manifest": "https://your/hosted/system/repo/system.json",
"download": "https://your/packaged/download/archive.zip",
"grid:": {
"type": 1,
"distance": 5,
"units": "ft"
},
"relationships": {
"requires": [
{
"id": "lib-wrapper",
"type": "module",
"compatibility": {
"minimum": "1.0.0.0",
"verified": "1.12.6.0"
}
}
]
},
"primaryTokenAttribute": "system.adversityTokens"
}
"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

@ -1,5 +1,6 @@
<form class="{{cssClass}} {{actor.type}} flexcol" autocomplete="off">
{{!-- Sheet Header --}}
{{!-- 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">
@ -15,6 +16,20 @@
</div>
</div>
</header>
</form>
{{!-- 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>