feat: initial commit
This commit is contained in:
commit
ab27f2e5a8
82 changed files with 34255 additions and 0 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
8
.eslintignore
Normal file
8
.eslintignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
/dist
|
||||
/.pnp.cjs
|
||||
/.pnp.loader.mjs
|
||||
/.yarn/
|
31
.eslintrc.cjs
Normal file
31
.eslintrc.cjs
Normal file
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
|
||||
extends: ['plugin:@typescript-eslint/recommended', 'plugin:jest/recommended', 'plugin:prettier/recommended'],
|
||||
|
||||
plugins: ['@typescript-eslint'],
|
||||
|
||||
rules: {},
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ['./*.cjs', './*.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
# SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vs/
|
||||
|
||||
# Node Modules
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
|
||||
# Local configuration
|
||||
foundryconfig.json
|
||||
|
||||
# Distribution files
|
||||
dist
|
||||
|
||||
# ESLint
|
||||
.eslintcache
|
||||
|
||||
# Junit results
|
||||
results.xml
|
||||
junit.xml
|
||||
|
||||
# yarn
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
176
.gitlab-ci.yml
Normal file
176
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,176 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
# SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
image: node:lts
|
||||
|
||||
variables:
|
||||
PACKAGE_NAME: tickwerk
|
||||
PACKAGE_TYPE: module
|
||||
PACKAGE_REGISTRY_URL: $CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$PACKAGE_NAME
|
||||
|
||||
stages:
|
||||
- check
|
||||
- build
|
||||
- prepare-release
|
||||
- release
|
||||
- publish
|
||||
|
||||
cache: &global_cache
|
||||
paths:
|
||||
- .yarn/cache
|
||||
|
||||
lint:
|
||||
stage: check
|
||||
before_script:
|
||||
- yarn install --immutable
|
||||
script:
|
||||
- yarn lint
|
||||
cache:
|
||||
<<: *global_cache
|
||||
|
||||
typecheck:
|
||||
stage: check
|
||||
before_script:
|
||||
- yarn install --immutable
|
||||
script:
|
||||
- yarn typecheck
|
||||
cache:
|
||||
<<: *global_cache
|
||||
|
||||
reuse:
|
||||
stage: check
|
||||
image:
|
||||
name: fsfe/reuse:latest
|
||||
entrypoint: ['']
|
||||
script:
|
||||
- reuse lint
|
||||
|
||||
build:
|
||||
stage: build
|
||||
before_script:
|
||||
- yarn install --immutable
|
||||
- if [[ ! -z ${CI_COMMIT_TAG+x} ]]; then export NODE_ENV=production; fi
|
||||
script:
|
||||
- yarn build
|
||||
cache:
|
||||
<<: *global_cache
|
||||
artifacts:
|
||||
paths:
|
||||
- dist
|
||||
expire_in: 1 week
|
||||
|
||||
publish-artifacts:
|
||||
stage: prepare-release
|
||||
image: alpine:latest
|
||||
before_script:
|
||||
- apk update
|
||||
- apk add zip curl
|
||||
script: |
|
||||
cd dist
|
||||
zip -r ../$PACKAGE_TYPE.zip .
|
||||
cd ..
|
||||
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file $PACKAGE_TYPE.zip "$PACKAGE_REGISTRY_URL/$CI_COMMIT_TAG/$PACKAGE_TYPE.zip"
|
||||
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file dist/$PACKAGE_TYPE.json "$PACKAGE_REGISTRY_URL/$CI_COMMIT_TAG/$PACKAGE_TYPE.json"
|
||||
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||
|
||||
changelog:
|
||||
stage: prepare-release
|
||||
before_script:
|
||||
- yarn install --immutable
|
||||
script:
|
||||
- yarn changelog
|
||||
cache:
|
||||
<<: *global_cache
|
||||
artifacts:
|
||||
paths:
|
||||
- CHANGELOG.md
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||
|
||||
.release-template: &release-template
|
||||
stage: release
|
||||
before_script:
|
||||
- yarn install
|
||||
- apt update
|
||||
- apt install --yes jq
|
||||
- REPOSITORY_URL=$(echo "${CI_REPOSITORY_URL}" | sed -e "s|gitlab-ci-token:.*@|${RELEASE_TOKEN}:${RELEASE_TOKEN_SECRET}@|g")
|
||||
- git remote set-url origin $REPOSITORY_URL
|
||||
- git config user.name $GITLAB_USER_LOGIN
|
||||
- git config user.email $GITLAB_USER_EMAIL
|
||||
- git branch -D ci-processing || true
|
||||
- git checkout -b ci-processing
|
||||
cache:
|
||||
<<: *global_cache
|
||||
script: |
|
||||
yarn bump-version --release=${RELEASE_TYPE}
|
||||
RELEASE_VERSION=$(jq -r '.version' < package.json)
|
||||
git add package.json module.json
|
||||
git --no-pager diff
|
||||
git commit -m "chore(release): ${RELEASE_VERSION}"
|
||||
git tag -f ${RELEASE_VERSION}
|
||||
git push origin ci-processing:${CI_BUILD_REF_NAME} -o ci.skip
|
||||
git push origin ${RELEASE_VERSION}
|
||||
only:
|
||||
- main
|
||||
when: manual
|
||||
|
||||
release-patch:
|
||||
variables:
|
||||
RELEASE_TYPE: patch
|
||||
<<: *release-template
|
||||
|
||||
release-minor:
|
||||
variables:
|
||||
RELEASE_TYPE: minor
|
||||
<<: *release-template
|
||||
|
||||
release-major:
|
||||
variables:
|
||||
RELEASE_TYPE: major
|
||||
<<: *release-template
|
||||
|
||||
release:
|
||||
stage: release
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
script:
|
||||
- echo 'release job'
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||
release:
|
||||
tag_name: $CI_COMMIT_TAG
|
||||
description: './CHANGELOG.md'
|
||||
assets:
|
||||
links:
|
||||
- name: '$PACKAGE_TYPE.zip'
|
||||
url: '$PACKAGE_REGISTRY_URL/$CI_COMMIT_TAG/$PACKAGE_TYPE.zip'
|
||||
filepath: /$PACKAGE_TYPE.zip
|
||||
link_type: package
|
||||
- name: '$PACKAGE_TYPE.json'
|
||||
url: '$PACKAGE_REGISTRY_URL/$CI_COMMIT_TAG/$PACKAGE_TYPE.json'
|
||||
filepath: /$PACKAGE_TYPE.json
|
||||
link_type: other
|
||||
|
||||
publish-latest-manifest:
|
||||
stage: publish
|
||||
image: alpine:latest
|
||||
before_script:
|
||||
- apk update
|
||||
- apk add zip curl
|
||||
script: |
|
||||
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file dist/$PACKAGE_TYPE.json "$PACKAGE_REGISTRY_URL/latest/$PACKAGE_TYPE.json"
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||
# publish-to-foundry-admin:
|
||||
# stage: publish
|
||||
# image: johannesloher/foundry-publish
|
||||
# variables:
|
||||
# FVTT_MANIFEST_PATH: dist/$PACKAGE_TYPE.json
|
||||
# FVTT_MANIFEST_URL: ${CI_PROJECT_URL}/-/releases/${CI_COMMIT_TAG}/downloads/$PACKAGE_TYPE.json
|
||||
# FVTT_DELETE_OBSOLETE_VERSIONS: "true"
|
||||
# script: foundry-publish
|
||||
# rules:
|
||||
# - if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
|
48
.gitlab/issue_templates/Bug Report.md
Normal file
48
.gitlab/issue_templates/Bug Report.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
Your issue may already have been reported! Please search on the issue tracker (https://git.f3l.de/dungeonslayers/tickwerk/-/issues) before submitting a new one.
|
||||
|
||||
Thanks for taking the time to fill out this bug report! In order to make it effective, please provide the following information.
|
||||
|
||||
# Issue Description
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
(What is the behavior that you expected?)
|
||||
|
||||
## Current Behavior
|
||||
|
||||
(What is the current behavior, i.e., what happens actually?)
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
(What are the steps to reproduce the problem?)
|
||||
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
## Context
|
||||
|
||||
(Please provide any additional context that might be helpful, e.g. log messages,
|
||||
screenshots, videos, or exports of problematic scenes or worlds.)
|
||||
|
||||
# Environment Details
|
||||
|
||||
## Version
|
||||
|
||||
(Which version(s) of Tickwerk are you seeing the problem on?)
|
||||
|
||||
## Foundry VTT Version
|
||||
|
||||
(Which version(s) and build of Foundry VTT are you seeing the problem on?)
|
||||
|
||||
## Operating System
|
||||
|
||||
(Which operating system are you using? (Windows, OS X, Linux (which distro)))
|
||||
|
||||
## Browser / App
|
||||
|
||||
(Are you using a Browser or the native Electron application?)
|
||||
|
||||
## Relevant Modules
|
||||
|
||||
(Please list any active modules (including their versions) that you think might be relevant.)
|
3
.gitlab/issue_templates/Bug Report.md.license
Normal file
3
.gitlab/issue_templates/Bug Report.md.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
11
.gitlab/issue_templates/Feature Request.md
Normal file
11
.gitlab/issue_templates/Feature Request.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
Your issue may already have been reported! Please search on the issue tracker (https://git.f3l.de/dungeonslayers/tickwerk/-/issues) before submitting a new one.
|
||||
|
||||
In order to submit an effective feature request, please provide the following information.
|
||||
|
||||
# Description
|
||||
|
||||
(Please describe the proposal in as much detail as you feel is necessary.)
|
||||
|
||||
# Context
|
||||
|
||||
(Is there anything else you can add about the proposal? You might want to link to related issues here if you haven't already.)
|
3
.gitlab/issue_templates/Feature Request.md.license
Normal file
3
.gitlab/issue_templates/Feature Request.md.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
_
|
3
.husky/.gitignore.license
Normal file
3
.husky/.gitignore.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
9
.husky/commit-msg
Executable file
9
.husky/commit-msg
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn run commitlint --edit "$1"
|
9
.husky/pre-commit
Executable file
9
.husky/pre-commit
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn run lint-staged
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
lts/*
|
3
.nvmrc.license
Normal file
3
.nvmrc.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
10
.prettierignore
Normal file
10
.prettierignore
Normal file
|
@ -0,0 +1,10 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
/dist
|
||||
/package-lock.json
|
||||
/.pnp.cjs
|
||||
/.pnp.loader.mjs
|
||||
/.yarn/
|
||||
/.vscode/
|
11
.prettierrc.cjs
Normal file
11
.prettierrc.cjs
Normal file
|
@ -0,0 +1,11 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'all',
|
||||
singleQuote: true,
|
||||
printWidth: 120,
|
||||
tabWidth: 2,
|
||||
};
|
8
.reuse/dep5
Normal file
8
.reuse/dep5
Normal file
|
@ -0,0 +1,8 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: tickwerk
|
||||
Upstream-Contact: Johannes Loher <johannes.loher@fg4f.de>
|
||||
Source: https://git.f3l.de/dungeonslayers/tickwerk
|
||||
|
||||
Files: .yarn/**
|
||||
Copyright: Copyright (c) 2016-present, Yarn Contributors. All rights reserved.
|
||||
License: BSD-2-Clause
|
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"arcanis.vscode-zipfs"
|
||||
]
|
||||
}
|
4
.vscode/extensions.json.license
vendored
Normal file
4
.vscode/extensions.json.license
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
|
||||
SPDX-License-Identifier: MIT
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "/usr/bin/chromium",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:30000/game",
|
||||
"webRoot": "${workspaceFolder}/dist"
|
||||
}
|
||||
]
|
||||
}
|
3
.vscode/launch.json.license
vendored
Normal file
3
.vscode/launch.json.license
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
16
.vscode/settings.json
vendored
Normal file
16
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"eslint.nodePath": ".yarn/sdks",
|
||||
"prettier.prettierPath": ".yarn/sdks/prettier/index.js",
|
||||
"typescript.tsdk": ".yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"importSorter.generalConfiguration.sortOnBeforeSave": true,
|
||||
"importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.type": "newLineEachExpressionAfterCountLimitExceptIfOnlyOne",
|
||||
"importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.count": 120,
|
||||
"importSorter.importStringConfiguration.tabSize": 2,
|
||||
"importSorter.importStringConfiguration.quoteMark": "single",
|
||||
"importSorter.importStringConfiguration.trailingComma": "multiLine"
|
||||
}
|
3
.vscode/settings.json.license
vendored
Normal file
3
.vscode/settings.json.license
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
77
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
Normal file
77
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
786
.yarn/releases/yarn-3.2.1.cjs
vendored
Executable file
786
.yarn/releases/yarn-3.2.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
20
.yarn/sdks/eslint/bin/eslint.js
vendored
Executable file
20
.yarn/sdks/eslint/bin/eslint.js
vendored
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require eslint/bin/eslint.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real eslint/bin/eslint.js your application uses
|
||||
module.exports = absRequire(`eslint/bin/eslint.js`);
|
20
.yarn/sdks/eslint/lib/api.js
vendored
Normal file
20
.yarn/sdks/eslint/lib/api.js
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require eslint
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real eslint your application uses
|
||||
module.exports = absRequire(`eslint`);
|
6
.yarn/sdks/eslint/package.json
vendored
Normal file
6
.yarn/sdks/eslint/package.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "eslint",
|
||||
"version": "8.15.0-sdk",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
}
|
5
.yarn/sdks/integrations.yml
vendored
Normal file
5
.yarn/sdks/integrations.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
# This file is automatically generated by @yarnpkg/sdks.
|
||||
# Manual changes might be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
20
.yarn/sdks/prettier/index.js
vendored
Executable file
20
.yarn/sdks/prettier/index.js
vendored
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/index.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real prettier/index.js your application uses
|
||||
module.exports = absRequire(`prettier/index.js`);
|
6
.yarn/sdks/prettier/package.json
vendored
Normal file
6
.yarn/sdks/prettier/package.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "prettier",
|
||||
"version": "2.6.2-sdk",
|
||||
"main": "./index.js",
|
||||
"type": "commonjs"
|
||||
}
|
20
.yarn/sdks/typescript/bin/tsc
vendored
Executable file
20
.yarn/sdks/typescript/bin/tsc
vendored
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/bin/tsc
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/bin/tsc your application uses
|
||||
module.exports = absRequire(`typescript/bin/tsc`);
|
20
.yarn/sdks/typescript/bin/tsserver
vendored
Executable file
20
.yarn/sdks/typescript/bin/tsserver
vendored
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/bin/tsserver
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/bin/tsserver your application uses
|
||||
module.exports = absRequire(`typescript/bin/tsserver`);
|
20
.yarn/sdks/typescript/lib/tsc.js
vendored
Normal file
20
.yarn/sdks/typescript/lib/tsc.js
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsc.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsc.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/tsc.js`);
|
196
.yarn/sdks/typescript/lib/tsserver.js
vendored
Normal file
196
.yarn/sdks/typescript/lib/tsserver.js
vendored
Normal file
|
@ -0,0 +1,196 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
if (!process.versions.pnp) {
|
||||
return tsserver;
|
||||
}
|
||||
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const isPortal = str => str.startsWith("portal:/");
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
|
||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||
// doesn't understand. This layer makes sure to remove the protocol
|
||||
// before forwarding it to TS, and to add it back on all returned paths.
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes it much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
// We only do this to modules owned by the the dependency tree roots.
|
||||
// This avoids breaking the resolution when jumping inside a vendor
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = normalize(str);
|
||||
|
||||
if (str.match(/\.zip\//)) {
|
||||
switch (hostInfo) {
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
// Update Oct 8 2021: VSCode changed their format in 1.61.
|
||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
case `vscode <1.61`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
// To make "go to definition" work,
|
||||
// We have to resolve the actual file system path from virtual path
|
||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||
case `coc-nvim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = resolve(`zipfile:${str}`);
|
||||
} break;
|
||||
|
||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||
// We have to resolve the actual file system path from virtual path,
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile://${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
str = `zip:${str}`;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
switch (hostInfo) {
|
||||
case `coc-nvim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||
// So in order to convert it back, we use .* to match all the thing
|
||||
// before `zipfile:`
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^.*zipfile:\//, ``)
|
||||
: str.replace(/^.*zipfile:/, ``);
|
||||
} break;
|
||||
|
||||
case `neovim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
|
||||
case `vscode`:
|
||||
default: {
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^\^?(zip:|\/zip)\/+/, ``)
|
||||
: str.replace(/^\^?(zip:|\/zip)\/+/, `/`);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
// TypeScript tries to resolve plugins using a path relative to itself
|
||||
// which doesn't work when using the global cache
|
||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||
// https://github.com/microsoft/vscode/issues/45856
|
||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||
this.projectService.allowLocalPluginLoads = true;
|
||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||
};
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string | object} */ message) {
|
||||
const isStringMessage = typeof message === 'string';
|
||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK && process.env.VSCODE_IPC_HOOK.match(/Code\/1\.([1-5][0-9]|60)\./)) {
|
||||
hostInfo += ` <1.61`;
|
||||
}
|
||||
}
|
||||
|
||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||
});
|
||||
|
||||
return originalOnMessage.call(
|
||||
this,
|
||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||
);
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||
return typeof value === `string` ? toEditorPath(value) : value;
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
return tsserver;
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsserver.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsserver.js your application uses
|
||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
|
196
.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
Normal file
196
.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
Normal file
|
@ -0,0 +1,196 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
if (!process.versions.pnp) {
|
||||
return tsserver;
|
||||
}
|
||||
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const isPortal = str => str.startsWith("portal:/");
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
|
||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||
// doesn't understand. This layer makes sure to remove the protocol
|
||||
// before forwarding it to TS, and to add it back on all returned paths.
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes it much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
// We only do this to modules owned by the the dependency tree roots.
|
||||
// This avoids breaking the resolution when jumping inside a vendor
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = normalize(str);
|
||||
|
||||
if (str.match(/\.zip\//)) {
|
||||
switch (hostInfo) {
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
// Update Oct 8 2021: VSCode changed their format in 1.61.
|
||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
case `vscode <1.61`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
// To make "go to definition" work,
|
||||
// We have to resolve the actual file system path from virtual path
|
||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||
case `coc-nvim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = resolve(`zipfile:${str}`);
|
||||
} break;
|
||||
|
||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||
// We have to resolve the actual file system path from virtual path,
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile://${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
str = `zip:${str}`;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
switch (hostInfo) {
|
||||
case `coc-nvim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||
// So in order to convert it back, we use .* to match all the thing
|
||||
// before `zipfile:`
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^.*zipfile:\//, ``)
|
||||
: str.replace(/^.*zipfile:/, ``);
|
||||
} break;
|
||||
|
||||
case `neovim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
|
||||
case `vscode`:
|
||||
default: {
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^\^?(zip:|\/zip)\/+/, ``)
|
||||
: str.replace(/^\^?(zip:|\/zip)\/+/, `/`);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
// TypeScript tries to resolve plugins using a path relative to itself
|
||||
// which doesn't work when using the global cache
|
||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||
// https://github.com/microsoft/vscode/issues/45856
|
||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||
this.projectService.allowLocalPluginLoads = true;
|
||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||
};
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string | object} */ message) {
|
||||
const isStringMessage = typeof message === 'string';
|
||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK && process.env.VSCODE_IPC_HOOK.match(/Code\/1\.([1-5][0-9]|60)\./)) {
|
||||
hostInfo += ` <1.61`;
|
||||
}
|
||||
}
|
||||
|
||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||
});
|
||||
|
||||
return originalOnMessage.call(
|
||||
this,
|
||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||
);
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||
return typeof value === `string` ? toEditorPath(value) : value;
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
return tsserver;
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
|
||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));
|
20
.yarn/sdks/typescript/lib/typescript.js
vendored
Normal file
20
.yarn/sdks/typescript/lib/typescript.js
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/typescript.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/typescript.js your application uses
|
||||
module.exports = absRequire(`typescript/lib/typescript.js`);
|
6
.yarn/sdks/typescript/package.json
vendored
Normal file
6
.yarn/sdks/typescript/package.json
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "typescript",
|
||||
"version": "4.6.4-sdk",
|
||||
"main": "./lib/typescript.js",
|
||||
"type": "commonjs"
|
||||
}
|
7
.yarnrc.yml
Normal file
7
.yarnrc.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
nodeLinker: pnp
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: '@yarnpkg/plugin-interactive-tools'
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
3
.yarnrc.yml.license
Normal file
3
.yarnrc.yml.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
19
LICENSE.md
Normal file
19
LICENSE.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
# Licensing
|
||||
|
||||
This project is being developed under the terms of the
|
||||
[LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT] for Foundry Virtual Tabletop.
|
||||
|
||||
The project itself is licensed under multiple licenses. [REUSE] is used to
|
||||
specify the licenses for the individual files. Most of the licenses are
|
||||
specified either inside the source file or by an accompanying `.license` file,
|
||||
but for some files, the licenses are specified in [.reuse/dep5].
|
||||
|
||||
[LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT]: https://foundryvtt.com/article/license/
|
||||
[REUSE]: https://reuse.software/
|
||||
[.reuse/dep5]: .reuse/dep5
|
9
LICENSES/BSD-2-Clause.txt
Normal file
9
LICENSES/BSD-2-Clause.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
Copyright (c) <year> <owner> All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
9
LICENSES/MIT.txt
Normal file
9
LICENSES/MIT.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
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.
|
110
README.md
Normal file
110
README.md
Normal file
|
@ -0,0 +1,110 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Siegfried Krug
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
# Tickwerk
|
||||
|
||||
A tick based combat system for [Foundry Virtual Tabletop].
|
||||
|
||||
This module adjusts the combat to use ticks instead of rounds for tracking time.
|
||||
It has special integration with the [Dungeonslayers 4] system, based on the
|
||||
fanwork “[Tickwerk]”, but is also be adaptable to other systems.
|
||||
|
||||
## Installation
|
||||
|
||||
To install and use the Tickwerk module for Foundry Virtual Tabletop,
|
||||
find it in the list in the **Install Module** dialog on the Setup menu of the
|
||||
application. Alternatively, paste the following Manifest URL in that dialog:
|
||||
|
||||
https://git.f3l.de/api/v4/projects/dungeonslayers%2Ftick-based-combat/packages/generic/tickwerk/latest/module.json
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
In order to build this module, recent versions of `node` and `yarn` are
|
||||
required. Most likely using `npm` also works but only `yarn` is officially
|
||||
supported. We recommend using the latest lts version of `node`. 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 project's dependencies. To do so, run
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
You can build the project by running
|
||||
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
Alternatively, you can run
|
||||
|
||||
```
|
||||
yarn watch
|
||||
```
|
||||
|
||||
to watch for changes and automatically build as necessary.
|
||||
|
||||
### Linking the built package to Foundry VTT
|
||||
|
||||
In order to provide a fluent development experience, it is recommended to link
|
||||
the built package 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": "<path to your home directory>/.local/share/FoundryVTT"
|
||||
}
|
||||
```
|
||||
|
||||
On platforms other than Linux you need to adjust the path accordingly.
|
||||
|
||||
Then run
|
||||
|
||||
```
|
||||
yarn link-package
|
||||
```
|
||||
|
||||
### Running the tests
|
||||
|
||||
You can run the tests with the following command:
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Code and content contributions are accepted. Please feel free to submit issues
|
||||
to the issue tracker or submit merge requests for code changes.
|
||||
|
||||
## Licensing
|
||||
|
||||
This project is being developed under the terms of the
|
||||
[LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT] for Foundry Virtual Tabletop.
|
||||
|
||||
The project itself is licensed under multiple licenses. [REUSE] is used to
|
||||
specify the licenses for the individual files. Most of the licenses are
|
||||
specified either inside the source file or by an accompanying `.license` file,
|
||||
but for some files, the licenses are specified in [.reuse/dep5].
|
||||
|
||||
[Foundry Virtual Tabletop]: http://foundryvtt.com/
|
||||
[Dungeonslayers 4]: https://git.f3l.de/dungeonslayers/ds4/
|
||||
[Tickwerk]: https://dungeonslayers.net/download/kalender2021/released/18_DS_tickwerk_ghost.pdf
|
||||
[LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT]: https://foundryvtt.com/article/license/
|
||||
[REUSE]: https://reuse.software/
|
||||
[.reuse/dep5]: .reuse/dep5
|
5
commitlint.config.cjs
Normal file
5
commitlint.config.cjs
Normal file
|
@ -0,0 +1,5 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
8
lang/de.json
Normal file
8
lang/de.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"TICKWERK.AdvanceTicks": "Auf der Tickleiste Vorrücken",
|
||||
"TICKWERK.StopWaiting": "Abwarten Beenden",
|
||||
"TICKWERK.Tick": "Tick",
|
||||
"TICKWERK.Wait": "Abwarten",
|
||||
"TICKWERK.Waiting": "Abwarten",
|
||||
"TICKWERK.WarningCannotStartCombat": "Der Kampf kann nur begonnen werden, wenn mindestens ein Kampfteilnehmer einen Tickwert hat."
|
||||
}
|
6
lang/de.json.license
Normal file
6
lang/de.json.license
Normal file
|
@ -0,0 +1,6 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
SPDX-FileCopyrightText: 2021 Siegfried Krug
|
||||
|
||||
SPDX-License-Identifier: MIT
|
8
lang/en.json
Normal file
8
lang/en.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"TICKWERK.AdvanceTicks": "Advance on the Tickbar",
|
||||
"TICKWERK.StopWaiting": "Stop Waiting",
|
||||
"TICKWERK.Tick": "Tick",
|
||||
"TICKWERK.Wait": "Wait",
|
||||
"TICKWERK.Waiting": "Waiting",
|
||||
"TICKWERK.WarningCannotStartCombat": "In order to start the combat, there needs to be at least one combatant with a tick value."
|
||||
}
|
6
lang/en.json.license
Normal file
6
lang/en.json.license
Normal file
|
@ -0,0 +1,6 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
SPDX-FileCopyrightText: 2021 Siegfried Krug
|
||||
|
||||
SPDX-License-Identifier: MIT
|
38
module.json
Normal file
38
module.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "tickwerk",
|
||||
"title": "Tickwerk",
|
||||
"description": "Tick based combat for Foundry Virtual Tabletop.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes Loher",
|
||||
"email": "johannes.loher@fg4f.de",
|
||||
"discord": "ghost#2000",
|
||||
"ko-fi": "ghostfvtt"
|
||||
}
|
||||
],
|
||||
"url": "https://git.f3l.de/dungeonslayers/tickwerk",
|
||||
"license": "",
|
||||
"readme": "",
|
||||
"bugs": "https://git.f3l.de/dungeonslayers/tickwerk/-/issues",
|
||||
"changelog": "",
|
||||
"version": "",
|
||||
"minimumCoreVersion": "9",
|
||||
"compatibleCoreVersion": "9",
|
||||
"esmodules": ["tickwerk.js"],
|
||||
"styles": ["styles/tickwerk.css"],
|
||||
"languages": [
|
||||
{
|
||||
"lang": "en",
|
||||
"name": "English",
|
||||
"path": "lang/en.json"
|
||||
},
|
||||
{
|
||||
"lang": "de",
|
||||
"name": "Deutsch",
|
||||
"path": "lang/de.json"
|
||||
}
|
||||
],
|
||||
"manifest": "https://git.f3l.de/api/v4/projects/dungeonslayers%2Ftick-based-combat/packages/generic/tickwerk/latest/module.json",
|
||||
"download": "",
|
||||
"manifestPlusVersion": "1.2.0"
|
||||
}
|
3
module.json.license
Normal file
3
module.json.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
22317
package-lock.json
generated
Normal file
22317
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
83
package.json
Normal file
83
package.json
Normal file
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "tickwerk",
|
||||
"description": "A tick based combat tracker for Foundry Virtual Tabletop",
|
||||
"version": "1.0.0",
|
||||
"license": "https://git.f3l.de/dungeonslayers/tickwerk#licensing",
|
||||
"homepage": "https://git.f3l.de/dungeonslayers/tickwerk",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.f3l.de/dungeonslayers/tickwerk"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://git.f3l.de/dungeonslayers/tickwerk/-/issues"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Johannes Loher",
|
||||
"email": "johannes.loher@fg4f.de"
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "run-s clean:files build:files",
|
||||
"build:files": "rollup -c",
|
||||
"watch": "rollup -c -w",
|
||||
"link-package": "node ./tools/link-package.js",
|
||||
"clean": "run-p clean:files clean:link",
|
||||
"clean:files": "rimraf dist",
|
||||
"clean:link": "node ./tools/link-package.js --clean",
|
||||
"lint": "eslint --ext .ts,.js,.cjs,.mjs .",
|
||||
"lint:fix": "eslint --ext .ts,.js,.cjs,.mjs --fix .",
|
||||
"format": "prettier --write \"./**/*.(ts|js|cjs|mjs|json|scss|yml)\"",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"typecheck:watch": "tsc --noEmit --watch",
|
||||
"bump-version": "node ./tools/bump-version.js",
|
||||
"postinstall": "husky install",
|
||||
"changelog": "conventional-changelog -p conventionalcommits -o CHANGELOG.md -r 2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "16.2.4",
|
||||
"@commitlint/config-conventional": "16.2.4",
|
||||
"@guanghechen/rollup-plugin-copy": "1.9.4",
|
||||
"@league-of-foundry-developers/foundry-vtt-types": "9.268.2",
|
||||
"@pixi/constants": "6.2.1",
|
||||
"@pixi/core": "6.2.1",
|
||||
"@pixi/display": "6.2.1",
|
||||
"@pixi/graphics": "6.2.1",
|
||||
"@pixi/math": "6.2.1",
|
||||
"@pixi/runner": "6.2.1",
|
||||
"@pixi/settings": "6.2.1",
|
||||
"@pixi/utils": "6.2.1",
|
||||
"@rollup/plugin-typescript": "8.3.2",
|
||||
"@seald-io/nedb": "3.0.0",
|
||||
"@swc/core": "1.2.183",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "5.23.0",
|
||||
"@typescript-eslint/parser": "5.23.0",
|
||||
"conventional-changelog-cli": "2.2.2",
|
||||
"conventional-changelog-conventionalcommits": "4.6.3",
|
||||
"eslint": "8.15.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"fs-extra": "10.1.0",
|
||||
"husky": "8.0.1",
|
||||
"lint-staged": "12.4.1",
|
||||
"npm-run-all": "4.1.5",
|
||||
"prettier": "2.6.2",
|
||||
"rimraf": "3.0.2",
|
||||
"rollup": "2.73.0",
|
||||
"rollup-plugin-styles": "4.0.0",
|
||||
"rollup-plugin-swc3": "0.3.0",
|
||||
"sass": "1.51.0",
|
||||
"semver": "7.3.7",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.6.4",
|
||||
"yargs": "17.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(ts|js|cjs|mjs)": "eslint --cache --fix",
|
||||
"*.(json|scss|yml)": "prettier --write"
|
||||
},
|
||||
"packageManager": "yarn@3.2.1"
|
||||
}
|
5
package.json.license
Normal file
5
package.json.license
Normal file
|
@ -0,0 +1,5 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver RÜmpelein
|
||||
SPDX-FileCopyrightText: 2021 Siegfried Krug
|
||||
|
||||
SPDX-License-Identifier: MIT
|
10
renovate.json
Normal file
10
renovate.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base", ":automergeAll", ":automergeBranch", ":prHourlyLimitNone", ":prConcurrentLimitNone"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackagePatterns": ["^@pixi"],
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
3
renovate.json.license
Normal file
3
renovate.json.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
62
rollup.config.js
Normal file
62
rollup.config.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import copy from '@guanghechen/rollup-plugin-copy';
|
||||
import styles from 'rollup-plugin-styles';
|
||||
import { swc } from 'rollup-plugin-swc3';
|
||||
|
||||
import { distDirectory, name, sourceDirectory } from './tools/const.js';
|
||||
|
||||
const staticFiles = [
|
||||
'.reuse',
|
||||
'lang',
|
||||
'LICENSE.md',
|
||||
'LICENSES',
|
||||
'README.md',
|
||||
'module.json.license',
|
||||
'module.json',
|
||||
'templates',
|
||||
];
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
/**
|
||||
* @type {import('rollup').RollupOptions}
|
||||
*/
|
||||
const config = {
|
||||
input: { [name]: `${sourceDirectory}/${name}.ts` },
|
||||
output: {
|
||||
dir: distDirectory,
|
||||
format: 'es',
|
||||
sourcemap: true,
|
||||
assetFileNames: '[name].[ext]',
|
||||
},
|
||||
plugins: [
|
||||
swc({
|
||||
minify: isProduction,
|
||||
jsc: {
|
||||
minify: isProduction && {
|
||||
sourceMap: true,
|
||||
mangle: {
|
||||
keepClassNames: true,
|
||||
keepFnNames: true,
|
||||
},
|
||||
},
|
||||
keepClassNames: true,
|
||||
},
|
||||
sourceMaps: true,
|
||||
}),
|
||||
styles({
|
||||
mode: ['extract', `styles/${name}.css`],
|
||||
url: false,
|
||||
sourceMap: true,
|
||||
minimize: isProduction,
|
||||
}),
|
||||
copy({
|
||||
targets: [{ src: staticFiles, dest: distDirectory }],
|
||||
verbose: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
39
src/apps/sidebar/combat-tracker.ts
Normal file
39
src/apps/sidebar/combat-tracker.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export const registerCombatTrackerFunctionality = () => {
|
||||
CONFIG.ui.combat = CombatTrackerMixin(CONFIG.ui.combat as typeof CombatTracker); // TODO: improve upstream types
|
||||
};
|
||||
|
||||
const CombatTrackerMixin = (BaseCombatTracker: typeof CombatTracker) => {
|
||||
return class TickwerkCombatTracker extends BaseCombatTracker {
|
||||
static override get defaultOptions(): ApplicationOptions {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
template: 'modules/tickwerk/templates/combat-tracker.hbs',
|
||||
});
|
||||
}
|
||||
|
||||
override async getData(options?: Partial<ApplicationOptions>): Promise<CombatTracker.Data> {
|
||||
const data = await super.getData(options);
|
||||
return {
|
||||
...data,
|
||||
turns: data.turns.map((turn) => ({ ...turn, waiting: this.viewed?.combatants.get(turn.id)?.waiting })),
|
||||
} as CombatTracker.Data; // TODO: Improve upstream types
|
||||
}
|
||||
override activateListeners(html: JQuery): void {
|
||||
super.activateListeners(html);
|
||||
html.find('.combatant-control[data-control="toggleWaiting"]').on('click', this._onToggleWaiting.bind(this));
|
||||
}
|
||||
|
||||
_onToggleWaiting(event: JQuery.ClickEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const button = event.currentTarget;
|
||||
const li = button.closest('.combatant');
|
||||
const combat = this.viewed;
|
||||
const combatant = combat?.combatants.get(li.dataset.combatantId);
|
||||
combatant?.toggleWaiting();
|
||||
}
|
||||
};
|
||||
};
|
5
src/constants.ts
Normal file
5
src/constants.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export const packageId = 'tickwerk' as const;
|
28
src/data/documents/active-effect.ts
Normal file
28
src/data/documents/active-effect.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from '../../helpers';
|
||||
|
||||
export const registerActiveEffectFunctionality = () => {
|
||||
Hooks.on<Hooks.CreateDocument<typeof ActiveEffect>>('createActiveEffect', onActiveEffectChanged);
|
||||
Hooks.on<Hooks.UpdateDocument<typeof ActiveEffect>>('updateActiveEffect', onActiveEffectChanged);
|
||||
Hooks.on<Hooks.DeleteDocument<typeof ActiveEffect>>('deleteActiveEffect', onActiveEffectChanged);
|
||||
};
|
||||
|
||||
const onActiveEffectChanged = (activeEffect: ActiveEffect) => {
|
||||
const game = getGame();
|
||||
const parent = activeEffect.parent;
|
||||
const actorId = parent?.id;
|
||||
if (!(parent instanceof Actor) || actorId === null || actorId === undefined) return;
|
||||
const statusId = activeEffect.getFlag('core', 'statusId');
|
||||
if (statusId === CONFIG.Combat.defeatedStatusId) {
|
||||
const relevantCombats = game.combats?.filter((combat) => combat.getCombatantByActor(actorId) !== undefined) ?? [];
|
||||
for (const combat of relevantCombats) {
|
||||
combat.setupTurns();
|
||||
if (combat === game.combat) {
|
||||
ui.combat?.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
139
src/data/documents/combat.ts
Normal file
139
src/data/documents/combat.ts
Normal file
|
@ -0,0 +1,139 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { packageId } from '../../constants';
|
||||
import { getGame } from '../../helpers';
|
||||
|
||||
import type { TickwerkCombatant } from './combatant';
|
||||
|
||||
export const registerCombatFunctionality = () => {
|
||||
CONFIG.Combat.documentClass = CombatMixin(CONFIG.Combat.documentClass);
|
||||
};
|
||||
|
||||
const CombatMixin = (BaseCombat: typeof Combat) => {
|
||||
return class TickwerkCombat extends BaseCombat {
|
||||
override get combatant() {
|
||||
return this.turns[0];
|
||||
}
|
||||
|
||||
override get round() {
|
||||
return this.tickValue;
|
||||
}
|
||||
|
||||
override get started() {
|
||||
return this.turns.length > 0 && (this.getFlag(packageId, 'started') ?? false);
|
||||
}
|
||||
|
||||
override get turn() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
override async nextRound(): Promise<never> {
|
||||
throw new Error('Not implemented!');
|
||||
}
|
||||
|
||||
get tickValue(): number {
|
||||
const tickValues = this.combatants
|
||||
.filter((combatant) => !combatant.isDefeated)
|
||||
.map((combatant) => combatant.initiative)
|
||||
.filter((tickValue): tickValue is number => tickValue !== null);
|
||||
const tickValue = Math.min(...tickValues);
|
||||
return tickValue === Infinity ? 0 : tickValue;
|
||||
}
|
||||
|
||||
override async nextTurn() {
|
||||
const game = getGame();
|
||||
|
||||
const combatant = this.combatant;
|
||||
if (combatant === undefined || combatant.initiative === null || combatant.id === null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const ticks = await Dialog.prompt({
|
||||
title: game.i18n.localize('TICKWERK.AdvanceTicks'),
|
||||
content: '<input name="ticks" type="number" value="5" min="0" />',
|
||||
label: game.i18n.localize('TICKWERK.AdvanceTicks'),
|
||||
callback: (html) => {
|
||||
const ticks = html[0]?.querySelector<HTMLInputElement>('input[name="ticks"]')?.value;
|
||||
return ticks !== undefined ? parseInt(ticks) : undefined;
|
||||
},
|
||||
rejectClose: false,
|
||||
});
|
||||
|
||||
if (ticks !== undefined && ticks !== null) {
|
||||
await combatant.update({ initiative: combatant.initiative + ticks });
|
||||
const advanceTime = ticks * CONFIG.time.roundTime;
|
||||
return this.update(undefined, { diff: false, advanceTime } as DocumentModificationContext); // TODO: improve upstream types to allow this without type assertion
|
||||
}
|
||||
}
|
||||
|
||||
override async resetAll() {
|
||||
for (const c of this.combatants) {
|
||||
c.data.update({ initiative: null });
|
||||
}
|
||||
return this.update(
|
||||
{ turn: 0, combatants: this.combatants.toObject(), flags: { [packageId]: { started: false } } },
|
||||
{ diff: false },
|
||||
);
|
||||
}
|
||||
|
||||
override setupTurns(): this['turns'] {
|
||||
const turns = this.combatants.contents.sort(this._sortCombatants);
|
||||
|
||||
const c = turns[0];
|
||||
this.current = {
|
||||
round: this.round,
|
||||
turn: 0,
|
||||
combatantId: c?.id ?? null,
|
||||
tokenId: c?.data.tokenId ?? null,
|
||||
};
|
||||
return (this.turns = turns);
|
||||
}
|
||||
|
||||
override async startCombat(): Promise<this | undefined> {
|
||||
const hasCombatantWithTickValue = this.combatants.find(
|
||||
(combatant) => !combatant.isDefeated && combatant.initiative !== null,
|
||||
);
|
||||
if (!hasCombatantWithTickValue) {
|
||||
ui.notifications?.warn('TICKWERK.WarningCannotStartCombat', { localize: true });
|
||||
return this;
|
||||
}
|
||||
return this.setFlag(packageId, 'started', true);
|
||||
}
|
||||
|
||||
protected override _sortCombatants(a: TickwerkCombatant, b: TickwerkCombatant): number {
|
||||
const da = a.isDefeated ? 1 : 0;
|
||||
const db = b.isDefeated ? 1 : 0;
|
||||
const cd = da - db;
|
||||
if (cd !== 0) return cd;
|
||||
|
||||
const wa = a.waiting ? 1 : 0;
|
||||
const wb = b.waiting ? 1 : 0;
|
||||
const cw = wa - wb;
|
||||
if (cw !== 0) return cw;
|
||||
|
||||
const ia = a.initiative ?? Infinity;
|
||||
const ib = b.initiative ?? Infinity;
|
||||
const ci = ia - ib;
|
||||
if (ci !== 0) return ci;
|
||||
|
||||
const tba = a.getFlag(packageId, 'tieBreaker') ?? 0;
|
||||
const tbb = b.getFlag(packageId, 'tieBreaker') ?? 0;
|
||||
const ctb = tba - tbb;
|
||||
if (ctb !== 0) return ctb;
|
||||
|
||||
return (b.id ?? '') > (a.id ?? '') ? 1 : -1;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface FlagConfig {
|
||||
Combat: {
|
||||
tickwerk?: {
|
||||
started?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
118
src/data/documents/combatant.ts
Normal file
118
src/data/documents/combatant.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import type { CombatantDataConstructorData } from '@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/combatantData';
|
||||
import { packageId } from '../../constants';
|
||||
|
||||
export const registerCombatantFunctionality = () => {
|
||||
CONFIG.Combatant.documentClass = CombatantMixin(CONFIG.Combatant.documentClass);
|
||||
};
|
||||
|
||||
const CombatantMixin = (BaseCombatant: typeof Combatant) => {
|
||||
return class TickwerkCombatant extends BaseCombatant {
|
||||
/**
|
||||
* An temporary property to make changes to the initiative available to other instances in their `_pre…` methods.
|
||||
*/
|
||||
_newInitiative: number | null | undefined;
|
||||
|
||||
/**
|
||||
* An temporary property to make changes to the tieBreaker available to other instances in their `_pre…` methods.
|
||||
*/
|
||||
_newTieBreaker: number | undefined;
|
||||
|
||||
/***
|
||||
* Is this combatant currently waiting?
|
||||
*/
|
||||
get waiting(): boolean {
|
||||
return this.getFlag(packageId, 'waiting') ?? false;
|
||||
}
|
||||
|
||||
toggleWaiting(): Promise<this | undefined> {
|
||||
return this.update({ [`flags.${packageId}.waiting`]: !this.waiting, initiative: this.parent?.round });
|
||||
}
|
||||
|
||||
protected override async _preCreate(...args: Parameters<Combatant['_preCreate']>): Promise<void> {
|
||||
await super._preCreate(...args);
|
||||
await this.#updateTieBreakerData(args[0]);
|
||||
}
|
||||
|
||||
protected override async _preUpdate(...args: Parameters<Combatant['_preUpdate']>): Promise<void> {
|
||||
await super._preUpdate(...args);
|
||||
await this.#updateTieBreakerData(args[0]);
|
||||
}
|
||||
|
||||
protected override _onCreate(): void {
|
||||
this._newInitiative = undefined;
|
||||
this._newTieBreaker = undefined;
|
||||
}
|
||||
|
||||
protected override _onUpdate(): void {
|
||||
this._newInitiative = undefined;
|
||||
this._newTieBreaker = undefined;
|
||||
}
|
||||
|
||||
async #updateTieBreakerData(data: DeepPartial<CombatantDataConstructorData>): Promise<void> {
|
||||
if ('initiative' in data) {
|
||||
const combatantsWithSameTickValue =
|
||||
this.parent?.combatants.filter((combatant) => {
|
||||
const otherInitiative =
|
||||
combatant._newInitiative !== undefined ? combatant._newInitiative : combatant.initiative;
|
||||
return otherInitiative === data.initiative;
|
||||
}) ?? [];
|
||||
const tieBreaker = await this.#getTieBreaker(combatantsWithSameTickValue);
|
||||
setProperty(data, `flags.${packageId}.tieBreaker`, tieBreaker);
|
||||
this._newInitiative = data.initiative;
|
||||
this._newTieBreaker = tieBreaker;
|
||||
}
|
||||
}
|
||||
|
||||
async #getTieBreaker(combatants: TickwerkCombatant[]): Promise<number> {
|
||||
const getTieBreaker = CONFIG.tickwerk?.getTieBreaker ?? defaultGetTieBreaker;
|
||||
return getTieBreaker(this, combatants);
|
||||
}
|
||||
|
||||
protected override _getInitiativeFormula(): string {
|
||||
const getInitiativeFormula = CONFIG.tickwerk?.getInitiativeFormula;
|
||||
if (getInitiativeFormula) return getInitiativeFormula(this);
|
||||
return super._getInitiativeFormula();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const defaultGetTieBreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise<number> => {
|
||||
if (combatants.length === 0) return 0;
|
||||
const tieBreakers = combatants.map((combatant) => {
|
||||
return (
|
||||
(combatant._newTieBreaker !== undefined
|
||||
? combatant._newTieBreaker
|
||||
: combatant.getFlag(packageId, 'tieBreaker')) ?? 0
|
||||
);
|
||||
});
|
||||
return Math.max(...tieBreakers) + 1;
|
||||
};
|
||||
|
||||
export type TickwerkCombatantConstructor = ReturnType<typeof CombatantMixin>;
|
||||
export type TickwerkCombatant = InstanceType<TickwerkCombatantConstructor>;
|
||||
|
||||
declare global {
|
||||
interface FlagConfig {
|
||||
Combatant: {
|
||||
tickwerk: {
|
||||
tieBreaker?: number | undefined;
|
||||
waiting?: boolean | undefined;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface DocumentClassConfig {
|
||||
Combatant: TickwerkCombatantConstructor;
|
||||
}
|
||||
|
||||
interface CONFIG {
|
||||
tickwerk?: {
|
||||
getTieBreaker?: (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]) => Promise<number>;
|
||||
getInitiativeFormula?: (combatant: TickwerkCombatant) => string;
|
||||
};
|
||||
}
|
||||
}
|
10
src/helpers.ts
Normal file
10
src/helpers.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export const getGame = (): Game => {
|
||||
if (!(game instanceof Game)) {
|
||||
throw new Error('game is not initialized yet.');
|
||||
}
|
||||
return game;
|
||||
};
|
26
src/logger.ts
Normal file
26
src/logger.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { packageId } from './constants';
|
||||
|
||||
const loggingContext = packageId;
|
||||
const loggingSeparator = '|';
|
||||
|
||||
type LogLevel = 'debug' | 'info' | 'warning' | 'error';
|
||||
type LoggingFunction = (...data: unknown[]) => void;
|
||||
|
||||
const getLoggingFunction = (type: LogLevel = 'info'): LoggingFunction => {
|
||||
const log = { debug: console.debug, info: console.info, warning: console.warn, error: console.error }[type];
|
||||
return (...data: unknown[]) => log(loggingContext, loggingSeparator, ...data);
|
||||
};
|
||||
|
||||
const logger = Object.freeze({
|
||||
debug: getLoggingFunction('debug'),
|
||||
info: getLoggingFunction('info'),
|
||||
warn: getLoggingFunction('warning'),
|
||||
error: getLoggingFunction('error'),
|
||||
getLoggingFunction,
|
||||
});
|
||||
|
||||
export default logger;
|
27
src/systems/ds4.ts
Normal file
27
src/systems/ds4.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { packageId } from '../constants';
|
||||
|
||||
import type { TickwerkCombatant } from '../data/documents/combatant';
|
||||
|
||||
export const registerDS4SpecificFunctionality = () => {
|
||||
if (CONFIG.tickwerk === undefined) CONFIG.tickwerk = {};
|
||||
foundry.utils.mergeObject(CONFIG.tickwerk, { getTieBreaker, getInitiativeFormula });
|
||||
};
|
||||
|
||||
const getTieBreaker = async (combatant: TickwerkCombatant, combatants: TickwerkCombatant[]): Promise<number> => {
|
||||
if (combatants.length === 0) return 0;
|
||||
const tieBreakers = combatants.map((combatant) => {
|
||||
return (
|
||||
(combatant._newTieBreaker !== undefined
|
||||
? combatant._newTieBreaker
|
||||
: combatant.getFlag(packageId, 'tieBreaker')) ?? 0
|
||||
);
|
||||
});
|
||||
return Math.max(...tieBreakers) + 1;
|
||||
};
|
||||
|
||||
const getInitiativeFormula = (combatant: TickwerkCombatant) => {
|
||||
const started = combatant.combat?.started ?? false;
|
||||
if (!started) return '-@combatValues.initiative.total';
|
||||
const tickValue = combatant.combat?.round ?? 0;
|
||||
return `max(${tickValue} + 10 - @combatValues.initiative.total, ${tickValue})`;
|
||||
};
|
9
src/systems/index.ts
Normal file
9
src/systems/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { getGame } from '../helpers';
|
||||
import { registerDS4SpecificFunctionality } from './ds4';
|
||||
|
||||
export const registerSystemSpecificFunctionality = () => {
|
||||
switch (getGame().system.id) {
|
||||
case 'ds4':
|
||||
registerDS4SpecificFunctionality();
|
||||
}
|
||||
};
|
19
src/tickwerk.ts
Normal file
19
src/tickwerk.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import '../styles/tickwerk.scss';
|
||||
|
||||
import { registerCombatTrackerFunctionality } from './apps/sidebar/combat-tracker';
|
||||
import { registerActiveEffectFunctionality } from './data/documents/active-effect';
|
||||
import { registerCombatFunctionality } from './data/documents/combat';
|
||||
import { registerCombatantFunctionality } from './data/documents/combatant';
|
||||
import { registerSystemSpecificFunctionality } from './systems';
|
||||
|
||||
Hooks.once('init', () => {
|
||||
registerActiveEffectFunctionality();
|
||||
registerCombatantFunctionality();
|
||||
registerCombatFunctionality();
|
||||
registerCombatTrackerFunctionality();
|
||||
registerSystemSpecificFunctionality();
|
||||
});
|
20
src/utils.ts
Normal file
20
src/utils.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
const defaultMessage =
|
||||
'There has been an unexpected error in the Tickwerk module. For more details, please take a look at the console (F12).';
|
||||
|
||||
/**
|
||||
* Tests if the given `value` is truthy.
|
||||
*
|
||||
* If it is not truthy, an {@link Error} is thrown, which depends on the given `message` parameter:
|
||||
* - If `message` is a string`, it is used to construct a new {@link Error} which then is thrown.
|
||||
* - If `message` is an instance of {@link Error}, it is thrown.
|
||||
* - If `message` is `undefined`, an {@link Error} with a default message is thrown.
|
||||
*/
|
||||
export const enforce = (value: unknown, message: string | Error = defaultMessage): asserts value => {
|
||||
if (!value) {
|
||||
throw message instanceof Error ? message : new Error(message);
|
||||
}
|
||||
};
|
9
styles/tickwerk.scss
Normal file
9
styles/tickwerk.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#combat li.combatant .tickwerk-token-initiative {
|
||||
flex: 0 0 60px;
|
||||
}
|
225
template.json
Normal file
225
template.json
Normal file
|
@ -0,0 +1,225 @@
|
|||
{
|
||||
"Actor": {
|
||||
"types": ["character", "creature"],
|
||||
"templates": {
|
||||
"base": {
|
||||
"attributes": {
|
||||
"body": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
},
|
||||
"mobility": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
},
|
||||
"mind": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
}
|
||||
},
|
||||
"traits": {
|
||||
"strength": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
},
|
||||
"constitution": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
},
|
||||
"agility": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
},
|
||||
"dexterity": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
},
|
||||
"intellect": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
},
|
||||
"aura": {
|
||||
"base": 0,
|
||||
"mod": 0
|
||||
}
|
||||
},
|
||||
"combatValues": {
|
||||
"hitPoints": {
|
||||
"mod": 0,
|
||||
"value": 0
|
||||
},
|
||||
"defense": {
|
||||
"mod": 0
|
||||
},
|
||||
"initiative": {
|
||||
"mod": 0
|
||||
},
|
||||
"movement": {
|
||||
"mod": 0
|
||||
},
|
||||
"meleeAttack": {
|
||||
"mod": 0
|
||||
},
|
||||
"rangedAttack": {
|
||||
"mod": 0
|
||||
},
|
||||
"spellcasting": {
|
||||
"mod": 0
|
||||
},
|
||||
"targetedSpellcasting": {
|
||||
"mod": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"creature": {
|
||||
"templates": ["base"],
|
||||
"baseInfo": {
|
||||
"loot": "",
|
||||
"foeFactor": 1,
|
||||
"creatureType": "humanoid",
|
||||
"sizeCategory": "normal",
|
||||
"experiencePoints": 0,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"character": {
|
||||
"templates": ["base"],
|
||||
"baseInfo": {
|
||||
"race": "",
|
||||
"class": "",
|
||||
"heroClass": "",
|
||||
"culture": ""
|
||||
},
|
||||
"progression": {
|
||||
"level": 0,
|
||||
"experiencePoints": 0,
|
||||
"talentPoints": {
|
||||
"total": 0,
|
||||
"used": 0
|
||||
},
|
||||
"progressPoints": {
|
||||
"total": 0,
|
||||
"used": 0
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"biography": "",
|
||||
"gender": "",
|
||||
"birthday": "",
|
||||
"birthplace": "",
|
||||
"age": 0,
|
||||
"height": 0,
|
||||
"hairColor": "",
|
||||
"weight": 0,
|
||||
"eyeColor": "",
|
||||
"specialCharacteristics": ""
|
||||
},
|
||||
"currency": {
|
||||
"gold": 0,
|
||||
"silver": 0,
|
||||
"copper": 0
|
||||
},
|
||||
"slayerPoints": {
|
||||
"value": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
"types": [
|
||||
"weapon",
|
||||
"armor",
|
||||
"shield",
|
||||
"spell",
|
||||
"equipment",
|
||||
"loot",
|
||||
"talent",
|
||||
"racialAbility",
|
||||
"language",
|
||||
"alphabet",
|
||||
"specialCreatureAbility"
|
||||
],
|
||||
"templates": {
|
||||
"base": {
|
||||
"description": ""
|
||||
},
|
||||
"physical": {
|
||||
"quantity": 1,
|
||||
"price": 0,
|
||||
"availability": "unset",
|
||||
"storageLocation": "-"
|
||||
},
|
||||
"equipable": {
|
||||
"equipped": false
|
||||
},
|
||||
"protective": {
|
||||
"armorValue": 0
|
||||
}
|
||||
},
|
||||
"weapon": {
|
||||
"templates": ["base", "physical", "equipable"],
|
||||
"attackType": "melee",
|
||||
"weaponBonus": 0,
|
||||
"opponentDefense": 0
|
||||
},
|
||||
"armor": {
|
||||
"templates": ["base", "physical", "equipable", "protective"],
|
||||
"armorMaterialType": "cloth",
|
||||
"armorType": "body"
|
||||
},
|
||||
"shield": {
|
||||
"templates": ["base", "physical", "equipable", "protective"]
|
||||
},
|
||||
"spell": {
|
||||
"templates": ["base", "equipable"],
|
||||
"spellType": "spellcasting",
|
||||
"bonus": "",
|
||||
"spellCategory": "unset",
|
||||
"maxDistance": {
|
||||
"value": "",
|
||||
"unit": "meter"
|
||||
},
|
||||
"effectRadius": {
|
||||
"value": "",
|
||||
"unit": "meter"
|
||||
},
|
||||
"duration": {
|
||||
"value": "",
|
||||
"unit": "custom"
|
||||
},
|
||||
"cooldownDuration": "0r",
|
||||
"minimumLevels": {
|
||||
"healer": null,
|
||||
"wizard": null,
|
||||
"sorcerer": null
|
||||
}
|
||||
},
|
||||
"equipment": {
|
||||
"templates": ["base", "physical", "equipable"]
|
||||
},
|
||||
"loot": {
|
||||
"templates": ["base", "physical"]
|
||||
},
|
||||
"talent": {
|
||||
"templates": ["base"],
|
||||
"rank": {
|
||||
"base": 0,
|
||||
"max": 0,
|
||||
"mod": 0
|
||||
}
|
||||
},
|
||||
"racialAbility": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"language": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"alphabet": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"specialCreatureAbility": {
|
||||
"templates": ["base"],
|
||||
"experiencePoints": 0
|
||||
}
|
||||
}
|
||||
}
|
6
template.json.license
Normal file
6
template.json.license
Normal file
|
@ -0,0 +1,6 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
SPDX-FileCopyrightText: 2021 Siegfried Krug
|
||||
|
||||
SPDX-License-Identifier: MIT
|
118
templates/combat-tracker.hbs
Normal file
118
templates/combat-tracker.hbs
Normal file
|
@ -0,0 +1,118 @@
|
|||
<section class="tab sidebar-tab directory flexcol" id="combat" data-tab="combat">
|
||||
<header id="combat-round">
|
||||
{{#if user.isGM}}
|
||||
<nav class="encounters flexrow">
|
||||
<a class="combat-create" title="{{localize 'COMBAT.Create'}}">
|
||||
<i class="fas fa-plus"></i>
|
||||
</a>
|
||||
{{#if combatCount}}
|
||||
<a class="combat-cycle" title="{{localize 'COMBAT.EncounterPrevious'}}"
|
||||
{{#if previousId}}data-combat-id="{{previousId}}"{{else}}disabled{{/if}}>
|
||||
<i class="fas fa-caret-left"></i>
|
||||
</a>
|
||||
<h4 class="encounter">{{localize "COMBAT.Encounter"}} {{currentIndex}} / {{combatCount}}</h4>
|
||||
<a class="combat-cycle" title="{{localize 'COMBAT.EncounterNext'}}"
|
||||
{{#if nextId}}data-combat-id="{{nextId}}"{{else}}disabled{{/if}}>
|
||||
<i class="fas fa-caret-right"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
<a class="combat-control" title="{{localize 'COMBAT.Delete'}}" data-control="endCombat" {{#unless combatCount}}disabled{{/unless}}>
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</nav>
|
||||
{{/if}}
|
||||
|
||||
<nav class="encounters flexrow {{#if hasCombat}}combat{{/if}}">
|
||||
{{#if user.isGM}}
|
||||
<a class="combat-control" title="{{localize 'COMBAT.RollAll'}}" data-control="rollAll" {{#unless turns}}disabled{{/unless}}>
|
||||
<i class="fas fa-users"></i>
|
||||
</a>
|
||||
<a class="combat-control" title="{{localize 'COMBAT.RollNPC'}}" data-control="rollNPC" {{#unless turns}}disabled{{/unless}}>
|
||||
<i class="fas fa-users-cog"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if combatCount}}
|
||||
{{#if combat.started}}
|
||||
<h3 class="encounter-title">{{localize 'TICKWERK.Tick'}} {{combat.tickValue}}</h3>
|
||||
{{else}}
|
||||
<h3 class="encounter-title">{{localize 'COMBAT.NotStarted'}}</h3>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<h3 class="encounter-title">{{localize "COMBAT.None"}}</h3>
|
||||
{{/if}}
|
||||
|
||||
{{#if user.isGM}}
|
||||
<a class="combat-control" title="{{localize 'COMBAT.InitiativeReset'}}" data-control="resetAll"
|
||||
{{#unless hasCombat}}disabled{{/unless}}>
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
<a class="combat-control" title="{{labels.scope}}"
|
||||
data-control="toggleSceneLink" {{#unless hasCombat}}disabled{{/unless}}>
|
||||
<i class="fas fa-{{#unless linked}}un{{/unless}}link"></i>
|
||||
</a>
|
||||
<a class="combat-settings" title="{{localize 'COMBAT.Settings'}}" data-control="trackerSettings">
|
||||
<i class="fas fa-cog"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<ol id="combat-tracker" class="directory-list">
|
||||
{{#each turns}}
|
||||
<li class="combatant actor directory-item flexrow {{this.css}}" data-combatant-id="{{this.id}}">
|
||||
<img class="token-image" data-src="{{this.img}}" title="{{this.name}}"/>
|
||||
<div class="token-name flexcol">
|
||||
<h4>{{this.name}}</h4>
|
||||
<div class="combatant-controls flexrow">
|
||||
{{#if (or ../user.isGM (and ../control this.owner))}}
|
||||
<a class="combatant-control" title="{{#if this.waiting}}{{localize 'TICKWERK.StopWaiting'}}{{else}}{{localize 'TICKWERK.Wait'}}{{/if}}" data-control="toggleWaiting">
|
||||
<i class="fas {{#if this.waiting}}fa-play-circle{{else}}fa-pause-circle{{/if}}"></i></a>
|
||||
{{/if}}
|
||||
{{#if ../user.isGM}}
|
||||
<a class="combatant-control {{#if this.hidden}}active{{/if}}" title="{{localize 'COMBAT.ToggleVis'}}" data-control="toggleHidden">
|
||||
<i class="fas fa-eye-slash"></i></a>
|
||||
<a class="combatant-control {{#if this.defeated}}active{{/if}}" title="{{localize 'COMBAT.ToggleDead'}}" data-control="toggleDefeated">
|
||||
<i class="fas fa-skull"></i></a>
|
||||
{{/if}}
|
||||
<div class="token-effects">
|
||||
{{#each this.effects}}
|
||||
<img class="token-effect" src="{{this}}"/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.hasResource}}
|
||||
<div class="token-resource">
|
||||
<span class="resource">{{this.resource}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="token-initiative tickwerk-token-initiative">
|
||||
{{#if this.hasRolled}}
|
||||
<span class="initiative">{{#if this.waiting}}{{localize "TICKWERK.Waiting"}}{{else}}{{this.initiative}}{{/if}}</span>
|
||||
{{else if this.owner}}
|
||||
<a class="combatant-control roll" title="{{localize 'COMBAT.InitiativeRoll'}}" data-control="rollInitiative"></a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
|
||||
<nav id="combat-controls" class="directory-footer flexrow">
|
||||
{{#if hasCombat}}
|
||||
{{#if user.isGM}}
|
||||
{{#if combat.started}}
|
||||
<a class="combat-control center" title="{{localize 'COMBAT.End'}}" data-control="endCombat">{{localize 'COMBAT.End'}}</a>
|
||||
<a class="combat-control" title="{{localize 'COMBAT.TurnNext'}}" data-control="nextTurn"><i class="fas fa-arrow-right"></i></a>
|
||||
{{else}}
|
||||
<a class="combat-control center" title="{{localize 'COMBAT.Begin'}}" data-control="startCombat">{{localize 'COMBAT.Begin'}}</a>
|
||||
{{/if}}
|
||||
{{else if (and control combat.started)}}
|
||||
<a class="combat-control center" title="{{localize 'COMBAT.TurnEnd'}}" data-control="nextTurn">{{localize 'COMBAT.TurnEnd'}}</a>
|
||||
<a class="combat-control" title="{{localize 'COMBAT.TurnNext'}}" data-control="nextTurn"><i class="fas fa-arrow-right"></i></a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</nav>
|
||||
</section>
|
85
tools/bump-version.js
Normal file
85
tools/bump-version.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
const repository = 'dungeonslayers/tickwerk';
|
||||
const gitlabURL = 'https://git.f3l.de';
|
||||
|
||||
const getLicenseURL = (version) => `${gitlabURL}/${repository}/-/raw/${version}/LICENSE.md`;
|
||||
const getReadmeURL = (version) => `${gitlabURL}/${repository}/-/raw/${version}/README.md`;
|
||||
const getChangelogURL = (version) => `${gitlabURL}/${repository}/-/releases/${version}`;
|
||||
const getDownloadURL = (version) => `${gitlabURL}/${repository}/-/releases/${version}/downloads/module.zip`;
|
||||
|
||||
const manifestPath = './module.json';
|
||||
|
||||
/**
|
||||
* Get the contents of the manifest file as object.
|
||||
* @returns {unknown} An object describing the manifest
|
||||
*/
|
||||
function getManifest() {
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
return fs.readJSONSync(manifestPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target version based on on the current version and the argument passed as release.
|
||||
* @param {string} currentVersion The current version
|
||||
* @param {semver.ReleaseType | string} release Either a semver release type or a valid semver version
|
||||
* @returns {string | null} The target version
|
||||
*/
|
||||
function getTargetVersion(currentVersion, release) {
|
||||
if (['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'].includes(release)) {
|
||||
return semver.inc(currentVersion, release);
|
||||
} else {
|
||||
return semver.valid(release);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update version and download URL.
|
||||
* @param {semver.ReleaseType | string} release Either a semver release type or a valid semver version
|
||||
*/
|
||||
function bumpVersion(release) {
|
||||
if (!release) {
|
||||
throw new Error('Missing release type');
|
||||
}
|
||||
|
||||
const packageJson = fs.readJSONSync('package.json');
|
||||
const manifest = getManifest();
|
||||
if (!manifest) throw new Error('Manifest JSON not found');
|
||||
|
||||
const currentVersion = packageJson.version;
|
||||
const targetVersion = getTargetVersion(currentVersion, release);
|
||||
|
||||
if (!targetVersion) {
|
||||
throw new Error('Incorrect version arguments');
|
||||
}
|
||||
if (targetVersion === currentVersion) {
|
||||
throw new Error('Target version is identical to current version');
|
||||
}
|
||||
|
||||
console.log(`Bumping version number to '${targetVersion}'`);
|
||||
packageJson.version = targetVersion;
|
||||
fs.writeJSONSync('package.json', packageJson, { spaces: 4 });
|
||||
manifest.license = getLicenseURL(targetVersion);
|
||||
manifest.readme = getReadmeURL(targetVersion);
|
||||
manifest.changelog = getChangelogURL(targetVersion);
|
||||
manifest.version = targetVersion;
|
||||
manifest.download = getDownloadURL(targetVersion);
|
||||
fs.writeJSONSync(manifestPath, manifest, { spaces: 4 });
|
||||
}
|
||||
|
||||
const argv = yargs(hideBin(process.argv)).usage('Usage: $0').option('release', {
|
||||
alias: 'r',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
description: 'Either a semver release type or a valid semver version',
|
||||
}).argv;
|
||||
const release = argv.r;
|
||||
bumpVersion(release);
|
9
tools/const.js
Normal file
9
tools/const.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export const name = 'tickwerk';
|
||||
export const sourceDirectory = './src';
|
||||
export const distDirectory = './dist';
|
||||
export const destinationDirectory = 'modules';
|
||||
export const foundryconfigFile = './foundryconfig.json';
|
55
tools/link-package.js
Normal file
55
tools/link-package.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import path from 'node:path';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
import { destinationDirectory, distDirectory, foundryconfigFile, name } from './const.js';
|
||||
|
||||
/**
|
||||
* Get the data path of Foundry VTT based on what is configured in the {@link foundryconfigFile}.
|
||||
*/
|
||||
function getDataPath() {
|
||||
const config = fs.readJSONSync(foundryconfigFile);
|
||||
|
||||
if (config?.dataPath) {
|
||||
if (!fs.existsSync(path.resolve(config.dataPath))) {
|
||||
throw new Error('User data path invalid, no Data directory found');
|
||||
}
|
||||
return path.resolve(config.dataPath);
|
||||
} else {
|
||||
throw new Error(`No user data path defined in ${foundryconfigFile}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Link the built package to the user data folder.
|
||||
* @param {boolean} clean Whether to remove the link instead of creating it
|
||||
*/
|
||||
async function linkPackage(clean) {
|
||||
if (!fs.existsSync(path.resolve('module.json'))) {
|
||||
throw new Error('Could not find module.json');
|
||||
}
|
||||
|
||||
const linkDirectory = path.resolve(getDataPath(), 'Data', destinationDirectory, name);
|
||||
|
||||
if (clean) {
|
||||
console.log(`Removing link to built package at ${linkDirectory}.`);
|
||||
await fs.remove(linkDirectory);
|
||||
} else if (!fs.existsSync(linkDirectory)) {
|
||||
console.log(`Linking built package to ${linkDirectory}.`);
|
||||
await fs.ensureDir(path.resolve(linkDirectory, '..'));
|
||||
await fs.symlink(path.resolve('.', distDirectory), linkDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
const argv = yargs(hideBin(process.argv)).usage('Usage: $0').option('clean', {
|
||||
alias: 'c',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Remove the link instead of creating it',
|
||||
}).argv;
|
||||
const clean = argv.c;
|
||||
await linkPackage(clean);
|
4
tsconfig.eslint.json
Normal file
4
tsconfig.eslint.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src", "*.js"]
|
||||
}
|
3
tsconfig.eslint.json.license
Normal file
3
tsconfig.eslint.json.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"lib": ["ES2021", "DOM"],
|
||||
"types": ["@league-of-foundry-developers/foundry-vtt-types"],
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"resolveJsonModule": true,
|
||||
"importsNotUsedAsValues": "error"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
4
tsconfig.json.license
Normal file
4
tsconfig.json.license
Normal file
|
@ -0,0 +1,4 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
|
||||
SPDX-License-Identifier: MIT
|
3
yarn.lock.license
Normal file
3
yarn.lock.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
Loading…
Reference in a new issue