diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3370fa4..bc2e0930 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,11 +45,12 @@ build: stage: build script: - npm run build + - mv dist ds4 cache: <<: *global_cache artifacts: paths: - - dist + - ds4 expire_in: 1 week deploy: @@ -58,7 +59,7 @@ deploy: dependencies: - build script: - - rsync --delete -az ./dist/ rsync://${DEPLOYMENT_USER}@${DEPLOYMENT_SERVER}:${DEPLOYMENT_PATH} + - rsync --delete -az ./ds4/ rsync://${DEPLOYMENT_USER}@${DEPLOYMENT_SERVER}:${DEPLOYMENT_PATH} environment: name: production url: https://vtt.f3l.de/ diff --git a/.gitlab/issue_templates/Bug Report.md b/.gitlab/issue_templates/Bug Report.md new file mode 100644 index 00000000..c209b177 --- /dev/null +++ b/.gitlab/issue_templates/Bug Report.md @@ -0,0 +1,29 @@ +# Description + +Please describe the issue. + +# Steps to Reproduce + +1. ... +2. ... +3. ... + +# Expected Behavior + +Please describe the expected behavior. + +# Actual Behavior + +Please describe the actual behavior. + +# Additional Details + +These are optional, please add them if it makes sense. + +- ![Screenshot]() +- [Logfile]() +- ... + +# Possible Solutions + +If you have any suggestions on how to solve the issue, please add them here. diff --git a/.gitlab/issue_templates/Feature Request.md b/.gitlab/issue_templates/Feature Request.md index f60644d2..d6dbe30b 100644 --- a/.gitlab/issue_templates/Feature Request.md +++ b/.gitlab/issue_templates/Feature Request.md @@ -1,9 +1,13 @@ -# Description +# Story As a …, I want … so that … +# Description + +Please add a more detailed description of the feature here. + # Acceptance criteria -* Criterion 1 -* Criterion 2 -* … +1. Criterion 1 +2. Criterion 2 +3. … diff --git a/README.md b/README.md index 8119bbdb..9b502a47 100644 --- a/README.md +++ b/README.md @@ -3,50 +3,89 @@ An implementation of the Dungeonslayers 4 game system for [Foundry Virtual Tabletop](http://foundryvtt.com). -## Prerequisites +This system provides character sheet support for Actors and Items and mechanical +support for dice and rules necessary to +play games of Dungeponslayers 4. -In order to build this system, a recent version of `npm` is required. +## Installation -## Building +To install and use the Dungeonslayers 4 system for Foundry Virtual Tabletop, +simply paste the following URL into the **Install System** dialog on the Setup +menu of the application. -To build the system, first install all required dependencies: +https://git.f3l.de/dungeonslayers/ds4/-/raw/master/src/system.json?inline=false + +## Development + +### Prerequisits + +In order to build this system, recent versions of `node` and `npm` are required. +We recommend using the latest lts version of `node`, which is `v14.15.4` at the +time of writing. If you use `nvm` to manage your `node` versions, you can simply +run +``` +nvm install +``` + +in the project's root directory. + +You also need to install the the project's dependencies. To do so, run ``` npm install ``` -Then build the project by running +### Building + +You can build the project by running ``` npm run build ``` -If you'd like the built system to be automatically linked to your local Foundry -VTT installation's data folder, add a file called `foundryconfig.json` to the -project root with the following contents: +Alternatively, you can run + +``` +npm run build:watch +``` + +to watch for changes and automatically build as necessary. + +### Linking the built system to Foundry VTT + +In order to provide a fluent development experience, it is recommended to link +the built system to your local Foundry VTT installation's data folder. In order +to do so, first add a file called `foundryconfig.json` to the project root with +the following content: ``` { - "dataPath": "//.local/share/FoundryVTT", - "repository": "", - "rawURL": "" + "dataPath": "/.local/share/FoundryVTT" } ``` +On platforms other than Linux you need to adjust the path accordingly. + Then run ``` npm run link ``` -If you want the system to be continuously build upon every saved change, just -run +### Running the tests + +You can run the tests with the following command: ``` -npm run build:watch +npm test ``` -# Licensing +## Contributing + +Code and content contributions are accepted. Please feel free to submit issues +to the issue tracker or submit merge requests for code changes. To create an issue send a mail to [git+dungeonslayers-ds4-155-issue-@git.f3l.de](mailto:git+dungeonslayers-ds4-155-issue-@git.f3l.de). + +## Licensing Dungeonslayers (© Christian Kennig) is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/de/deed.en). @@ -56,5 +95,5 @@ CC BY-NC-SA 3.0. Hence the modified icons are also published under this license. A copy of this license can be found under [src/assets/official/LICENSE](src/assets/official/LICENSE). -The rest of this project is licensed under the MIT License, a copy of which can -be found under [LICENSE](LICENSE). +The software component of this project is licensed under the MIT License, a copy +of which can be found under [LICENSE](LICENSE). diff --git a/gulpfile.js b/gulpfile.js index 83aa3820..dc62e8b7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,429 +1,429 @@ -const gulp = require("gulp"); -const fs = require("fs-extra"); -const path = require("path"); -const chalk = require("chalk"); -const archiver = require("archiver"); -const stringify = require("json-stringify-pretty-compact"); -const typescript = require("typescript"); - -const ts = require("gulp-typescript"); -const less = require("gulp-less"); -const sass = require("gulp-sass"); -const git = require("gulp-git"); - -const argv = require("yargs").argv; - -sass.compiler = require("sass"); - -function getConfig() { - const configPath = path.resolve(process.cwd(), "foundryconfig.json"); - let config; - - if (fs.existsSync(configPath)) { - config = fs.readJSONSync(configPath); - return config; - } else { - return; - } -} - -function getManifest() { - const json = {}; - - if (fs.existsSync("src")) { - json.root = "src"; - } else { - json.root = "dist"; - } - - const modulePath = path.join(json.root, "module.json"); - const systemPath = path.join(json.root, "system.json"); - - if (fs.existsSync(modulePath)) { - json.file = fs.readJSONSync(modulePath); - json.name = "module.json"; - } else if (fs.existsSync(systemPath)) { - json.file = fs.readJSONSync(systemPath); - json.name = "system.json"; - } else { - return; - } - - return json; -} - -/** - * TypeScript transformers - * @returns {typescript.TransformerFactory} - */ -function createTransformer() { - /** - * @param {typescript.Node} node - */ - function shouldMutateModuleSpecifier(node) { - if (!typescript.isImportDeclaration(node) && !typescript.isExportDeclaration(node)) return false; - if (node.moduleSpecifier === undefined) return false; - if (!typescript.isStringLiteral(node.moduleSpecifier)) return false; - if (!node.moduleSpecifier.text.startsWith("./") && !node.moduleSpecifier.text.startsWith("../")) return false; - if (path.extname(node.moduleSpecifier.text) !== "") return false; - return true; - } - - /** - * Transforms import/export declarations to append `.js` extension - * @param {typescript.TransformationContext} context - */ - function importTransformer(context) { - return (node) => { - /** - * @param {typescript.Node} node - */ - function visitor(node) { - if (shouldMutateModuleSpecifier(node)) { - if (typescript.isImportDeclaration(node)) { - const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`); - return typescript.updateImportDeclaration( - node, - node.decorators, - node.modifiers, - node.importClause, - newModuleSpecifier - ); - } else if (typescript.isExportDeclaration(node)) { - const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`); - return typescript.updateExportDeclaration( - node, - node.decorators, - node.modifiers, - node.exportClause, - newModuleSpecifier - ); - } - } - return typescript.visitEachChild(node, visitor, context); - } - - return typescript.visitNode(node, visitor); - }; - } - - return importTransformer; -} - -const tsConfig = ts.createProject("tsconfig.json", { - getCustomTransformers: (_program) => ({ - after: [createTransformer()], - }), -}); - -/********************/ -/* BUILD */ -/********************/ - -/** - * Build TypeScript - */ -function buildTS() { - return gulp.src("src/**/*.ts").pipe(tsConfig()).pipe(gulp.dest("dist")); -} - -/** - * Build Less - */ -function buildLess() { - return gulp.src("src/*.less").pipe(less()).pipe(gulp.dest("dist")); -} - -/** - * Build SASS - */ -function buildSASS() { - return gulp.src("src/*.scss").pipe(sass().on("error", sass.logError)).pipe(gulp.dest("dist")); -} - -/** - * Copy static files - */ -async function copyFiles() { - const statics = ["lang", "fonts", "assets", "templates", "module.json", "system.json", "template.json"]; - try { - for (const file of statics) { - if (fs.existsSync(path.join("src", file))) { - await fs.copy(path.join("src", file), path.join("dist", file)); - } - } - return Promise.resolve(); - } catch (err) { - Promise.reject(err); - } -} - -/** - * Watch for changes for each build step - */ -function buildWatch() { - gulp.watch("src/**/*.ts", { ignoreInitial: false }, buildTS); - gulp.watch("src/**/*.less", { ignoreInitial: false }, buildLess); - gulp.watch("src/**/*.scss", { ignoreInitial: false }, buildSASS); - gulp.watch(["src/fonts", "src/lang", "src/templates", "src/*.json"], { ignoreInitial: false }, copyFiles); -} - -/********************/ -/* CLEAN */ -/********************/ - -/** - * Remove built files from `dist` folder - * while ignoring source files - */ -async function clean() { - const name = path.basename(path.resolve(".")); - const files = []; - - // If the project uses TypeScript - if (fs.existsSync(path.join("src", `${name}.ts`))) { - files.push( - "lang", - "templates", - "assets", - "module", - `${name}.js`, - "module.json", - "system.json", - "template.json" - ); - } - - // If the project uses Less or SASS - if (fs.existsSync(path.join("src", `${name}.less`)) || fs.existsSync(path.join("src", `${name}.scss`))) { - files.push("fonts", `${name}.css`); - } - - console.log(" ", chalk.yellow("Files to clean:")); - console.log(" ", chalk.blueBright(files.join("\n "))); - - // Attempt to remove the files - try { - for (const filePath of files) { - await fs.remove(path.join("dist", filePath)); - } - return Promise.resolve(); - } catch (err) { - Promise.reject(err); - } -} - -/********************/ -/* LINK */ -/********************/ - -/** - * Link build to User Data folder - */ -async function linkUserData() { - const name = path.basename(path.resolve(".")); - const config = fs.readJSONSync("foundryconfig.json"); - - let destDir; - try { - if ( - fs.existsSync(path.resolve(".", "dist", "module.json")) || - fs.existsSync(path.resolve(".", "src", "module.json")) - ) { - destDir = "modules"; - } else if ( - fs.existsSync(path.resolve(".", "dist", "system.json")) || - fs.existsSync(path.resolve(".", "src", "system.json")) - ) { - destDir = "systems"; - } else { - throw Error(`Could not find ${chalk.blueBright("module.json")} or ${chalk.blueBright("system.json")}`); - } - - let linkDir; - if (config.dataPath) { - if (!fs.existsSync(path.join(config.dataPath, "Data"))) - throw Error("User Data path invalid, no Data directory found"); - - linkDir = path.join(config.dataPath, "Data", destDir, name); - } else { - throw Error("No User Data path defined in foundryconfig.json"); - } - - if (argv.clean || argv.c) { - console.log(chalk.yellow(`Removing build in ${chalk.blueBright(linkDir)}`)); - - await fs.remove(linkDir); - } else if (!fs.existsSync(linkDir)) { - console.log(chalk.green(`Copying build to ${chalk.blueBright(linkDir)}`)); - await fs.symlink(path.resolve("./dist"), linkDir); - } - return Promise.resolve(); - } catch (err) { - Promise.reject(err); - } -} - -/*********************/ -/* PACKAGE */ -/*********************/ - -/** - * Package build - */ -async function packageBuild() { - const manifest = getManifest(); - - return new Promise((resolve, reject) => { - try { - // Remove the package dir without doing anything else - if (argv.clean || argv.c) { - console.log(chalk.yellow("Removing all packaged files")); - fs.removeSync("package"); - return; - } - - // Ensure there is a directory to hold all the packaged versions - fs.ensureDirSync("package"); - - // Initialize the zip file - const zipName = `${manifest.file.name}-v${manifest.file.version}.zip`; - const zipFile = fs.createWriteStream(path.join("package", zipName)); - const zip = archiver("zip", { zlib: { level: 9 } }); - - zipFile.on("close", () => { - console.log(chalk.green(zip.pointer() + " total bytes")); - console.log(chalk.green(`Zip file ${zipName} has been written`)); - return resolve(); - }); - - zip.on("error", (err) => { - throw err; - }); - - zip.pipe(zipFile); - - // Add the directory with the final code - zip.directory("dist/", manifest.file.name); - - zip.finalize(); - } catch (err) { - return reject(err); - } - }); -} - -/*********************/ -/* PACKAGE */ -/*********************/ - -/** - * Update version and URLs in the manifest JSON - */ -function updateManifest(cb) { - const packageJson = fs.readJSONSync("package.json"); - const config = getConfig(), - manifest = getManifest(), - rawURL = config.rawURL, - repoURL = config.repository, - manifestRoot = manifest.root; - - if (!config) cb(Error(chalk.red("foundryconfig.json not found"))); - if (!manifest) cb(Error(chalk.red("Manifest JSON not found"))); - if (!rawURL || !repoURL) cb(Error(chalk.red("Repository URLs not configured in foundryconfig.json"))); - - try { - const version = argv.update || argv.u; - - /* Update version */ - - const versionMatch = /^(\d{1,}).(\d{1,}).(\d{1,})$/; - const currentVersion = manifest.file.version; - let targetVersion = ""; - - if (!version) { - cb(Error("Missing version number")); - } - - if (versionMatch.test(version)) { - targetVersion = version; - } else { - targetVersion = currentVersion.replace(versionMatch, (substring, major, minor, patch) => { - console.log(substring, Number(major) + 1, Number(minor) + 1, Number(patch) + 1); - if (version === "major") { - return `${Number(major) + 1}.0.0`; - } else if (version === "minor") { - return `${major}.${Number(minor) + 1}.0`; - } else if (version === "patch") { - return `${major}.${minor}.${Number(patch) + 1}`; - } else { - return ""; - } - }); - } - - if (targetVersion === "") { - return cb(Error(chalk.red("Error: Incorrect version arguments."))); - } - - if (targetVersion === currentVersion) { - return cb(Error(chalk.red("Error: Target version is identical to current version."))); - } - console.log(`Updating version number to '${targetVersion}'`); - - packageJson.version = targetVersion; - manifest.file.version = targetVersion; - - /* Update URLs */ - - const result = `${rawURL}/v${manifest.file.version}/package/${manifest.file.name}-v${manifest.file.version}.zip`; - - manifest.file.url = repoURL; - manifest.file.manifest = `${rawURL}/master/${manifestRoot}/${manifest.name}`; - manifest.file.download = result; - - const prettyProjectJson = stringify(manifest.file, { - maxLength: 35, - indent: "\t", - }); - - fs.writeJSONSync("package.json", packageJson, { spaces: "\t" }); - fs.writeFileSync(path.join(manifest.root, manifest.name), prettyProjectJson, "utf8"); - - return cb(); - } catch (err) { - cb(err); - } -} - -function gitAdd() { - return gulp.src("package").pipe(git.add({ args: "--no-all" })); -} - -function gitCommit() { - return gulp.src("./*").pipe( - git.commit(`v${getManifest().file.version}`, { - args: "-a", - disableAppendPaths: true, - }) - ); -} - -function gitTag() { - const manifest = getManifest(); - return git.tag(`v${manifest.file.version}`, `Updated to ${manifest.file.version}`, (err) => { - if (err) throw err; - }); -} - -const execGit = gulp.series(gitAdd, gitCommit, gitTag); - -const execBuild = gulp.parallel(buildTS, buildLess, buildSASS, copyFiles); - -exports.build = gulp.series(clean, execBuild); -exports.watch = buildWatch; -exports.clean = clean; -exports.link = linkUserData; -exports.package = packageBuild; -exports.update = updateManifest; -exports.publish = gulp.series(clean, updateManifest, execBuild, packageBuild, execGit); +const gulp = require("gulp"); +const fs = require("fs-extra"); +const path = require("path"); +const chalk = require("chalk"); +const archiver = require("archiver"); +const stringify = require("json-stringify-pretty-compact"); +const typescript = require("typescript"); + +const ts = require("gulp-typescript"); +const less = require("gulp-less"); +const sass = require("gulp-sass"); +const git = require("gulp-git"); + +const argv = require("yargs").argv; + +sass.compiler = require("sass"); + +function getConfig() { + const configPath = path.resolve(process.cwd(), "foundryconfig.json"); + let config; + + if (fs.existsSync(configPath)) { + config = fs.readJSONSync(configPath); + return config; + } else { + return; + } +} + +function getManifest() { + const json = {}; + + if (fs.existsSync("src")) { + json.root = "src"; + } else { + json.root = "dist"; + } + + const modulePath = path.join(json.root, "module.json"); + const systemPath = path.join(json.root, "system.json"); + + if (fs.existsSync(modulePath)) { + json.file = fs.readJSONSync(modulePath); + json.name = "module.json"; + } else if (fs.existsSync(systemPath)) { + json.file = fs.readJSONSync(systemPath); + json.name = "system.json"; + } else { + return; + } + + return json; +} + +/** + * TypeScript transformers + * @returns {typescript.TransformerFactory} + */ +function createTransformer() { + /** + * @param {typescript.Node} node + */ + function shouldMutateModuleSpecifier(node) { + if (!typescript.isImportDeclaration(node) && !typescript.isExportDeclaration(node)) return false; + if (node.moduleSpecifier === undefined) return false; + if (!typescript.isStringLiteral(node.moduleSpecifier)) return false; + if (!node.moduleSpecifier.text.startsWith("./") && !node.moduleSpecifier.text.startsWith("../")) return false; + if (path.extname(node.moduleSpecifier.text) !== "") return false; + return true; + } + + /** + * Transforms import/export declarations to append `.js` extension + * @param {typescript.TransformationContext} context + */ + function importTransformer(context) { + return (node) => { + /** + * @param {typescript.Node} node + */ + function visitor(node) { + if (shouldMutateModuleSpecifier(node)) { + if (typescript.isImportDeclaration(node)) { + const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`); + return typescript.updateImportDeclaration( + node, + node.decorators, + node.modifiers, + node.importClause, + newModuleSpecifier, + ); + } else if (typescript.isExportDeclaration(node)) { + const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`); + return typescript.updateExportDeclaration( + node, + node.decorators, + node.modifiers, + node.exportClause, + newModuleSpecifier, + ); + } + } + return typescript.visitEachChild(node, visitor, context); + } + + return typescript.visitNode(node, visitor); + }; + } + + return importTransformer; +} + +const tsConfig = ts.createProject("tsconfig.json", { + getCustomTransformers: (_program) => ({ + after: [createTransformer()], + }), +}); + +/********************/ +/* BUILD */ +/********************/ + +/** + * Build TypeScript + */ +function buildTS() { + return gulp.src("src/**/*.ts").pipe(tsConfig()).pipe(gulp.dest("dist")); +} + +/** + * Build Less + */ +function buildLess() { + return gulp.src("src/*.less").pipe(less()).pipe(gulp.dest("dist")); +} + +/** + * Build SASS + */ +function buildSASS() { + return gulp.src("src/*.scss").pipe(sass().on("error", sass.logError)).pipe(gulp.dest("dist")); +} + +/** + * Copy static files + */ +async function copyFiles() { + const statics = ["lang", "fonts", "assets", "templates", "module.json", "system.json", "template.json"]; + try { + for (const file of statics) { + if (fs.existsSync(path.join("src", file))) { + await fs.copy(path.join("src", file), path.join("dist", file)); + } + } + return Promise.resolve(); + } catch (err) { + Promise.reject(err); + } +} + +/** + * Watch for changes for each build step + */ +function buildWatch() { + gulp.watch("src/**/*.ts", { ignoreInitial: false }, buildTS); + gulp.watch("src/**/*.less", { ignoreInitial: false }, buildLess); + gulp.watch("src/**/*.scss", { ignoreInitial: false }, buildSASS); + gulp.watch(["src/fonts", "src/lang", "src/templates", "src/*.json"], { ignoreInitial: false }, copyFiles); +} + +/********************/ +/* CLEAN */ +/********************/ + +/** + * Remove built files from `dist` folder + * while ignoring source files + */ +async function clean() { + const name = path.basename(path.resolve(".")); + const files = []; + + // If the project uses TypeScript + if (fs.existsSync(path.join("src", `${name}.ts`))) { + files.push( + "lang", + "templates", + "assets", + "module", + `${name}.js`, + "module.json", + "system.json", + "template.json", + ); + } + + // If the project uses Less or SASS + if (fs.existsSync(path.join("src", `${name}.less`)) || fs.existsSync(path.join("src", `${name}.scss`))) { + files.push("fonts", `${name}.css`); + } + + console.log(" ", chalk.yellow("Files to clean:")); + console.log(" ", chalk.blueBright(files.join("\n "))); + + // Attempt to remove the files + try { + for (const filePath of files) { + await fs.remove(path.join("dist", filePath)); + } + return Promise.resolve(); + } catch (err) { + Promise.reject(err); + } +} + +/********************/ +/* LINK */ +/********************/ + +/** + * Link build to User Data folder + */ +async function linkUserData() { + const name = path.basename(path.resolve(".")); + const config = fs.readJSONSync("foundryconfig.json"); + + let destDir; + try { + if ( + fs.existsSync(path.resolve(".", "dist", "module.json")) || + fs.existsSync(path.resolve(".", "src", "module.json")) + ) { + destDir = "modules"; + } else if ( + fs.existsSync(path.resolve(".", "dist", "system.json")) || + fs.existsSync(path.resolve(".", "src", "system.json")) + ) { + destDir = "systems"; + } else { + throw Error(`Could not find ${chalk.blueBright("module.json")} or ${chalk.blueBright("system.json")}`); + } + + let linkDir; + if (config.dataPath) { + if (!fs.existsSync(path.join(config.dataPath, "Data"))) + throw Error("User Data path invalid, no Data directory found"); + + linkDir = path.join(config.dataPath, "Data", destDir, name); + } else { + throw Error("No User Data path defined in foundryconfig.json"); + } + + if (argv.clean || argv.c) { + console.log(chalk.yellow(`Removing build in ${chalk.blueBright(linkDir)}`)); + + await fs.remove(linkDir); + } else if (!fs.existsSync(linkDir)) { + console.log(chalk.green(`Copying build to ${chalk.blueBright(linkDir)}`)); + await fs.symlink(path.resolve("./dist"), linkDir); + } + return Promise.resolve(); + } catch (err) { + Promise.reject(err); + } +} + +/*********************/ +/* PACKAGE */ +/*********************/ + +/** + * Package build + */ +async function packageBuild() { + const manifest = getManifest(); + + return new Promise((resolve, reject) => { + try { + // Remove the package dir without doing anything else + if (argv.clean || argv.c) { + console.log(chalk.yellow("Removing all packaged files")); + fs.removeSync("package"); + return; + } + + // Ensure there is a directory to hold all the packaged versions + fs.ensureDirSync("package"); + + // Initialize the zip file + const zipName = `${manifest.file.name}-v${manifest.file.version}.zip`; + const zipFile = fs.createWriteStream(path.join("package", zipName)); + const zip = archiver("zip", { zlib: { level: 9 } }); + + zipFile.on("close", () => { + console.log(chalk.green(zip.pointer() + " total bytes")); + console.log(chalk.green(`Zip file ${zipName} has been written`)); + return resolve(); + }); + + zip.on("error", (err) => { + throw err; + }); + + zip.pipe(zipFile); + + // Add the directory with the final code + zip.directory("dist/", manifest.file.name); + + zip.finalize(); + } catch (err) { + return reject(err); + } + }); +} + +/*********************/ +/* PACKAGE */ +/*********************/ + +/** + * Update version and URLs in the manifest JSON + */ +function updateManifest(cb) { + const packageJson = fs.readJSONSync("package.json"); + const config = getConfig(), + manifest = getManifest(), + rawURL = config.rawURL, + repoURL = config.repository, + manifestRoot = manifest.root; + + if (!config) cb(Error(chalk.red("foundryconfig.json not found"))); + if (!manifest) cb(Error(chalk.red("Manifest JSON not found"))); + if (!rawURL || !repoURL) cb(Error(chalk.red("Repository URLs not configured in foundryconfig.json"))); + + try { + const version = argv.update || argv.u; + + /* Update version */ + + const versionMatch = /^(\d{1,}).(\d{1,}).(\d{1,})$/; + const currentVersion = manifest.file.version; + let targetVersion = ""; + + if (!version) { + cb(Error("Missing version number")); + } + + if (versionMatch.test(version)) { + targetVersion = version; + } else { + targetVersion = currentVersion.replace(versionMatch, (substring, major, minor, patch) => { + console.log(substring, Number(major) + 1, Number(minor) + 1, Number(patch) + 1); + if (version === "major") { + return `${Number(major) + 1}.0.0`; + } else if (version === "minor") { + return `${major}.${Number(minor) + 1}.0`; + } else if (version === "patch") { + return `${major}.${minor}.${Number(patch) + 1}`; + } else { + return ""; + } + }); + } + + if (targetVersion === "") { + return cb(Error(chalk.red("Error: Incorrect version arguments."))); + } + + if (targetVersion === currentVersion) { + return cb(Error(chalk.red("Error: Target version is identical to current version."))); + } + console.log(`Updating version number to '${targetVersion}'`); + + packageJson.version = targetVersion; + manifest.file.version = targetVersion; + + /* Update URLs */ + + const result = `${rawURL}/v${manifest.file.version}/package/${manifest.file.name}-v${manifest.file.version}.zip`; + + manifest.file.url = repoURL; + manifest.file.manifest = `${rawURL}/master/${manifestRoot}/${manifest.name}`; + manifest.file.download = result; + + const prettyProjectJson = stringify(manifest.file, { + maxLength: 35, + indent: "\t", + }); + + fs.writeJSONSync("package.json", packageJson, { spaces: "\t" }); + fs.writeFileSync(path.join(manifest.root, manifest.name), prettyProjectJson, "utf8"); + + return cb(); + } catch (err) { + cb(err); + } +} + +function gitAdd() { + return gulp.src("package").pipe(git.add({ args: "--no-all" })); +} + +function gitCommit() { + return gulp.src("./*").pipe( + git.commit(`v${getManifest().file.version}`, { + args: "-a", + disableAppendPaths: true, + }), + ); +} + +function gitTag() { + const manifest = getManifest(); + return git.tag(`v${manifest.file.version}`, `Updated to ${manifest.file.version}`, (err) => { + if (err) throw err; + }); +} + +const execGit = gulp.series(gitAdd, gitCommit, gitTag); + +const execBuild = gulp.parallel(buildTS, buildLess, buildSASS, copyFiles); + +exports.build = gulp.series(clean, execBuild); +exports.watch = buildWatch; +exports.clean = clean; +exports.link = linkUserData; +exports.package = packageBuild; +exports.update = updateManifest; +exports.publish = gulp.series(clean, updateManifest, execBuild, packageBuild, execGit); diff --git a/src/ds4.scss b/src/ds4.scss index 2fedca1f..db46a09a 100644 --- a/src/ds4.scss +++ b/src/ds4.scss @@ -16,8 +16,10 @@ @import "scss/components/basic_property"; @import "scss/components/tabs"; @import "scss/components/items"; + @import "scss/components/talents"; @import "scss/components/description"; @import "scss/components/character_values"; @import "scss/components/attributes_traits"; @import "scss/components/combat_values"; + @import "scss/components/character_progression"; } diff --git a/src/lang/en.json b/src/lang/en.json index de17c09b..267e0b2c 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1,10 +1,12 @@ { "DS4.UserInteractionAddItem": "Add item", "DS4.NotOwned": "No owner", - "DS4.Description": "Description", - "DS4.DescriptionAbbr": "Desc", - "DS4.Details": "Details", - "DS4.Effects": "Effects", + "DS4.HeadingDescription": "Description", + "DS4.HeadingDetails": "Details", + "DS4.HeadingEffects": "Effects", + "DS4.HeadingInventory": "Inventory", + "DS4.HeadingProfile": "Profile", + "DS4.HeadingTalents": "Talents & Abilities", "DS4.AttackType": "Attack Type", "DS4.AttackTypeAbbr": "AT", "DS4.WeaponBonus": "Weapon Bonus", @@ -29,10 +31,19 @@ "DS4.ItemAvailabilityNowhere": "Nowhere", "DS4.ItemName": "Name", "DS4.ItemTypeWeapon": "Weapon", + "DS4.ItemTypeWeaponPlural": "Weapons", "DS4.ItemTypeArmor": "Armor", + "DS4.ItemTypeArmorPlural": "Armor", "DS4.ItemTypeShield": "Shield", + "DS4.ItemTypeShieldPlural": "Shields", "DS4.ItemTypeTrinket": "Trinket", + "DS4.ItemTypeTrinketPlural": "Trinkets", "DS4.ItemTypeEquipment": "Equipment", + "DS4.ItemTypeEquipmentPlural": "Equipment", + "DS4.ItemTypeTalent": "Talent", + "DS4.ItemTypeTalentPlural": "Talents", + "DS4.ItemTypeRacialAbility": "Racial Ability", + "DS4.ItemTypeRacialAbilityPlural": "Racial Abilities", "DS4.ArmorType": "Armor Type", "DS4.ArmorTypeAbbr": "AT", "DS4.ArmorMaterialType": "Material Type", @@ -78,8 +89,26 @@ "DS4.BaseInfoClass": "Class", "DS4.BaseInfoHeroClass": "Hero Class", "DS4.BaseInfoRacialAbilities": "Racial Abilites", + "DS4.BaseInfoCulture": "Culture", "DS4.ProgressionLevel": "Level", "DS4.ProgressionExperiencePoints": "Experience Points", "DS4.ProgressionTalentPoints": "Talent Points", - "DS4.ProgressionProgressPoints": "Progress Points" + "DS4.ProgressionProgressPoints": "Progress Points", + "DS4.TalentRank": "Rank", + "DS4.TalentRankBase": "Acquired Ranks", + "DS4.TalentRankMax": "Maximum Ranks", + "DS4.TalentRankMod": "Additional Ranks", + "DS4.TalentRankTotal": "Total Ranks", + "DS4.LanguageLanguages": "Languages", + "DS4.LanguageAlphabets": "Alphabets", + "DS4.ProfileGender": "Gender", + "DS4.ProfileBirthday": "Birthday", + "DS4.ProfileBirthplace": "Birthplace", + "DS4.ProfileAge": "Age", + "DS4.ProfileHeight": "Height", + "DS4.ProfilHairColor": "Hair Color", + "DS4.ProfileWeight": "Weight", + "DS4.ProfileEyeColor": "Eye Color", + "DS4.ProfileSpecialCharacteristics": "Special Characteristics", + "DS4.WarningManageActiveEffectOnOwnedItem": "Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update." } diff --git a/src/module/actor/actor-data.ts b/src/module/actor/actor-data.ts index 93385b65..4269bce0 100644 --- a/src/module/actor/actor-data.ts +++ b/src/module/actor/actor-data.ts @@ -4,6 +4,8 @@ export interface DS4ActorDataType { combatValues: DS4ActorDataCombatValues; baseInfo: DS4ActorDataBaseInfo; progression: DS4ActorDataProgression; + language: DS4ActorDataLanguage; + profile: DS4ActorDataProfile; } interface DS4ActorDataAttributes { @@ -23,8 +25,9 @@ interface UsableResource { used: T; } -interface CurrentData extends ModifiableData { - current: T; +interface ResourceData extends ModifiableData { + value: T; + max?: T; } // Blueprint in case we need more detailed differentiation @@ -40,7 +43,7 @@ interface DS4ActorDataTraits { } interface DS4ActorDataCombatValues { - hitPoints: CurrentData; + hitPoints: ResourceData; defense: ModifiableData; initiative: ModifiableData; movement: ModifiableData; @@ -55,6 +58,7 @@ interface DS4ActorDataBaseInfo { class: string; heroClass: string; racialAbilities: string; + culture: string; } interface DS4ActorDataProgression { @@ -63,3 +67,20 @@ interface DS4ActorDataProgression { talentPoints: UsableResource; progressPoints: UsableResource; } + +interface DS4ActorDataLanguage { + languages: string; + alphabets: string; +} + +interface DS4ActorDataProfile { + gender: string; + birthday: string; + birthplace: string; + age: number; + height: number; + hairColor: string; + weight: number; + eyeColor: string; + specialCharacteristics: string; +} diff --git a/src/module/actor/actor.ts b/src/module/actor/actor.ts index c17d6843..7f2f8209 100644 --- a/src/module/actor/actor.ts +++ b/src/module/actor/actor.ts @@ -18,5 +18,7 @@ export class DS4Actor extends Actor Object.values(combatValues).forEach( (combatValue: ModifiableData) => (combatValue.total = combatValue.base + combatValue.mod), ); + + combatValues.hitPoints.max = combatValues.hitPoints.total; } } diff --git a/src/module/config.ts b/src/module/config.ts index 89d285cf..28e0a655 100644 --- a/src/module/config.ts +++ b/src/module/config.ts @@ -48,6 +48,8 @@ export const DS4 = { shield: "DS4.ItemTypeShield", trinket: "DS4.ItemTypeTrinket", equipment: "DS4.ItemTypeEquipment", + talent: "DS4.ItemTypeTalent", + racialAbility: "DS4.ItemTypeRacialAbility", }, /** @@ -135,10 +137,11 @@ export const DS4 = { class: "DS4.BaseInfoClass", heroClass: "DS4.BaseInfoHeroClass", racialAbilities: "DS4.BaseInfoRacialAbilities", + culture: "DS4.BaseInfoCulture", }, /** - * Definme the progression info of a character + * Define the progression info of a character */ progression: { level: "DS4.ProgressionLevel", @@ -146,4 +149,42 @@ export const DS4 = { talentPoints: "DS4.ProgressionTalentPoints", progressPoints: "DS4.ProgressionProgressPoints", }, + + /** + * Define the language info of a character + */ + language: { + languages: "DS4.LanguageLanguages", + alphabets: "DS4.LanguageAlphabets", + }, + + /** + * Define the profile info of a character + */ + profile: { + gender: "DS4.ProfileGender", + birthday: "DS4.ProfileBirthday", + birthplace: "DS4.ProfileBirthplace", + age: "DS4.ProfileAge", + height: "DS4.ProfileHeight", + hairColor: "DS4.ProfilHairColor", + weight: "DS4.ProfileWeight", + eyeColor: "DS4.ProfileEyeColor", + specialCharacteristics: "DS4.ProfileSpecialCharacteristics", + }, + + /** + * Define the profile info types for hanndlebars of a character + */ + profileDTypes: { + gender: "String", + birthday: "String", + birthplace: "String", + age: "Number", + height: "Number", + hairColor: "String", + weight: "Number", + eyeColor: "String", + specialCharacteristics: "String", + }, }; diff --git a/src/module/ds4.ts b/src/module/ds4.ts index ddc135ae..1c593ff6 100644 --- a/src/module/ds4.ts +++ b/src/module/ds4.ts @@ -46,8 +46,13 @@ async function registerHandlebarsPartials() { "systems/ds4/templates/item/partials/effects.hbs", "systems/ds4/templates/item/partials/body.hbs", "systems/ds4/templates/actor/partials/items-overview.hbs", + "systems/ds4/templates/actor/partials/talents-overview.hbs", + "systems/ds4/templates/actor/partials/overview-add-button.hbs", + "systems/ds4/templates/actor/partials/overview-control-buttons.hbs", "systems/ds4/templates/actor/partials/attributes-traits.hbs", "systems/ds4/templates/actor/partials/combat-values.hbs", + "systems/ds4/templates/actor/partials/profile.hbs", + "systems/ds4/templates/actor/partials/character-progression.hbs", ]; return loadTemplates(templatePaths); } @@ -75,6 +80,8 @@ Hooks.once("setup", function () { "combatValues", "baseInfo", "progression", + "language", + "profile", ]; // Exclude some from sorting where the default order matters diff --git a/src/module/item/item-data.ts b/src/module/item/item-data.ts index 58f4dc90..76b90e78 100644 --- a/src/module/item/item-data.ts +++ b/src/module/item/item-data.ts @@ -1,5 +1,13 @@ -// TODO: Actually add a type for data -export type DS4ItemDataType = DS4Weapon | DS4Armor | DS4Shield | DS4Trinket | DS4Equipment; +import { ModifiableData } from "../actor/actor-data"; + +export type DS4ItemDataType = + | DS4Weapon + | DS4Armor + | DS4Shield + | DS4Trinket + | DS4Equipment + | DS4Talent + | DS4RacialAbility; // types @@ -14,9 +22,18 @@ interface DS4Armor extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4It armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves"; } +export interface DS4Talent extends DS4ItemBase { + rank: DS4TalentRank; +} + +interface DS4TalentRank extends ModifiableData { + max: number; +} + interface DS4Shield extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {} interface DS4Trinket extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {} interface DS4Equipment extends DS4ItemBase, DS4ItemPhysical {} +type DS4RacialAbility = DS4ItemBase; // templates @@ -30,6 +47,10 @@ interface DS4ItemPhysical { storageLocation: string; } +export function isDS4ItemDataTypePhysical(input: DS4ItemDataType): boolean { + return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input; +} + interface DS4ItemEquipable { equipped: boolean; } diff --git a/src/module/item/item-sheet.ts b/src/module/item/item-sheet.ts index ac8197aa..8d8df70f 100644 --- a/src/module/item/item-sheet.ts +++ b/src/module/item/item-sheet.ts @@ -1,5 +1,5 @@ import { DS4Item } from "./item"; -import { DS4ItemDataType } from "./item-data"; +import { DS4ItemDataType, isDS4ItemDataTypePhysical } from "./item-data"; /** * Extend the basic ItemSheet with some very simple modifications @@ -26,7 +26,13 @@ export class DS4ItemSheet extends ItemSheet { /** @override */ getData(): ItemSheetData { - const data = { ...super.getData(), config: CONFIG.DS4, isOwned: this.item.isOwned, actor: this.item.actor }; + const data = { + ...super.getData(), + config: CONFIG.DS4, + isOwned: this.item.isOwned, + actor: this.item.actor, + isPhysical: isDS4ItemDataTypePhysical(this.item.data.data), + }; console.log(data); return data; } @@ -54,29 +60,38 @@ export class DS4ItemSheet extends ItemSheet { if (!this.options.editable) return; - html.find(".effect-create").on("click", this._onEffectCreate.bind(this)); - - html.find(".effect-edit").on("click", (ev) => { - const li = $(ev.currentTarget).parents(".effect"); - console.log(li.data("effectId")); - const effect = this.item.effects.get(li.data("effectId")); - effect.sheet.render(true); - }); - - html.find(".effect-delete").on("click", async (ev) => { - const li = $(ev.currentTarget).parents(".effect"); - await this.item.deleteEmbeddedEntity("ActiveEffect", li.data("effectId")); - }); + html.find(".effect-control").on("click", this._onManageActiveEffect.bind(this)); } /** - * Handle creating a new ActiveEffect for the item using initial data defined in the HTML dataset + * Handle management of ActiveEffects. * @param {Event} event The originating click event - * @private */ - private async _onEffectCreate(event: JQuery.ClickEvent): Promise { + private async _onManageActiveEffect(event: JQuery.ClickEvent): Promise { event.preventDefault(); + if (this.item.isOwned) { + return ui.notifications.warn(game.i18n.localize("DS4.WarningManageActiveEffectOnOwnedItem")); + } + const a = event.currentTarget; + const li = $(a).parents(".effect"); + + switch (a.dataset["action"]) { + case "create": + return this._createActiveEffect(); + case "edit": + const effect = this.item.effects.get(li.data("effectId")); + return effect.sheet.render(true); + case "delete": { + return this.item.deleteEmbeddedEntity("ActiveEffect", li.data("effectId")); + } + } + } + + /** + * Create a new ActiveEffect for the item using default data. + */ + private async _createActiveEffect(): Promise { const label = `New Effect`; const createData = { diff --git a/src/module/item/item.ts b/src/module/item/item.ts index c32f0cfe..e9a1aa3e 100644 --- a/src/module/item/item.ts +++ b/src/module/item/item.ts @@ -1,6 +1,6 @@ import { DS4Actor } from "../actor/actor"; import { DS4ActorDataType } from "../actor/actor-data"; -import { DS4ItemDataType } from "./item-data"; +import { DS4ItemDataType, DS4Talent } from "./item-data"; /** * Extend the basic Item with some very simple modifications. @@ -12,10 +12,18 @@ export class DS4Item extends Item { */ prepareData(): void { super.prepareData(); + this.prepareDerivedData(); // Get the Item's data // const itemData = this.data; // const actorData = this.actor ? this.actor.data : {}; // const data = itemData.data; } + + prepareDerivedData(): void { + if (this.type === "talent") { + const data = this.data.data as DS4Talent; + data.rank.total = data.rank.base + data.rank.mod; + } + } } diff --git a/src/scss/components/_attributes_traits.scss b/src/scss/components/_attributes_traits.scss index 19cef982..09cb469b 100644 --- a/src/scss/components/_attributes_traits.scss +++ b/src/scss/components/_attributes_traits.scss @@ -8,7 +8,6 @@ } .attribute-value { border: 2px groove $c-border-groove; - line-height: $default-input-height; font-size: 1.5em; text-align: center; padding-left: 2px; @@ -17,6 +16,7 @@ input, .attribute-value-total { grid-column: span 2; + line-height: $default-input-height; } } } @@ -32,7 +32,6 @@ .trait-value { border: 2px groove $c-border-groove; font-size: 1.5em; - line-height: $default-input-height; text-align: center; padding-left: 2px; padding-right: 2px; @@ -40,6 +39,7 @@ input, .trait-value-total { grid-column: span 2; + line-height: $default-input-height; } } } diff --git a/src/scss/components/_basic_property.scss b/src/scss/components/_basic_property.scss index 6f725dad..40978da4 100644 --- a/src/scss/components/_basic_property.scss +++ b/src/scss/components/_basic_property.scss @@ -1,13 +1,25 @@ .basic-properties { flex: 0 0 100%; + gap: 2px; .basic-property { - .basic-property-label { + display: grid; + align-content: end; + padding-left: 1px; + padding-right: 1px; + + & > label { font-weight: bold; } - .basic-property-select { + & > select { display: block; width: 100%; } + + .input-divider { + text-align: center; + } + + @include mark-invalid-or-disabled-input; } } diff --git a/src/scss/components/_character_progression.scss b/src/scss/components/_character_progression.scss new file mode 100644 index 00000000..c2b3fdcd --- /dev/null +++ b/src/scss/components/_character_progression.scss @@ -0,0 +1,28 @@ +.progression { + .progression-entry { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-end; + align-items: center; + + padding-right: 3px; + h2.progression-label { + font-family: $font-heading; + display: block; + height: 50px; + padding: 0px; + color: $c-light-grey; + border: none; + line-height: 50px; + margin: $header-top-margin 0; + text-align: right; + //flex: 0; + } + input.progression-value { + margin-left: 5px; + flex: 0 0 40px; + text-align: left; + } + } +} diff --git a/src/scss/components/_description.scss b/src/scss/components/_description.scss index 8bd3b18b..cc4c1322 100644 --- a/src/scss/components/_description.scss +++ b/src/scss/components/_description.scss @@ -9,7 +9,7 @@ .side-property { margin: 2px 0; display: grid; - grid-template-columns: 40% auto; + grid-template-columns: minmax(30%, auto) auto; justify-content: left; label { @@ -23,6 +23,8 @@ width: calc(100% - 2px); } + @include mark-invalid-or-disabled-input; + input[type="checkbox"] { width: auto; height: 100%; @@ -31,6 +33,10 @@ } } +.description { + height: 100%; +} + .sheet-body .tab .editor { height: 100%; } diff --git a/src/scss/components/_forms.scss b/src/scss/components/_forms.scss index 2a992d7b..4c99b156 100644 --- a/src/scss/components/_forms.scss +++ b/src/scss/components/_forms.scss @@ -43,7 +43,7 @@ header.sheet-header { display: block; height: 50px; padding: 0px; - flex: 0 0 0; + flex: 0 0 auto; color: $c-light-grey; border: none; line-height: 50px; diff --git a/src/scss/components/_items.scss b/src/scss/components/_items.scss index 4dd97f78..6382c587 100644 --- a/src/scss/components/_items.scss +++ b/src/scss/components/_items.scss @@ -31,6 +31,7 @@ input { border: 0; padding: 0; + background-color: transparent; } input[type="checkbox"] { @@ -38,6 +39,8 @@ height: 100%; margin: 0px; } + + @include mark-invalid-or-disabled-input; } .item-name { @@ -54,9 +57,6 @@ width: 2.5em; padding: 0; } - .item-num-val:invalid { - background-color: color.mix(lightcoral, $c-light-grey, 25%); - } .item-description { font-size: 75%; diff --git a/src/scss/components/_talents.scss b/src/scss/components/_talents.scss new file mode 100644 index 00000000..2f8db41b --- /dev/null +++ b/src/scss/components/_talents.scss @@ -0,0 +1,3 @@ +.talent-ranks-equation { + text-align: center; +} diff --git a/src/scss/global/_flex.scss b/src/scss/global/_flex.scss index 271c64b3..3f23d105 100644 --- a/src/scss/global/_flex.scss +++ b/src/scss/global/_flex.scss @@ -18,6 +18,9 @@ .flex1 { flex: 1; } + .flex125 { + flex: 1.25; + } .flex15 { flex: 1.5; } @@ -51,6 +54,9 @@ .flex1 { flex: 1; } + .flex125 { + flex: 1.25; + } .flex15 { flex: 1.5; } diff --git a/src/scss/global/_grid.scss b/src/scss/global/_grid.scss index 8498a314..b8903f70 100644 --- a/src/scss/global/_grid.scss +++ b/src/scss/global/_grid.scss @@ -28,8 +28,8 @@ } .grid-6col { - grid-column: span 5 / span 5; - grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-column: span 6 / span 6; + grid-template-columns: repeat(6, minmax(0, 1fr)); } .grid-7col { diff --git a/src/scss/global/_window.scss b/src/scss/global/_window.scss index bbe12083..36985d71 100644 --- a/src/scss/global/_window.scss +++ b/src/scss/global/_window.scss @@ -1,5 +1,12 @@ .window-app { font-family: $font-primary; + input[type="text"], + input[type="number"], + input[type="password"], + input[type="date"], + input[type="time"] { + width: 100%; + } } .rollable { diff --git a/src/scss/utils/_colors.scss b/src/scss/utils/_colors.scss index 7a2200d1..55fb8c0a 100644 --- a/src/scss/utils/_colors.scss +++ b/src/scss/utils/_colors.scss @@ -2,3 +2,4 @@ $c-white: #fff; $c-black: #000; $c-light-grey: #777; $c-border-groove: #eeede0; +$c-invalid-input: rgba(lightcoral, 50%); diff --git a/src/scss/utils/_mixins.scss b/src/scss/utils/_mixins.scss index 7e028c29..adc2e69a 100644 --- a/src/scss/utils/_mixins.scss +++ b/src/scss/utils/_mixins.scss @@ -19,3 +19,12 @@ display: grid; place-items: center; } + +@mixin mark-invalid-or-disabled-input { + input:invalid { + background-color: $c-invalid-input; + } + input:disabled { + background-color: transparent; + } +} diff --git a/src/system.json b/src/system.json index 3929e3ab..a64a1a8c 100644 --- a/src/system.json +++ b/src/system.json @@ -20,9 +20,9 @@ ], "gridDistance": 1, "gridUnits": "m", - "primaryTokenAttribute": "combatValues.hitPoints.current", + "primaryTokenAttribute": "combatValues.hitPoints", "url": "https://git.f3l.de/dungeonslayers/ds4", - "manifest": "https://git.f3l.de/dungeonslayers/ds4/-/blob/master/src/system.json", - "download": "https://git.f3l.de/dungeonslayers/ds4/-/archive/master/ds4-master.zip", + "manifest": "https://git.f3l.de/dungeonslayers/ds4/-/raw/master/src/system.json?inline=false", + "download": "https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/0.1.0/download?job=build", "license": "MIT" } diff --git a/src/template.json b/src/template.json index b78fd3e1..6e8d6354 100644 --- a/src/template.json +++ b/src/template.json @@ -48,7 +48,7 @@ "hitPoints": { "base": 0, "mod": 0, - "current": 0 + "value": 0 }, "defense": { "base": 0, @@ -83,7 +83,8 @@ "race": "", "class": "", "heroClass": "", - "racialAbilities": "" + "racialAbilities": "", + "culture": "" }, "progression": { "level": 0, @@ -96,11 +97,26 @@ "total": 0, "used": 0 } + }, + "language": { + "languages": "", + "alphabets": "" + }, + "profile": { + "gender": "", + "birthday": "", + "birthplace": "", + "age": 0, + "height": 0, + "hairColor": "", + "weight": 0, + "eyeColor": "", + "specialCharacteristics": "" } } }, "Item": { - "types": ["weapon", "armor", "shield", "trinket", "equipment"], + "types": ["weapon", "armor", "shield", "trinket", "equipment", "talent", "racialAbility"], "templates": { "base": { "description": "" @@ -137,6 +153,17 @@ }, "equipment": { "templates": ["base", "physical"] + }, + "talent": { + "templates": ["base"], + "rank": { + "base": 0, + "max": 0, + "mod": 0 + } + }, + "racialAbility": { + "templates": ["base"] } } } diff --git a/src/templates/actor/actor-sheet.hbs b/src/templates/actor/actor-sheet.hbs index 475ce2fd..8e3e5f54 100644 --- a/src/templates/actor/actor-sheet.hbs +++ b/src/templates/actor/actor-sheet.hbs @@ -2,96 +2,66 @@ {{!-- Sheet Header --}}
-
+

-
-
- {{!-- The grid classes are defined in scss/global/_grid.scss. To use, use both the "grid" and "grid-Ncol" - class where "N" can be any number from 1 to 12 and will create that number of columns. --}} -
- {{!-- "flex-group-center" is also defined in the _grid.scss file and it will add a small amount of - padding, a border, and will center all of its child elements content and text. --}} -
- -
- + {{> systems/ds4/templates/actor/partials/character-progression.hbs}} + +
+
+ + +
+
+ + +
+
+ +
+ / +
-
-
-
- -
- -
-
-
- -
- / - -
-
-
- -
- / - -
-
+
+ +
+ / +
-
- -
- -
+
+ +
-
- -
- -
-
-
- -
- -
-
-
- -
- -
+
+ +
- +
+
{{> systems/ds4/templates/actor/partials/attributes-traits.hbs}} {{> systems/ds4/templates/actor/partials/combat-values.hbs}} -
{{!-- Sheet Tab Navigation --}} {{!-- Sheet Body --}} @@ -101,6 +71,12 @@ {{editor content=data.biography target="data.biography" button=true owner=owner editable=editable}} + {{! Profile Tab --}} + {{> systems/ds4/templates/actor/partials/profile.hbs}} + + {{!-- Talents Tab --}} + {{> systems/ds4/templates/actor/partials/talents-overview.hbs}} + {{!-- Items Tab --}} {{> systems/ds4/templates/actor/partials/items-overview.hbs}} diff --git a/src/templates/actor/partials/character-progression.hbs b/src/templates/actor/partials/character-progression.hbs new file mode 100644 index 00000000..f22376f0 --- /dev/null +++ b/src/templates/actor/partials/character-progression.hbs @@ -0,0 +1,15 @@ +
+
+

+

+ +
+
+

+

+ +
+
diff --git a/src/templates/actor/partials/items-overview.hbs b/src/templates/actor/partials/items-overview.hbs index 0cc57faf..e41d6f98 100644 --- a/src/templates/actor/partials/items-overview.hbs +++ b/src/templates/actor/partials/items-overview.hbs @@ -5,29 +5,6 @@ {{!-- INLINE PARTIAL DEFINITIONS --}} {{!-- ======================================================================== --}} -{{!-- -!-- Render an "add" button for a given data type. -!-- -!-- @param datType: hand over the dataType to the partial as hash parameter ---}} -{{#*inline "addItemButton"}} - -{{/inline}} -{{!-- -!-- Render a group of an "edit" and a "delete" button for the current item. -!-- The current item is defined by the data-item-id HTML property of the parent li element. ---}} -{{#*inline "itemControlButtons"}} -
- - -
-{{/inline}} - {{!-- !-- Render a header row for a given data type. @@ -55,9 +32,9 @@ {{!-- item type specifics --}} {{> @partial-block }} {{!-- description --}} -
{{localize 'DS4.Description'}}
+
{{localize 'DS4.HeadingDescription'}}
{{!-- add button --}} - {{> addItemButton dataType=dataType }} + {{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }} {{/inline}} @@ -94,7 +71,7 @@ {{!-- description --}}
{{{item.data.data.description}}}
{{!-- control buttons --}} - {{> itemControlButtons}} + {{> systems/ds4/templates/actor/partials/overview-control-buttons.hbs }} {{/inline}} @@ -102,10 +79,10 @@ {{!-- ======================================================================== --}} -
+
{{!-- WEAPONS --}} -

{{localize 'DS4.ItemTypeWeapon'}}

+

{{localize 'DS4.ItemTypeWeaponPlural'}}

    {{#> itemListHeader dataType='weapon'}}
    {{localize 'DS4.AttackTypeAbbr'}}
    @@ -129,7 +106,7 @@
{{!-- ARMOR --}} -

{{localize 'DS4.ItemTypeArmor'}}

+

{{localize 'DS4.ItemTypeArmorPlural'}}

    {{#> itemListHeader dataType='armor'}}
    {{localize 'DS4.ArmorMaterialTypeAbbr'}}
    @@ -153,7 +130,7 @@ {{!-- SHIELD --}} -

    {{localize 'DS4.ItemTypeShield'}}

    {{!-- SPECIFIC --}} +

    {{localize 'DS4.ItemTypeShieldPlural'}}

    {{!-- SPECIFIC --}}
      {{#> itemListHeader dataType='shield' }}
      @@ -168,7 +145,7 @@
    {{!-- TRINKET --}} -

    {{localize 'DS4.ItemTypeTrinket'}}

    +

    {{localize 'DS4.ItemTypeTrinketPlural'}}

      {{#> itemListHeader dataType='trinket'}}
      {{localize 'DS4.StorageLocation'}}
      @@ -182,7 +159,7 @@
    {{!-- EQUIPMENT --}} -

    {{localize 'DS4.ItemTypeEquipment'}}

    +

    {{localize 'DS4.ItemTypeEquipmentPlural'}}

      {{#> itemListHeader dataType='equipment'}}
      {{localize 'DS4.StorageLocation'}}
      diff --git a/src/templates/actor/partials/overview-add-button.hbs b/src/templates/actor/partials/overview-add-button.hbs new file mode 100644 index 00000000..86e5d774 --- /dev/null +++ b/src/templates/actor/partials/overview-add-button.hbs @@ -0,0 +1,11 @@ +{{! +!-- Render an "add" button for adding an item of given data type. +!-- +!-- @param datType: hand over the dataType to the partial as hash parameter +}} + \ No newline at end of file diff --git a/src/templates/actor/partials/overview-control-buttons.hbs b/src/templates/actor/partials/overview-control-buttons.hbs new file mode 100644 index 00000000..d10dbc3f --- /dev/null +++ b/src/templates/actor/partials/overview-control-buttons.hbs @@ -0,0 +1,8 @@ +{{!-- +!-- Render a group of an "edit" and a "delete" button for the current item. +!-- The current item is defined by the data-item-id HTML property of the parent li element. +--}} +
      + + +
      diff --git a/src/templates/actor/partials/profile.hbs b/src/templates/actor/partials/profile.hbs new file mode 100644 index 00000000..b0034128 --- /dev/null +++ b/src/templates/actor/partials/profile.hbs @@ -0,0 +1,13 @@ +
      +
      + {{#each data.profile as |profile-data-value profile-data-key|}} +
      + + +
      + {{/each}} +
      +
      \ No newline at end of file diff --git a/src/templates/actor/partials/talents-overview.hbs b/src/templates/actor/partials/talents-overview.hbs new file mode 100644 index 00000000..179703e2 --- /dev/null +++ b/src/templates/actor/partials/talents-overview.hbs @@ -0,0 +1,123 @@ +{{!-- ======================================================================== --}} +{{!-- INLINE PARTIAL DEFINITIONS --}} +{{!-- ======================================================================== --}} +{{!-- TODO: remove duplicate add and delete button definition --}} + + +{{!-- +!-- Render an input element for a rank value property of an item. +!-- +!-- @param item: the item +!-- @param property: the key of the property in item.data.data (if 'base', the max value is set automatically) +!-- @param disabled: if given, is placed plainly into the input as HTML property; +!-- meant to be set to "disabled" to disable the input element +--}} +{{#*inline "talentRankValue"}} + +{{/inline}} + + +{{!-- +!-- Render a talent list row from a given item. +!-- It is a flexbox with a child for each item value of interest. +!-- The partial assumes a variable item to be given in the context. +!-- +!-- @param item: hand over the item to the partial as hash parameter +!-- @param partial-block: hand over custom children of the flexbox in the partial block. +--}} +{{#*inline "talentListEntry"}} +
    1. + {{!-- image --}} +
      + +
      + {{!-- name --}} + +
      + {{!-- acquired rank --}} + {{> talentRankValue item=item property='base' localizeString='DS4.TalentRankBase'}} + ( of + {{!-- maximum acquirable rank --}} + {{> talentRankValue item=item property='max' localizeString='DS4.TalentRankMax'}} + ) + + {{!-- additional ranks --}} + {{> talentRankValue item=item property='mod' localizeString='DS4.TalentRankMod'}} + = + {{!-- derived total rank --}} + {{> talentRankValue item=item property='total' localizeString='DS4.TalentRankTotal' disabled='disabled'}} +
      + {{!-- description --}} +
      {{{item.data.data.description}}}
      + {{!-- control buttons --}} + {{> systems/ds4/templates/actor/partials/overview-control-buttons.hbs }} +
    2. +{{/inline}} + + +{{!-- +!-- Render a racial ability list row from a given item. +!-- It is a flexbox with a child for each item value of interest. +!-- The partial assumes a variable item to be given in the context. +!-- +!-- @param item: hand over the item to the partial as hash parameter +!-- @param partial-block: hand over custom children of the flexbox in the partial block. +--}} +{{#*inline "racialAbilityListEntry"}} +
    3. + {{!-- image --}} +
      + +
      + {{!-- name --}} + + {{!-- description --}} +
      {{{item.data.data.description}}}
      + {{!-- control buttons --}} + {{> systems/ds4/templates/actor/partials/overview-control-buttons.hbs }} +
    4. +{{/inline}} + +{{!-- ======================================================================== --}} + + +
      +

      {{localize 'DS4.ItemTypeTalentPlural'}}

      +
        +
      1. + {{!-- image --}} +
        + {{!-- name --}} +
        {{localize 'DS4.ItemName'}}
        + {{!-- rank info --}} +
        {{localize 'DS4.TalentRank'}}
        + {{!-- description --}} +
        {{localize 'DS4.HeadingDescription'}}
        + {{!-- add button --}} + {{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType='talent' }} +
      2. + {{#each itemsByType.talent as |item id|}} + {{> talentListEntry item=item}} + {{/each}} +
      + +

      {{localize 'DS4.ItemTypeRacialAbilityPlural'}}

      +
        +
      1. + {{!-- image --}} +
        + {{!-- name --}} +
        {{localize 'DS4.ItemName'}}
        + {{!-- description --}} +
        {{localize 'DS4.HeadingDescription'}}
        + {{!-- add button --}} + {{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType='racialAbility' }} +
      2. + {{#each itemsByType.racialAbility as |item id|}} + {{> racialAbilityListEntry item=item}} + {{/each}} +
      +
      \ No newline at end of file diff --git a/src/templates/item/armor-sheet.hbs b/src/templates/item/armor-sheet.hbs index a0a671dc..b7887131 100644 --- a/src/templates/item/armor-sheet.hbs +++ b/src/templates/item/armor-sheet.hbs @@ -6,8 +6,8 @@

      {{localize (lookup config.itemTypes item.type)}}

      - - {{#select data.armorType}} {{#each config.armorTypes as |value key|}} @@ -16,8 +16,8 @@
      - - {{#select data.armorMaterialType}} {{#each config.armorMaterialTypes as |value key|}} @@ -26,8 +26,8 @@
      - - {{localize "DS4.ArmorValue"}} +
      diff --git a/src/templates/item/partials/body.hbs b/src/templates/item/partials/body.hbs index 9b5cde20..1f465d9f 100644 --- a/src/templates/item/partials/body.hbs +++ b/src/templates/item/partials/body.hbs @@ -2,9 +2,11 @@ {{!-- Sheet Tab Navigation --}} {{!-- Sheet Body --}} @@ -13,10 +15,12 @@ {{!-- Description Tab --}} {{> systems/ds4/templates/item/partials/description.hbs}} - {{!-- Details Tab --}} - {{> systems/ds4/templates/item/partials/details.hbs}} - {{!-- Effects Tab --}} {{> systems/ds4/templates/item/partials/effects.hbs}} + {{#if isPhysical}} + {{!-- Details Tab --}} + {{> systems/ds4/templates/item/partials/details.hbs}} + {{/if}} + \ No newline at end of file diff --git a/src/templates/item/partials/description.hbs b/src/templates/item/partials/description.hbs index 1776f1f2..d7088a30 100644 --- a/src/templates/item/partials/description.hbs +++ b/src/templates/item/partials/description.hbs @@ -11,19 +11,21 @@ {{actor.name}}
-
- - -
-
- - -
+ {{#if isPhysical}} +
+ + +
+
+ + +
+ {{/if}} {{else}} - {{localize "DS4.NotOwned"}} + {{localize "DS4.NotOwned"}} {{/if}}
-
+
{{editor content=data.description target="data.description" button=true owner=owner editable=editable}}
\ No newline at end of file diff --git a/src/templates/item/partials/effects.hbs b/src/templates/item/partials/effects.hbs index 819290d6..b829bfac 100644 --- a/src/templates/item/partials/effects.hbs +++ b/src/templates/item/partials/effects.hbs @@ -5,16 +5,16 @@
Name
{{#each item.effects as |effect id|}}
  • {{effect.label}}

    - - + +
  • {{/each}} diff --git a/src/templates/item/racialAbility-sheet.hbs b/src/templates/item/racialAbility-sheet.hbs new file mode 100644 index 00000000..46dcf476 --- /dev/null +++ b/src/templates/item/racialAbility-sheet.hbs @@ -0,0 +1,13 @@ +
    +
    + +
    +

    +

    {{localize (lookup config.itemTypes item.type)}}

    +
    +
    + + {{!-- Common Item body --}} + {{> systems/ds4/templates/item/partials/body.hbs}} + +
    diff --git a/src/templates/item/shield-sheet.hbs b/src/templates/item/shield-sheet.hbs index 1e893d2a..ede89655 100644 --- a/src/templates/item/shield-sheet.hbs +++ b/src/templates/item/shield-sheet.hbs @@ -6,8 +6,8 @@

    {{localize (lookup config.itemTypes item.type)}}

    - - {{localize "DS4.ArmorValue"}} +
    diff --git a/src/templates/item/talent-sheet.hbs b/src/templates/item/talent-sheet.hbs new file mode 100644 index 00000000..a4be9b03 --- /dev/null +++ b/src/templates/item/talent-sheet.hbs @@ -0,0 +1,37 @@ +{{!-- ======================================================================== --}} +{{!-- INLINE PARTIAL DEFINITIONS --}} +{{!-- ======================================================================== --}} + + +{{#*inline "talentRankBasicProperty" }} +
    + + +
    +{{/inline}} + + +{{!-- ======================================================================== --}} + + +
    +
    + +
    +

    +

    {{localize (lookup config.itemTypes item.type)}}

    +
    + {{> talentRankBasicProperty data=data property='base' localizeString='DS4.TalentRankBase' }} + {{> talentRankBasicProperty data=data property='max' localizeString='DS4.TalentRankMax'}} + {{> talentRankBasicProperty data=data property='mod' localizeString='DS4.TalentRankMod'}} + {{> talentRankBasicProperty data=data property='total' localizeString='DS4.TalentRankTotal' disabled='disabled'}} +
    +
    +
    + + {{!-- Common Item body --}} + {{> systems/ds4/templates/item/partials/body.hbs}} + +
    diff --git a/src/templates/item/weapon-sheet.hbs b/src/templates/item/weapon-sheet.hbs index 7a7efcfd..24a5f2d7 100644 --- a/src/templates/item/weapon-sheet.hbs +++ b/src/templates/item/weapon-sheet.hbs @@ -6,8 +6,8 @@

    {{localize (lookup config.itemTypes item.type)}}

    - - {{#select data.attackType}} {{#each config.attackTypes as |value key|}} @@ -16,13 +16,13 @@
    - - {{localize "DS4.WeaponBonus"}} +
    - - {{localize "DS4.OpponentDefense"}} +