Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29b6a98c89 | |||
| b1d73e6b08 | |||
| 509a5efd9d | |||
| fea7c1c271 | |||
| 412573fc4d | |||
| 47e2ebe45b | |||
| 90de66d668 | |||
| dcc24b47ec | |||
| c23de0ea66 | |||
| c571e6a209 | |||
| 2691ff521f | |||
| 2143bcc8db | |||
| 891769816a | |||
| 9b77a0c552 | |||
| 6cec1da910 | |||
| 1b66c24258 | |||
| dfc9b823a4 | |||
| 02aabbea36 | |||
| 6fbc7e7864 | |||
| 308073877b | |||
| 81adfb7ffd | |||
| b85efd663b | |||
| 3db03f5159 | |||
| 30d6f71fc7 | |||
| f9f07cbc7e | |||
| 386d80639c | |||
| 716c1b49ae | |||
| c35e93975b | |||
| 5889a8111d | |||
| d380efeed5 | |||
| 62f13c8cc3 | |||
| 7a1ab85d23 | |||
| 45c5396ff4 | |||
| dc621f2223 | |||
| 1327f92f41 |
@@ -0,0 +1,358 @@
|
|||||||
|
/**
|
||||||
|
* ESLint configuration for Vermine2047 FoundryVTT system
|
||||||
|
* Compatible with FoundryVTT v11-v14
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2022: true,
|
||||||
|
node: false
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
impliedStrict: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
// FoundryVTT global objects
|
||||||
|
game: 'readonly',
|
||||||
|
ui: 'readonly',
|
||||||
|
Hooks: 'readonly',
|
||||||
|
CONFIG: 'readonly',
|
||||||
|
Canvas: 'readonly',
|
||||||
|
ChatMessage: 'readonly',
|
||||||
|
Roll: 'readonly',
|
||||||
|
Actor: 'readonly',
|
||||||
|
Item: 'readonly',
|
||||||
|
Dialog: 'readonly',
|
||||||
|
foundry: 'readonly',
|
||||||
|
Handlebars: 'readonly',
|
||||||
|
renderTemplate: 'readonly'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Possible Problems
|
||||||
|
'no-console': 'error',
|
||||||
|
'no-constant-condition': 'error',
|
||||||
|
'no-control-regex': 'error',
|
||||||
|
'no-debugger': 'error',
|
||||||
|
'no-dupe-args': 'error',
|
||||||
|
'no-dupe-keys': 'error',
|
||||||
|
'no-duplicate-case': 'error',
|
||||||
|
'no-empty': 'warn',
|
||||||
|
'no-empty-character-class': 'error',
|
||||||
|
'no-ex-assign': 'error',
|
||||||
|
'no-extra-boolean-cast': 'error',
|
||||||
|
'no-fallthrough': 'error',
|
||||||
|
'no-func-assign': 'error',
|
||||||
|
'no-inner-html': 'off', // Foundry uses innerHTML extensively
|
||||||
|
'no-invalid-regexp': 'error',
|
||||||
|
'no-irregular-whitespace': 'error',
|
||||||
|
'no-misleading-character-class': 'error',
|
||||||
|
'no-new-symbol': 'error',
|
||||||
|
'no-obj-calls': 'error',
|
||||||
|
'no-octal': 'error',
|
||||||
|
'no-prototype-builtins': 'error',
|
||||||
|
'no-regex-spaces': 'error',
|
||||||
|
'no-self-assign': 'error',
|
||||||
|
'no-sparse-arrays': 'error',
|
||||||
|
'no-template-curly-in-string': 'warn',
|
||||||
|
'no-unexpected-multiline': 'error',
|
||||||
|
'no-unreachable': 'error',
|
||||||
|
'no-unsafe-finally': 'error',
|
||||||
|
'no-unsafe-negation': 'error',
|
||||||
|
'no-unsafe-optional-chaining': 'error',
|
||||||
|
'no-useless-backreference': 'error',
|
||||||
|
'require-atomic-updates': 'off',
|
||||||
|
'use-isnan': 'error',
|
||||||
|
'valid-typeof': 'error',
|
||||||
|
|
||||||
|
// Suggestions
|
||||||
|
'accessor-pairs': 'warn',
|
||||||
|
'arrow-body-style': ['warn', 'as-needed'],
|
||||||
|
'block-scoped-var': 'error',
|
||||||
|
'camelcase': ['warn', { allow: ['^_', '^VERMINE_'] }],
|
||||||
|
'class-methods-use-this': 'off', // Many utility methods don't use this
|
||||||
|
'complexity': ['warn', 20],
|
||||||
|
'consistent-return': 'warn',
|
||||||
|
'consistent-this': 'warn',
|
||||||
|
'curly': ['warn', 'multi-line', 'consistent'],
|
||||||
|
'default-case': 'warn',
|
||||||
|
'default-case-last': 'warn',
|
||||||
|
'default-param-last': 'warn',
|
||||||
|
'dot-locale-compare': 'warn',
|
||||||
|
'dot-notation': ['warn', { allowKeywords: true }],
|
||||||
|
'eqeqeq': ['error', 'always', { null: 'ignore' }],
|
||||||
|
'func-name-matching': 'warn',
|
||||||
|
'func-names': ['warn', 'as-needed'],
|
||||||
|
'func-style': ['warn', 'declaration', { allowArrowFunctions: true }],
|
||||||
|
'grouped-accessor-pairs': 'warn',
|
||||||
|
'guard-for-in': 'warn',
|
||||||
|
'id-blacklist': 'off',
|
||||||
|
'id-length': 'off',
|
||||||
|
'id-match': 'off',
|
||||||
|
'init-declarations': ['warn', 'always'],
|
||||||
|
'line-comment-position': 'off',
|
||||||
|
'lines-between-class-members': ['warn', 'always', { exceptAfterSingleLine: true }],
|
||||||
|
'logical-assignment-operators': ['warn', 'always'],
|
||||||
|
'max-classes-per-file': ['warn', 1],
|
||||||
|
'max-depth': ['warn', 5],
|
||||||
|
'max-lines': ['warn', { max: 500, skipBlankLines: true, skipComments: true }],
|
||||||
|
'max-lines-per-function': ['warn', { max: 100, skipBlankLines: true, skipComments: true, IIFEs: true }],
|
||||||
|
'max-nested-callbacks': ['warn', 3],
|
||||||
|
'max-params': ['warn', 5],
|
||||||
|
'max-statements': ['warn', 30],
|
||||||
|
'multiline-comment-style': 'off',
|
||||||
|
'new-cap': ['warn', { newIsCap: true, capIsNew: false }],
|
||||||
|
'no-alert': 'warn',
|
||||||
|
'no-array-constructor': 'error',
|
||||||
|
'no-bitwise': 'warn',
|
||||||
|
'no-caller': 'error',
|
||||||
|
'no-case-declarations': 'error',
|
||||||
|
'no-class-assign': 'error',
|
||||||
|
'no-cond-assign': ['error', 'always'],
|
||||||
|
'no-confusing-arrow': ['warn', { allowParens: true }],
|
||||||
|
'no-const-assign': 'error',
|
||||||
|
'no-continue': 'off',
|
||||||
|
'no-delete-var': 'error',
|
||||||
|
'no-div-regex': 'warn',
|
||||||
|
'no-else-return': ['warn', { allowElseIf: true }],
|
||||||
|
'no-empty-destructuring': 'warn',
|
||||||
|
'no-empty-function': ['warn', { allow: ['constructors'] }],
|
||||||
|
'no-empty-pattern': 'warn',
|
||||||
|
'no-eq-null': 'off',
|
||||||
|
'no-eval': 'error',
|
||||||
|
'no-extend-native': 'error',
|
||||||
|
'no-extra-bind': 'warn',
|
||||||
|
'no-extra-label': 'warn',
|
||||||
|
'no-floating-decimal': 'warn',
|
||||||
|
'no-global-assign': 'error',
|
||||||
|
'no-implicit-coercion': ['warn', { allow: ['!!', '+'] }],
|
||||||
|
'no-implicit-globals': 'error',
|
||||||
|
'no-implied-eval': 'error',
|
||||||
|
'no-inline-comments': 'off',
|
||||||
|
'no-invalid-this': 'off',
|
||||||
|
'no-iterator': 'warn',
|
||||||
|
'no-label-var': 'error',
|
||||||
|
'no-labels': ['warn', { allowLoop: true, allowSwitch: true }],
|
||||||
|
'no-lone-blocks': 'warn',
|
||||||
|
'no-lonely-if': 'warn',
|
||||||
|
'no-loop-func': 'warn',
|
||||||
|
'@typescript-eslint/no-magic-numbers': 'off',
|
||||||
|
'no-multi-assign': 'warn',
|
||||||
|
'no-multi-str': 'warn',
|
||||||
|
'no-negated-condition': 'warn',
|
||||||
|
'no-nested-ternary': 'warn',
|
||||||
|
'no-new': 'warn',
|
||||||
|
'no-new-func': 'warn',
|
||||||
|
'no-new-wrappers': 'error',
|
||||||
|
'no-nonoctal-decimal-escape': 'error',
|
||||||
|
'no-object-multi-space': 'warn',
|
||||||
|
'no-octal-escape': 'error',
|
||||||
|
'no-param-reassign': ['warn', { props: false }],
|
||||||
|
'no-plusplus': 'off',
|
||||||
|
'no-promise-executor-return': 'error',
|
||||||
|
'no-proto': 'error',
|
||||||
|
'no-redeclare': 'error',
|
||||||
|
'no-regex-spaces': 'error',
|
||||||
|
'no-restricted-globals': 'off',
|
||||||
|
'no-restricted-imports': 'off',
|
||||||
|
'no-restricted-modules': 'off',
|
||||||
|
'no-restricted-properties': 'off',
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
'no-return-assign': ['error', 'always'],
|
||||||
|
'no-return-await': 'error',
|
||||||
|
'no-script-url': 'warn',
|
||||||
|
'no-sequences': 'error',
|
||||||
|
'no-setter-return': 'error',
|
||||||
|
'no-shadow': 'warn',
|
||||||
|
'no-shadow-restricted-names': 'error',
|
||||||
|
'no-sparse-arrays': 'error',
|
||||||
|
'no-tabs': 'warn',
|
||||||
|
'no-template-curly-in-string': 'warn',
|
||||||
|
'no-ternary': 'off',
|
||||||
|
'no-this-before-super': 'error',
|
||||||
|
'no-throw-literal': 'warn',
|
||||||
|
'no-undef': ['error', { typeof: true }],
|
||||||
|
'no-undef-init': 'warn',
|
||||||
|
'no-undefined': 'off',
|
||||||
|
'no-underscore-dangle': ['warn', { allow: ['_id', '_on', '_source', '_total', '_html'] }],
|
||||||
|
'no-unneeded-ternary': ['warn', { defaultAssignment: false }],
|
||||||
|
'no-unreachable-loop': 'warn',
|
||||||
|
'no-unsafe-finally': 'error',
|
||||||
|
'no-unsafe-negation': 'error',
|
||||||
|
'no-unused-expressions': ['warn', { allowShortCircuit: true, allowTernary: true, enforceForJSX: false }],
|
||||||
|
'no-unused-labels': 'warn',
|
||||||
|
'no-unused-private-class-members': 'warn',
|
||||||
|
'no-unused-vars': ['warn', { args: 'none', caughtErrors: 'none', ignoreRestSiblings: true }],
|
||||||
|
'no-use-before-define': ['warn', { functions: false, classes: false, variables: true }],
|
||||||
|
'no-useless-call': 'warn',
|
||||||
|
'no-useless-catch': 'warn',
|
||||||
|
'no-useless-computed-key': ['warn', { enforceForClassMembers: false }],
|
||||||
|
'no-useless-concat': 'warn',
|
||||||
|
'no-useless-constructor': 'warn',
|
||||||
|
'no-useless-escape': 'warn',
|
||||||
|
'no-useless-rename': 'warn',
|
||||||
|
'no-useless-return': 'warn',
|
||||||
|
'no-var': 'error',
|
||||||
|
'no-void': ['warn', { allowAsStatement: true }],
|
||||||
|
'no-warning-comments': 'warn',
|
||||||
|
'no-with': 'error',
|
||||||
|
'object-shorthand': ['warn', 'always', { ignoreConstructors: false, avoidQuotes: true }],
|
||||||
|
'one-var': ['warn', 'never'],
|
||||||
|
'one-var-declaration-per-line': ['warn', 'initializations'],
|
||||||
|
'operator-assignment': ['warn', 'always'],
|
||||||
|
'prefer-arrow-callback': 'warn',
|
||||||
|
'prefer-const': ['error', { destructuring: 'all', ignoreReadBeforeAssign: false }],
|
||||||
|
'prefer-destructuring': ['warn', { array: false, object: true }],
|
||||||
|
'prefer-exponentiation-operator': 'warn',
|
||||||
|
'prefer-named-capture-group': 'off',
|
||||||
|
'prefer-numeric-literals': 'warn',
|
||||||
|
'prefer-object-has-own': 'warn',
|
||||||
|
'prefer-object-spread': 'warn',
|
||||||
|
'prefer-promise-reject-errors': ['error', { allowEmptyReject: false }],
|
||||||
|
'prefer-regex-literals': ['warn', { disallowRedundantWrapping: true }],
|
||||||
|
'prefer-rest-params': 'warn',
|
||||||
|
'prefer-spread': 'warn',
|
||||||
|
'prefer-template': 'warn',
|
||||||
|
'quote-props': ['warn', 'as-needed'],
|
||||||
|
'radix': ['error', 'always'],
|
||||||
|
'require-await': 'warn',
|
||||||
|
'require-unicode-regexp': 'off',
|
||||||
|
'require-yield': 'error',
|
||||||
|
'sort-imports': 'off',
|
||||||
|
'sort-keys': 'off',
|
||||||
|
'sort-vars': 'off',
|
||||||
|
'spaced-comment': ['warn', 'always', { line: { markers: ['!', '/'] }, block: { balanced: true, markers: ['!', '*'], exceptions: ['*'] } }],
|
||||||
|
'strict': ['error', 'never'],
|
||||||
|
'symbol-description': 'warn',
|
||||||
|
'unicode-bom': ['error', 'never'],
|
||||||
|
'vars-on-top': 'off',
|
||||||
|
'yoda': ['warn', 'never', { exceptRange: true }],
|
||||||
|
|
||||||
|
// Layout & Formatting
|
||||||
|
'array-bracket-newline': ['warn', 'consistent'],
|
||||||
|
'array-bracket-spacing': ['warn', 'never'],
|
||||||
|
'array-element-newline': ['warn', 'consistent'],
|
||||||
|
'arrow-parens': ['warn', 'always'],
|
||||||
|
'arrow-spacing': ['warn', { before: true, after: true }],
|
||||||
|
'block-spacing': ['warn', 'always'],
|
||||||
|
'brace-style': ['warn', '1tbs', { allowSingleLine: true }],
|
||||||
|
'comma-dangle': ['warn', {
|
||||||
|
arrays: 'always-multiline',
|
||||||
|
objects: 'always-multiline',
|
||||||
|
imports: 'always-multiline',
|
||||||
|
exports: 'always-multiline',
|
||||||
|
functions: 'always-multiline'
|
||||||
|
}],
|
||||||
|
'comma-spacing': ['warn', { before: false, after: true }],
|
||||||
|
'comma-style': ['warn', 'last', { exceptions: { VariableDeclarator: true, ArrayExpression: true, ObjectExpression: true } }],
|
||||||
|
'computed-property-spacing': ['warn', 'never', { enforceForClassMembers: true }],
|
||||||
|
'dot-notation': ['warn', { allowKeywords: true }],
|
||||||
|
'eol-last': ['warn', 'always'],
|
||||||
|
'func-call-spacing': ['warn', 'never'],
|
||||||
|
'func-style': ['warn', 'declaration', { allowArrowFunctions: true }],
|
||||||
|
'function-call-argument-newline': ['warn', 'consistent'],
|
||||||
|
'function-paren-newline': ['warn', 'consistent'],
|
||||||
|
'generator-star-spacing': ['warn', { before: false, after: true }],
|
||||||
|
'implicit-arrow-linebreak': ['warn', 'beside'],
|
||||||
|
'indent': ['warn', 2, {
|
||||||
|
SwitchCase: 1,
|
||||||
|
VariableDeclarator: { var: 2, let: 2, const: 3 },
|
||||||
|
outerIIFEBody: 1,
|
||||||
|
MemberExpression: 'off',
|
||||||
|
FunctionDeclaration: { body: 1, parameters: 1, parameters: { var: 2, let: 2, const: 3 } },
|
||||||
|
FunctionExpression: { body: 1, parameters: 1, parameters: { var: 2, let: 2, const: 3 } },
|
||||||
|
StaticBlock: { body: 1 },
|
||||||
|
ClassBody: 1
|
||||||
|
}],
|
||||||
|
'jsx-quotes': 'off',
|
||||||
|
'key-spacing': ['warn', { beforeColon: false, afterColon: true, mode: 'strict' }],
|
||||||
|
'keyword-spacing': ['warn', { before: true, after: true, overrides: { return: { after: true }, throw: { after: true }, case: { after: true } } }],
|
||||||
|
'line-comment-position': 'off',
|
||||||
|
'linebreak-style': ['warn', 'unix'],
|
||||||
|
'lines-around-comment': 'off',
|
||||||
|
'lines-between-class-members': ['warn', 'always', { exceptAfterSingleLine: true }],
|
||||||
|
'max-len': ['warn', {
|
||||||
|
code: 120,
|
||||||
|
tabWidth: 2,
|
||||||
|
comments: 120,
|
||||||
|
ignoreComments: false,
|
||||||
|
ignoreTrailingComments: true,
|
||||||
|
ignoreUrls: true,
|
||||||
|
ignoreStrings: true,
|
||||||
|
ignoreTemplateLiterals: true,
|
||||||
|
ignoreRegExpLiterals: true
|
||||||
|
}],
|
||||||
|
'max-statements-per-line': ['warn', { max: 1 }],
|
||||||
|
'multiline-comment-style': 'off',
|
||||||
|
'multiline-ternary': ['warn', 'always-multiline'],
|
||||||
|
'new-parens': 'warn',
|
||||||
|
'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }],
|
||||||
|
'no-extra-parens': ['warn', 'all', { conditionalAssign: false, returnAssign: false, nestedBinaryExpressions: false, ignoreJSX: 'all', enforceForArrowConditionals: false, enforceForSequenceExpressions: false, enforceForNewInMemberExpressions: false }],
|
||||||
|
'no-extra-semi': 'warn',
|
||||||
|
'no-floating-decimal': 'warn',
|
||||||
|
'no-mixed-operators': ['warn', { groups: [['+', '-', '*', '/', '%', '**'], ['&', '|', '^', '~', '<<', '>>', '>>>'], ['==', '!=', '===', '!==', '>', '>=', '<', '<='], ['&&', '||'], ['in', 'instanceof']], allowSamePrecedence: false }],
|
||||||
|
'no-mixed-spaces-and-tabs': 'warn',
|
||||||
|
'no-multi-spaces': ['warn', { ignoreEOLComments: false, exceptions: { Property: true, BinaryExpression: false, VariableDeclarator: true, ImportDeclaration: true } }],
|
||||||
|
'no-multiple-empty-lines': ['warn', { max: 1, maxEOF: 0, maxBOF: 0 }],
|
||||||
|
'no-tabs': 'warn',
|
||||||
|
'no-trailing-spaces': ['warn', { skipBlankLines: false, ignoreComments: false }],
|
||||||
|
'no-whitespace-before-property': 'warn',
|
||||||
|
'nonblock-statement-body-position': ['warn', 'beside', { overrides: { if: 'beside', while: 'beside', do: 'beside', for: 'beside' } }],
|
||||||
|
'object-curly-newline': ['warn', { multiline: true, consistent: true }],
|
||||||
|
'object-curly-spacing': ['warn', 'always'],
|
||||||
|
'object-property-newline': ['warn', { allowAllPropertiesOnSameLine: true }],
|
||||||
|
'operator-linebreak': ['warn', 'after', { overrides: { '?': 'before', ':': 'before', '||': 'after', '&&': 'after', '|>': 'after' } }],
|
||||||
|
'padded-blocks': ['warn', 'never'],
|
||||||
|
'padding-line-between-statements': 'off',
|
||||||
|
'prefer-exponentiation-operator': 'warn',
|
||||||
|
'quote-props': ['warn', 'as-needed'],
|
||||||
|
'quotes': ['warn', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
|
||||||
|
'rest-spread-spacing': ['warn', 'never'],
|
||||||
|
'semi': ['warn', 'always'],
|
||||||
|
'semi-spacing': ['warn', { before: false, after: true }],
|
||||||
|
'semi-style': ['warn', 'last'],
|
||||||
|
'space-before-blocks': ['warn', 'always'],
|
||||||
|
'space-before-function-paren': ['warn', { anonymous: 'always', named: 'never', asyncArrow: 'always' }],
|
||||||
|
'space-in-parens': ['warn', 'never'],
|
||||||
|
'space-infix-ops': 'warn',
|
||||||
|
'space-unary-ops': ['warn', { words: true, nonwords: false, overrides: {} }],
|
||||||
|
'switch-colon-spacing': ['warn', { after: true, before: false }],
|
||||||
|
'template-curly-spacing': 'warn',
|
||||||
|
'template-tag-spacing': ['warn', 'never'],
|
||||||
|
'unicode-bom': ['error', 'never'],
|
||||||
|
'wrap-iife': ['warn', 'outside', { functionPrototypeMethods: true }],
|
||||||
|
'wrap-regex': 'warn',
|
||||||
|
'yield-star-spacing': ['warn', { before: false, after: true }]
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.hbs', '*.handlebars'],
|
||||||
|
rules: {
|
||||||
|
// Handlebars templates don't need linting
|
||||||
|
'no-undef': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/tests/**', '**/*.test.js', '**/*.spec.js'],
|
||||||
|
env: {
|
||||||
|
jest: true,
|
||||||
|
mocha: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ignorePatterns: [
|
||||||
|
'node_modules/',
|
||||||
|
'dist/',
|
||||||
|
'build/',
|
||||||
|
'*.min.js',
|
||||||
|
'*.min.css'
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
name: Release Creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: RouxAntoine/checkout@v3.5.4
|
||||||
|
|
||||||
|
# Extrait la version depuis le tag (ex: v1.2.3 → 1.2.3)
|
||||||
|
- name: Extract tag version number
|
||||||
|
id: get_version
|
||||||
|
uses: battila7/get-version-action@v2
|
||||||
|
|
||||||
|
# Met à jour version, manifest et download dans system.json
|
||||||
|
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||||
|
id: sub_manifest_link_version
|
||||||
|
uses: microsoft/variable-substitution@v1
|
||||||
|
with:
|
||||||
|
files: "system.json"
|
||||||
|
env:
|
||||||
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
|
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||||
|
manifest: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/latest/system.json
|
||||||
|
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/vermine2047-${{github.event.release.tag_name}}.zip
|
||||||
|
|
||||||
|
# Compile le CSS depuis les sources LESS
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
|
||||||
|
- name: Build CSS
|
||||||
|
run: npm install && npm run build
|
||||||
|
|
||||||
|
# Crée le zip de release avec tous les fichiers nécessaires au système
|
||||||
|
- run: |
|
||||||
|
apt update -y
|
||||||
|
apt install -y zip
|
||||||
|
|
||||||
|
- run: zip -r ./vermine2047-${{github.event.release.tag_name}}.zip system.json module/ css/ templates/ lang/ assets/ packs/
|
||||||
|
|
||||||
|
- name: setup go
|
||||||
|
uses: https://github.com/actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ">=1.20.1"
|
||||||
|
|
||||||
|
- name: Use Go Action
|
||||||
|
id: use-go-action
|
||||||
|
uses: https://gitea.com/actions/release-action@main
|
||||||
|
with:
|
||||||
|
files: |-
|
||||||
|
./vermine2047-${{github.event.release.tag_name}}.zip
|
||||||
|
system.json
|
||||||
|
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
|
||||||
|
|
||||||
|
- name: Publish to Foundry server
|
||||||
|
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||||
|
id: 'vermine2047'
|
||||||
|
version: ${{github.event.release.tag_name}}
|
||||||
|
manifest: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/latest/system.json'
|
||||||
|
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/vermine2047-${{github.event.release.tag_name}}.zip'
|
||||||
|
compatibility-minimum: '14'
|
||||||
|
compatibility-verified: '14'
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Vermine 2047 — Copilot Instructions
|
||||||
|
|
||||||
|
## Build & Development
|
||||||
|
|
||||||
|
Requires Node.js v16.15.1 (see `.nvmrc`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run watch # Watch SCSS + templates, proxy Foundry at localhost:30000 (requires Foundry running)
|
||||||
|
npm run buildStyle # Compile SCSS once to css/vermine2047.css
|
||||||
|
npm run pullYAMLtoLDB # Build LevelDB compendium packs from src/packs/ YAML → packs/
|
||||||
|
npm run pushLDBtoYAML # Extract LevelDB packs to editable YAML in src/packs/
|
||||||
|
npx eslint module/ # Lint JavaScript (ESLint configured in .eslintrc.js)
|
||||||
|
```
|
||||||
|
|
||||||
|
There are no automated tests. Use `npm run watch` during development — browser-sync proxies Foundry over HTTPS and live-reloads SCSS, `.hbs`, and `.html` templates on change. `system.json` declares `hotReload` for `.css`, `.scss`, `.hbs`, and `.json` extensions, so Foundry auto-refreshes those assets client-side when the dev server is running.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
This is a **FoundryVTT game system** (`system.json` → system ID `vermine2047`), compatible with Foundry v11–v14. It implements the French post-apocalyptic TTRPG **Vermine 2047**.
|
||||||
|
|
||||||
|
**Entry point**: `module/vermine2047.mjs` — loaded as an ES module. On the `init` hook it registers custom document classes, sheet classes, combat classes, Handlebars helpers, settings, hooks, and preloads templates.
|
||||||
|
|
||||||
|
**Actor types** (4): `character`, `npc`, `group`, `creature`
|
||||||
|
**Item types** (12): `item`, `weapon`, `defense`, `vehicle`, `ability`, `specialty`, `background`, `trauma`, `evolution`, `rumor`, `target`, `rite`
|
||||||
|
|
||||||
|
**Key source files** (`module/system/`):
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|---|---|
|
||||||
|
| `config.mjs` | All game constants (`CONFIG.VERMINE`) — abilities, skills, totems, threat/role/pattern levels, traits, damage types |
|
||||||
|
| `roll.mjs` | `VermineUtils` class — d10 dice pool system with success counting, totem mechanics, rerolls, Dice So Nice integration |
|
||||||
|
| `dice3d.mjs` | Dice So Nice 3D dice configuration (appearance, colors, themes for totem/standard dice) |
|
||||||
|
| `fight.mjs` | Confrontation system (`VermineFight`), plus `VermineCombat`, `VermineCombatant`, `VermineCombatTracker` |
|
||||||
|
| `group-link.mjs` | `GroupLink` — bidirectional sync of members/encounters between group actors and character/NPC actors via Foundry hooks |
|
||||||
|
| `hooks.mjs` | All Foundry hook registrations (chat messages, hotbar drop, combat, preCreate, Dice So Nice) |
|
||||||
|
| `settings.mjs` | System settings (game mode: survie/cauchemar/apocalypse) |
|
||||||
|
| `handlebars-manager.mjs` | Template preloading paths + all Handlebars helpers (level config lookups, math, conditionals) |
|
||||||
|
| `effects.mjs` | Active effect management |
|
||||||
|
| `dialogs/rollDialog.mjs` | Advanced roll dialog (ability/skill selection, difficulty, totems, specialties, assist/pool bonuses) |
|
||||||
|
| `applications.mjs` | `TotemPicker` and `TraitSelector` applications (still AppV1) |
|
||||||
|
| `applications/sheets/` | ApplicationV2 sheet classes: `base-actor-sheet.mjs`, `base-item-sheet.mjs` + 4 actor + 12 item sheets |
|
||||||
|
|
||||||
|
**Data model**: Defined via Foundry DataModels (`module/models/`) extending `foundry.abstract.TypeDataModel`. Type-specific derived data is computed in each DataModel's `prepareDerivedData()`. The legacy `template.json` has been removed in favor of `system.json` → `documentTypes`.
|
||||||
|
|
||||||
|
**Sheet inheritance**: ApplicationV2 (`HandlebarsApplicationMixin(ActorSheetV2)`/`ItemSheetV2`) in `module/applications/sheets/`. `VermineBaseActorSheet` → `VermineCharacterSheetV2`, etc. Sheets expose `CONFIG.VERMINE` as `context.config` in template data. Each sheet is split into parts via `static PARTS`; tab navigation uses Core's `templates/generic/tab-navigation.hbs`.
|
||||||
|
|
||||||
|
**Data preparation**: `VermineActor.prepareDerivedData()` delegates to the type-specific DataModel. Each DataModel implements its own `prepareDerivedData()` with type-specific computation logic.
|
||||||
|
|
||||||
|
**Global access**: On `init`, `game.vermine2047` is populated with `{ VermineActor, VermineItem, VermineUtils, VermineCombat, GroupLink }` for easy access in macros and scripts.
|
||||||
|
|
||||||
|
**Compendium packs**: Stored in `packs/` as LevelDB databases. Edit source data in `src/packs/` as YAML, then `npm run pullYAMLtoLDB` to build. The CI release workflow runs this automatically.
|
||||||
|
|
||||||
|
## Key Conventions
|
||||||
|
|
||||||
|
- **All `.mjs` files**: ES module syntax only. Foundry globals (`game`, `Hooks`, `CONFIG`, `Actor`, `Item`, `ChatMessage`, `Roll`, `Handlebars`, `renderTemplate`, `foundry`, `ui`, `Canvas`, `Dialog`) are available but declared as readonly globals in `.eslintrc.js`.
|
||||||
|
- **ESLint conventions**: 2-space indent, single quotes, semicolons required, `===` equality (except `== null`). Constants named with `VERMINE_` prefix are exempt from camelCase. Max 1 class per file, 500 lines per file, 100 lines per function, 5 levels of nesting max. No `console.log` calls — use `ui.notifications`.
|
||||||
|
- **Code language**: Source code and comments are in French. UI strings in `lang/fr.json` and `lang/en.json`. Use `game.i18n.localize()` for all user-visible text.
|
||||||
|
- **Template naming**: Actor sheets use ApplicationV2 part templates in `templates/actor/appv2/{type}-{part}.hbs` (e.g. `character-main.hbs`, `npc-characteristics.hbs`). Item sheets at `templates/item/item-{type}-sheet.html`. Chat card templates at `templates/item/chatCards/{type}.hbs`. Legacy templates in `templates/actor/` subdirectories are preserved for dialog/chat use.
|
||||||
|
- **Dice system**: d10 pools with success counting (result ≥ difficulty). Totem dice (human/adapted) count double on success. Formula syntax: `{N}d10cs>={threshold}[label]`. Totem dice: `(1d10cs>={threshold}[totem_label]*2)`.
|
||||||
|
- **CSS**: SCSS sources in `scss/` compile to `css/vermine2047.css`. Entry point: `vermine2047.scss` which imports partials: `_app.scss`, `_flex.scss`, `dialog.scss`, `item-sheet.scss`, `itemCards.scss`, `roll.scss`, `special-applications.scss`, `special-inputs.scss`, `style.scss`, `base_work.scss`. Add new SCSS partials as `_component.scss`, import in `vermine2047.scss`.
|
||||||
|
- **Data tools**: The `pushLDBtoYAML` / `pullYAMLtoLDB` scripts use `@foundryvtt/foundryvtt-cli` to convert between LevelDB packs and editable YAML. When adding pack content, edit YAML in `src/packs/` then rebuild.
|
||||||
|
- **Actor updates**: Use `actor.update({...})` with dot-notation paths (e.g., `'system.adaptation.totems.human.value'`). The `GroupLink` hooks system automatically syncs group memberships on actor changes.
|
||||||
|
- **New template partials**: Must be listed in `preloadHandlebarsTemplates()` in `handlebars-manager.mjs` for pre-compilation.
|
||||||
|
- **Full architecture documentation**: See `docs/technical/ARCHITECTURE.md`.
|
||||||
@@ -31,12 +31,16 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
|
# Build CSS from LESS
|
||||||
|
- name: Build CSS
|
||||||
|
run: npm run build:less
|
||||||
|
|
||||||
# Pull YAML to LDB packs
|
# Pull YAML to LDB packs
|
||||||
- name: Build Packs
|
- name: Build Packs
|
||||||
run: npm run pullYAMLtoLDB
|
run: npm run pullYAMLtoLDB
|
||||||
|
|
||||||
# Create a zip file with all files required by the module to add to the release
|
# Create a zip file with all files required by the module to add to the release
|
||||||
- run: zip -r ./system.zip system.json template.json assets/ css/ lang/ module/ templates/ packs/
|
- run: zip -r ./system.zip system.json assets/ css/ lang/ module/ templates/ packs/
|
||||||
|
|
||||||
# Create a release for this specific version
|
# Create a release for this specific version
|
||||||
- name: Update Release with Files
|
- name: Update Release with Files
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# IDE
|
# IDE
|
||||||
.idea/
|
.idea/
|
||||||
.vsode
|
.vscode/
|
||||||
|
.github/
|
||||||
|
|
||||||
# Node Modules
|
# Node Modules
|
||||||
node_modules
|
node_modules
|
||||||
@@ -13,3 +14,12 @@ packs/*/*
|
|||||||
*.lock
|
*.lock
|
||||||
jsconfig.json
|
jsconfig.json
|
||||||
foundry
|
foundry
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Ancien dossier SCSS (archivé)
|
||||||
|
scss_old/
|
||||||
|
|
||||||
|
# Fichiers CSS générés
|
||||||
|
css/vermine2047.*.css
|
||||||
|
!css/vermine2047.css
|
||||||
|
regles/
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"atom": false,
|
||||||
|
"borderZero": {
|
||||||
|
"style": "none"
|
||||||
|
},
|
||||||
|
"colorVariables": true,
|
||||||
|
"comment": false,
|
||||||
|
"depthLevel": 10,
|
||||||
|
"duplicateProperty": true,
|
||||||
|
"emptyRule": true,
|
||||||
|
"finalNewline": true,
|
||||||
|
"hexLength": "long",
|
||||||
|
"hexNotation": "lowercase",
|
||||||
|
"hexValidation": true,
|
||||||
|
"idSelector": false,
|
||||||
|
"important": true,
|
||||||
|
"importPath": [
|
||||||
|
"./less"
|
||||||
|
],
|
||||||
|
"maxCharPerLine": 120,
|
||||||
|
"newlineAfterBlock": true,
|
||||||
|
"propertyOrdering": false,
|
||||||
|
"propertyUnits": true,
|
||||||
|
"qualifyingElement": [
|
||||||
|
"button",
|
||||||
|
"input",
|
||||||
|
"select",
|
||||||
|
"textarea"
|
||||||
|
],
|
||||||
|
"selectorNaming": false,
|
||||||
|
"singleLinePerProperty": true,
|
||||||
|
"singleLinePerSelector": false,
|
||||||
|
"spaceAfterComma": true,
|
||||||
|
"spaceAfterComment": true,
|
||||||
|
"spaceAfterPropertyColon": "one",
|
||||||
|
"spaceAfterPropertyValue": true,
|
||||||
|
"spaceAroundCombinator": true,
|
||||||
|
"spaceAroundOperator": true,
|
||||||
|
"spaceBeforeBrace": "one",
|
||||||
|
"spaceBetweenParens": false,
|
||||||
|
"stringQuotes": "double",
|
||||||
|
"trailingSemicolon": true,
|
||||||
|
"trailingWhitespace": true,
|
||||||
|
"urlFormat": "relative",
|
||||||
|
"urlQuotes": true,
|
||||||
|
"variableNaming": false,
|
||||||
|
"zeroUnit": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# AGENTS.md — Vermine2047 FoundryVTT system
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
FoundryVTT v14 game system for the French post-apocalyptic TTRPG "Vermine 2047". Bilingual FR/EN.
|
||||||
|
|
||||||
|
- **Entrypoint**: `module/vermine2047.mjs` (declared in `system.json` esmodules)
|
||||||
|
- **CSS**: `css/vermine2047.min.css` (compiled from LESS, **not** SCSS)
|
||||||
|
- **System ID**: `vermine2047`
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
| Layer | Location | Notes |
|
||||||
|
|-------|----------|-------|
|
||||||
|
| Entry/init | `module/vermine2047.mjs` | `Hooks.once('init',...)` — register DataModels before Document classes |
|
||||||
|
| DataModels | `module/models/*.mjs` | FoundryV2 `TypeDataModel` — 4 Actor types, 12 Item types |
|
||||||
|
| Documents | `module/documents/*.mjs` | `VermineActor`, `VermineItem` (thin wrappers) |
|
||||||
|
| Sheets (AppV2) | `module/applications/sheets/*.mjs` | ApplicationV2 sheets per type |
|
||||||
|
| System | `module/system/*.mjs` | config, roll, fight, dialogs, hooks, settings, dice3d, group-link |
|
||||||
|
| Templates (HBS) | `templates/actor/appv2/*.hbs` | ApplicationV2 templates |
|
||||||
|
| Legacy templates | `templates/actor/*.hbs`, `templates/actor/{character,group,npc,creature}/*.hbs` | Coexisting legacy templates |
|
||||||
|
|
||||||
|
### Actor types: character, npc, group, creature
|
||||||
|
### Item types: item, weapon, defense, vehicle, ability, specialty, background, trauma, evolution, rumor, target, rite
|
||||||
|
|
||||||
|
Key objects at runtime:
|
||||||
|
- `game.vermine2047` → `{ VermineUtils, VermineCombat, GroupLink }`
|
||||||
|
- `CONFIG.VERMINE` → game configuration (totems, traits, skills, etc.)
|
||||||
|
- `CONFIG.ui.combat` → custom `VermineCombatTracker`
|
||||||
|
|
||||||
|
Dice system: d10-based, success-counting (`Xd10cs>=N`). Totems give domain bonuses. Initiative formula:
|
||||||
|
`(@abilities.reflexes.value + @skills.alertness.value)d10cs>=@combatStatus.difficulty`
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build:less # LESS → vermine2047.min.css
|
||||||
|
npm run build:less:dev # LESS → vermine2047.dev.css (unminified)
|
||||||
|
npm run build:all-css # both
|
||||||
|
npm run watch # gulp watch + browser-sync proxy to localhost:30000
|
||||||
|
npm run lint:less # lesshint
|
||||||
|
npm run pushLDBtoYAML # extract Foundry packs → src/packs/*.yml
|
||||||
|
npm run pullYAMLtoLDB # compile src/packs/*.yml → packs/ LevelDB
|
||||||
|
make build / watch / lint # Makefile wrappers for above
|
||||||
|
```
|
||||||
|
|
||||||
|
- Node version required: `v16.15.1` (per `.nvmrc`)
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **DataModels before Document classes**: In `init` hook, register `CONFIG.Actor.dataModels.*` and `CONFIG.Item.dataModels.*` **before** setting `CONFIG.Actor.documentClass` / `CONFIG.Item.documentClass`.
|
||||||
|
- **`no-console: 'error'`**: ESLint blocks `console.log/warn/info`. Use `console.error` sparingly or disable the rule inline.
|
||||||
|
- **ESLint is very verbose**: ~350 rules. Common failures: missing semicolons, trailing commas, single quotes, 2-space indent, `max-len: 120`. Run `npx eslint module/` before committing.
|
||||||
|
- **Two template systems**: Both legacy (`templates/actor/*.hbs`) and ApplicationV2 (`templates/actor/appv2/*.hbs`) are preloaded. Don't confuse them.
|
||||||
|
- **Packs in two places**: `src/packs/*.yml` (YAML source, tracked) ↔ `packs/*` (LevelDB binary, gitignored). Edit YAML source, then run `pullYAMLtoLDB` to compile.
|
||||||
|
- **CSS auto-loaded**: `system.json` declares styles; **do not** inject `<link>` manually (causes MIME type errors).
|
||||||
|
- **`game-mode` setting**: Default in code is `'e'` (likely a typo for `'1'`). Values: `1`=Survie, `2`=Cauchemar, `3`=Apocalypse.
|
||||||
|
- **editMode flag disabled**: No auto-set on actor creation (causes ActiveEffect errors). Enable manually via sheet checkbox.
|
||||||
|
- **GroupLink auto-syncs**: `GroupLink.registerHooks()` runs on init. Actor updates propagate to groups and vice versa.
|
||||||
|
- **Custom dice**: Dice So Nice integration uses user color (regular/human/adapted color sets) with custom d10 face images.
|
||||||
|
- **No tests**: `npm test` is a stub (exits 1). No test framework installed.
|
||||||
|
- **No CI**: `.github/` is gitignored; no workflows configured.
|
||||||
|
- **HotReload**: `system.json` flags hotReload for css, scss, hbs, json extensions.
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# CHANGELOG
|
|
||||||
|
|
||||||
## 0.1.13
|
|
||||||
- ajout des historiques
|
|
||||||
- ajout des rites
|
|
||||||
|
|
||||||
## 0.1.12
|
|
||||||
- passage des packs en LevelDB
|
|
||||||
|
|
||||||
## 0.1.11
|
|
||||||
- passage à une compatibilité v11
|
|
||||||
|
|
||||||
## 0.1.10
|
|
||||||
- révision du template évolution
|
|
||||||
- révision du template traumatisme
|
|
||||||
- ajout du type rite
|
|
||||||
- test import de pack
|
|
||||||
|
|
||||||
## 0.1.9
|
|
||||||
- versions condensées des méthodes (@kristov)
|
|
||||||
- renommage des templates
|
|
||||||
- image actors par défaut
|
|
||||||
- image des créatures
|
|
||||||
- ajout du totem et de l'apprentissage dans les capacités
|
|
||||||
|
|
||||||
## 0.1.8
|
|
||||||
- refactoring sheet vers actor
|
|
||||||
|
|
||||||
## 0.1.7
|
|
||||||
- sang-froid, effort, et santé
|
|
||||||
|
|
||||||
## 0.1.6
|
|
||||||
- finition de l'onglet totem
|
|
||||||
- ajout du logo de totem dans la sidebar
|
|
||||||
- ajout de traductions manquantes
|
|
||||||
|
|
||||||
## 0.1.5
|
|
||||||
- début de mise en forme des feuilles créature et pnj
|
|
||||||
- possibilité de changer le type de capacité (pour ajouter des capacités de totem)
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Code Author :
|
||||||
|
- Developed by LeRatierBretonnien / www.uberwald.me
|
||||||
|
|
||||||
|
Code LICENCE :
|
||||||
|
C BY-NC-SA 4.0
|
||||||
|
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||||
|
This license requires that reusers give credit to the creator. It allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, for noncommercial purposes only. If others modify or adapt the material, they must license the modified material under identical terms.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Makefile pour Vermine2047
|
||||||
|
# Ce fichier fournit des commandes courantes pour le développement
|
||||||
|
# Utilise uniquement LESS comme préprocesseur CSS
|
||||||
|
|
||||||
|
.PHONY: help install build build-less build-dev build-css watch clean lint
|
||||||
|
|
||||||
|
# Couleurs pour l'affichage
|
||||||
|
GREEN := \033[0;32m
|
||||||
|
YELLOW := \033[1;33m
|
||||||
|
NC := \033[0m # No Color
|
||||||
|
|
||||||
|
help: ## Affiche cette aide
|
||||||
|
@echo "Commandes disponibles pour Vermine2047:"
|
||||||
|
@echo ""
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[0;32m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
install: ## Installe les dépendances npm
|
||||||
|
@echo "$(YELLOW)Installation des dépendances npm...$(NC)"
|
||||||
|
npm install
|
||||||
|
@echo "$(GREEN)✓ Dépendances installées$(NC)"
|
||||||
|
|
||||||
|
build: build-less ## Compile LESS → vermine2047.min.css
|
||||||
|
|
||||||
|
build-less: ## Compile le LESS en CSS minifié (vermine2047.min.css)
|
||||||
|
@echo "$(YELLOW)Compilation du LESS...$(NC)"
|
||||||
|
npm run build:less
|
||||||
|
@echo "$(GREEN)✓ LESS compilé$(NC)"
|
||||||
|
|
||||||
|
build-dev: ## Compile le LESS en CSS non minifié (vermine2047.dev.css)
|
||||||
|
@echo "$(YELLOW)Compilation du LESS (mode dev)...$(NC)"
|
||||||
|
npm run build:less:dev
|
||||||
|
@echo "$(GREEN)✓ LESS compilé en mode dev$(NC)"
|
||||||
|
|
||||||
|
build-css: build-less build-dev ## Compile tout le CSS (minifié + dev)
|
||||||
|
|
||||||
|
watch: ## Lance le mode watch (recompilation automatique)
|
||||||
|
@echo "$(YELLOW)Lancement du mode watch...$(NC)"
|
||||||
|
npm run watch
|
||||||
|
|
||||||
|
clean: clean-css ## Nettoie les fichiers CSS générés
|
||||||
|
|
||||||
|
clean-css: ## Supprime les fichiers CSS compilés (garde vermine2047.css original)
|
||||||
|
@echo "$(YELLOW)Nettoyage des fichiers CSS...$(NC)"
|
||||||
|
npm run clean:css
|
||||||
|
@echo "$(GREEN)✓ Fichiers CSS nettoyés$(NC)"
|
||||||
|
|
||||||
|
rebuild: clean build ## Reconstruit tout le CSS
|
||||||
|
@echo "$(YELLOW)Reconstruction complète du CSS...$(NC)"
|
||||||
|
npm run rebuild:css
|
||||||
|
@echo "$(GREEN)✓ CSS reconstruit$(NC)"
|
||||||
|
|
||||||
|
lint: lint-less ## Lance le linting du code LESS
|
||||||
|
|
||||||
|
lint-less: ## Vérifie la qualité du code LESS
|
||||||
|
@echo "$(YELLOW)Linting du code LESS...$(NC)"
|
||||||
|
npm run lint:less
|
||||||
|
@echo "$(GREEN)✓ Linting terminé$(NC)"
|
||||||
|
|
||||||
|
# Commandes utilitaires
|
||||||
|
|
||||||
|
launch-foundry: ## Lance FoundryVTT
|
||||||
|
npm run launch_Foundry12
|
||||||
|
|
||||||
|
push-yaml: ## Push LDB vers YAML
|
||||||
|
node ./tools/pushLDBtoYAML.mjs
|
||||||
|
|
||||||
|
pull-yaml: ## Pull YAML vers LDB
|
||||||
|
node ./tools/pullYAMLtoLDB.mjs
|
||||||
@@ -1,60 +1,136 @@
|
|||||||
# Vermine 2047 System
|
# Vermine 2047 — Système Foundry VTT
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
##TODO
|
Système non-officiel pour le jeu de rôle **Vermine 2047**, univers post-apo français par **Studio Agate**.
|
||||||
|
|
||||||
### fiche de perso
|
► Achetez le jeu : https://www.kickandgo.net/boutique-agate/category.php?cl=UyQcRrFtYcYrAnLbOfDj&idp=20
|
||||||
|
|
||||||
- [X] création de spécialitées depuis le (+) des compétences
|
Interface bilingue **FR/EN**.
|
||||||
- [X] fixer les cases hexas , comprtement chelou , piste = un click à l'air d'envoyer 2 update, verifier le onChange
|
|
||||||
- [X] dialog d'edition des min-max
|
|
||||||
|
|
||||||
### fiche de groupe
|
---
|
||||||
- [ ] pas encore penché dessus
|
|
||||||
|
|
||||||
#### Members et encounters
|
## Types d'Acteurs
|
||||||
|
|
||||||
- [ ] faire une classe GroupLink pour avoir les actors en objets dans les array group.members[], group.encounters[], et character.encounters[],
|
| Type | Fiche | Description |
|
||||||
- [ ] faire une fonction sur le Hook.onUpdateActor => update des groupes dans characters, update des encounters et members dans groups
|
|------|-------|-------------|
|
||||||
|
| **character** | AppV2 | Personnage joueur — compétences, capacités, totem, équipement |
|
||||||
|
| **npc** | AppV2 | PNJ avec rôle/menace, seuils de blessure |
|
||||||
|
| **group** | AppV2 | Groupe avec membres, rencontres, synchro liée |
|
||||||
|
| **creature** | AppV2 | Créature avec gabarit/taille/rôle, attaques custom |
|
||||||
|
|
||||||
### fiche de pnj créature
|
## Types d'Objets
|
||||||
- [ ] à faire,
|
|
||||||
- [ ] lister les gabarit/taille/roles(creatures) et menace/experience/role(pnj)... stocker les modifs dans CONFIG.VERMINE,
|
|
||||||
|
|
||||||
|
| Type | Utilisation |
|
||||||
|
|------|-------------|
|
||||||
|
| **item** | Objets génériques (matériel, ressources) |
|
||||||
|
| **weapon** | Armes — dégâts, portée, fiabilité, rareté |
|
||||||
|
| **defense** | Armures/boucliers — protection, couverture |
|
||||||
|
| **vehicle** | Véhicules — vitesse, structure, armement |
|
||||||
|
| **ability** | Capacités spéciales (apprentissage, domaine) |
|
||||||
|
| **specialty** | Spécialités de compétence |
|
||||||
|
| **background** | Historiques de personnage |
|
||||||
|
| **trauma** | Traumatismes physiques/mentaux |
|
||||||
|
| **evolution** | Évolutions du personnage |
|
||||||
|
| **rumor** | Rumeurs (groupe) |
|
||||||
|
| **target** | Cibles (groupe) |
|
||||||
|
| **rite** | Rites (magie/rituels) |
|
||||||
|
|
||||||
### les jets de dés
|
## Système de Dés
|
||||||
- [ ] redesign de rollDialog => `<details>+<sumary>`=> rendre moins dense
|
|
||||||
- [X] envoyer les spécialités utilisables au rollDialog
|
|
||||||
- [X] envoyer les items utilisables au rollDialog
|
|
||||||
- [ ] gérer le fait de choisir quel totem garder : recalcul des réussites
|
|
||||||
- [X] refacto des template chat de roll
|
|
||||||
- [X] gérer les dés de totems humains et adapté : couleur différente/double succès +update actor
|
|
||||||
- [X] gérer les rerolls depuis chat(cf noc)
|
|
||||||
- [X] gérer les rerolls après le jet en fonction du score d'effort et de la carac
|
|
||||||
- [X] faire l'update l' l'actor juste après s'etre accorder des rerolls, et avoir utiliser le sang-froid
|
|
||||||
- [X] update des reserves de sang-froids lors de jets
|
|
||||||
- [X] ajout des domaines de prédilections
|
|
||||||
- [ ] gérer les réussites auto
|
|
||||||
- [ ] gérer les seuils auto si compétence non maitrisée
|
|
||||||
|
|
||||||
### le combat
|
- **Comptage de succès sur d10** : `Xd10cs>=N`
|
||||||
- [ ] modifier la difficulté en fonction de l'état du combatant /offensif/actif/passif/
|
- Difficulté = nombre de succès requis
|
||||||
|
- Les totems donnent des bonus de domaine (dés humains/adaptés)
|
||||||
|
- Relances via l'Effort et le Sang-froid
|
||||||
|
- Succès automatiques et seuils d'échec
|
||||||
|
- Intégration **Dice So Nice!** avec faces de d10 personnalisées
|
||||||
|
|
||||||
|
### Initiative
|
||||||
|
Formule : `(@abilities.reflexes.value + @skills.alertness.value)d10cs>=@combatStatus.difficulty`
|
||||||
|
|
||||||
### les items
|
---
|
||||||
- [X]ajouter apprentissage aux abilities
|
|
||||||
- [X] passer le type d'arme en select/options
|
## Architecture
|
||||||
- [X] ajouter handicap de rareté
|
|
||||||
- [ ] ajouter pour items Item "competence nécessaire"
|
```
|
||||||
- [-] gérer les rolls d'items dans le chat
|
module/
|
||||||
- [-] repasser sur les différents itemTypes et sheets
|
├── vermine2047.mjs # Point d'entrée (Hooks.once init)
|
||||||
- [X] verifier le selector de traits (trait pratique cf : msg pretre)
|
├── models/ # DataModels (Foundry v2 TypeDataModel)
|
||||||
- [X] construire une selecteur de traits, traits= CONFIG.VERMINE.traits
|
│ ├── actor/ # character.mjs, npc.mjs, group.mjs, creature.mjs
|
||||||
traits:[
|
│ └── item/ # Les 12 types d'objets
|
||||||
key:{
|
├── documents/ # VermineActor, VermineItem
|
||||||
name:string,
|
├── applications/
|
||||||
description:string,
|
│ └── sheets/ # Fiches AppV2 par type
|
||||||
value:number
|
├── system/
|
||||||
}
|
│ ├── config.mjs # CONFIG.VERMINE (totems, traits, compétences…)
|
||||||
]
|
│ ├── roll.mjs # VermineUtils.roll()
|
||||||
- [X] ajouter des dégats sur chaques fiches item
|
│ ├── fight.mjs # Système de combat, VermineCombat, VermineCombatant
|
||||||
|
│ ├── functions.mjs # Fonctions partagées
|
||||||
|
│ ├── hooks.mjs # Hooks (DiceSoNice, chat, pause…)
|
||||||
|
│ ├── dice3d.mjs # Couleurs Dice So Nice, initUserDice
|
||||||
|
│ ├── dialogs/rollDialog.mjs
|
||||||
|
│ ├── settings.mjs # Paramètres du système (mode de jeu…)
|
||||||
|
│ ├── tour.mjs # Visites guidées (stub)
|
||||||
|
│ └── group-link.mjs # GroupLink : synchro acteurs↔groupes
|
||||||
|
templates/
|
||||||
|
├── actor/appv2/ # Templates AppV2 des fiches acteurs
|
||||||
|
├── actor/ # Templates legacy (coexistence)
|
||||||
|
└── item/ # Templates des fiches objets
|
||||||
|
css/
|
||||||
|
└── vermine2047.min.css # Compilé depuis LESS
|
||||||
|
less/
|
||||||
|
└── base.less # Source LESS principale
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objets Clés
|
||||||
|
|
||||||
|
- `game.vermine2047` → `{ VermineUtils, VermineCombat, GroupLink }`
|
||||||
|
- `CONFIG.VERMINE` → totems, traits, compétences, domaines, états de combat…
|
||||||
|
- `CONFIG.ui.combat` → `VermineCombatTracker` personnalisé
|
||||||
|
|
||||||
|
## Commandes
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build:less # LESS → css/vermine2047.min.css
|
||||||
|
npm run build:less:dev # LESS → css/vermine2047.dev.css (non minifié)
|
||||||
|
npm run build:all-css # Les deux
|
||||||
|
npm run watch # Gulp watch + browser-sync → localhost:30000
|
||||||
|
npm run lint:less # Vérification LESS
|
||||||
|
npm run pushLDBtoYAML # Extraire les packs → src/packs/*.yml
|
||||||
|
npm run pullYAMLtoLDB # Compiler les packs → packs/* (LevelDB)
|
||||||
|
make build|watch|lint # Raccourcis Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prérequis
|
||||||
|
- Node.js `v16.15.1` (cf `.nvmrc`)
|
||||||
|
- Foundry VTT **v14**
|
||||||
|
|
||||||
|
## Notes de Développement
|
||||||
|
|
||||||
|
- **DataModels avant Document classes** : Dans le hook `init`, enregistrer `CONFIG.Actor.dataModels.*` et `CONFIG.Item.dataModels.*` **avant** de définir `CONFIG.Actor.documentClass` / `CONFIG.Item.documentClass`.
|
||||||
|
- **Deux systèmes de templates** : AppV2 (`templates/actor/appv2/`) et legacy (`templates/actor/*.hbs`) coexistent.
|
||||||
|
- **Packs** : Éditer les sources YAML dans `src/packs/*.yml`, puis lancer `pullYAMLtoLDB`.
|
||||||
|
- **CSS** : Déclaré dans `system.json` — ne **pas** injecter de `<link>` manuellement.
|
||||||
|
- **ESLint** : ~350 règles. Lancer `npx eslint module/` avant chaque commit. Pas de `console.log`.
|
||||||
|
- **editMode** : Ne se met plus automatiquement à la création d'acteur (cause des erreurs ActiveEffect). Activer manuellement via la case à cocher.
|
||||||
|
- **GroupLink** : Synchronisation automatique bidirectionnelle acteurs↔groupes.
|
||||||
|
- **HotReload** : Activé pour css, scss, hbs, json dans `system.json`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## English Summary
|
||||||
|
|
||||||
|
**Vermine 2047** is a French post-apocalyptic TTRPG. This is an unofficial Foundry VTT system (v14).
|
||||||
|
|
||||||
|
- **4 actor types**: character, npc, group, creature
|
||||||
|
- **12 item types**: item, weapon, defense, vehicle, ability, specialty, background, trauma, evolution, rumor, target, rite
|
||||||
|
- **Dice**: d10 success-counting with totem domains, Effort rerolls, Dice So Nice! 3D integration
|
||||||
|
- **Sheets**: AppV2 with tab navigation, edit/play mode toggle
|
||||||
|
- **Bilingual**: FR/EN interface
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
Projet fan non-officiel. L'univers et les marques Vermine 2047 appartiennent aux Éditions John Doe.
|
||||||
|
|||||||
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
# vermine2047 Audit Findings
|
||||||
|
|
||||||
|
Security audit of `vermine2047` at commit `b1d73e6b08bf`. Each finding links to a detailed write-up (`NNN-*.md`) and a proposed patch (`NNN-*.patch`).
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Total findings: 0**
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
## Dropped in adjudication
|
||||||
|
|
||||||
|
These findings reproduced in Phase 4 but the Phase 4.5 gate judged them false positives or out of a realistic threat model.
|
||||||
|
|
||||||
|
- **Unpinned mutable CI action enabling supply chain hijacking** (high, .gitea/workflows/release.yaml): Malformed URL in `uses` breaks workflow parsing; step is unreachable and prevents execution entirely.; Relies on improbable upstream compromise; standard configuration gap rather than immediate vulnerability.
|
||||||
|
|
||||||
|
## Run details
|
||||||
|
|
||||||
|
- run id: `aa4595a1`
|
||||||
|
- commit: `b1d73e6b08bf7ed0157a03137cc3bb50c8d3a26c`
|
||||||
|
- branch: `main`
|
||||||
|
- scope: 214 file(s)
|
||||||
|
- files triaged: 214
|
||||||
|
- files escalated: 14
|
||||||
|
- files deep-reviewed: 14
|
||||||
|
- findings proposed: 1
|
||||||
|
- findings verified: 0
|
||||||
|
- findings discarded: 0
|
||||||
|
- findings dropped in adjudication: 1
|
||||||
|
- artifacts: 0 written, 0 failed, 0 pending
|
||||||
|
|
||||||
|
## How to read this directory
|
||||||
|
|
||||||
|
Each finding has a paired `NNN-<slug>.md` (the human report) and `NNN-<slug>.patch` (the proposed fix as a unified diff). The report carries the narrative; the patch is the suggested change. Regenerate a single finding's artifacts with `/audit --regen --finding N`.
|
||||||
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 776 KiB |
@@ -0,0 +1,680 @@
|
|||||||
|
uid=78_0 RootWebArea "Foundry Virtual Tabletop" url="https://localhost:31000/game"
|
||||||
|
uid=78_1 ignored
|
||||||
|
uid=78_2 generic
|
||||||
|
uid=78_3 list
|
||||||
|
uid=78_4 generic
|
||||||
|
uid=78_5 generic
|
||||||
|
uid=78_6 generic
|
||||||
|
uid=78_7 generic
|
||||||
|
uid=78_8 generic
|
||||||
|
uid=78_9 generic
|
||||||
|
uid=78_10 list
|
||||||
|
uid=78_11 listitem level="1"
|
||||||
|
uid=78_12 tab "Outils de token" selectable
|
||||||
|
uid=78_13 listitem level="1"
|
||||||
|
uid=78_14 tab "Outils de tuile" selectable
|
||||||
|
uid=78_15 listitem level="1"
|
||||||
|
uid=78_16 tab "Outils de dessin" selectable
|
||||||
|
uid=78_17 listitem level="1"
|
||||||
|
uid=78_18 tab "Outils de mur" selectable
|
||||||
|
uid=78_19 listitem level="1"
|
||||||
|
uid=78_20 tab "Outils de lumière" selectable
|
||||||
|
uid=78_21 listitem level="1"
|
||||||
|
uid=78_22 tab "Outils de son d’ambiance" selectable
|
||||||
|
uid=78_23 listitem level="1"
|
||||||
|
uid=78_24 tab "Outils de région" selectable
|
||||||
|
uid=78_25 listitem level="1"
|
||||||
|
uid=78_26 tab "Notes" selectable
|
||||||
|
uid=78_27 list
|
||||||
|
uid=78_28 listitem level="1"
|
||||||
|
uid=78_29 button "Sélection de tokens" pressed
|
||||||
|
uid=78_30 listitem level="1"
|
||||||
|
uid=78_31 button "Sélection de cibles"
|
||||||
|
uid=78_32 listitem level="1"
|
||||||
|
uid=78_33 button "Règle"
|
||||||
|
uid=78_34 listitem level="1"
|
||||||
|
uid=78_35 button "Déplacement sans contrainte"
|
||||||
|
uid=78_36 generic
|
||||||
|
uid=78_37 generic
|
||||||
|
uid=78_38 list
|
||||||
|
uid=78_39 listitem level="1"
|
||||||
|
uid=78_40 ignored
|
||||||
|
uid=78_41 StaticText "Gamemaster [MJ]"
|
||||||
|
uid=78_42 InlineTextBox "Gamemaster [MJ]"
|
||||||
|
uid=78_43 generic
|
||||||
|
uid=78_44 generic
|
||||||
|
uid=78_45 LabelText
|
||||||
|
uid=78_46 StaticText "Latence"
|
||||||
|
uid=78_42 InlineTextBox "Latence"
|
||||||
|
uid=78_47 StaticText " "
|
||||||
|
uid=78_42 InlineTextBox " "
|
||||||
|
uid=78_48 StaticText "1ms"
|
||||||
|
uid=78_42 InlineTextBox "1ms"
|
||||||
|
uid=78_49 generic
|
||||||
|
uid=78_50 LabelText
|
||||||
|
uid=78_51 StaticText "IPS"
|
||||||
|
uid=78_42 InlineTextBox "IPS"
|
||||||
|
uid=78_52 StaticText " "
|
||||||
|
uid=78_42 InlineTextBox " "
|
||||||
|
uid=78_53 StaticText "--"
|
||||||
|
uid=78_42 InlineTextBox "--"
|
||||||
|
uid=78_54 button
|
||||||
|
uid=78_55 generic
|
||||||
|
uid=78_56 navigation
|
||||||
|
uid=78_57 list
|
||||||
|
uid=78_58 generic
|
||||||
|
uid=78_59 sectionheader
|
||||||
|
uid=78_60 generic
|
||||||
|
uid=78_61 generic
|
||||||
|
uid=78_62 LabelText
|
||||||
|
uid=78_63 LabelText
|
||||||
|
uid=78_64 sectionfooter
|
||||||
|
uid=78_65 generic roledescription="Barre de raccourcis"
|
||||||
|
uid=78_66 generic
|
||||||
|
uid=78_67 button "Couper le son"
|
||||||
|
uid=78_68 button "Menu principal"
|
||||||
|
uid=78_69 list
|
||||||
|
uid=78_70 button "Emplacement vide"
|
||||||
|
uid=78_71 ignored
|
||||||
|
uid=78_72 StaticText "1"
|
||||||
|
uid=78_42 InlineTextBox "1"
|
||||||
|
uid=78_73 button "Emplacement vide"
|
||||||
|
uid=78_74 ignored
|
||||||
|
uid=78_75 StaticText "2"
|
||||||
|
uid=78_42 InlineTextBox "2"
|
||||||
|
uid=78_76 button "Emplacement vide"
|
||||||
|
uid=78_77 ignored
|
||||||
|
uid=78_78 StaticText "3"
|
||||||
|
uid=78_42 InlineTextBox "3"
|
||||||
|
uid=78_79 button "Emplacement vide"
|
||||||
|
uid=78_80 ignored
|
||||||
|
uid=78_81 StaticText "4"
|
||||||
|
uid=78_42 InlineTextBox "4"
|
||||||
|
uid=78_82 button "Emplacement vide"
|
||||||
|
uid=78_83 ignored
|
||||||
|
uid=78_84 StaticText "5"
|
||||||
|
uid=78_42 InlineTextBox "5"
|
||||||
|
uid=78_85 button "Emplacement vide"
|
||||||
|
uid=78_86 ignored
|
||||||
|
uid=78_87 StaticText "6"
|
||||||
|
uid=78_42 InlineTextBox "6"
|
||||||
|
uid=78_88 button "Emplacement vide"
|
||||||
|
uid=78_89 ignored
|
||||||
|
uid=78_90 StaticText "7"
|
||||||
|
uid=78_42 InlineTextBox "7"
|
||||||
|
uid=78_91 button "Emplacement vide"
|
||||||
|
uid=78_92 ignored
|
||||||
|
uid=78_93 StaticText "8"
|
||||||
|
uid=78_42 InlineTextBox "8"
|
||||||
|
uid=78_94 button "Emplacement vide"
|
||||||
|
uid=78_95 ignored
|
||||||
|
uid=78_96 StaticText "9"
|
||||||
|
uid=78_42 InlineTextBox "9"
|
||||||
|
uid=78_97 button "Emplacement vide"
|
||||||
|
uid=78_98 ignored
|
||||||
|
uid=78_99 StaticText "0"
|
||||||
|
uid=78_42 InlineTextBox "0"
|
||||||
|
uid=78_100 generic
|
||||||
|
uid=78_101 navigation
|
||||||
|
uid=78_102 button "Page suivante"
|
||||||
|
uid=78_103 ignored
|
||||||
|
uid=78_104 StaticText "1"
|
||||||
|
uid=78_42 InlineTextBox "1"
|
||||||
|
uid=78_105 button "Page précédente"
|
||||||
|
uid=78_106 ignored
|
||||||
|
uid=78_107 button "Verrouiller la barre de raccourcis"
|
||||||
|
uid=78_108 button "Effacer la barre de raccourcis"
|
||||||
|
uid=78_109 generic
|
||||||
|
uid=78_110 generic
|
||||||
|
uid=78_111 generic
|
||||||
|
uid=78_112 ignored
|
||||||
|
uid=78_113 list
|
||||||
|
uid=78_114 generic "Tchat"
|
||||||
|
uid=78_115 generic
|
||||||
|
uid=78_116 generic value="
|
||||||
|
"
|
||||||
|
uid=78_117 paragraph
|
||||||
|
uid=78_118 LineBreak "
|
||||||
|
"
|
||||||
|
uid=78_42 InlineTextBox "
|
||||||
|
"
|
||||||
|
uid=78_119 ignored
|
||||||
|
uid=78_42 generic
|
||||||
|
uid=78_42 StaticText "Entrer un message"
|
||||||
|
uid=78_42 InlineTextBox "Entrer un message"
|
||||||
|
uid=78_120 generic
|
||||||
|
uid=78_121 generic
|
||||||
|
uid=78_122 button "Public en tant qu'utilisateur" pressed
|
||||||
|
uid=78_123 button "Privé pour les maîtres de jeu"
|
||||||
|
uid=78_124 button "Aveugle pour les maîtres de jeu"
|
||||||
|
uid=78_125 button "Seulement pour soi-même"
|
||||||
|
uid=78_126 button "Public en tant que personnage"
|
||||||
|
uid=78_127 generic
|
||||||
|
uid=78_128 tablist orientation="horizontal"
|
||||||
|
uid=78_129 list
|
||||||
|
uid=78_130 listitem level="1"
|
||||||
|
uid=78_131 tab "Messages du tchat" selectable
|
||||||
|
uid=78_132 listitem level="1"
|
||||||
|
uid=78_133 tab "Rencontres de combat" selectable
|
||||||
|
uid=78_134 listitem level="1"
|
||||||
|
uid=78_135 tab "Scènes" selectable
|
||||||
|
uid=78_136 listitem level="1"
|
||||||
|
uid=78_137 tab "Objets plaçables" selectable
|
||||||
|
uid=78_138 listitem level="1"
|
||||||
|
uid=78_139 tab "Acteurs" selectable
|
||||||
|
uid=78_140 listitem level="1"
|
||||||
|
uid=78_141 tab "Objets" selectable
|
||||||
|
uid=78_142 listitem level="1"
|
||||||
|
uid=78_143 tab "Journaux" selectable
|
||||||
|
uid=78_144 listitem level="1"
|
||||||
|
uid=78_145 tab "Tables aléatoires" selectable
|
||||||
|
uid=78_146 listitem level="1"
|
||||||
|
uid=78_147 tab "Jeux de cartes" selectable
|
||||||
|
uid=78_148 listitem level="1"
|
||||||
|
uid=78_149 tab "Macros" selectable
|
||||||
|
uid=78_150 listitem level="1"
|
||||||
|
uid=78_151 tab "Playlists" selectable
|
||||||
|
uid=78_152 listitem level="1"
|
||||||
|
uid=78_153 tab "Compendiums" selectable
|
||||||
|
uid=78_154 listitem level="1"
|
||||||
|
uid=78_155 tab "Paramètres" selectable
|
||||||
|
uid=78_156 listitem level="1"
|
||||||
|
uid=78_157 button "Réduire"
|
||||||
|
uid=78_158 generic
|
||||||
|
uid=78_159 ignored
|
||||||
|
uid=78_160 ignored
|
||||||
|
uid=78_161 ignored
|
||||||
|
uid=78_162 ignored
|
||||||
|
uid=78_163 generic
|
||||||
|
uid=78_164 sectionheader
|
||||||
|
uid=78_165 ignored
|
||||||
|
uid=78_166 button "Créer un acteur"
|
||||||
|
uid=78_167 ignored
|
||||||
|
uid=78_168 ignored
|
||||||
|
uid=78_169 StaticText "Créer un acteur"
|
||||||
|
uid=78_42 InlineTextBox "Créer un acteur"
|
||||||
|
uid=78_170 button "Créer un dossier"
|
||||||
|
uid=78_171 ignored
|
||||||
|
uid=78_172 ignored
|
||||||
|
uid=78_173 StaticText "Créer un dossier"
|
||||||
|
uid=78_42 InlineTextBox "Créer un dossier"
|
||||||
|
uid=78_174 search
|
||||||
|
uid=78_175 button "Recherche par nom uniquement"
|
||||||
|
uid=78_176 searchbox "Chercher dans les Acteurs"
|
||||||
|
uid=78_177 ignored
|
||||||
|
uid=78_178 ignored
|
||||||
|
uid=78_179 generic
|
||||||
|
uid=78_180 button "Trier par ordre alphabétique"
|
||||||
|
uid=78_181 button "Réduire tous les dossiers"
|
||||||
|
uid=78_182 list
|
||||||
|
uid=78_183 listitem level="1"
|
||||||
|
uid=78_184 image "Acteur" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/creature.webp"
|
||||||
|
uid=78_185 generic
|
||||||
|
uid=78_186 StaticText "Acteur"
|
||||||
|
uid=78_42 InlineTextBox "Acteur"
|
||||||
|
uid=78_187 listitem level="1"
|
||||||
|
uid=78_188 image "Acteur (2)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/group.webp"
|
||||||
|
uid=78_189 generic
|
||||||
|
uid=78_190 StaticText "Acteur (2)"
|
||||||
|
uid=78_42 InlineTextBox "Acteur (2)"
|
||||||
|
uid=78_191 listitem level="1"
|
||||||
|
uid=78_192 image "Acteur (3)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/character.webp"
|
||||||
|
uid=78_193 generic
|
||||||
|
uid=78_194 StaticText "Acteur (3)"
|
||||||
|
uid=78_42 InlineTextBox "Acteur (3)"
|
||||||
|
uid=78_195 sectionfooter
|
||||||
|
uid=78_196 ignored
|
||||||
|
uid=78_197 ignored
|
||||||
|
uid=78_198 ignored
|
||||||
|
uid=78_199 ignored
|
||||||
|
uid=78_200 ignored
|
||||||
|
uid=78_201 ignored
|
||||||
|
uid=78_202 ignored
|
||||||
|
uid=78_203 ignored
|
||||||
|
uid=78_204 ignored
|
||||||
|
uid=78_205 ignored
|
||||||
|
uid=78_206 ignored
|
||||||
|
uid=78_207 ignored
|
||||||
|
uid=78_208 ignored
|
||||||
|
uid=78_209 ignored
|
||||||
|
uid=78_210 ignored
|
||||||
|
uid=78_211 ignored
|
||||||
|
uid=78_212 ignored
|
||||||
|
uid=78_213 ignored
|
||||||
|
uid=78_214 ignored
|
||||||
|
uid=78_215 ignored
|
||||||
|
uid=78_216 ignored
|
||||||
|
uid=78_217 ignored
|
||||||
|
uid=78_218 ignored
|
||||||
|
uid=78_219 figure
|
||||||
|
uid=78_220 image url="https://localhost:31000/ui/pause.svg"
|
||||||
|
uid=78_221 Figcaption
|
||||||
|
uid=78_222 StaticText "JEU EN PAUSE"
|
||||||
|
uid=78_42 InlineTextBox "JEU EN PAUSE"
|
||||||
|
uid=78_223 form
|
||||||
|
uid=78_224 banner
|
||||||
|
uid=78_225 heading "Personnage: Acteur (3)" level="1"
|
||||||
|
uid=78_226 StaticText "Personnage: Acteur (3)"
|
||||||
|
uid=78_42 InlineTextBox "Personnage: Acteur (3)"
|
||||||
|
uid=78_227 button "Basculer les contrôles"
|
||||||
|
uid=78_228 button "Copier l'UUID du document"
|
||||||
|
uid=78_229 button "Fermer la fenêtre"
|
||||||
|
uid=78_230 generic
|
||||||
|
uid=78_231 ignored
|
||||||
|
uid=78_232 ignored
|
||||||
|
uid=78_233 button "MODE ÉDITION"
|
||||||
|
uid=78_234 StaticText "MODE ÉDITION"
|
||||||
|
uid=78_42 InlineTextBox "MODE ÉDITION"
|
||||||
|
uid=78_235 image "logo Vermine" url="https://localhost:31000/systems/vermine2047/assets/images/ui/logo.webp"
|
||||||
|
uid=78_236 ignored
|
||||||
|
uid=78_237 image "Acteur (3)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/character.webp"
|
||||||
|
uid=78_238 generic
|
||||||
|
uid=78_239 heading "ADAPTATION" level="3"
|
||||||
|
uid=78_240 StaticText "ADAPTATION"
|
||||||
|
uid=78_42 InlineTextBox "ADAPTATION"
|
||||||
|
uid=78_241 heading "L'Humain" level="5"
|
||||||
|
uid=78_242 StaticText "L'Humain"
|
||||||
|
uid=78_42 InlineTextBox "L'Humain"
|
||||||
|
uid=78_243 image url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/human.webp"
|
||||||
|
uid=78_244 heading "L'adapté" level="5"
|
||||||
|
uid=78_245 StaticText "L'adapté"
|
||||||
|
uid=78_42 InlineTextBox "L'adapté"
|
||||||
|
uid=78_246 image url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/adapted.webp"
|
||||||
|
uid=78_247 ignored
|
||||||
|
uid=78_248 ignored
|
||||||
|
uid=78_249 ignored
|
||||||
|
uid=78_250 generic
|
||||||
|
uid=78_251 generic
|
||||||
|
uid=78_252 ignored
|
||||||
|
uid=78_253 ignored
|
||||||
|
uid=78_254 ignored
|
||||||
|
uid=78_255 ignored
|
||||||
|
uid=78_256 generic
|
||||||
|
uid=78_257 generic
|
||||||
|
uid=78_258 ignored
|
||||||
|
uid=78_259 ignored
|
||||||
|
uid=78_260 list
|
||||||
|
uid=78_261 listitem level="1"
|
||||||
|
uid=78_262 ignored
|
||||||
|
uid=78_263 heading "LA HORDE" level="4"
|
||||||
|
uid=78_264 StaticText "LA HORDE"
|
||||||
|
uid=78_42 InlineTextBox "LA HORDE"
|
||||||
|
uid=78_265 image "La Horde" url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/horde.webp"
|
||||||
|
uid=78_266 navigation roledescription="Navigation dans lâonglet des feuilles de personnages"
|
||||||
|
uid=78_267 generic
|
||||||
|
uid=78_268 ignored
|
||||||
|
uid=78_269 StaticText "Caractéristiques et compétences"
|
||||||
|
uid=78_42 InlineTextBox "Caractéristiques "
|
||||||
|
uid=78_42 InlineTextBox "et compétences"
|
||||||
|
uid=78_270 generic
|
||||||
|
uid=78_271 ignored
|
||||||
|
uid=78_272 StaticText "Totem et ajustements"
|
||||||
|
uid=78_42 InlineTextBox "Totem et "
|
||||||
|
uid=78_42 InlineTextBox "ajustements"
|
||||||
|
uid=78_273 generic
|
||||||
|
uid=78_274 ignored
|
||||||
|
uid=78_275 StaticText "Matériel"
|
||||||
|
uid=78_42 InlineTextBox "Matériel"
|
||||||
|
uid=78_276 generic
|
||||||
|
uid=78_277 ignored
|
||||||
|
uid=78_278 StaticText "Histoire"
|
||||||
|
uid=78_42 InlineTextBox "Histoire"
|
||||||
|
uid=78_279 generic
|
||||||
|
uid=78_280 ignored
|
||||||
|
uid=78_281 StaticText "Combat et reserves"
|
||||||
|
uid=78_42 InlineTextBox "Combat "
|
||||||
|
uid=78_42 InlineTextBox "et "
|
||||||
|
uid=78_42 InlineTextBox "reserves"
|
||||||
|
uid=78_282 generic
|
||||||
|
uid=78_283 sectionheader
|
||||||
|
uid=78_284 generic
|
||||||
|
uid=78_285 heading "Nom Acteur (3)" level="1"
|
||||||
|
uid=78_286 LabelText
|
||||||
|
uid=78_287 StaticText "Nom"
|
||||||
|
uid=78_42 InlineTextBox "Nom"
|
||||||
|
uid=78_288 ignored
|
||||||
|
uid=78_289 StaticText "Acteur (3)"
|
||||||
|
uid=78_42 InlineTextBox "Acteur (3)"
|
||||||
|
uid=78_290 ignored
|
||||||
|
uid=78_291 LabelText
|
||||||
|
uid=78_292 StaticText "Profil"
|
||||||
|
uid=78_42 InlineTextBox "Profil"
|
||||||
|
uid=78_293 ignored
|
||||||
|
uid=78_294 ignored
|
||||||
|
uid=78_295 LabelText
|
||||||
|
uid=78_296 StaticText "Age"
|
||||||
|
uid=78_42 InlineTextBox "Age"
|
||||||
|
uid=78_297 ignored
|
||||||
|
uid=78_298 ignored
|
||||||
|
uid=78_299 StaticText "15"
|
||||||
|
uid=78_42 InlineTextBox "15"
|
||||||
|
uid=78_300 generic
|
||||||
|
uid=78_301 StaticText "(Jeune)"
|
||||||
|
uid=78_42 InlineTextBox "(Jeune)"
|
||||||
|
uid=78_302 generic
|
||||||
|
uid=78_303 heading "Totem La Horde" level="1"
|
||||||
|
uid=78_304 LabelText
|
||||||
|
uid=78_305 StaticText "Totem"
|
||||||
|
uid=78_42 InlineTextBox "Totem"
|
||||||
|
uid=78_306 generic
|
||||||
|
uid=78_307 StaticText "La Horde"
|
||||||
|
uid=78_42 InlineTextBox "La Horde"
|
||||||
|
uid=78_308 ignored
|
||||||
|
uid=78_309 LabelText
|
||||||
|
uid=78_310 StaticText "Réputation"
|
||||||
|
uid=78_42 InlineTextBox "Réputation"
|
||||||
|
uid=78_311 ignored
|
||||||
|
uid=78_312 StaticText "0"
|
||||||
|
uid=78_42 InlineTextBox "0"
|
||||||
|
uid=78_313 LabelText
|
||||||
|
uid=78_314 StaticText "Expérience"
|
||||||
|
uid=78_42 InlineTextBox "Expérience"
|
||||||
|
uid=78_315 ignored
|
||||||
|
uid=78_316 StaticText "0"
|
||||||
|
uid=78_42 InlineTextBox "0"
|
||||||
|
uid=78_317 heading "CARACTÉRISTIQUES" level="3"
|
||||||
|
uid=78_318 StaticText "CARACTÉRISTIQUES"
|
||||||
|
uid=78_42 InlineTextBox "CARACTÉRISTIQUES"
|
||||||
|
uid=78_319 ignored
|
||||||
|
uid=78_320 ignored
|
||||||
|
uid=78_321 heading "PHYSIQUE" level="4"
|
||||||
|
uid=78_322 StaticText "PHYSIQUE"
|
||||||
|
uid=78_42 InlineTextBox "PHYSIQUE"
|
||||||
|
uid=78_323 generic
|
||||||
|
uid=78_324 LabelText
|
||||||
|
uid=78_325 StaticText "Vigueur"
|
||||||
|
uid=78_42 InlineTextBox "Vigueur"
|
||||||
|
uid=78_326 generic
|
||||||
|
uid=78_327 LabelText
|
||||||
|
uid=78_328 StaticText "Santé"
|
||||||
|
uid=78_42 InlineTextBox "Santé"
|
||||||
|
uid=78_329 ignored
|
||||||
|
uid=78_330 heading "MANUEL" level="4"
|
||||||
|
uid=78_331 StaticText "MANUEL"
|
||||||
|
uid=78_42 InlineTextBox "MANUEL"
|
||||||
|
uid=78_332 generic
|
||||||
|
uid=78_333 LabelText
|
||||||
|
uid=78_334 StaticText "Précision"
|
||||||
|
uid=78_42 InlineTextBox "Précision"
|
||||||
|
uid=78_335 generic
|
||||||
|
uid=78_336 LabelText
|
||||||
|
uid=78_337 StaticText "Réflexes"
|
||||||
|
uid=78_42 InlineTextBox "Réflexes"
|
||||||
|
uid=78_338 ignored
|
||||||
|
uid=78_339 heading "MENTAL" level="4"
|
||||||
|
uid=78_340 StaticText "MENTAL"
|
||||||
|
uid=78_42 InlineTextBox "MENTAL"
|
||||||
|
uid=78_341 generic
|
||||||
|
uid=78_342 LabelText
|
||||||
|
uid=78_343 StaticText "Savoir"
|
||||||
|
uid=78_42 InlineTextBox "Savoir"
|
||||||
|
uid=78_344 generic
|
||||||
|
uid=78_345 LabelText
|
||||||
|
uid=78_346 StaticText "Perception"
|
||||||
|
uid=78_42 InlineTextBox "Perception"
|
||||||
|
uid=78_347 ignored
|
||||||
|
uid=78_348 heading "SOCIAL" level="4"
|
||||||
|
uid=78_349 StaticText "SOCIAL"
|
||||||
|
uid=78_42 InlineTextBox "SOCIAL"
|
||||||
|
uid=78_350 generic
|
||||||
|
uid=78_351 LabelText
|
||||||
|
uid=78_352 StaticText "Volonté"
|
||||||
|
uid=78_42 InlineTextBox "Volonté"
|
||||||
|
uid=78_353 generic
|
||||||
|
uid=78_354 LabelText
|
||||||
|
uid=78_355 StaticText "Empathie"
|
||||||
|
uid=78_42 InlineTextBox "Empathie"
|
||||||
|
uid=78_356 heading "COMPÉTENCES" level="3"
|
||||||
|
uid=78_357 StaticText "COMPÉTENCES"
|
||||||
|
uid=78_42 InlineTextBox "COMPÉTENCES"
|
||||||
|
uid=78_358 ignored
|
||||||
|
uid=78_359 ignored
|
||||||
|
uid=78_360 heading "L'HOMME" level="4"
|
||||||
|
uid=78_361 StaticText "L'HOMME"
|
||||||
|
uid=78_42 InlineTextBox "L'HOMME"
|
||||||
|
uid=78_362 generic
|
||||||
|
uid=78_363 LabelText
|
||||||
|
uid=78_364 StaticText "Arts "
|
||||||
|
uid=78_42 InlineTextBox "Arts "
|
||||||
|
uid=78_365 superscript
|
||||||
|
uid=78_366 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_367 generic
|
||||||
|
uid=78_368 LabelText
|
||||||
|
uid=78_369 StaticText "Civilisation "
|
||||||
|
uid=78_42 InlineTextBox "Civilisation "
|
||||||
|
uid=78_370 superscript
|
||||||
|
uid=78_371 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_372 generic
|
||||||
|
uid=78_373 LabelText
|
||||||
|
uid=78_374 StaticText "Psychologie "
|
||||||
|
uid=78_42 InlineTextBox "Psychologie "
|
||||||
|
uid=78_375 superscript
|
||||||
|
uid=78_376 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_377 generic
|
||||||
|
uid=78_378 LabelText
|
||||||
|
uid=78_379 StaticText "Rumeurs"
|
||||||
|
uid=78_42 InlineTextBox "Rumeurs"
|
||||||
|
uid=78_380 generic
|
||||||
|
uid=78_381 LabelText
|
||||||
|
uid=78_382 StaticText "Soins "
|
||||||
|
uid=78_42 InlineTextBox "Soins "
|
||||||
|
uid=78_383 superscript
|
||||||
|
uid=78_384 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_385 ignored
|
||||||
|
uid=78_386 heading "L'ANIMAL" level="4"
|
||||||
|
uid=78_387 StaticText "L'ANIMAL"
|
||||||
|
uid=78_42 InlineTextBox "L'ANIMAL"
|
||||||
|
uid=78_388 generic
|
||||||
|
uid=78_389 LabelText
|
||||||
|
uid=78_390 StaticText "Animalisme "
|
||||||
|
uid=78_42 InlineTextBox "Animalisme "
|
||||||
|
uid=78_391 superscript
|
||||||
|
uid=78_392 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_393 generic
|
||||||
|
uid=78_394 LabelText
|
||||||
|
uid=78_395 StaticText "Dissection "
|
||||||
|
uid=78_42 InlineTextBox "Dissection "
|
||||||
|
uid=78_396 superscript
|
||||||
|
uid=78_397 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_398 generic
|
||||||
|
uid=78_399 LabelText
|
||||||
|
uid=78_400 StaticText "Faune "
|
||||||
|
uid=78_42 InlineTextBox "Faune "
|
||||||
|
uid=78_401 superscript
|
||||||
|
uid=78_402 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_403 generic
|
||||||
|
uid=78_404 LabelText
|
||||||
|
uid=78_405 StaticText "Répulsion"
|
||||||
|
uid=78_42 InlineTextBox "Répulsion"
|
||||||
|
uid=78_406 generic
|
||||||
|
uid=78_407 LabelText
|
||||||
|
uid=78_408 StaticText "Pistage"
|
||||||
|
uid=78_42 InlineTextBox "Pistage"
|
||||||
|
uid=78_409 ignored
|
||||||
|
uid=78_410 heading "LA MACHINE" level="4"
|
||||||
|
uid=78_411 StaticText "LA MACHINE"
|
||||||
|
uid=78_42 InlineTextBox "LA MACHINE"
|
||||||
|
uid=78_412 generic
|
||||||
|
uid=78_413 LabelText
|
||||||
|
uid=78_414 StaticText "Artisanat "
|
||||||
|
uid=78_42 InlineTextBox "Artisanat "
|
||||||
|
uid=78_415 superscript
|
||||||
|
uid=78_416 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_417 generic
|
||||||
|
uid=78_418 LabelText
|
||||||
|
uid=78_419 StaticText "Bricolage"
|
||||||
|
uid=78_42 InlineTextBox "Bricolage"
|
||||||
|
uid=78_420 generic
|
||||||
|
uid=78_421 LabelText
|
||||||
|
uid=78_422 StaticText "Mécanique "
|
||||||
|
uid=78_42 InlineTextBox "Mécanique "
|
||||||
|
uid=78_423 superscript
|
||||||
|
uid=78_424 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_425 generic
|
||||||
|
uid=78_426 LabelText
|
||||||
|
uid=78_427 StaticText "Pilotage "
|
||||||
|
uid=78_42 InlineTextBox "Pilotage "
|
||||||
|
uid=78_428 superscript
|
||||||
|
uid=78_429 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_430 generic
|
||||||
|
uid=78_431 LabelText
|
||||||
|
uid=78_432 StaticText "Technologie "
|
||||||
|
uid=78_42 InlineTextBox "Technologie "
|
||||||
|
uid=78_433 superscript
|
||||||
|
uid=78_434 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_435 ignored
|
||||||
|
uid=78_436 heading "L'ARME" level="4"
|
||||||
|
uid=78_437 StaticText "L'ARME"
|
||||||
|
uid=78_42 InlineTextBox "L'ARME"
|
||||||
|
uid=78_438 generic
|
||||||
|
uid=78_439 LabelText
|
||||||
|
uid=78_440 StaticText "Armes à feu "
|
||||||
|
uid=78_42 InlineTextBox "Armes à feu "
|
||||||
|
uid=78_441 superscript
|
||||||
|
uid=78_442 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_443 generic
|
||||||
|
uid=78_444 LabelText
|
||||||
|
uid=78_445 StaticText "Tir à l'arc"
|
||||||
|
uid=78_42 InlineTextBox "Tir à l'arc"
|
||||||
|
uid=78_446 generic
|
||||||
|
uid=78_447 LabelText
|
||||||
|
uid=78_448 StaticText "Armurerie "
|
||||||
|
uid=78_42 InlineTextBox "Armurerie "
|
||||||
|
uid=78_449 superscript
|
||||||
|
uid=78_450 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_451 generic
|
||||||
|
uid=78_452 LabelText
|
||||||
|
uid=78_453 StaticText "Lancer"
|
||||||
|
uid=78_42 InlineTextBox "Lancer"
|
||||||
|
uid=78_454 generic
|
||||||
|
uid=78_455 LabelText
|
||||||
|
uid=78_456 StaticText "Mêlée"
|
||||||
|
uid=78_42 InlineTextBox "Mêlée"
|
||||||
|
uid=78_457 ignored
|
||||||
|
uid=78_458 heading "LA SURVIE" level="4"
|
||||||
|
uid=78_459 StaticText "LA SURVIE"
|
||||||
|
uid=78_42 InlineTextBox "LA SURVIE"
|
||||||
|
uid=78_460 generic
|
||||||
|
uid=78_461 LabelText
|
||||||
|
uid=78_462 StaticText "Vigilance"
|
||||||
|
uid=78_42 InlineTextBox "Vigilance"
|
||||||
|
uid=78_463 generic
|
||||||
|
uid=78_464 LabelText
|
||||||
|
uid=78_465 StaticText "Athlétisme"
|
||||||
|
uid=78_42 InlineTextBox "Athlétisme"
|
||||||
|
uid=78_466 generic
|
||||||
|
uid=78_467 LabelText
|
||||||
|
uid=78_468 StaticText "Alimentation"
|
||||||
|
uid=78_42 InlineTextBox "Alimentation"
|
||||||
|
uid=78_469 generic
|
||||||
|
uid=78_470 LabelText
|
||||||
|
uid=78_471 StaticText "Discrétion"
|
||||||
|
uid=78_42 InlineTextBox "Discrétion"
|
||||||
|
uid=78_472 generic
|
||||||
|
uid=78_473 LabelText
|
||||||
|
uid=78_474 StaticText "Corps-à-corps"
|
||||||
|
uid=78_42 InlineTextBox "Corps-à-corps"
|
||||||
|
uid=78_475 ignored
|
||||||
|
uid=78_476 heading "LA TERRE" level="4"
|
||||||
|
uid=78_477 StaticText "LA TERRE"
|
||||||
|
uid=78_42 InlineTextBox "LA TERRE"
|
||||||
|
uid=78_478 generic
|
||||||
|
uid=78_479 LabelText
|
||||||
|
uid=78_480 StaticText "Environnement "
|
||||||
|
uid=78_42 InlineTextBox "Environnement "
|
||||||
|
uid=78_481 superscript
|
||||||
|
uid=78_482 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_483 generic
|
||||||
|
uid=78_484 LabelText
|
||||||
|
uid=78_485 StaticText "Flore "
|
||||||
|
uid=78_42 InlineTextBox "Flore "
|
||||||
|
uid=78_486 superscript
|
||||||
|
uid=78_487 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_488 generic
|
||||||
|
uid=78_489 LabelText
|
||||||
|
uid=78_490 StaticText "Route"
|
||||||
|
uid=78_42 InlineTextBox "Route"
|
||||||
|
uid=78_491 generic
|
||||||
|
uid=78_492 LabelText
|
||||||
|
uid=78_493 StaticText "Toxiques "
|
||||||
|
uid=78_42 InlineTextBox "Toxiques "
|
||||||
|
uid=78_494 superscript
|
||||||
|
uid=78_495 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_496 generic
|
||||||
|
uid=78_497 LabelText
|
||||||
|
uid=78_498 StaticText "Vestiges "
|
||||||
|
uid=78_42 InlineTextBox "Vestiges "
|
||||||
|
uid=78_499 superscript
|
||||||
|
uid=78_500 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_501 ignored
|
||||||
|
uid=78_502 ignored
|
||||||
|
uid=78_503 ignored
|
||||||
|
uid=78_504 ignored
|
||||||
|
uid=78_505 ignored
|
||||||
|
uid=78_506 ignored
|
||||||
|
uid=78_507 ignored
|
||||||
|
uid=78_508 ignored
|
||||||
|
uid=78_509 ignored
|
||||||
|
uid=78_510 ignored
|
||||||
|
uid=78_511 ignored
|
||||||
|
uid=78_512 ignored
|
||||||
|
uid=78_513 ignored
|
||||||
|
uid=78_514 ignored
|
||||||
|
uid=78_515 ignored
|
||||||
|
uid=78_516 ignored
|
||||||
|
uid=78_517 ignored
|
||||||
|
uid=78_518 ignored
|
||||||
|
uid=78_519 ignored
|
||||||
|
uid=78_520 ignored
|
||||||
|
uid=78_521 ignored
|
||||||
|
uid=78_522 ignored
|
||||||
|
uid=78_523 ignored
|
||||||
|
uid=78_524 ignored
|
||||||
|
uid=78_525 ignored
|
||||||
|
uid=78_526 ignored
|
||||||
|
uid=78_527 ignored
|
||||||
|
uid=78_528 ignored
|
||||||
|
uid=78_529 ignored
|
||||||
|
uid=78_530 ignored
|
||||||
|
uid=78_531 ignored
|
||||||
|
uid=78_532 ignored
|
||||||
|
uid=78_533 ignored
|
||||||
|
uid=78_534 ignored
|
||||||
|
uid=78_535 ignored
|
||||||
|
uid=78_536 ignored
|
||||||
|
uid=78_537 ignored
|
||||||
|
uid=78_538 ignored
|
||||||
|
uid=78_539 ignored
|
||||||
|
uid=78_540 ignored
|
||||||
|
uid=78_541 ignored
|
||||||
|
uid=78_542 ignored
|
||||||
|
uid=78_543 ignored
|
||||||
|
uid=78_544 ignored
|
||||||
|
uid=78_545 ignored
|
||||||
|
uid=78_546 ignored
|
||||||
|
uid=78_547 ignored
|
||||||
|
uid=78_548 ignored
|
||||||
|
uid=78_549 ignored
|
||||||
|
uid=78_550 ignored
|
||||||
|
uid=78_551 ignored
|
||||||
|
uid=78_552 ignored
|
||||||
|
uid=78_553 ignored
|
||||||
|
uid=78_554 ignored
|
||||||
|
uid=78_555 generic
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
vermine2047.min.css
|
||||||
@@ -0,0 +1,696 @@
|
|||||||
|
# Guide Utilisateur - Vermine2047 pour FoundryVTT
|
||||||
|
|
||||||
|
## Table des matières
|
||||||
|
1. [Introduction](#introduction)
|
||||||
|
2. [Installation](#installation)
|
||||||
|
3. [Création d'un personnage](#création-dun-personnage)
|
||||||
|
4. [Les jets de dés](#les-jets-de-dés)
|
||||||
|
5. [Le système de totems](#le-système-de-totems)
|
||||||
|
6. [La gestion des groupes](#la-gestion-des-groupes)
|
||||||
|
7. [Le combat](#le-combat)
|
||||||
|
8. [Les items](#les-items)
|
||||||
|
9. [Gestion des PNJ et Créatures](#gestion-des-pnj-et-créatures)
|
||||||
|
10. [Astuces et bonnes pratiques](#astuces-et-bonnes-pratiques)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Bienvenue dans Vermine2047, un système pour FoundryVTT qui implémente les règles du jeu de rôle post-apocalyptique Vermine 2047.
|
||||||
|
|
||||||
|
### À propos de Vermine2047
|
||||||
|
|
||||||
|
Vermine 2047 est un jeu de rôle dans un monde post-apocalyptique où les joueurs incarnent des survivants dans un environnement hostile. Le système utilise des dés d10 avec un système de seuils de réussite et des mécaniques uniques comme les totems et les domaines de prédilection.
|
||||||
|
|
||||||
|
### Compatibilité
|
||||||
|
|
||||||
|
- **FoundryVTT**: v11 à v14
|
||||||
|
- **Version du système**: 0.1.14
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prérequis
|
||||||
|
|
||||||
|
- FoundryVTT installé (version 11 ou supérieure)
|
||||||
|
- Module "Dice So Nice!" recommandé pour les dés 3D
|
||||||
|
|
||||||
|
### Installation du système
|
||||||
|
|
||||||
|
1. **Via le compendium de Foundry**
|
||||||
|
- Ouvrez FoundryVTT
|
||||||
|
- Allez dans "Game Systems"
|
||||||
|
- Cliquez sur "Install System"
|
||||||
|
- Recherchez "Vermine2047"
|
||||||
|
- Cliquez sur "Install"
|
||||||
|
|
||||||
|
2. **Via l'URL du manifest**
|
||||||
|
- Allez dans "Game Systems"
|
||||||
|
- Cliquez sur "Install System"
|
||||||
|
- Dans l'onglet "From Manifest URL", entrez:
|
||||||
|
```
|
||||||
|
https://raw.githubusercontent.com/rwanoux/vermine2047/refs/heads/main/system.json
|
||||||
|
```
|
||||||
|
- Cliquez sur "Install"
|
||||||
|
|
||||||
|
3. **Créer un nouveau monde**
|
||||||
|
- Sélectionnez "Vermine2047" comme système
|
||||||
|
- Donnez un nom à votre monde
|
||||||
|
- Configurez les paramètres
|
||||||
|
|
||||||
|
### Configuration recommandée
|
||||||
|
|
||||||
|
- Activez le module "Dice So Nice!" pour les dés 3D
|
||||||
|
- Configurez le mode de jeu (Survie, Cauchemar, Apocalypse) dans les paramètres du monde
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Création d'un personnage
|
||||||
|
|
||||||
|
### Étape 1: Créer un acteur
|
||||||
|
|
||||||
|
1. Cliquez sur l'icône "Actors" dans la barre latérale gauche
|
||||||
|
2. Cliquez sur "Create Actor"
|
||||||
|
3. Sélectionnez "Character" comme type
|
||||||
|
4. Donnez un nom à votre personnage
|
||||||
|
|
||||||
|
### Étape 2: Choisir un totem
|
||||||
|
|
||||||
|
Le totem est au cœur de votre personnage et détermine ses affinités.
|
||||||
|
|
||||||
|
1. Dans l'onglet "Totem et ajustements"
|
||||||
|
2. Cliquez sur le bouton "choisissez un totem"
|
||||||
|
3. Sélectionnez un totem dans la liste
|
||||||
|
4. Cliquez sur "Sélectionner"
|
||||||
|
|
||||||
|
**Les 10 totems disponibles:**
|
||||||
|
- **Humain**: Favorise les compétences humaines et du monde civilisé
|
||||||
|
- **Prédateur**: Favorise la chasse et la survie
|
||||||
|
- **Charognard**: Favorise la récupération et l'utilisation d'outils
|
||||||
|
- **Symbiote**: Favorise les interactions sociales
|
||||||
|
- **Parasite**: Favorise la discrétion et la survie
|
||||||
|
- **Bâtisseur**: Favorise la construction et la manipulation
|
||||||
|
- **Horde**: Favorise le combat en groupe
|
||||||
|
- **Ruche**: Favorise l'organisation collective
|
||||||
|
- **Solitaire**: Favorise l'autonomie
|
||||||
|
- **Adapté**: Favorise l'adaptation à l'environnement
|
||||||
|
|
||||||
|
### Étape 3: Définir les caractéristiques
|
||||||
|
|
||||||
|
Dans l'onglet "Caractéristiques et compétences", vous trouverez 8 caractéristiques réparties en 4 catégories:
|
||||||
|
|
||||||
|
**Physique:**
|
||||||
|
- Vigueur: Résistance physique
|
||||||
|
- Santé: Résistance aux blessures
|
||||||
|
|
||||||
|
**Manuel:**
|
||||||
|
- Précision: Dextérité et coordination
|
||||||
|
- Réflexes: Réactivité et vitesse
|
||||||
|
|
||||||
|
**Mental:**
|
||||||
|
- Connaissance: Savoir et mémoire
|
||||||
|
- Perception: Observation et intuition
|
||||||
|
|
||||||
|
**Social:**
|
||||||
|
- Volonté: Détermination et courage
|
||||||
|
- Empathie: Compréhension des autres
|
||||||
|
|
||||||
|
**Conseil:** Commencez avec des valeurs de 1-2 pour un personnage équilibré, ou 3-4 pour un spécialiste.
|
||||||
|
|
||||||
|
### Étape 4: Définir les compétences
|
||||||
|
|
||||||
|
Chaque compétence a une valeur de 0 à 5:
|
||||||
|
- 0: Incompétent
|
||||||
|
- 1: Débutant
|
||||||
|
- 2: Compétent
|
||||||
|
- 3: Expert
|
||||||
|
- 4: Maître
|
||||||
|
- 5: Légende
|
||||||
|
|
||||||
|
**Catégories de compétences:**
|
||||||
|
- **Humain**: Arts, civilisation, psychologie, rumeurs, soins
|
||||||
|
- **Animal**: Animalisme, dissection, vie sauvage, répulsion, pistes
|
||||||
|
- **Outil**: Artisanat, bricolage, mécanique, pilotage, technologie
|
||||||
|
- **Arme**: Armes à feu, tir à l'arc, armurerie, lancer, mêlée
|
||||||
|
- **Survie**: Vigilance, athlétisme, nourriture, discrétion, corps à corps
|
||||||
|
- **Monde**: Environnement, flore, route, toxiques, ruines
|
||||||
|
|
||||||
|
**Astuce:** Le domaine de prédilection (sélectionnable en haut de chaque catégorie) donne des bonus quand il est aligné avec votre totem.
|
||||||
|
|
||||||
|
### Étape 5: Ajouter des spécialités
|
||||||
|
|
||||||
|
Les spécialités donnent +1D quand elles sont utilisées avec la compétence parente.
|
||||||
|
|
||||||
|
1. Dans l'onglet "Caractéristiques et compétences"
|
||||||
|
2. Cliquez sur l'icône "+" à côté d'une compétence
|
||||||
|
3. Une spécialité sera créée avec le nom de la compétence
|
||||||
|
4. Vous pouvez renommer la spécialité
|
||||||
|
|
||||||
|
### Étape 6: Définir les réserves
|
||||||
|
|
||||||
|
Dans l'onglet "Combat et réserves":
|
||||||
|
- **Effort**: Réserve pour les actions physiques (basée sur Vigueur + Santé + Réflexes + Précision)
|
||||||
|
- **Sang-Froid**: Réserve pour les actions mentales (basée sur Connaissance + Perception + Volonté + Empathie)
|
||||||
|
|
||||||
|
### Étape 7: Définir l'identité
|
||||||
|
|
||||||
|
Dans l'onglet "Histoire":
|
||||||
|
- Age
|
||||||
|
- Origine
|
||||||
|
- Profil
|
||||||
|
- Concept
|
||||||
|
- Instincts
|
||||||
|
- Interdits
|
||||||
|
- Objectifs
|
||||||
|
- Relations
|
||||||
|
- Biographie
|
||||||
|
|
||||||
|
### Étape 8: Équipement
|
||||||
|
|
||||||
|
Dans l'onglet "Matériel", vous pouvez ajouter:
|
||||||
|
- Armes
|
||||||
|
- Protections
|
||||||
|
- Véhicules
|
||||||
|
- Objets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Les jets de dés
|
||||||
|
|
||||||
|
### Ouvrir le dialogue de jet
|
||||||
|
|
||||||
|
Il y a plusieurs façons de lancer un jet:
|
||||||
|
|
||||||
|
1. **Depuis la fiche de personnage**
|
||||||
|
- Cliquez sur une caractéristique ou une compétence
|
||||||
|
- Un dialogue de jet s'ouvre avec la caractéristique/compétence présélectionnée
|
||||||
|
|
||||||
|
2. **Depuis la barre d'outils**
|
||||||
|
- Cliquez sur l'icône de dés dans la barre d'outils (à droite)
|
||||||
|
- Un dialogue de jet vide s'ouvre
|
||||||
|
|
||||||
|
3. **Depuis le chat**
|
||||||
|
- Tapez `/roll` ou utilisez la commande de jet
|
||||||
|
|
||||||
|
### Le dialogue de jet
|
||||||
|
|
||||||
|
Le dialogue de jet a été conçu pour être intuitif et compact.
|
||||||
|
|
||||||
|
#### Sélection de base
|
||||||
|
|
||||||
|
- **Caractéristique**: Sélectionnez une caractéristique (Vigueur, Santé, etc.)
|
||||||
|
- **Compétence**: Sélectionnez une compétence (optionnel)
|
||||||
|
- **Score**: Affiche la valeur de la caractéristique sélectionnée
|
||||||
|
|
||||||
|
#### Difficulté et Handicap
|
||||||
|
|
||||||
|
- **Difficulté**: Sélectionnez le niveau de difficulté
|
||||||
|
- Evidente (3+): Tâche très simple
|
||||||
|
- Facile (5+): Tâche simple
|
||||||
|
- Difficile (7+): Tâche standard
|
||||||
|
- Très difficile (9+): Tâche complexe
|
||||||
|
- Impossible (10+): Tâche extrêmement difficile
|
||||||
|
|
||||||
|
- **Handicap**: Sélectionnez le niveau de handicap
|
||||||
|
- Aucun: Pas de handicap
|
||||||
|
- (I): Handicap mineur
|
||||||
|
- (II): Handicap majeur
|
||||||
|
|
||||||
|
#### Bonus
|
||||||
|
|
||||||
|
La section "Bonus" peut être dépliée pour accéder aux options supplémentaires:
|
||||||
|
|
||||||
|
- **Entraide**: +1D si quelqu'un vous aide
|
||||||
|
- **Groupe**: +0 à +5D basé sur la taille du groupe
|
||||||
|
- **Sang-Froid**: +0 à +5D (basé sur votre réserve de Sang-Froid)
|
||||||
|
- **Équipement**: +1D si vous utilisez un outil approprié
|
||||||
|
- **Dés de totem**: Cochez pour utiliser les dés de totem
|
||||||
|
- Totem humain: +XD (où X est la valeur de votre totem humain)
|
||||||
|
- Totem adapté: +XD (où X est la valeur de votre totem adapté)
|
||||||
|
|
||||||
|
**Astuce:** Si vous avez les deux totems (humain et adapté) avec des valeurs > 0, vous pouvez choisir quel totem garder après le jet.
|
||||||
|
|
||||||
|
#### Total du pool de dés
|
||||||
|
|
||||||
|
Le dialogue affiche le total du pool de dés en temps réel:
|
||||||
|
- **0D**: Aucune caractéristique sélectionnée
|
||||||
|
- **3D**: Caractéristique de valeur 3
|
||||||
|
- **4D**: Caractéristique 3 + Compétence 1
|
||||||
|
- **5D+**: Avec bonus
|
||||||
|
|
||||||
|
### Les bonus de domaine de totem
|
||||||
|
|
||||||
|
Votre totem influence vos jets en fonction du domaine de prédilection:
|
||||||
|
|
||||||
|
- Si votre domaine de prédilection est dans les domaines de votre totem, vous obtenez +1 dé
|
||||||
|
- Si votre domaine de prédilection est dans les domaines du totem opposé, vous subissez -1 dé
|
||||||
|
|
||||||
|
**Exemple:**
|
||||||
|
- Totem: Prédateur (domaines: animal, survie)
|
||||||
|
- Domaine de prédilection: Survie
|
||||||
|
- Bonus: +1 dé sur tous les jets de survie
|
||||||
|
|
||||||
|
### Les réussites automatiques
|
||||||
|
|
||||||
|
En fonction de votre niveau de maîtrise d'une compétence, vous obtenez des réussites automatiques:
|
||||||
|
|
||||||
|
| Niveau | Réussites automatiques | Avec spécialité |
|
||||||
|
|--------|------------------------|-----------------|
|
||||||
|
| Incompétent (0) | 0 | 0 |
|
||||||
|
| Débutant (1) | 0 | 0 |
|
||||||
|
| Compétent (2) | 0 | +1 |
|
||||||
|
| Expert (3) | +1 | +1 |
|
||||||
|
| Maître (4) | +1 | +2 |
|
||||||
|
| Légende (5) | +2 | +2 |
|
||||||
|
|
||||||
|
### Les seuils automatiques
|
||||||
|
|
||||||
|
Si vous n'êtes pas maîtrisé dans une compétence, un seuil plus élevé est automatiquement appliqué:
|
||||||
|
|
||||||
|
| Niveau | Seuil automatique |
|
||||||
|
|--------|-------------------|
|
||||||
|
| Incompétent (0) | 9 (Très difficile) |
|
||||||
|
| Débutant (1) | 7 (Difficile) |
|
||||||
|
| Compétent (2+) | Difficulté normale |
|
||||||
|
|
||||||
|
### Les dés de totem
|
||||||
|
|
||||||
|
Les dés de totem sont spéciaux:
|
||||||
|
- Ils comptent double en cas de réussite (2 réussites au lieu de 1)
|
||||||
|
- Ils sont de couleur différente pour les distinguer
|
||||||
|
- Vous pouvez utiliser les dés de totem humain et adapté simultanément
|
||||||
|
- Si vous utilisez les deux, vous pouvez choisir quel totem garder après le jet
|
||||||
|
|
||||||
|
**Exemple:**
|
||||||
|
- Pool: 3d10 + 1d10 totem humain
|
||||||
|
- Résultat: 4, 7, 2, 9 (totem humain)
|
||||||
|
- Si le seuil est 7: 2 réussites (7 et 9) + 2 réussites supplémentaires pour le 9 du totem = 4 réussites totales
|
||||||
|
|
||||||
|
### Relances
|
||||||
|
|
||||||
|
Les relances sont disponibles pour les compétences maîtrisées:
|
||||||
|
- **Niveau 2 (Compétent)**: 1 relance
|
||||||
|
- **Niveau 3 (Expert)**: 1 relance
|
||||||
|
- **Niveau 4 (Maître)**: 2 relances
|
||||||
|
- **Niveau 5 (Légende)**: 2 relances
|
||||||
|
|
||||||
|
Pour utiliser une relance:
|
||||||
|
1. Le MJ ou vous-même pouvez accorder des relances
|
||||||
|
2. Cliquez sur le dé que vous voulez relancer
|
||||||
|
3. Le dé sera marqué comme "rerolled"
|
||||||
|
4. Un nouveau jet sera effectué pour ce dé
|
||||||
|
|
||||||
|
**Astuce:** Vous pouvez aussi utiliser votre réserve de Sang-Froid pour obtenir des relances supplémentaires.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Le système de totems
|
||||||
|
|
||||||
|
### Sélection du totem
|
||||||
|
|
||||||
|
Le totem est choisi lors de la création du personnage et détermine:
|
||||||
|
- Vos affinités naturelles
|
||||||
|
- Vos bonus de domaine
|
||||||
|
- Votre perception du monde
|
||||||
|
|
||||||
|
### Gestion des dés de totem
|
||||||
|
|
||||||
|
Dans la fiche de personnage, onglet "Totem et ajustements":
|
||||||
|
- Vous voyez les valeurs de vos totems humain et adapté (0-3 chacun)
|
||||||
|
- La somme maximale est de 5 (ex: 3 humain + 2 adapté)
|
||||||
|
- Cliquez sur les flèches pour ajuster les valeurs
|
||||||
|
|
||||||
|
**Attention:** La somme des deux totems ne peut pas dépasser 5.
|
||||||
|
|
||||||
|
### Domaines de prédilection
|
||||||
|
|
||||||
|
Chaque catégorie de compétence peut être votre domaine de prédilection:
|
||||||
|
- Humain
|
||||||
|
- Animal
|
||||||
|
- Outil
|
||||||
|
- Arme
|
||||||
|
- Survie
|
||||||
|
- Monde
|
||||||
|
|
||||||
|
Pour définir votre domaine de prédilection:
|
||||||
|
1. Dans l'onglet "Caractéristiques et compétences"
|
||||||
|
2. Cliquez sur le bouton radio à côté du nom de la catégorie
|
||||||
|
3. La catégorie sélectionnée devient votre domaine de prédilection
|
||||||
|
|
||||||
|
**Bonus:** Si votre domaine de prédilection est dans les domaines de votre totem, vous obtenez des bonus supplémentaires.
|
||||||
|
|
||||||
|
### Totems et PNJ/Créatures
|
||||||
|
|
||||||
|
Les PNJ et créatures peuvent aussi avoir des totems, qui influencent leurs caractéristiques et comportements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## La gestion des groupes
|
||||||
|
|
||||||
|
### Qu'est-ce qu'un groupe?
|
||||||
|
|
||||||
|
Un groupe représente:
|
||||||
|
- Une communauté de survivants
|
||||||
|
- Un clan
|
||||||
|
- Une bande
|
||||||
|
- Une famille élargie
|
||||||
|
|
||||||
|
### Créer un groupe
|
||||||
|
|
||||||
|
1. Cliquez sur "Create Actor"
|
||||||
|
2. Sélectionnez "Group" comme type
|
||||||
|
3. Donnez un nom au groupe
|
||||||
|
4. Définissez le totem du groupe
|
||||||
|
5. Ajoutez des membres
|
||||||
|
|
||||||
|
### Ajouter des membres à un groupe
|
||||||
|
|
||||||
|
1. Ouvrez la fiche du groupe
|
||||||
|
2. Dans l'onglet "Membres", cliquez sur "+ Ajouter un membre"
|
||||||
|
3. Sélectionnez le personnage dans la liste
|
||||||
|
4. Cliquez sur "Ajouter"
|
||||||
|
|
||||||
|
**Synchronisation automatique:** Quand vous ajoutez un personnage à un groupe, le groupe apparaît automatiquement dans la fiche du personnage.
|
||||||
|
|
||||||
|
### Gérer les rencontres
|
||||||
|
|
||||||
|
Les "rencontres" représentent les PNJ et créatures associés à un groupe:
|
||||||
|
1. Ouvrez la fiche du groupe
|
||||||
|
2. Dans l'onglet "Rencontres", cliquez sur "+ Ajouter une rencontre"
|
||||||
|
3. Sélectionnez le PNJ ou la créature
|
||||||
|
4. Cliquez sur "Ajouter"
|
||||||
|
|
||||||
|
### Retirer un personnage d'un groupe
|
||||||
|
|
||||||
|
1. Ouvrez la fiche du groupe
|
||||||
|
2. Trouvez le membre dans la liste
|
||||||
|
3. Cliquez sur l'icône de suppression (poubelle)
|
||||||
|
4. Confirmez
|
||||||
|
|
||||||
|
**Synchronisation automatique:** Le personnage sera aussi retiré de la liste des groupes dans sa fiche.
|
||||||
|
|
||||||
|
### Mode de jeu
|
||||||
|
|
||||||
|
La fiche de personnage a deux modes:
|
||||||
|
- **Mode Edit**: Tous les champs sont modifiables
|
||||||
|
- **Mode Jeu**: Les champs sont désactivés pour éviter les modifications accidentelles
|
||||||
|
|
||||||
|
Pour basculer entre les modes:
|
||||||
|
- Cliquez sur l'icône de cadenas en haut de la fiche
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Le combat
|
||||||
|
|
||||||
|
### Initiative
|
||||||
|
|
||||||
|
L'initiative dans Vermine2047 est basée sur:
|
||||||
|
- Caractéristique: Réflexes
|
||||||
|
- Compétence: Vigilance
|
||||||
|
- Statut de combat: Offensif (+), Actif (neutre), Passif (-)
|
||||||
|
|
||||||
|
**Formule:** `(Réflexes + Vigilance)d10cs>=difficulté`
|
||||||
|
|
||||||
|
**Difficultés par statut:**
|
||||||
|
- Offensif: 5 (facile)
|
||||||
|
- Actif: 7 (standard)
|
||||||
|
- Passif: 9 (difficile)
|
||||||
|
|
||||||
|
### Statuts de combat
|
||||||
|
|
||||||
|
Chaque participant au combat a un statut:
|
||||||
|
- **Offensif**: Agressif, prend des risques
|
||||||
|
- **Actif**: Équilibré, réactif
|
||||||
|
- **Passif**: Défensif, prudent
|
||||||
|
|
||||||
|
Pour changer le statut:
|
||||||
|
1. Dans le combat tracker
|
||||||
|
2. Cliquez sur le nom du participant
|
||||||
|
3. Sélectionnez le nouveau statut
|
||||||
|
|
||||||
|
### Tracker de combat
|
||||||
|
|
||||||
|
Le tracker de combat affiche:
|
||||||
|
- L'ordre d'initiative
|
||||||
|
- Le statut de chaque participant
|
||||||
|
- Les points de vie
|
||||||
|
- Les réserves
|
||||||
|
|
||||||
|
### Actions de combat
|
||||||
|
|
||||||
|
Les actions de combat fonctionnent comme les jets de dés normaux, mais avec:
|
||||||
|
- Des bonus spécifiques au combat
|
||||||
|
- Des modifications de difficulté basées sur le statut
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Les items
|
||||||
|
|
||||||
|
### Types d'items
|
||||||
|
|
||||||
|
1. **Objet (item)**: Objet général (nourriture, outils, etc.)
|
||||||
|
2. **Arme (weapon)**: Arme de mêlée ou à distance
|
||||||
|
3. **Protection (defense)**: Armure, bouclier, etc.
|
||||||
|
4. **Véhicule (vehicle)**: Transport
|
||||||
|
5. **Capacité (ability)**: Compétence spéciale
|
||||||
|
6. **Spécialité (specialty)**: Spécialisation dans une compétence
|
||||||
|
7. **Historique (background)**: Historique du personnage
|
||||||
|
8. **Traumatisme (trauma)**: Traumatisme psychologique
|
||||||
|
9. **Évolution (evolution)**: Évolution du personnage
|
||||||
|
10. **Rumeur (rumor)**: Information
|
||||||
|
11. **Cible (target)**: Objectif
|
||||||
|
12. **Rite (rite)**: Rituel
|
||||||
|
|
||||||
|
### Créer un item
|
||||||
|
|
||||||
|
1. Dans la fiche du personnage
|
||||||
|
2. Allez dans l'onglet approprié (Matériel, Totem et ajustements, etc.)
|
||||||
|
3. Cliquez sur l'icône "+" à côté du titre de la section
|
||||||
|
4. Sélectionnez le type d'item
|
||||||
|
5. Remplissez les informations
|
||||||
|
|
||||||
|
### Utiliser un item dans le chat
|
||||||
|
|
||||||
|
1. Glissez-déposez l'item depuis votre fiche vers le chat
|
||||||
|
2. Ou cliquez sur l'icône de l'item et sélectionnez "Post to Chat"
|
||||||
|
3. Une carte avec les informations de l'item sera affichée
|
||||||
|
|
||||||
|
### Caractéristiques des items
|
||||||
|
|
||||||
|
**Tous les items:**
|
||||||
|
- Description
|
||||||
|
- Rareté (0-5)
|
||||||
|
- Fiabilité
|
||||||
|
- Handicap de rareté
|
||||||
|
- Quantité
|
||||||
|
- Poids
|
||||||
|
- Traits
|
||||||
|
- Dégâts
|
||||||
|
|
||||||
|
**Armes:**
|
||||||
|
- Portée min/max
|
||||||
|
- Dégâts (valeur, type, bonus de vigueur)
|
||||||
|
- Munitions
|
||||||
|
|
||||||
|
**Protections:**
|
||||||
|
- Niveau
|
||||||
|
- Niveau spécifique
|
||||||
|
- Mobilité
|
||||||
|
- Bouclier (oui/non)
|
||||||
|
|
||||||
|
**Capacités:**
|
||||||
|
- Type (personnage, groupe, créature, totem)
|
||||||
|
- Totem
|
||||||
|
- Niveau
|
||||||
|
- Seuil d'apprentissage
|
||||||
|
- Handicap d'apprentissage
|
||||||
|
- Effets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gestion des PNJ et Créatures
|
||||||
|
|
||||||
|
### Créer un PNJ
|
||||||
|
|
||||||
|
1. Cliquez sur "Create Actor"
|
||||||
|
2. Sélectionnez "NPC" comme type
|
||||||
|
3. Donnez un nom au PNJ
|
||||||
|
4. Configurez les attributs:
|
||||||
|
- Menace (1-4): Niveau de dangerosité
|
||||||
|
- Expérience (1-4): Niveau d'expérience
|
||||||
|
- Rôle (1-4): Importance dans le scénario
|
||||||
|
5. Ajoutez des compétences si nécessaire
|
||||||
|
6. Ajoutez de l'équipement
|
||||||
|
|
||||||
|
### Créer une créature
|
||||||
|
|
||||||
|
1. Cliquez sur "Create Actor"
|
||||||
|
2. Sélectionnez "Creature" comme type
|
||||||
|
3. Donnez un nom à la créature
|
||||||
|
4. Configurez les attributs:
|
||||||
|
- Gabarit (1-4): Type de créature
|
||||||
|
- Taille (1-3): Taille physique
|
||||||
|
- Rôle (1-4): Importance dans le scénario
|
||||||
|
- Meute (0-3): Taille du groupe
|
||||||
|
- Modes: Types de scénarios où la créature apparaît
|
||||||
|
5. Ajoutez des compétences si nécessaire
|
||||||
|
|
||||||
|
### Menace, Expérience et Rôle (PNJ)
|
||||||
|
|
||||||
|
Ces trois attributs déterminent les capacités du PNJ:
|
||||||
|
|
||||||
|
**Menace:**
|
||||||
|
- Mineure (1): Peu dangereuse
|
||||||
|
- Sérieuse (2): Dangereuse
|
||||||
|
- Majeure (3): Très dangereuse
|
||||||
|
- Mortelle (4): Extrêmement dangereuse
|
||||||
|
|
||||||
|
**Expérience:**
|
||||||
|
- Débutant (1): Peu expérimenté
|
||||||
|
- Compétent (2): Expérimenté
|
||||||
|
- Expert (3): Très expérimenté
|
||||||
|
- Maître (4): Maître dans son domaine
|
||||||
|
|
||||||
|
**Rôle:**
|
||||||
|
- Mineur (1): Personnage secondaire
|
||||||
|
- Secondaire (2): Personnage important
|
||||||
|
- Important (3): Personnage principal
|
||||||
|
- Majeur (4): Antagoniste principal
|
||||||
|
|
||||||
|
### Gabarit, Taille, Rôle et Meute (Créature)
|
||||||
|
|
||||||
|
**Gabarit:**
|
||||||
|
- Insecte (1): Très petit
|
||||||
|
- Rat (2): Petit
|
||||||
|
- Chien (3): Moyen
|
||||||
|
- Ours (4): Grand
|
||||||
|
|
||||||
|
**Taille:**
|
||||||
|
- Petit (1)
|
||||||
|
- Moyen (2)
|
||||||
|
- Grand (3)
|
||||||
|
|
||||||
|
**Rôle:**
|
||||||
|
- Mineur (1): Créature secondaire
|
||||||
|
- Secondaire (2): Créature importante
|
||||||
|
- Important (3): Créature principale
|
||||||
|
- Majeur (4): Boss
|
||||||
|
|
||||||
|
**Meute:**
|
||||||
|
- Solitaire (0): Agit seul
|
||||||
|
- Petit groupe (1)
|
||||||
|
- Groupe (2)
|
||||||
|
- Grande meute (3)
|
||||||
|
|
||||||
|
**Modes:**
|
||||||
|
- Survie: Agit dans des scénarios de survie
|
||||||
|
- Cauchemar: Agit dans des scénarios de cauchemar
|
||||||
|
- Apocalypse: Agit dans des scénarios d'apocalypse
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Astuces et bonnes pratiques
|
||||||
|
|
||||||
|
### Pour les Joueurs
|
||||||
|
|
||||||
|
1. **Choisissez un totem qui correspond à votre style de jeu**
|
||||||
|
- Humain: Pour les sociaux et les civils
|
||||||
|
- Prédateur: Pour les chasseurs et les guerriers
|
||||||
|
- Adapté: Pour les polyvalents
|
||||||
|
|
||||||
|
2. **Définissez un domaine de prédilection**
|
||||||
|
- Cela vous donnera des bonus avec votre totem
|
||||||
|
|
||||||
|
3. **Utilisez les spécialités**
|
||||||
|
- Les spécialités donnent +1D et des réussites automatiques
|
||||||
|
|
||||||
|
4. **Gérez vos réserves**
|
||||||
|
- Effort: Pour les actions physiques
|
||||||
|
- Sang-Froid: Pour les actions mentales et les relances
|
||||||
|
|
||||||
|
5. **Utilisez les dés de totem**
|
||||||
|
- Ils comptent double en cas de réussite
|
||||||
|
- Vous pouvez utiliser les deux totems simultanément
|
||||||
|
|
||||||
|
### Pour les MJ
|
||||||
|
|
||||||
|
1. **Créez des groupes pour organiser vos PNJ**
|
||||||
|
- Les groupes permettent de gérer plusieurs PNJ ensemble
|
||||||
|
- Les rencontres dans un groupe sont synchronisées
|
||||||
|
|
||||||
|
2. **Utilisez les statuts de combat**
|
||||||
|
- Offensif pour les personnages agressifs
|
||||||
|
- Actif pour les personnages équilibrés
|
||||||
|
- Passif pour les personnages défensifs
|
||||||
|
|
||||||
|
3. **Configurez correctement les PNJ et créatures**
|
||||||
|
- Menace/Expérience/Rôle pour les PNJ
|
||||||
|
- Gabarit/Taille/Rôle/Meute pour les créatures
|
||||||
|
|
||||||
|
4. **Utilisez les modes pour les créatures**
|
||||||
|
- Cela permet de filtrer les créatures par type de scénario
|
||||||
|
|
||||||
|
5. **Encouragez l'utilisation des domaines de prédilection**
|
||||||
|
- Cela rend le système de totems plus impactant
|
||||||
|
|
||||||
|
### Pour les développeurs
|
||||||
|
|
||||||
|
1. **Utilisez les helpers Handlebars**
|
||||||
|
- De nombreux helpers sont disponibles pour afficher les données
|
||||||
|
- `skillLevel`, `threatLevel`, etc.
|
||||||
|
|
||||||
|
2. **Respectez les conventions de nommage**
|
||||||
|
- `vermine-` préfixe pour les classes CSS
|
||||||
|
- `VERMINE` namespace pour les configurations
|
||||||
|
|
||||||
|
3. **Utilisez GroupLink pour la synchronisation**
|
||||||
|
- Ne modifiez pas directement les tableaux de membres/rencontres
|
||||||
|
- Utilisez les méthodes de GroupLink
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résolution des problèmes
|
||||||
|
|
||||||
|
### Problèmes courants
|
||||||
|
|
||||||
|
1. **Les dés de totem ne fonctionnent pas**
|
||||||
|
- Vérifiez que les valeurs des totems sont > 0
|
||||||
|
- Vérifiez que la somme des totems ne dépasse pas 5
|
||||||
|
|
||||||
|
2. **Les bonus de domaine ne s'appliquent pas**
|
||||||
|
- Vérifiez que vous avez défini un domaine de prédilection
|
||||||
|
- Vérifiez que votre totem a des domaines configurés
|
||||||
|
|
||||||
|
3. **Les groupes ne se synchronisent pas**
|
||||||
|
- Vérifiez que GroupLink est bien initialisé
|
||||||
|
- Vérifiez que les hooks sont actifs
|
||||||
|
|
||||||
|
4. **Les templates ne s'affichent pas correctement**
|
||||||
|
- Vérifiez que les templates sont en `.hbs`
|
||||||
|
- Vérifiez que les références sont correctes
|
||||||
|
|
||||||
|
### Contact
|
||||||
|
|
||||||
|
Pour de l'aide ou pour signaler un problème:
|
||||||
|
- Rejoignez le Discord Vermine: https://discord.gg/qejqmSxr
|
||||||
|
- Rejoignez le Discord Foundry Vermine: https://discord.gg/FqGHYvXg
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
Vermine2047 System est sous licence MIT. Voir le fichier LICENSE.txt pour plus de détails.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auteurs
|
||||||
|
|
||||||
|
- François-Xavier Guillois
|
||||||
|
- Rwanoux (Discord: rwanoux)
|
||||||
|
- Pretre (Discord: pretre)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Guide mis à jour: 2026-06-04*
|
||||||
|
*Version: 0.1.14*
|
||||||
@@ -1,40 +1,83 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const sass = require('gulp-sass')(require('sass'));
|
const less = require('gulp-less');
|
||||||
|
const autoprefixer = require('gulp-autoprefixer');
|
||||||
|
const cleanCSS = require('gulp-clean-css');
|
||||||
|
const rename = require('gulp-rename');
|
||||||
var browserSync = require('browser-sync').create();
|
var browserSync = require('browser-sync').create();
|
||||||
|
|
||||||
function buildStyles() {
|
// ============================================
|
||||||
return gulp.src('./scss/vermine2047.scss')
|
// LESS Tasks
|
||||||
.pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError))
|
// ============================================
|
||||||
|
|
||||||
|
function buildLess() {
|
||||||
|
return gulp.src('./less/vermine2047.less')
|
||||||
|
.pipe(less().on('error', function(err) {
|
||||||
|
console.error('LESS compilation error:', err.message);
|
||||||
|
this.emit('end');
|
||||||
|
}))
|
||||||
|
.pipe(autoprefixer({
|
||||||
|
overrideBrowserslist: ['> 1%', 'last 2 versions', 'Firefox ESR'],
|
||||||
|
cascade: false
|
||||||
|
}))
|
||||||
|
.pipe(cleanCSS({ compatibility: 'ie11' }))
|
||||||
|
.pipe(rename({ suffix: '.min' }))
|
||||||
.pipe(gulp.dest('./css'))
|
.pipe(gulp.dest('./css'))
|
||||||
.pipe(browserSync.stream());
|
.pipe(browserSync.stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build LESS without minification (for debugging)
|
||||||
|
function buildLessDev() {
|
||||||
|
return gulp.src('./less/vermine2047.less')
|
||||||
|
.pipe(less().on('error', function(err) {
|
||||||
|
console.error('LESS compilation error:', err.message);
|
||||||
|
this.emit('end');
|
||||||
|
}))
|
||||||
|
.pipe(autoprefixer({
|
||||||
|
overrideBrowserslist: ['> 1%', 'last 2 versions', 'Firefox ESR'],
|
||||||
|
cascade: false
|
||||||
|
}))
|
||||||
|
.pipe(rename({ suffix: '.dev' }))
|
||||||
|
.pipe(gulp.dest('./css'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchLess() {
|
||||||
|
gulp.watch(['./less/**/*.less', './less/*.less'], buildLess);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
|
||||||
function reloadTemplatesHTML() {
|
function reloadTemplatesHTML() {
|
||||||
|
return browserSync.reload("templates/**/*.html");
|
||||||
return browserSync.reload("templates/**/*.html")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadTemplatesHBS() {
|
function reloadTemplatesHBS() {
|
||||||
|
return browserSync.reload("templates/**/*.hbs");
|
||||||
return browserSync.reload("templates/**/*.hbs")
|
|
||||||
}
|
}
|
||||||
exports.buildStyles = buildStyles;
|
|
||||||
|
// ============================================
|
||||||
|
// Exports
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
exports.buildLess = buildLess;
|
||||||
|
exports.buildLessDev = buildLessDev;
|
||||||
|
exports.buildCSS = gulp.series(buildLess);
|
||||||
|
exports.buildAllCSS = gulp.series(buildLess, buildLessDev);
|
||||||
exports.watch = function () {
|
exports.watch = function () {
|
||||||
browserSync.init(
|
browserSync.init({
|
||||||
{
|
|
||||||
server: false,
|
server: false,
|
||||||
proxy: {
|
proxy: {
|
||||||
target: "https://localhost:30000/",
|
target: "https://localhost:30000/",
|
||||||
ws: true,
|
ws: true,
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
// Watch templates
|
||||||
);
|
|
||||||
gulp.watch("./templates/**/*.html").on('change', reloadTemplatesHTML);
|
gulp.watch("./templates/**/*.html").on('change', reloadTemplatesHTML);
|
||||||
gulp.watch("./templates/**/*.hbs").on('change', reloadTemplatesHBS);
|
gulp.watch("./templates/**/*.hbs").on('change', reloadTemplatesHBS);
|
||||||
gulp.watch(['./scss/**/*.scss', './scss/*.scss'], buildStyles);
|
|
||||||
|
|
||||||
|
// Watch LESS files
|
||||||
|
gulp.watch(['./less/**/*.less', './less/*.less'], buildLess);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.default = buildLess;
|
||||||
|
|||||||
|
After Width: | Height: | Size: 834 KiB |
@@ -13,6 +13,22 @@
|
|||||||
},
|
},
|
||||||
"VERMINE.WorldSettings.GameMode.Name": "Game Mode Selection",
|
"VERMINE.WorldSettings.GameMode.Name": "Game Mode Selection",
|
||||||
"VERMINE.WorldSettings.GameMode.Hint": "Just like some video games offer different modes, Vermine 2047 lets players choose their Game Mode and set the degree of realism, supernatural and dangerosity of the universe themselves.",
|
"VERMINE.WorldSettings.GameMode.Hint": "Just like some video games offer different modes, Vermine 2047 lets players choose their Game Mode and set the degree of realism, supernatural and dangerosity of the universe themselves.",
|
||||||
|
"TYPES.Item.item": "Item",
|
||||||
|
"TYPES.Item.weapon": "Weapon",
|
||||||
|
"TYPES.Item.defense": "Defense",
|
||||||
|
"TYPES.Item.vehicle": "Vehicle",
|
||||||
|
"TYPES.Item.ability": "Ability",
|
||||||
|
"TYPES.Item.specialty": "Specialty",
|
||||||
|
"TYPES.Item.background": "Background",
|
||||||
|
"TYPES.Item.trauma": "Trauma",
|
||||||
|
"TYPES.Item.evolution": "Adaptation",
|
||||||
|
"TYPES.Item.rumor": "Rumor",
|
||||||
|
"TYPES.Item.target": "Target",
|
||||||
|
"TYPES.Item.rite": "Rite",
|
||||||
|
"TYPES.Actor.character": "Character",
|
||||||
|
"TYPES.Actor.npc": "NPC",
|
||||||
|
"TYPES.Actor.group": "Group",
|
||||||
|
"TYPES.Actor.creature": "Creature",
|
||||||
"GAME_MODES": {
|
"GAME_MODES": {
|
||||||
"heroic": {
|
"heroic": {
|
||||||
"name": "Heroic"
|
"name": "Heroic"
|
||||||
@@ -22,7 +38,10 @@
|
|||||||
},
|
},
|
||||||
"legendary": {
|
"legendary": {
|
||||||
"name": "Legendary"
|
"name": "Legendary"
|
||||||
}
|
},
|
||||||
|
"survival": "Survival",
|
||||||
|
"nightmare": "Nightmare",
|
||||||
|
"apocalypse": "Apocalypse"
|
||||||
},
|
},
|
||||||
"ROLLS": {
|
"ROLLS": {
|
||||||
"tool": "Dice Roller",
|
"tool": "Dice Roller",
|
||||||
@@ -86,7 +105,8 @@
|
|||||||
"relations": "Relations",
|
"relations": "Relations",
|
||||||
"morale": "Morale",
|
"morale": "Morale",
|
||||||
"morale_level": "Morale level",
|
"morale_level": "Morale level",
|
||||||
"notes": "Notes"
|
"notes": "Notes",
|
||||||
|
"biography": "Biography"
|
||||||
},
|
},
|
||||||
"ADVERSITY": {
|
"ADVERSITY": {
|
||||||
"threat": "Threat",
|
"threat": "Threat",
|
||||||
@@ -108,7 +128,11 @@
|
|||||||
"skills": "Skills",
|
"skills": "Skills",
|
||||||
"pattern": "Pattern",
|
"pattern": "Pattern",
|
||||||
"size": "Size",
|
"size": "Size",
|
||||||
"pack": "Group"
|
"pack": "Group",
|
||||||
|
"gear_hindrance": "Gear Hindrance",
|
||||||
|
"skills_placeholder": "Skills Placeholder",
|
||||||
|
"threat_details": "Threat Details",
|
||||||
|
"role_details": "Role Details"
|
||||||
},
|
},
|
||||||
"VERMINE": {
|
"VERMINE": {
|
||||||
"name": "Vermine",
|
"name": "Vermine",
|
||||||
@@ -116,6 +140,9 @@
|
|||||||
"level": "Level",
|
"level": "Level",
|
||||||
"experience": "Experience",
|
"experience": "Experience",
|
||||||
"reputation": "Reputation",
|
"reputation": "Reputation",
|
||||||
|
"sheet": {
|
||||||
|
"description": "Description"
|
||||||
|
},
|
||||||
"pool": "Reserve",
|
"pool": "Reserve",
|
||||||
"pools": "Reserves",
|
"pools": "Reserves",
|
||||||
"self_control": "Self-control",
|
"self_control": "Self-control",
|
||||||
@@ -180,7 +207,106 @@
|
|||||||
"encounters": "Encounters",
|
"encounters": "Encounters",
|
||||||
"road": "The Road",
|
"road": "The Road",
|
||||||
"totem_picker": "Totem picker",
|
"totem_picker": "Totem picker",
|
||||||
"actor_picker": "Actor picker"
|
"actor_picker": "Actor picker",
|
||||||
|
"ability_category": {
|
||||||
|
"physical": "Physical",
|
||||||
|
"manual": "Manual",
|
||||||
|
"mental": "Mental",
|
||||||
|
"social": "Social"
|
||||||
|
},
|
||||||
|
"skill_category": {
|
||||||
|
"man": "Man",
|
||||||
|
"animal": "Animal",
|
||||||
|
"tool": "Tool",
|
||||||
|
"weapon": "Weapon",
|
||||||
|
"survival": "Survival",
|
||||||
|
"world": "World"
|
||||||
|
},
|
||||||
|
"CharacterNamePlaceholder": "Character Name",
|
||||||
|
"none": "None",
|
||||||
|
"bonuses": "Bonuses",
|
||||||
|
"choose_ability": "Choose Ability",
|
||||||
|
"choose_skill": "Choose Skill",
|
||||||
|
"grant_reroll": "Grant Reroll",
|
||||||
|
"handicap": "Handicap",
|
||||||
|
"keep_totem": "Keep Totem",
|
||||||
|
"morale_crisis": "Morale Crisis",
|
||||||
|
"morale_high": "High Morale",
|
||||||
|
"morale_low": "Low Morale",
|
||||||
|
"morale_normal": "Normal Morale",
|
||||||
|
"rerolls_possible": "Rerolls Possible",
|
||||||
|
"reserve": "Reserve",
|
||||||
|
"score": "Score",
|
||||||
|
"success_count": "Success Count",
|
||||||
|
"success_required": "Success Required",
|
||||||
|
"test_of": "Test of",
|
||||||
|
"total": "Total",
|
||||||
|
"totem_dice": "Totem Dice",
|
||||||
|
"totem_hint": "Totem Hint",
|
||||||
|
"FightTool": "Fight Tool",
|
||||||
|
"RollTool": "Roll Tool",
|
||||||
|
"Roll4Fight": "Roll for Fight",
|
||||||
|
"Selected": "Selected",
|
||||||
|
"PurposeTrait": "Purpose Trait",
|
||||||
|
"SpleenTrait": "Spleen Trait",
|
||||||
|
"ConfrontationHint": "Confrontation Hint",
|
||||||
|
"Achievement": "Achievement",
|
||||||
|
"Conservation": "Conservation",
|
||||||
|
"error_cannot_reroll": "Cannot reroll",
|
||||||
|
"error_no_rerolls_left": "No rerolls left",
|
||||||
|
"error_no_actor_selected": "No actor selected",
|
||||||
|
"error_not_enough_self_control": "Not enough self control",
|
||||||
|
"error_select_ability": "Select an ability",
|
||||||
|
"tabs": {
|
||||||
|
"abilities": "Abilities",
|
||||||
|
"combat": "Combat",
|
||||||
|
"equipment": "Equipment",
|
||||||
|
"stories": "Stories",
|
||||||
|
"totem": "Totem"
|
||||||
|
},
|
||||||
|
"base_values": "Base Values",
|
||||||
|
"computed_values": "Computed Values",
|
||||||
|
"cost": "Cost",
|
||||||
|
"creature": "Creature",
|
||||||
|
"effect": "Effect",
|
||||||
|
"error_select_skill": "Select a skill",
|
||||||
|
"error_unknown_actor": "Unknown actor",
|
||||||
|
"instincts": "Instincts",
|
||||||
|
"instincts_placeholder": "eg: Aggressive, Defensive",
|
||||||
|
"learn": "Learn",
|
||||||
|
"major_objectives": "Major Objectives",
|
||||||
|
"minor_objectives": "Minor Objectives",
|
||||||
|
"morale": "Morale",
|
||||||
|
"needed": "Needed",
|
||||||
|
"objective_placeholder": "eg: Survive, Explore",
|
||||||
|
"objectives": "Objectives",
|
||||||
|
"pack": "Pack",
|
||||||
|
"pattern": "Pattern",
|
||||||
|
"preferred_category": "Preferred Category",
|
||||||
|
"prohibits": "Prohibits",
|
||||||
|
"prohibits_placeholder": "eg: Fire, Water",
|
||||||
|
"rarity_0": "Common",
|
||||||
|
"rarity_1": "Rare",
|
||||||
|
"rarity_2": "Very Rare",
|
||||||
|
"reserves": "Reserves",
|
||||||
|
"ritual": "Ritual",
|
||||||
|
"sexes": {
|
||||||
|
"female": "Female",
|
||||||
|
"male": "Male"
|
||||||
|
},
|
||||||
|
"size": "Size",
|
||||||
|
"total_attack": "Total Attack",
|
||||||
|
"total_damage": "Total Damage",
|
||||||
|
"trance": "Trance",
|
||||||
|
"vote_reserve": "Vote Reserve",
|
||||||
|
"wound_thresholds": "Wound Thresholds",
|
||||||
|
"types": {
|
||||||
|
"shield": "Shield"
|
||||||
|
},
|
||||||
|
"roll": "Roll",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"playMode": "Play Mode",
|
||||||
|
"editMode": "Edit Mode"
|
||||||
},
|
},
|
||||||
"UI": {
|
"UI": {
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
@@ -195,9 +321,15 @@
|
|||||||
"temporary": "Temporary effects",
|
"temporary": "Temporary effects",
|
||||||
"passive": "Passive effects",
|
"passive": "Passive effects",
|
||||||
"inactive": "Inactive effects"
|
"inactive": "Inactive effects"
|
||||||
}
|
},
|
||||||
|
"effects.inactive": "Inactive",
|
||||||
|
"effects.passive": "Passive",
|
||||||
|
"effects.temporary": "Temporary"
|
||||||
},
|
},
|
||||||
"ITEMS": {
|
"ITEMS": {
|
||||||
|
"item": "Item",
|
||||||
|
"items": "Items",
|
||||||
|
"new_item": "New item",
|
||||||
"defense": "Protection",
|
"defense": "Protection",
|
||||||
"defenses": "Protections",
|
"defenses": "Protections",
|
||||||
"new_defense": "New protection",
|
"new_defense": "New protection",
|
||||||
@@ -216,6 +348,12 @@
|
|||||||
"abilities": "Abilities",
|
"abilities": "Abilities",
|
||||||
"specialties": "Specialties",
|
"specialties": "Specialties",
|
||||||
"new_specialty": "New specialty",
|
"new_specialty": "New specialty",
|
||||||
|
"target": "Target",
|
||||||
|
"targets": "Targets",
|
||||||
|
"new_target": "New target",
|
||||||
|
"rite": "Rite",
|
||||||
|
"rites": "Rites",
|
||||||
|
"new_rite": "New rite",
|
||||||
"evolution": "Adaptation",
|
"evolution": "Adaptation",
|
||||||
"new_evolution": "New adaptation",
|
"new_evolution": "New adaptation",
|
||||||
"evolutions": "Adaptations",
|
"evolutions": "Adaptations",
|
||||||
@@ -227,7 +365,8 @@
|
|||||||
"rituel": "Ritual",
|
"rituel": "Ritual",
|
||||||
"transe": "Trance",
|
"transe": "Trance",
|
||||||
"effects": "Effects",
|
"effects": "Effects",
|
||||||
"details": "Details"
|
"details": "Details",
|
||||||
|
"shield": "Shield"
|
||||||
},
|
},
|
||||||
"ABILITIES": {
|
"ABILITIES": {
|
||||||
"vigor": {
|
"vigor": {
|
||||||
@@ -498,10 +637,26 @@
|
|||||||
},
|
},
|
||||||
"ruins": {
|
"ruins": {
|
||||||
"name": "Ruins"
|
"name": "Ruins"
|
||||||
|
},
|
||||||
|
"athletics": {
|
||||||
|
"name": "Athletics"
|
||||||
|
},
|
||||||
|
"fauna": {
|
||||||
|
"name": "Fauna"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SEXES": {
|
"SEXES": {
|
||||||
"male": "Masculine",
|
"male": "Masculine",
|
||||||
"female": "Feminine"
|
"female": "Feminine"
|
||||||
|
},
|
||||||
|
"COMBAT": {
|
||||||
|
"Encounter": "Encounter",
|
||||||
|
"None": "None",
|
||||||
|
"Round": "Round"
|
||||||
|
},
|
||||||
|
"SIZE_LEVELS": {
|
||||||
|
"small": "Small",
|
||||||
|
"medium": "Medium",
|
||||||
|
"large": "Large"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,26 @@
|
|||||||
{
|
{
|
||||||
|
"NAME": "Nom",
|
||||||
|
"NONE": "Aucun",
|
||||||
|
"ORIGIN": "Origine",
|
||||||
|
"PROFILE": "Profil",
|
||||||
|
"THEME": "Concept",
|
||||||
|
"TOTEM": "Totem",
|
||||||
|
"TYPES.Item.item": "Objet",
|
||||||
|
"TYPES.Item.weapon": "Arme",
|
||||||
|
"TYPES.Item.defense": "Protection",
|
||||||
|
"TYPES.Item.vehicle": "Véhicule",
|
||||||
|
"TYPES.Item.ability": "Capacité",
|
||||||
|
"TYPES.Item.specialty": "Spécialité",
|
||||||
|
"TYPES.Item.background": "Historique",
|
||||||
|
"TYPES.Item.trauma": "Traumatisme",
|
||||||
|
"TYPES.Item.evolution": "Adaptation",
|
||||||
|
"TYPES.Item.rumor": "Rumeur",
|
||||||
|
"TYPES.Item.target": "Cible",
|
||||||
|
"TYPES.Item.rite": "Rite",
|
||||||
|
"TYPES.Actor.character": "Personnage",
|
||||||
|
"TYPES.Actor.npc": "PNJ",
|
||||||
|
"TYPES.Actor.group": "Groupe",
|
||||||
|
"TYPES.Actor.creature": "Créature",
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"world": {
|
"world": {
|
||||||
"game_mode": {
|
"game_mode": {
|
||||||
@@ -22,7 +44,10 @@
|
|||||||
},
|
},
|
||||||
"legendary": {
|
"legendary": {
|
||||||
"name": "Légendaire"
|
"name": "Légendaire"
|
||||||
}
|
},
|
||||||
|
"survival": "Survie",
|
||||||
|
"nightmare": "Cauchemar",
|
||||||
|
"apocalypse": "Apocalypse"
|
||||||
},
|
},
|
||||||
"ROLLS": {
|
"ROLLS": {
|
||||||
"tool": "Lanceur de dés",
|
"tool": "Lanceur de dés",
|
||||||
@@ -71,6 +96,7 @@
|
|||||||
"old": "Vieux"
|
"old": "Vieux"
|
||||||
},
|
},
|
||||||
"IDENTITY": {
|
"IDENTITY": {
|
||||||
|
"biography": "Biographie",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"height": "Taille",
|
"height": "Taille",
|
||||||
"weight": "Poids",
|
"weight": "Poids",
|
||||||
@@ -98,17 +124,21 @@
|
|||||||
"vigor": "Vigueur",
|
"vigor": "Vigueur",
|
||||||
"wounds": "Blessures",
|
"wounds": "Blessures",
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
"specialties": "Spécialité",
|
"specialties": "Spécialités",
|
||||||
"rerolls": "Relances",
|
"rerolls": "Relances",
|
||||||
"contact": "Contact",
|
"contact": "Contact",
|
||||||
"reaction": "Réaction",
|
"reaction": "Réaction",
|
||||||
"pools": "Réserves",
|
"pools": "Réserves",
|
||||||
"gear": "Matériel",
|
"gear": "Matériel",
|
||||||
|
"gear_hindrance": "Handicap Matériel",
|
||||||
"protection": "Protection",
|
"protection": "Protection",
|
||||||
"skills": "Compétences",
|
"skills": "Compétences",
|
||||||
|
"skills_placeholder": "ex: Armes à feu, Soins, Discrétion",
|
||||||
"pattern": "Gabarit",
|
"pattern": "Gabarit",
|
||||||
"size": "Taille",
|
"size": "Taille",
|
||||||
"pack": "Groupe"
|
"pack": "Groupe",
|
||||||
|
"threat_details": "Détails de Menace",
|
||||||
|
"role_details": "Détails de Rôle"
|
||||||
},
|
},
|
||||||
"VERMINE": {
|
"VERMINE": {
|
||||||
"name": "Vermine",
|
"name": "Vermine",
|
||||||
@@ -116,14 +146,67 @@
|
|||||||
"level": "Niveau",
|
"level": "Niveau",
|
||||||
"experience": "Expérience",
|
"experience": "Expérience",
|
||||||
"reputation": "Réputation",
|
"reputation": "Réputation",
|
||||||
|
"sheet": {
|
||||||
|
"description": "Description"
|
||||||
|
},
|
||||||
"pool": "Réserve",
|
"pool": "Réserve",
|
||||||
"pools": "Réserves",
|
"pools": "Réserves",
|
||||||
|
"reserves": "Réserves",
|
||||||
"self_control": "Sang-Froid",
|
"self_control": "Sang-Froid",
|
||||||
"effort": "Effort",
|
"effort": "Effort",
|
||||||
|
"preferred_category": "Catégorie préférée",
|
||||||
|
"wounds": {
|
||||||
|
"name": "Blessures",
|
||||||
|
"threshold": "Seuil",
|
||||||
|
"light": "Légère",
|
||||||
|
"heavy": "Grave",
|
||||||
|
"deadly": "Mortelle",
|
||||||
|
"light_wounds": "Blessure légère",
|
||||||
|
"heavy_wounds": "Blessure grave",
|
||||||
|
"deadly_wounds": "Blessure mortelle"
|
||||||
|
},
|
||||||
|
"ability_category": {
|
||||||
|
"physical": "Physiques",
|
||||||
|
"manual": "Manuelles",
|
||||||
|
"mental": "Mentales",
|
||||||
|
"social": "Sociales"
|
||||||
|
},
|
||||||
|
"skill_category": {
|
||||||
|
"man": "Homme",
|
||||||
|
"animal": "Animal",
|
||||||
|
"tool": "Outil",
|
||||||
|
"weapon": "Arme",
|
||||||
|
"survival": "Survie",
|
||||||
|
"world": "Monde"
|
||||||
|
},
|
||||||
|
"rarity_0": "Commun",
|
||||||
|
"rarity_1": "Peu commun",
|
||||||
|
"rarity_2": "Rare",
|
||||||
|
"rarity_3": "Très rare",
|
||||||
|
"test_of": "test de",
|
||||||
|
"rerolls_possible": "relances possibles",
|
||||||
|
"grant_reroll": "accorder des relances",
|
||||||
|
"success_count": "nombre de succès",
|
||||||
|
"success_required": "succès requis",
|
||||||
|
"error_no_actor_selected": "Vous n'avez pas de personnage attitré ou de token sélectionné",
|
||||||
|
"error_cannot_reroll": "Vous ne pouvez pas relancer un dés sur ce jet",
|
||||||
|
"error_no_rerolls_left": "Plus de relances possibles",
|
||||||
|
"error_select_ability": "Veuillez sélectionner une caractéristique",
|
||||||
|
"error_not_enough_self_control": "Vous n'avez pas assez de Sang-Froid",
|
||||||
|
"FightTool": "Outil de combat",
|
||||||
|
"Roll4Fight": "Lancer pour le combat",
|
||||||
|
"Selected": "Sélectionné",
|
||||||
|
"Achievement": "Accomplissement",
|
||||||
|
"Conservation": "Conservation",
|
||||||
|
"ConfrontationHint": "Résolvez la confrontation en sélectionnant des dés",
|
||||||
|
"PurposeTrait": "Trait de but",
|
||||||
|
"SpleenTrait": "Trait de rate",
|
||||||
"group": "Groupe",
|
"group": "Groupe",
|
||||||
"abilities": "Caractéristiques",
|
"abilities": "Caractéristiques",
|
||||||
"ability": "Caractéristique",
|
"ability": "Caractéristique",
|
||||||
|
"skills": "Compétences",
|
||||||
"skills_title": "Compétences",
|
"skills_title": "Compétences",
|
||||||
|
"skill": "Compétence",
|
||||||
"skill_title": "Compétence",
|
"skill_title": "Compétence",
|
||||||
"skill_mastery": "Maîtrise",
|
"skill_mastery": "Maîtrise",
|
||||||
"bonus": "Bonus",
|
"bonus": "Bonus",
|
||||||
@@ -131,6 +214,8 @@
|
|||||||
"reroll": "Relance",
|
"reroll": "Relance",
|
||||||
"equipment": "Equipement",
|
"equipment": "Equipement",
|
||||||
"specialty": "Spécialité",
|
"specialty": "Spécialité",
|
||||||
|
"specificLevel": "Niveau spécifique",
|
||||||
|
"competence": "Compétence",
|
||||||
"technique": "Technique",
|
"technique": "Technique",
|
||||||
"techniques": "Techniques",
|
"techniques": "Techniques",
|
||||||
"difficulty": "Difficulté",
|
"difficulty": "Difficulté",
|
||||||
@@ -149,15 +234,25 @@
|
|||||||
"rarity_sm": "Rar.",
|
"rarity_sm": "Rar.",
|
||||||
"reliability": "Fiabilité",
|
"reliability": "Fiabilité",
|
||||||
"reliability_sm": "Fiab.",
|
"reliability_sm": "Fiab.",
|
||||||
"wounds": {
|
"choose_ability": "Choisissez une caractéristique",
|
||||||
"name": "Blessures",
|
"choose_skill": "Choisissez une compétence",
|
||||||
"threshold": "Seuil",
|
"none": "Aucun",
|
||||||
"light": "Légère",
|
"total": "Total",
|
||||||
"heavy": "Grave",
|
"bonuses": "Bonus",
|
||||||
"deadly": "Mortelle",
|
"handicap": "Handicap",
|
||||||
"light_wounds": "Blessure légère",
|
"score": "Score",
|
||||||
"heavy_wounds": "Blessure grave",
|
"totem_dice": "Dés de totem",
|
||||||
"deadly_wounds": "Blessure mortelle"
|
"keep_totem": "Garder le totem",
|
||||||
|
"totem_hint": "Cochez pour utiliser les dés de totem (double réussite possible)",
|
||||||
|
"error_select_skill": "Veuillez sélectionner une compétence",
|
||||||
|
"needed": "nécessaire",
|
||||||
|
"cost": "Coût",
|
||||||
|
"learn": "Apprentissage",
|
||||||
|
"ritual": "Rituel",
|
||||||
|
"trance": "Transe",
|
||||||
|
"effect": "Effet",
|
||||||
|
"types": {
|
||||||
|
"shield": "Bouclier"
|
||||||
},
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"abilities": "Caractéristiques et compétences",
|
"abilities": "Caractéristiques et compétences",
|
||||||
@@ -170,11 +265,15 @@
|
|||||||
"ammo_sm": "Mun",
|
"ammo_sm": "Mun",
|
||||||
"trait": "Trait",
|
"trait": "Trait",
|
||||||
"traits": "Traits",
|
"traits": "Traits",
|
||||||
|
"traits_selector": "Sélecteur de traits",
|
||||||
"clew": "Indice",
|
"clew": "Indice",
|
||||||
"combat": "Combat",
|
"combat": "Combat",
|
||||||
"stories": "Histoires",
|
"stories": "Histoires",
|
||||||
"gear": "Matériel",
|
"gear": "Matériel",
|
||||||
"information": "Informations",
|
"information": "Informations",
|
||||||
|
"editMode": "Mode édition",
|
||||||
|
"playMode": "Mode jeu",
|
||||||
|
"modes": "Modes de jeu",
|
||||||
"boost": "boost",
|
"boost": "boost",
|
||||||
"group_abilities": "Capacités de groupe",
|
"group_abilities": "Capacités de groupe",
|
||||||
"totem_abilities": "Capacités de totem",
|
"totem_abilities": "Capacités de totem",
|
||||||
@@ -186,8 +285,48 @@
|
|||||||
"group_members": "Membres",
|
"group_members": "Membres",
|
||||||
"encounters": "Rencontres",
|
"encounters": "Rencontres",
|
||||||
"road": "La Route",
|
"road": "La Route",
|
||||||
|
"reserve": "Réserve",
|
||||||
"totem_picker": "Sélecteur de totem",
|
"totem_picker": "Sélecteur de totem",
|
||||||
"actor_picker": "Sélecteur de personnage"
|
"actor_picker": "Sélecteur de personnage",
|
||||||
|
"morale": "Moral",
|
||||||
|
"reserve": "Réserve de groupe",
|
||||||
|
"objectives": "Objectifs",
|
||||||
|
"major_objectives": "Objectifs majeurs",
|
||||||
|
"minor_objectives": "Objectifs mineurs",
|
||||||
|
"instincts": "Instincts",
|
||||||
|
"prohibits": "Interdictions",
|
||||||
|
"vote_reserve": "Vote pour utiliser la réserve",
|
||||||
|
"morale_high": "Haut",
|
||||||
|
"morale_normal": "Normal",
|
||||||
|
"morale_low": "Bas",
|
||||||
|
"morale_crisis": "Crise",
|
||||||
|
"objective_placeholder": "Objectif...",
|
||||||
|
"instincts_placeholder": "Ex: Triompher, relever un défi...",
|
||||||
|
"prohibits_placeholder": "Ex: Fuir, abandonner...",
|
||||||
|
"error_unknown_actor": "Acteur inconnu",
|
||||||
|
"creature": "Créature",
|
||||||
|
"pattern": "Gabarit",
|
||||||
|
"size": "Taille",
|
||||||
|
"pack": "Meute",
|
||||||
|
"computed_values": "Valeurs calculées",
|
||||||
|
"base_values": "Valeurs de base",
|
||||||
|
"total_attack": "Attaque totale",
|
||||||
|
"total_damage": "Dégâts totaux",
|
||||||
|
"wound_thresholds": "Seuils de blessures",
|
||||||
|
"CharacterNamePlaceholder": "Nom du personnage",
|
||||||
|
"sexes": {
|
||||||
|
"female": "Féminin",
|
||||||
|
"male": "Masculin"
|
||||||
|
},
|
||||||
|
"RollTool": "Outil de lancer de dés",
|
||||||
|
"roll": "Lancer",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"Sheet": {
|
||||||
|
"character": "Personnage",
|
||||||
|
"npc": "PNJ",
|
||||||
|
"group": "Groupe",
|
||||||
|
"creature": "Créature"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"UI": {
|
"UI": {
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
@@ -205,6 +344,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ITEMS": {
|
"ITEMS": {
|
||||||
|
"item": "Objet",
|
||||||
|
"items": "Objets",
|
||||||
|
"new_item": "Nouvel objet",
|
||||||
"defense": "Protection",
|
"defense": "Protection",
|
||||||
"defenses": "Protections",
|
"defenses": "Protections",
|
||||||
"new_defense": "Nouvelle protection",
|
"new_defense": "Nouvelle protection",
|
||||||
@@ -223,6 +365,13 @@
|
|||||||
"abilities": "Capacités",
|
"abilities": "Capacités",
|
||||||
"specialties": "Spécialités",
|
"specialties": "Spécialités",
|
||||||
"new_specialty": "Nouvelle spécialité",
|
"new_specialty": "Nouvelle spécialité",
|
||||||
|
"target": "Cible",
|
||||||
|
"targets": "Cibles",
|
||||||
|
"new_target": "Nouvelle cible",
|
||||||
|
"rite": "Rite",
|
||||||
|
"rites": "Rites",
|
||||||
|
"new_rite": "Nouveau rite",
|
||||||
|
"shield": "Bouclier",
|
||||||
"evolution": "Adaptation",
|
"evolution": "Adaptation",
|
||||||
"new_evolution": "Nouvelle adaptation",
|
"new_evolution": "Nouvelle adaptation",
|
||||||
"evolutions": "Adaptations",
|
"evolutions": "Adaptations",
|
||||||
@@ -282,6 +431,11 @@
|
|||||||
"important": "Important",
|
"important": "Important",
|
||||||
"major": "Majeur"
|
"major": "Majeur"
|
||||||
},
|
},
|
||||||
|
"SIZE_LEVELS": {
|
||||||
|
"small": "Petit",
|
||||||
|
"medium": "Moyen",
|
||||||
|
"large": "Grand"
|
||||||
|
},
|
||||||
"PATTERN_LEVELS": {
|
"PATTERN_LEVELS": {
|
||||||
"insect": "Insecte",
|
"insect": "Insecte",
|
||||||
"rat": "Rat",
|
"rat": "Rat",
|
||||||
@@ -417,7 +571,7 @@
|
|||||||
"name": "Arts"
|
"name": "Arts"
|
||||||
},
|
},
|
||||||
"civilization": {
|
"civilization": {
|
||||||
"name": "Civilisations"
|
"name": "Civilisation"
|
||||||
},
|
},
|
||||||
"psychology": {
|
"psychology": {
|
||||||
"name": "Psychologie"
|
"name": "Psychologie"
|
||||||
@@ -434,14 +588,14 @@
|
|||||||
"dissection": {
|
"dissection": {
|
||||||
"name": "Dissection"
|
"name": "Dissection"
|
||||||
},
|
},
|
||||||
"wildlife": {
|
"fauna": {
|
||||||
"name": "Faune"
|
"name": "Faune"
|
||||||
},
|
},
|
||||||
"repulsion": {
|
"repulsion": {
|
||||||
"name": "Répulsion"
|
"name": "Répulsion"
|
||||||
},
|
},
|
||||||
"tracks": {
|
"tracks": {
|
||||||
"name": "Traces"
|
"name": "Pistage"
|
||||||
},
|
},
|
||||||
"crafting": {
|
"crafting": {
|
||||||
"name": "Artisanat"
|
"name": "Artisanat"
|
||||||
@@ -461,42 +615,39 @@
|
|||||||
"firearms": {
|
"firearms": {
|
||||||
"name": "Armes à feu"
|
"name": "Armes à feu"
|
||||||
},
|
},
|
||||||
|
"archery": {
|
||||||
|
"name": "Tir à l'arc"
|
||||||
|
},
|
||||||
"armory": {
|
"armory": {
|
||||||
"name": "Armurerie"
|
"name": "Armurerie"
|
||||||
},
|
},
|
||||||
"shield": {
|
|
||||||
"name": "Bouclier"
|
|
||||||
},
|
|
||||||
"close": {
|
|
||||||
"name": "Corps-à-corps"
|
|
||||||
},
|
|
||||||
"archery": {
|
|
||||||
"name": "Armes de tir"
|
|
||||||
},
|
|
||||||
"throwing": {
|
"throwing": {
|
||||||
"name": "Lancer"
|
"name": "Lancer"
|
||||||
},
|
},
|
||||||
"melee": {
|
"melee": {
|
||||||
"name": "Mêlée"
|
"name": "Mêlée"
|
||||||
},
|
},
|
||||||
"atletics": {
|
|
||||||
"name": "Atlétisme"
|
|
||||||
},
|
|
||||||
"stealth": {
|
|
||||||
"name": "Discrétion"
|
|
||||||
},
|
|
||||||
"alertness": {
|
"alertness": {
|
||||||
"name": "Vigilance"
|
"name": "Vigilance"
|
||||||
},
|
},
|
||||||
"flora": {
|
"athletics": {
|
||||||
"name": "Flore"
|
"name": "Athlétisme"
|
||||||
},
|
},
|
||||||
"food": {
|
"food": {
|
||||||
"name": "Alimentation"
|
"name": "Alimentation"
|
||||||
},
|
},
|
||||||
|
"stealth": {
|
||||||
|
"name": "Discrétion"
|
||||||
|
},
|
||||||
|
"close": {
|
||||||
|
"name": "Corps-à-corps"
|
||||||
|
},
|
||||||
"environment": {
|
"environment": {
|
||||||
"name": "Environnement"
|
"name": "Environnement"
|
||||||
},
|
},
|
||||||
|
"flora": {
|
||||||
|
"name": "Flore"
|
||||||
|
},
|
||||||
"road": {
|
"road": {
|
||||||
"name": "Route"
|
"name": "Route"
|
||||||
},
|
},
|
||||||
@@ -505,10 +656,28 @@
|
|||||||
},
|
},
|
||||||
"ruins": {
|
"ruins": {
|
||||||
"name": "Vestiges"
|
"name": "Vestiges"
|
||||||
|
},
|
||||||
|
"shield": {
|
||||||
|
"name": "Bouclier"
|
||||||
|
},
|
||||||
|
"wildlife": {
|
||||||
|
"name": "Faune"
|
||||||
|
},
|
||||||
|
"atletics": {
|
||||||
|
"name": "Athlétisme"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SEXES": {
|
"SEXES": {
|
||||||
"male": "Masculin",
|
"male": "Masculin",
|
||||||
"female": "Féminin"
|
"female": "Féminin"
|
||||||
|
},
|
||||||
|
"COMBAT": {
|
||||||
|
"Begin": "Commencer",
|
||||||
|
"Encounter": "Rencontre",
|
||||||
|
"End": "Terminer",
|
||||||
|
"None": "Aucun",
|
||||||
|
"NotStarted": "Non commencé",
|
||||||
|
"Round": "Tour",
|
||||||
|
"TurnEnd": "Fin de tour"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,645 @@
|
|||||||
|
@import "totem";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor {
|
||||||
|
|
||||||
|
// ── Fix contrast: dark text on light parchment bg ───────────────────
|
||||||
|
.window-content,
|
||||||
|
& {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
label, span, p, li, a:not(.active) {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h5 {
|
||||||
|
color: @color-text-dark-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: @theme-color-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:not([type="radio"]):not([type="checkbox"]),
|
||||||
|
select, textarea {
|
||||||
|
color: @color-text-light-0;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 1px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-header {
|
||||||
|
h1 label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.char-name-value {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
input:not([type="radio"]):not([type="checkbox"]) {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability input.hexa {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
color: @color-text-dark-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability label {
|
||||||
|
color: @color-text-dark-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-header {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: @color-text-dark-header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hexa {
|
||||||
|
color: @color-text-dark-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-tabs a {
|
||||||
|
color: @color-text-dark-inactive;
|
||||||
|
&.active {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Two-column layout for character sheets ──────────────────────────
|
||||||
|
&.character {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: auto 1fr auto !important;
|
||||||
|
|
||||||
|
> header.window-header {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> section.window-content {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div.window-resize-handle {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.character .window-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 240px 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> .tab.main {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1 / -1;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
> nav.tabs {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tab:not(.main) {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 2;
|
||||||
|
display: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stacked layout for NPC sheets (header + tabs + content) ────────
|
||||||
|
&.npc {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: auto 1fr auto !important;
|
||||||
|
|
||||||
|
> header.window-header {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> section.window-content {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div.window-resize-handle {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.npc .window-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto auto 1fr;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> .tab.main {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
> nav.tabs {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tab:not(.main) {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
display: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> .tab {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charname input {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 30px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hexa {
|
||||||
|
.hexa-style();
|
||||||
|
.transition();
|
||||||
|
margin: 0.2rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 1;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
.hexa-style(rgb(0, 0, 0), rgba(0, 0, 0, 0.288));
|
||||||
|
&:hover { .hexa-style(rgb(43, 43, 43), rgba(0, 0, 0, 0.288)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unavailable {
|
||||||
|
background: radial-gradient(circle, rgba(66, 15, 15, 0.664) 0%, rgba(131, 70, 70, 0.432) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"] {
|
||||||
|
width: calc(100% - 2px);
|
||||||
|
height: calc(100% - 2px);
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 1px 0;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0);
|
||||||
|
|
||||||
|
&.hexa { .hexa-style(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
input[disabled],
|
||||||
|
select[disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:hover:not(:disabled),
|
||||||
|
input[type="text"]:focus,
|
||||||
|
select:hover:not(:disabled),
|
||||||
|
select:focus,
|
||||||
|
input[type="number"]:hover:not(:disabled),
|
||||||
|
input[type="number"]:focus,
|
||||||
|
textarea:hover:not(:disabled),
|
||||||
|
textarea:focus {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
border: none;
|
||||||
|
appearance: none;
|
||||||
|
min-width: fit-content;
|
||||||
|
max-width: fit-content;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
margin: 0 0.2rem;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mce-panel span {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rollable:hover,
|
||||||
|
a:hover {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 5px 5px @theme-color-accent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chooseTotem {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-shadow: 0 0 10px @theme-color-highlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header-toggle {
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0.2rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, cover, 50% 0%);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 80%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-with-frieze {
|
||||||
|
margin-left: 18% !important;
|
||||||
|
margin-right: 10% !important;
|
||||||
|
|
||||||
|
li {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.major-totem {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(-8deg);
|
||||||
|
opacity: 1;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper {
|
||||||
|
margin-top: 1rem;
|
||||||
|
height: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.second-paper {
|
||||||
|
margin-top: 4rem;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-header {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.char-name,
|
||||||
|
.char-vermine2047 {
|
||||||
|
border-bottom: none;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-vermine2047 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: @theme-color-light;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
border-bottom: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 1.4em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0 0 0.2rem;
|
||||||
|
|
||||||
|
&.characteristics {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.totem &,
|
||||||
|
.tab.equipment &,
|
||||||
|
.tab.stories & {
|
||||||
|
margin-top: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.tabs[data-group="sheet"] {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
height: 54px;
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, auto, right top);
|
||||||
|
background-size: 100% 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 4rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
height: 2.4rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
color: #606060;
|
||||||
|
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.404);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 5px 5px @theme-color-accent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-shadow: 0 5px 5px rgba(30, 82, 37, 0.6039215686);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability {
|
||||||
|
padding-right: 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-bottom: 1px solid rgba(170, 170, 152, 0.664);
|
||||||
|
box-shadow: 0px 0px 15px rgba(128, 128, 128, 0) inset;
|
||||||
|
transition: 0.2s;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-width: min-content;
|
||||||
|
container-type: inline-size;
|
||||||
|
container-name: ability-row;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 0px 15px gray inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
min-width: 40%;
|
||||||
|
flex: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
max-width: fit-content;
|
||||||
|
margin: 0 1rem;
|
||||||
|
flex: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.specialties {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -0.2rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-dots {
|
||||||
|
height: 100%;
|
||||||
|
align-self: center;
|
||||||
|
flex: 1.5;
|
||||||
|
min-width: fit-content;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
max-width: 0.7rem;
|
||||||
|
height: 0.7rem;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
font-style: oblique;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
&.dice-pool-dot {
|
||||||
|
.background-image(radial-gradient(circle, @dice-pool-color 25%, rgb(0, 0, 0) 100%), @dice-pool-color);
|
||||||
|
max-width: 0.7rem;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dice-reroll-dot {
|
||||||
|
background: radial-gradient(circle, @dice-reroll-color 25%, rgb(0, 0, 0) 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-category {
|
||||||
|
padding: 0.3rem;
|
||||||
|
|
||||||
|
&.preferred {
|
||||||
|
box-shadow: 0px 0px 30px rgba(0, 128, 0, 0.306) inset;
|
||||||
|
|
||||||
|
h4, label {
|
||||||
|
text-shadow: 0px 0px 5px rgba(0, 128, 0, 0.411);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#edit {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reserve-grid {
|
||||||
|
line-height: 0.5rem;
|
||||||
|
transform-origin: 0% 50%;
|
||||||
|
max-width: fit-content;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
div.flexrow,
|
||||||
|
input,
|
||||||
|
.hexa {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 1rem;
|
||||||
|
min-height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .flexrow {
|
||||||
|
position: relative;
|
||||||
|
max-width: fit-content;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-list {
|
||||||
|
.item-name .item-image {
|
||||||
|
flex: 0 0 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-size: 30px;
|
||||||
|
border: none;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-list .item.flexrow > div:not(.item-name):not(.item-controls) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px;
|
||||||
|
border-bottom: 1px solid rgba(170, 170, 152, 0.664);
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── V2 tab navigation — chitin-plate style ─────────────────────────
|
||||||
|
nav.sheet-tabs[data-application-part="tabs"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: @color-chitin-dark;
|
||||||
|
border-bottom: 2px solid @color-amber;
|
||||||
|
min-height: 36px;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 16px;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 1;
|
||||||
|
transition: background 0.15s;
|
||||||
|
position: relative;
|
||||||
|
border-right: 1px solid fade(@color-membrane, 12%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: fade(@color-honeycomb, 10%);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: linear-gradient(180deg, @color-amber 0%, @color-honeycomb 100%);
|
||||||
|
color: @color-chitin-dark;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: @color-acid-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> i {
|
||||||
|
font-size: @font-size-14;
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span, > i {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container ability-row (max-width: 240px) {
|
||||||
|
.skill-dots,
|
||||||
|
span.hexa {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor.creature {
|
||||||
|
.sheet-header {
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||||
|
padding: 10px;
|
||||||
|
max-height: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields { flex: 1; }
|
||||||
|
.resources { margin-bottom: 10px; }
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 60px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
select {
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.charname {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
font-size: @font-size-18;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
.card-style();
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
label { font-weight: bold; }
|
||||||
|
span {
|
||||||
|
font-weight: bold;
|
||||||
|
color: @theme-color-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdb {
|
||||||
|
&:not(.row) {
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
h4:first-child {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
text-align: center;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.unstyled {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 3px 0;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-3col {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.mdb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor.group {
|
||||||
|
.char-header {
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||||
|
max-height: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-details {
|
||||||
|
h1.char-name input,
|
||||||
|
.char-vermine2047 a.chooseTotem {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
.reserve-control,
|
||||||
|
.morale-control {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li.actor {
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-name, .item-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.objective-item {
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-4;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-display {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
span { font-weight: bold; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.identity-field {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input { width: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.level input,
|
||||||
|
.reputation input {
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor.npc {
|
||||||
|
.sheet-header {
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||||
|
padding: 0.5rem;
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-value,
|
||||||
|
.resource-value {
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card, .npc-card {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
.card-title();
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
|
||||||
|
i { margin-right: 5px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skills-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-category {
|
||||||
|
.card-style();
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-value {
|
||||||
|
min-width: 25px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rarity-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 5px;
|
||||||
|
|
||||||
|
&.rarity-0 { .rarity-badge(@rarity-0-bg, @rarity-0-text); }
|
||||||
|
&.rarity-1 { .rarity-badge(@rarity-1-bg, @rarity-1-text); }
|
||||||
|
&.rarity-2 { .rarity-badge(@rarity-2-bg, @rarity-2-text); }
|
||||||
|
&.rarity-3 { .rarity-badge(@rarity-3-bg, @rarity-3-text); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-category-selector {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.wounds-card {
|
||||||
|
margin-top: 15px;
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.wound-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wound-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
input { flex: 1; }
|
||||||
|
span {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-section {
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-category-header {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
.form-group-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header .resource {
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
label { font-weight: bold; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
padding: 3px 5px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
min-height: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.editor-content {
|
||||||
|
min-height: 80px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hexa {
|
||||||
|
.hexa-style();
|
||||||
|
.transition();
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor {
|
||||||
|
.totem-details {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img.img-totem {
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
filter: grayscale(1);
|
||||||
|
opacity: 0.15;
|
||||||
|
position: absolute;
|
||||||
|
width: 30%;
|
||||||
|
height: auto;
|
||||||
|
pointer-events: none;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
left: 35%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.minor-totems {
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(146, 156, 111, 0.5215686275);
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 2rem;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human, &.adapted {
|
||||||
|
.transition(0.3s);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human img.img-totem,
|
||||||
|
&.adapted img.img-totem {
|
||||||
|
filter: drop-shadow(0px 0px 20px rgb(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human.major,
|
||||||
|
&.adapted.major {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human.major img,
|
||||||
|
&.adapted.major img {
|
||||||
|
filter: drop-shadow(0px 0px 10px red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-dice {
|
||||||
|
.human-dice,
|
||||||
|
.adapted-dice {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.human-dice i,
|
||||||
|
.adapted-dice i {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
color: @totem-human-color;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapted-dice {
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 2rem;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapted-dice i {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
color: @totem-adapted-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.human {
|
||||||
|
left: 0;
|
||||||
|
img { left: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapted {
|
||||||
|
right: 0;
|
||||||
|
img { right: 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,691 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
src: url("@{fonts-path}/dcc_sharp_distress_black_by_dccanim.otf");
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.system-vermine2047 {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
color: @theme-color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override Foundry Core default CSS custom properties
|
||||||
|
// for better readability on the dark Vermine2047 theme.
|
||||||
|
|
||||||
|
// AppV2 windows (sheets) use .themed.theme-dark on their content
|
||||||
|
.system-vermine2047 .themed.theme-dark {
|
||||||
|
--color-text-primary: #888888;
|
||||||
|
--color-text-emphatic: #999999;
|
||||||
|
--color-text-secondary: #777777;
|
||||||
|
--color-text-subtle: #666666;
|
||||||
|
--color-text-light-highlight: #888888;
|
||||||
|
--color-text-light-heading: #777777;
|
||||||
|
--color-text-light-primary: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old-style Application windows (settings-config, dialogs) inherit from body.theme-dark
|
||||||
|
body.system-vermine2047.theme-dark {
|
||||||
|
--color-text-primary: #888888;
|
||||||
|
--color-text-emphatic: #999999;
|
||||||
|
--color-text-secondary: #777777;
|
||||||
|
--color-text-subtle: #666666;
|
||||||
|
--color-text-light-highlight: #888888;
|
||||||
|
--color-text-light-heading: #777777;
|
||||||
|
--color-text-light-primary: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-config button,
|
||||||
|
#settings-config a.button {
|
||||||
|
--button-text-color: #000000;
|
||||||
|
--button-border-color: #666666;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.system-vermine2047.theme-dark .form-group > label {
|
||||||
|
color: #777777;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-config p,
|
||||||
|
#settings-config .hint {
|
||||||
|
color: #777777;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.unstyled {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-app {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
.shadow(0px, 0px, 30px, rgba(106, 176, 76, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-vermine2047 {
|
||||||
|
.window-content,
|
||||||
|
form.application.sheet.vermine2047 {
|
||||||
|
.background-image(url("@{ui-path}/box_background.webp"), repeat);
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog .window-content {
|
||||||
|
.background-image(url("@{ui-path}/fond_chat_box.webp"), repeat);
|
||||||
|
padding: 0.5rem;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-content .row {
|
||||||
|
&.smb { margin-bottom: 0.25rem; }
|
||||||
|
&.mdb { margin-bottom: 0.5rem; }
|
||||||
|
&.lgb { margin-bottom: 1rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.rollable:hover,
|
||||||
|
.rollable:focus {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 0 10px @color-acid-green;
|
||||||
|
cursor: pointer;
|
||||||
|
.transition();
|
||||||
|
}
|
||||||
|
|
||||||
|
.rollable {
|
||||||
|
.transition();
|
||||||
|
}
|
||||||
|
|
||||||
|
img.profile-img {
|
||||||
|
filter: drop-shadow(0px 0px 20px rgb(110, 133, 27));
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.system-vermine2047 img#logo {
|
||||||
|
content: url("@{assets-path}/images/ui/logo_vermine_foundry.webp");
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-log,
|
||||||
|
.chat-log {
|
||||||
|
.message {
|
||||||
|
.background-image(url("@{ui-path}/box_background.webp"), repeat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-column: span 2 / span 2;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-2col { .grid(); }
|
||||||
|
.grid-3col { grid-column: span 3; grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||||
|
.grid-4col { grid-column: span 4; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||||
|
.grid-5col { grid-column: span 5; grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||||
|
.grid-6col { grid-column: span 6; grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
||||||
|
.grid-7col { grid-column: span 7; grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
||||||
|
.grid-8col { grid-column: span 8; grid-template-columns: repeat(8, minmax(0, 1fr)); }
|
||||||
|
.grid-9col { grid-column: span 9; grid-template-columns: repeat(9, minmax(0, 1fr)); }
|
||||||
|
.grid-10col { grid-column: span 10; grid-template-columns: repeat(10, minmax(0, 1fr)); }
|
||||||
|
.grid-11col { grid-column: span 11; grid-template-columns: repeat(11, minmax(0, 1fr)); }
|
||||||
|
.grid-12col { grid-column: span 12; grid-template-columns: repeat(12, minmax(0, 1fr)); }
|
||||||
|
|
||||||
|
.grid-start-2 { grid-column-start: 2; }
|
||||||
|
.grid-start-3 { grid-column-start: 3; }
|
||||||
|
.grid-start-4 { grid-column-start: 4; }
|
||||||
|
.grid-start-5 { grid-column-start: 5; }
|
||||||
|
.grid-start-6 { grid-column-start: 6; }
|
||||||
|
.grid-start-7 { grid-column-start: 7; }
|
||||||
|
.grid-start-8 { grid-column-start: 8; }
|
||||||
|
.grid-start-9 { grid-column-start: 9; }
|
||||||
|
.grid-start-10 { grid-column-start: 10; }
|
||||||
|
.grid-start-11 { grid-column-start: 11; }
|
||||||
|
.grid-start-12 { grid-column-start: 12; }
|
||||||
|
|
||||||
|
.grid-span-2 { grid-column-end: span 2; }
|
||||||
|
.grid-span-3 { grid-column-end: span 3; }
|
||||||
|
.grid-span-4 { grid-column-end: span 4; }
|
||||||
|
.grid-span-5 { grid-column-end: span 5; }
|
||||||
|
.grid-span-6 { grid-column-end: span 6; }
|
||||||
|
.grid-span-7 { grid-column-end: span 7; }
|
||||||
|
.grid-span-8 { grid-column-end: span 8; }
|
||||||
|
.grid-span-9 { grid-column-end: span 9; }
|
||||||
|
.grid-span-10 { grid-column-end: span 10; }
|
||||||
|
.grid-span-11 { grid-column-end: span 11; }
|
||||||
|
.grid-span-12 { grid-column-end: span 12; }
|
||||||
|
|
||||||
|
.flex-group-center,
|
||||||
|
.flex-group-left,
|
||||||
|
.flex-group-right {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-group-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-group-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-align-left { align-items: flex-start; }
|
||||||
|
.flex-align-right { align-items: flex-end; }
|
||||||
|
.gap-xs { gap: 2px; }
|
||||||
|
.gap-sm { gap: 4px; }
|
||||||
|
.gap-md { gap: 8px; }
|
||||||
|
.gap-lg { gap: 16px; }
|
||||||
|
.flexshrink { flex: 0; }
|
||||||
|
.flex-between { justify-content: space-between; }
|
||||||
|
.flexlarge { flex: 2; }
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
outline: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #577822;
|
||||||
|
border: 1px solid var(--color-border-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-vermine2047 {
|
||||||
|
.item-form {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header {
|
||||||
|
flex: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.profile-img {
|
||||||
|
flex: 0 0 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.charname {
|
||||||
|
height: 50px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-tabs {
|
||||||
|
flex: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-body .tab,
|
||||||
|
.editor {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
min-height: 75px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
min-width: 100%;
|
||||||
|
|
||||||
|
.editor-content {
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor:hover .editor-edit {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tox {
|
||||||
|
min-height: 25vh;
|
||||||
|
|
||||||
|
.tox-editor-container {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tox-edit-area {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-header {
|
||||||
|
height: 28px;
|
||||||
|
margin: 2px 0;
|
||||||
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border: 2px groove #eeede0;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 5px;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
color: #444;
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
flex: 2;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
h3, h4 {
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-controls {
|
||||||
|
display: flex;
|
||||||
|
flex: 0;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 2px;
|
||||||
|
border-bottom: 1px solid #c9c7b8;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
.item-image {
|
||||||
|
flex: 0 0 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-size: 30px;
|
||||||
|
border: none;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-prop {
|
||||||
|
text-align: center;
|
||||||
|
border-left: 1px solid #c9c7b8;
|
||||||
|
border-right: 1px solid #c9c7b8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-header {
|
||||||
|
height: 28px;
|
||||||
|
margin: 2px 0;
|
||||||
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border: 2px groove #eeede0;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
padding-left: 5px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.effects .item {
|
||||||
|
.effect-source,
|
||||||
|
.effect-duration,
|
||||||
|
.effect-controls {
|
||||||
|
text-align: center;
|
||||||
|
border-left: 1px solid #c9c7b8;
|
||||||
|
border-right: 1px solid #c9c7b8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect-controls {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-formula {
|
||||||
|
flex: 0 0 200px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.game-mode {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: auto;
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
top: 1rem;
|
||||||
|
z-index: 900;
|
||||||
|
width: 55%;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 900;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(255, 255, 255, 0.767) 0%,
|
||||||
|
rgba(0, 0, 0, 0.61) 17%,
|
||||||
|
rgba(0, 0, 0, 0.548) 19%,
|
||||||
|
rgba(222, 255, 221, 0.575) 24%,
|
||||||
|
rgba(255, 255, 255, 0.637) 43%,
|
||||||
|
rgba(0, 0, 0, 0.486) 47%,
|
||||||
|
rgba(254, 255, 254, 0.466) 50%,
|
||||||
|
rgba(0, 0, 0, 0.699) 63%,
|
||||||
|
rgba(134, 160, 137, 0.479) 64%,
|
||||||
|
rgba(213, 248, 210, 0.493) 100%
|
||||||
|
);
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.game-mode#game-mode-1 { color: @game-mode-1-color; }
|
||||||
|
span.game-mode#game-mode-2 { color: @game-mode-2-color; }
|
||||||
|
span.game-mode#game-mode-3 { color: @game-mode-3-color; }
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
.shadow();
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#chat-log,
|
||||||
|
ol.chat-log {
|
||||||
|
header.message-header {
|
||||||
|
background-color: #000;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vermine-roll-message {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 4px 10px 10px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
margin: 6px 0 8px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.3) inset;
|
||||||
|
font-weight: 900;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .flexrow:not(.roll-total):not(.roll-results) {
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reroll section ──────────────────────────────────────────────
|
||||||
|
.reroll-fromroll {
|
||||||
|
> h4 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
margin: 6px 0 4px;
|
||||||
|
border-bottom: none;
|
||||||
|
font-weight: 900;
|
||||||
|
|
||||||
|
#allowed_reroll {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.reroll {
|
||||||
|
.transition(0.3s);
|
||||||
|
max-height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
font-size: @font-size-12;
|
||||||
|
padding: 2px 12px;
|
||||||
|
max-width: fit-content;
|
||||||
|
box-shadow: 0px 0px 2px #000;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, cover, 50% 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.visible { max-height: 15rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dice results ────────────────────────────────────────────────
|
||||||
|
ul.roll-results {
|
||||||
|
list-style: none;
|
||||||
|
padding: 6px 0;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
li.die {
|
||||||
|
position: relative;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
flex: none;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
.transition();
|
||||||
|
border-bottom: 4px solid @color-hemolymph;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -1rem;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 100;
|
||||||
|
font-size: smaller;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0;
|
||||||
|
text-shadow: 0px 0px 5px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human { border-top: 4px solid @totem-human-color; }
|
||||||
|
&.adapted { border-top: 4px solid @totem-adapted-color; }
|
||||||
|
&.rerollable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover { transform: translateY(0.5rem); }
|
||||||
|
}
|
||||||
|
&.success { border-bottom-color: @color-acid-green; }
|
||||||
|
&.adapted::after { content: "adapté"; }
|
||||||
|
&.human::after { content: "humain"; }
|
||||||
|
&.rerolled { transform: translateY(0rem); }
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([data-result]) {
|
||||||
|
background-image: url("@{dice-path}/d10c-1.webp");
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-face dice background images
|
||||||
|
.generate-dice-faces(@i: 1) when (@i =< 10) {
|
||||||
|
li.die[data-result="@{i}"] {
|
||||||
|
background-image: url("@{dice-path}/d10c-@{i}.webp");
|
||||||
|
}
|
||||||
|
.generate-dice-faces(@i + 1);
|
||||||
|
}
|
||||||
|
.generate-dice-faces();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Total section ───────────────────────────────────────────────
|
||||||
|
div.roll-total {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.3) inset;
|
||||||
|
border-radius: 2px;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
font-size: @font-size-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
#total {
|
||||||
|
color: @color-acid-green;
|
||||||
|
text-shadow: 0 0 4px fade(@color-acid-green, 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#required {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.item-card header img {
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Padding supplémentaire pour les fiches groupe, npc et créature
|
||||||
|
.system-vermine2047 {
|
||||||
|
.group-content,
|
||||||
|
.npc-content,
|
||||||
|
.creature-content {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,475 @@
|
|||||||
|
@import "variables";
|
||||||
|
@import "utilities";
|
||||||
|
|
||||||
|
.window-app.vermineDialog {
|
||||||
|
max-width: 50vw;
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
.window-content {
|
||||||
|
.background-image(url("@{ui-path}/box_background.webp"), repeat);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
details > summary::after {
|
||||||
|
content: "▶️";
|
||||||
|
position: relative;
|
||||||
|
right: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] > summary::after {
|
||||||
|
content: "🔽";
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
justify-content: space-around;
|
||||||
|
.shadow(0px, 1px, 10px, rgba(0, 0, 0, 0.555));
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0.2rem;
|
||||||
|
|
||||||
|
> * { margin: 0 0.3rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
max-width: fit-content;
|
||||||
|
.custom-select-style();
|
||||||
|
option { max-width: fit-content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
flex: 0.3;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] { .custom-input-style(); }
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] { .custom-checkbox-radio(); }
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
width: 0.8rem;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
top: 5%;
|
||||||
|
left: 5%;
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
background-size: 30% 30%;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([disabled]):hover::after { background-size: 90% 90%; }
|
||||||
|
|
||||||
|
&:checked::after {
|
||||||
|
content: "";
|
||||||
|
background-size: 70% 70%;
|
||||||
|
top: 5%;
|
||||||
|
left: 5%;
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(26, 1, 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app.vermine2047.trait-selector {
|
||||||
|
.form-group {
|
||||||
|
.shadow(0, 0, 30px, gray);
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
border: 3px solid #8e9010;
|
||||||
|
|
||||||
|
&:has(input[type="checkbox"]:checked) {
|
||||||
|
border: 3px solid green;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 2px solid #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app .actor.choose {
|
||||||
|
div.actor {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
.shadow(0px, 0px, 8px, #000);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.actor-name {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgba(255, 255, 255, 0.562);
|
||||||
|
border: 5px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
.shadow(0px, 0px, 8px, #000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
min-height: 500px;
|
||||||
|
|
||||||
|
.tabs.moods-headings {
|
||||||
|
max-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Roll Dialog V2 ─────────────────────────────────────────────────────
|
||||||
|
.application.vermine-roll {
|
||||||
|
.window-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roll-dialog-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
|
||||||
|
.dice-pool {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
color: @color-chitin-dark;
|
||||||
|
background: @color-text-light-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
.custom-select-style();
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] { .custom-input-style(); }
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] { .custom-checkbox-radio(); }
|
||||||
|
|
||||||
|
// ── Main roll section: ability + skill side-by-side ─────────────
|
||||||
|
.main-roll-section {
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
> .flexcol {
|
||||||
|
flex: 1;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
font-size: @font-size-13;
|
||||||
|
color: @color-amber;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
min-height: 28px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-score {
|
||||||
|
gap: 4px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 0.85;
|
||||||
|
|
||||||
|
#abilityScoreValue {
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-acid-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Collapsible sections (specialties, difficulty, bonuses) ─────
|
||||||
|
details {
|
||||||
|
margin-top: 2px;
|
||||||
|
|
||||||
|
summary {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.5) inset;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "▶";
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-values,
|
||||||
|
.current-specialty,
|
||||||
|
.bonus-count {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
text-transform: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open] > summary::after {
|
||||||
|
content: "▼";
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:not(summary) {
|
||||||
|
padding: 8px;
|
||||||
|
background: fade(@color-chitin-dark, 40%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Difficulty section ──────────────────────────────────────────
|
||||||
|
.difficulty-controls {
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
> .flexcol {
|
||||||
|
flex: 1;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Specialties section ─────────────────────────────────────────
|
||||||
|
.specialty-options {
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bonuses grid ────────────────────────────────────────────────
|
||||||
|
.bonus-grid {
|
||||||
|
margin: 0;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.bonus-item {
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
> label.label {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] + label {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Totems sub-section
|
||||||
|
.totems-section {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
> label.label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-options {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-hint {
|
||||||
|
width: 100%;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Total dice pool section ─────────────────────────────────────
|
||||||
|
.total-section {
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
border-top: 1px solid @color-border-dark-4;
|
||||||
|
margin-top: 4px;
|
||||||
|
gap: 10px;
|
||||||
|
background: fade(@color-chitin-dark, 50%);
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-16;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-value {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-28;
|
||||||
|
color: @color-acid-green;
|
||||||
|
text-shadow: 0 0 8px fade(@color-acid-green, 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-selector {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
padding: 1px 4px;
|
||||||
|
width: auto;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Footer buttons ───────────────────────────────────────────────
|
||||||
|
.sheet-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin-top: auto;
|
||||||
|
border-top: 1px solid @color-border-dark-4;
|
||||||
|
background: fade(@color-chitin-dark, 60%);
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-13;
|
||||||
|
padding: 6px 20px;
|
||||||
|
border: none;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
.transition();
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: @font-size-14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
@import "variables";
|
||||||
|
@import "utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .item-formula {
|
||||||
|
flex: 0 0 200px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.item .window-content {
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
.flexrow { align-items: center; }
|
||||||
|
|
||||||
|
header, h1, h2, h3, h4, h5 {
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, cover, 50% 50%);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3, h4 { text-align: center; }
|
||||||
|
h5 { margin-bottom: 0; }
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: @color-text-light-highlight;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
border: 1px solid @color-border-dark-3;
|
||||||
|
border-left: 3px solid @theme-color-primary;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
.transition();
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-color: @theme-color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexrow {
|
||||||
|
min-width: 5rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damages-row {
|
||||||
|
margin: 0;
|
||||||
|
.radios {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-pannes,
|
||||||
|
.damage-state,
|
||||||
|
.damage-effect {
|
||||||
|
text-align: center;
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
.custom-select-style();
|
||||||
|
color: @color-text-light-1;
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
|
||||||
|
|
||||||
|
option {
|
||||||
|
background: @color-bg-option;
|
||||||
|
color: @color-text-dark-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.traits {
|
||||||
|
.shadow();
|
||||||
|
|
||||||
|
h3, h4 { margin: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor,
|
||||||
|
.editor-content {
|
||||||
|
color: @color-text-dark-primary;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
min-height: 6rem;
|
||||||
|
|
||||||
|
p { margin: 0.25rem 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#chat-log div.item-card header img,
|
||||||
|
ol.chat-log div.item-card header img {
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.sans-font {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message .message-header {
|
||||||
|
line-height: 20px;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0px 0px 5px black;
|
||||||
|
background: #1918135e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-around {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexsmall {
|
||||||
|
flex: 0.5;
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
// ============================================
|
||||||
|
// Utilitaires LESS pour Vermine2047
|
||||||
|
// Mixins, fonctions et classes utilitaires
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Mixin pour les ombres standard
|
||||||
|
.shadow() {
|
||||||
|
box-shadow: 0 2rem @theme-color-shadow;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.shadow(@color) {
|
||||||
|
box-shadow: 0 2rem @color;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.shadow(@color, @blur) {
|
||||||
|
box-shadow: 0 @blur @color;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.shadow(@h-offset, @v-offset, @blur, @color) {
|
||||||
|
box-shadow: @h-offset @v-offset @blur @color;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour le style des hexagones
|
||||||
|
.hexa-style(@bg-color: rgba(255, 255, 255, 0.425), @bg-color-end: rgba(0, 0, 0, 0.288)) {
|
||||||
|
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||||||
|
background: radial-gradient(circle, @bg-color 0%, @bg-color-end 100%);
|
||||||
|
max-height: 1.5rem;
|
||||||
|
max-width: 1.5rem;
|
||||||
|
min-width: 1.5rem;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
color: #000;
|
||||||
|
vertical-align: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les boutons rollable
|
||||||
|
.rollable-style() {
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 0 10px red;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les inputs de type hexa
|
||||||
|
.input-hexa-style() {
|
||||||
|
.hexa-style();
|
||||||
|
input {
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
input[type="radio"]::after,
|
||||||
|
input[type="radio"]::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les classes flex
|
||||||
|
.flex-container(@direction: row, @wrap: nowrap, @justify: flex-start, @align: stretch) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: @direction;
|
||||||
|
flex-wrap: @wrap;
|
||||||
|
justify-content: @justify;
|
||||||
|
align-items: @align;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour le style des cartes
|
||||||
|
.card-style(@bg-color: rgba(0, 0, 0, 0.1), @border-color: #444) {
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
background: @bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les titres de carte
|
||||||
|
.card-title(@border-color: #666) {
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: 1px solid @border-color;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les éléments de liste avec bordure
|
||||||
|
.list-item-with-border(@border-color: #333) {
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @border-color;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les groupes de formulaires
|
||||||
|
.form-group-style(@margin-bottom: 10px) {
|
||||||
|
margin-bottom: @margin-bottom;
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les ressources
|
||||||
|
.resource-style(@bg-color: rgba(0, 0, 0, 0.05)) {
|
||||||
|
padding: 5px;
|
||||||
|
background: @bg-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0 5px;
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 60px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.resource-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les badges de rareté
|
||||||
|
.rarity-badge(@bg-color, @text-color) {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 5px;
|
||||||
|
background: @bg-color;
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour le fond avec image
|
||||||
|
.background-image(@url, @repeat: no-repeat, @size: auto, @position: center) {
|
||||||
|
background: @url @repeat @position / @size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les transitions
|
||||||
|
.transition(@property: all, @duration: 0.2s, @timing: ease-out) {
|
||||||
|
transition: @property @duration @timing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les styles de l'éditeur TinyMCE
|
||||||
|
.tiny-mce-style() {
|
||||||
|
.tox {
|
||||||
|
min-height: 25vh;
|
||||||
|
.tox-editor-container {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.tox-edit-area {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les styles des inputs personnalisés
|
||||||
|
.custom-input-style() {
|
||||||
|
appearance: none;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, auto, center);
|
||||||
|
background-size: 100% auto;
|
||||||
|
height: 0.4rem;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 0px 13px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
margin-top: -0.3rem;
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
.background-image(url("@{totems-path}/human.webp"), no-repeat, cover);
|
||||||
|
filter: contrast(2);
|
||||||
|
box-shadow: 0px 0px 10px #000;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 10px #ff0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les checkbox et radio boutons personnalisés
|
||||||
|
.custom-checkbox-radio() {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
box-shadow: 0px 0px 3px #85854e;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
.transition();
|
||||||
|
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||||||
|
box-shadow: 0px 0px 6px #000 inset;
|
||||||
|
background-color: rgba(61, 11, 11, 0.658);
|
||||||
|
|
||||||
|
&[disabled=true] {
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: " ";
|
||||||
|
.background-image(url("@{totems-path}/human.webp"), no-repeat, auto, 50% 150%);
|
||||||
|
position: relative;
|
||||||
|
top: 10%;
|
||||||
|
left: 0%;
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
display: block;
|
||||||
|
border-radius: 0%;
|
||||||
|
padding: 0;
|
||||||
|
.transition();
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: rgba(26, 107, 12, 0.658);
|
||||||
|
&:after {
|
||||||
|
font-weight: 900;
|
||||||
|
background-color: rgba(26, 1, 1, 0);
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les selects personnalisés
|
||||||
|
.custom-select-style() {
|
||||||
|
border: none;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, auto, 100% 100%);
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0px 0px 15px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, auto, 100% 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
// ============================================
|
||||||
|
// Variables LESS pour Vermine2047
|
||||||
|
// Converti depuis les variables CSS du fichier vermine2047.css
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Couleurs de texte - clair
|
||||||
|
@color-text-light-highlight: #96d696;
|
||||||
|
@color-text-light-heading: #9fd8a8;
|
||||||
|
@color-text-light-primary: #a4b5b3;
|
||||||
|
|
||||||
|
// Couleurs de texte - foncé
|
||||||
|
@color-text-dark-primary: #131919;
|
||||||
|
@color-text-dark-secondary: #444b4a;
|
||||||
|
@color-text-dark-header: #1d2223;
|
||||||
|
@color-text-dark-inactive: #71797a;
|
||||||
|
|
||||||
|
// Couleurs de texte - autres
|
||||||
|
@color-text-hyperlink: #5aaf0a;
|
||||||
|
|
||||||
|
// Niveaux de gris pour le texte clair
|
||||||
|
@color-text-light-0: #fff;
|
||||||
|
@color-text-light-1: #e0f0f0;
|
||||||
|
@color-text-light-2: #c9e0c0;
|
||||||
|
@color-text-light-3: #90c4a4;
|
||||||
|
@color-text-light-4: #80c08b;
|
||||||
|
@color-text-light-5: #60b06b;
|
||||||
|
@color-text-light-6: #40a05d;
|
||||||
|
@color-text-light-7: #208028;
|
||||||
|
|
||||||
|
// Niveaux de gris pour le texte foncé
|
||||||
|
@color-text-dark-1: #111;
|
||||||
|
@color-text-dark-2: #222;
|
||||||
|
@color-text-dark-3: #444;
|
||||||
|
@color-text-dark-4: #555;
|
||||||
|
@color-text-dark-5: #666;
|
||||||
|
@color-text-dark-6: #777;
|
||||||
|
|
||||||
|
// Couleurs de bordure - clair
|
||||||
|
@color-border-light-1: #b0d9b0;
|
||||||
|
@color-border-light-2: #80c0c0;
|
||||||
|
|
||||||
|
// Couleurs de bordure - foncé
|
||||||
|
@color-border-dark-1: #131919;
|
||||||
|
@color-border-dark-2: #1d2223;
|
||||||
|
@color-border-dark-3: #2d3333;
|
||||||
|
@color-border-dark-4: #3d4444;
|
||||||
|
@color-border-dark-5: #668888;
|
||||||
|
|
||||||
|
// Couleurs d'ombre
|
||||||
|
@color-shadow-primary: #7bb60d;
|
||||||
|
@color-shadow-highlight: #85cc01d0;
|
||||||
|
@color-shadow-dark: #000;
|
||||||
|
|
||||||
|
// Couleurs de soulignement
|
||||||
|
@color-underline-inactive: #71797a;
|
||||||
|
@color-underline-active: #1a1944;
|
||||||
|
@color-underline-header: #228247;
|
||||||
|
|
||||||
|
// Couleurs de bordure - autres
|
||||||
|
@color-border-light-highlight: #b0d9b0;
|
||||||
|
@color-border-light-primary: #a4b5b3;
|
||||||
|
@color-border-light-secondary: #9fc7d8;
|
||||||
|
@color-border-light-tertiary: #71797a;
|
||||||
|
@color-border-dark: #000;
|
||||||
|
@color-border-dark-primary: #131919;
|
||||||
|
@color-border-dark-secondary: #1d2223;
|
||||||
|
@color-border-dark-tertiary: #444b4a;
|
||||||
|
@color-border-highlight: #85c019;
|
||||||
|
@color-border-highlight-alt: #70c008;
|
||||||
|
|
||||||
|
// Couleurs de fond
|
||||||
|
@color-bg-btn-minor-inactive: #9fc7d8;
|
||||||
|
@color-bg-btn-minor-active: #a4b5b3;
|
||||||
|
@color-bg-option: #ccdada;
|
||||||
|
|
||||||
|
// Autres couleurs
|
||||||
|
@color-checkbox-checked: #666;
|
||||||
|
@color-ownership-none: #00ff55;
|
||||||
|
@color-ownership-observer: #71797a;
|
||||||
|
@color-ownership-owner: #a4b5b3;
|
||||||
|
|
||||||
|
// Niveaux de log
|
||||||
|
@color-level-info: #b95c87;
|
||||||
|
@color-level-warning: #04b184;
|
||||||
|
@color-level-error: #03750;
|
||||||
|
@color-level-success: #3c266c;
|
||||||
|
|
||||||
|
// Z-index
|
||||||
|
@z-index-canvas: 0;
|
||||||
|
@z-index-app: 30;
|
||||||
|
@z-index-ui: 60;
|
||||||
|
@z-index-window: 100;
|
||||||
|
@z-index-tooltip: 9999;
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
@sidebar-width: 300px;
|
||||||
|
@sidebar-header-height: 32px;
|
||||||
|
@sidebar-item-height: 48px;
|
||||||
|
@hotbar-height: 52px;
|
||||||
|
@hotbar-width: 578px;
|
||||||
|
@macro-size: 50px;
|
||||||
|
@players-width: 200px;
|
||||||
|
@form-field-height: 26px;
|
||||||
|
|
||||||
|
// Polices
|
||||||
|
@font-mono: monospace;
|
||||||
|
|
||||||
|
// Tailles de police
|
||||||
|
@font-size-11: 0.6875rem;
|
||||||
|
@font-size-12: 0.75rem;
|
||||||
|
@font-size-13: 0.8125rem;
|
||||||
|
@font-size-14: 0.875rem;
|
||||||
|
@font-size-16: 1rem;
|
||||||
|
@font-size-18: 1.125rem;
|
||||||
|
@font-size-20: 1.25rem;
|
||||||
|
@font-size-24: 1.5rem;
|
||||||
|
@font-size-28: 1.75rem;
|
||||||
|
@font-size-32: 2rem;
|
||||||
|
@font-size-48: 3rem;
|
||||||
|
|
||||||
|
// Hauteurs de ligne
|
||||||
|
@line-height-12: 0.75rem;
|
||||||
|
@line-height-16: 1rem;
|
||||||
|
@line-height-20: 1.25rem;
|
||||||
|
@line-height-30: 1.875rem;
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Variables spécifiques Vermine2047
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Couleurs thématiques
|
||||||
|
@theme-color-primary: #7e7544;
|
||||||
|
@theme-color-secondary: #dfdfdf;
|
||||||
|
@theme-color-accent: #1fa832;
|
||||||
|
@theme-color-dark: #191813;
|
||||||
|
@theme-color-light: #4e564c;
|
||||||
|
@theme-color-shadow: rgba(0, 0, 0, 0.7098039216);
|
||||||
|
@theme-color-highlight: #005a3c;
|
||||||
|
|
||||||
|
// Couleurs insecte / organique
|
||||||
|
@color-acid-green: #6ab04c;
|
||||||
|
@color-amber: #e8b84b;
|
||||||
|
@color-chitin-dark: #2a2520;
|
||||||
|
@color-hemolymph: #8b4513;
|
||||||
|
@color-membrane: rgba(106, 176, 76, 0.1);
|
||||||
|
@color-honeycomb: #c9a84c;
|
||||||
|
|
||||||
|
// Couleurs pour les dés et totems
|
||||||
|
@dice-pool-color: rgb(94, 90, 77);
|
||||||
|
@dice-reroll-color: rgb(187, 182, 165);
|
||||||
|
@totem-human-color: #064930;
|
||||||
|
@totem-adapted-color: #553402;
|
||||||
|
|
||||||
|
// Couleurs pour les niveaux de rareté
|
||||||
|
@rarity-0-bg: #444;
|
||||||
|
@rarity-0-text: #aaa;
|
||||||
|
@rarity-1-bg: #5a7a5a;
|
||||||
|
@rarity-1-text: #fff;
|
||||||
|
@rarity-2-bg: #7a9a7a;
|
||||||
|
@rarity-2-text: #000;
|
||||||
|
@rarity-3-bg: #9a5a9a;
|
||||||
|
@rarity-3-text: #fff;
|
||||||
|
|
||||||
|
// Couleurs pour les modes de jeu
|
||||||
|
@game-mode-1-color: rgba(235, 218, 143, 0.8);
|
||||||
|
@game-mode-2-color: #83f883;
|
||||||
|
@game-mode-3-color: rgba(245, 124, 124, 0.8);
|
||||||
|
|
||||||
|
// Chemins des assets
|
||||||
|
@assets-path: "/systems/vermine2047/assets";
|
||||||
|
@images-path: "@{assets-path}/images";
|
||||||
|
@fonts-path: "@{assets-path}/fonts";
|
||||||
|
@ui-path: "@{images-path}/ui";
|
||||||
|
@totems-path: "@{images-path}/ui/totems";
|
||||||
|
@dice-path: "@{assets-path}/dice";
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// ============================================
|
||||||
|
// Fichier principal LESS pour Vermine2047
|
||||||
|
// Ce fichier importe tous les modules LESS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// 1. Import de police Google (doit être en premier car c'est du CSS pur)
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
|
||||||
|
|
||||||
|
// 2. Variables et utilitaires (doivent être importés avant tout le reste)
|
||||||
|
@import "variables";
|
||||||
|
@import "utilities";
|
||||||
|
|
||||||
|
// 3. Styles de base
|
||||||
|
@import "base";
|
||||||
|
|
||||||
|
// 4. Legacy SCSS converti en LESS
|
||||||
|
@import "legacy";
|
||||||
|
|
||||||
|
// 5. Styles des acteurs
|
||||||
|
@import "actor/actor";
|
||||||
|
@import "actor/totem";
|
||||||
|
@import "actor/npc";
|
||||||
|
@import "actor/group";
|
||||||
|
@import "actor/creature";
|
||||||
|
|
||||||
|
// 6. Styles des items
|
||||||
|
@import "items";
|
||||||
|
|
||||||
|
// 7. Styles des dialogs
|
||||||
|
@import "dialogs";
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Notes:
|
||||||
|
// - L'ordre des imports est important
|
||||||
|
// - Les variables doivent être définies avant d'être utilisées
|
||||||
|
// - Les mixins doivent être définis avant d'être appelés
|
||||||
|
// ============================================
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export { default as VermineBaseActorSheet } from "./base-actor-sheet.mjs"
|
||||||
|
export { VermineBaseItemSheet } from "./base-item-sheet.mjs"
|
||||||
|
export { default as VermineCharacterSheetV2 } from "./character-sheet.mjs"
|
||||||
|
export { default as VermineNpcSheetV2 } from "./npc-sheet.mjs"
|
||||||
|
export { default as VermineGroupSheetV2 } from "./group-sheet.mjs"
|
||||||
|
export { default as VermineCreatureSheetV2 } from "./creature-sheet.mjs"
|
||||||
|
export {
|
||||||
|
VermineItemSheetV2,
|
||||||
|
VermineWeaponSheetV2,
|
||||||
|
VermineDefenseSheetV2,
|
||||||
|
VermineVehicleSheetV2,
|
||||||
|
VermineAbilitySheetV2,
|
||||||
|
VermineSpecialtySheetV2,
|
||||||
|
VermineBackgroundSheetV2,
|
||||||
|
VermineTraumaSheetV2,
|
||||||
|
VermineEvolutionSheetV2,
|
||||||
|
VermineRumorSheetV2,
|
||||||
|
VermineTargetSheetV2,
|
||||||
|
VermineRiteSheetV2
|
||||||
|
} from "./item-sheets.mjs"
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
import { onManageActiveEffect } from "../../system/effects.mjs"
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fiche de base pour tous les acteurs Vermine 2047 (ApplicationV2).
|
||||||
|
* Remplace VermineActorSheet (AppV1).
|
||||||
|
*/
|
||||||
|
export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||||
|
|
||||||
|
// ── Mode édition / jeu ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||||
|
|
||||||
|
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||||
|
|
||||||
|
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
|
||||||
|
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
|
||||||
|
|
||||||
|
// ── Options par défaut ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["vermine2047", "actor"],
|
||||||
|
position: { width: 800, height: "auto" },
|
||||||
|
form: { submitOnChange: true },
|
||||||
|
window: { resizable: true },
|
||||||
|
dragDrop: [{ dragSelector: ".item", dropSelector: null }],
|
||||||
|
actions: {
|
||||||
|
editImage: VermineBaseActorSheet.#onEditImage,
|
||||||
|
toggleSheet: VermineBaseActorSheet.#onToggleSheet,
|
||||||
|
edit: VermineBaseActorSheet.#onItemEdit,
|
||||||
|
delete: VermineBaseActorSheet.#onItemDelete,
|
||||||
|
create: VermineBaseActorSheet.#onItemCreate,
|
||||||
|
roll: VermineBaseActorSheet.#onRollItem,
|
||||||
|
clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
|
||||||
|
effectControl: VermineBaseActorSheet.#onEffectControl,
|
||||||
|
chooseTotem: VermineBaseActorSheet.#onChooseTotem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Drag & Drop ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#dragDrop
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options)
|
||||||
|
this.#dragDrop = this.#createDragDropHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
#createDragDropHandlers() {
|
||||||
|
return this.options.dragDrop.map(d => {
|
||||||
|
d.permissions = {
|
||||||
|
dragstart: this._canDragStart.bind(this),
|
||||||
|
drop: this._canDragDrop.bind(this)
|
||||||
|
}
|
||||||
|
d.callbacks = {
|
||||||
|
dragover: this._onDragOver.bind(this),
|
||||||
|
drop: this._onDrop.bind(this)
|
||||||
|
}
|
||||||
|
return new foundry.applications.ux.DragDrop.implementation(d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_canDragStart() { return this.isEditable }
|
||||||
|
_canDragDrop() { return this.isEditable }
|
||||||
|
|
||||||
|
// ── Soumission du formulaire ────────────────────────────────────────
|
||||||
|
|
||||||
|
/** @override - coerce string values from HTML form inputs to numbers */
|
||||||
|
_prepareSubmitData(event, form, formData, updateData) {
|
||||||
|
const fd = foundry.utils.deepClone(formData.object)
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(fd)) {
|
||||||
|
if (!key.startsWith("system.") || typeof value === "number") continue
|
||||||
|
|
||||||
|
const segments = key.slice(7).split(".")
|
||||||
|
let node = this.document.system.schema
|
||||||
|
for (const seg of segments) {
|
||||||
|
if (node instanceof foundry.data.fields.SchemaField) node = node.fields[seg]
|
||||||
|
else { node = undefined; break }
|
||||||
|
}
|
||||||
|
if (!(node instanceof foundry.data.fields.NumberField)) continue
|
||||||
|
|
||||||
|
// Handle arrays from duplicate-named form inputs
|
||||||
|
let raw = Array.isArray(value) ? value.filter(v => v !== "" && v !== null).pop() : value
|
||||||
|
if (raw === undefined) continue
|
||||||
|
if (typeof raw === "string" && raw.trim() === "") { fd[key] = 0; continue }
|
||||||
|
|
||||||
|
const num = Number(typeof raw === "string" ? raw.trim() : raw)
|
||||||
|
if (!isNaN(num)) fd[key] = num
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Contexte commun ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const enrich = async (path) => {
|
||||||
|
const val = foundry.utils.getProperty(this.document.system, path);
|
||||||
|
return val ? await foundry.applications.ux.TextEditor.implementation.enrichHTML(val, { async: true }) : "";
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
fields: this.document.schema.fields,
|
||||||
|
systemFields: this.document.system.schema.fields,
|
||||||
|
actor: this.document,
|
||||||
|
system: this.document.system,
|
||||||
|
source: this.document.toObject(),
|
||||||
|
config: CONFIG.VERMINE,
|
||||||
|
rollData: this.document.getRollData(),
|
||||||
|
isGM: game.user.isGM,
|
||||||
|
isEditMode: this.isEditMode,
|
||||||
|
isPlayMode: this.isPlayMode,
|
||||||
|
isEditable: this.isEditable,
|
||||||
|
enrichedNotes: await enrich("identity.notes"),
|
||||||
|
enrichedBiography: await enrich("identity.biography"),
|
||||||
|
enrichedRelations: await enrich("identity.relations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rendu ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async _onRoll(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const el = event.currentTarget
|
||||||
|
const type = el.dataset.type
|
||||||
|
const label = el.dataset.label
|
||||||
|
if (!type || !label) return
|
||||||
|
const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
|
||||||
|
const dialog = await RollDialog.create({
|
||||||
|
actorId: this.document.id,
|
||||||
|
rolltype: type,
|
||||||
|
label
|
||||||
|
})
|
||||||
|
if (dialog) dialog.render(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_onRender(context, options) {
|
||||||
|
super._onRender(context, options)
|
||||||
|
// Activate initial tabs (force to bypass changeTab's early-return when the
|
||||||
|
// tab is already set as active in tabGroups — Foundry v12 doesn't call
|
||||||
|
// changeTab on initial render, so the active class is never applied)
|
||||||
|
for (const [group, tab] of Object.entries(this.tabGroups ?? {})) {
|
||||||
|
this.changeTab(tab, group, {force: true})
|
||||||
|
}
|
||||||
|
// Move toggle from hidden main tab to visible position (only for sheets where
|
||||||
|
// .tab.main is not already displayed as a permanent sidebar via !important)
|
||||||
|
const mainTab = this.element.querySelector(".tab.main")
|
||||||
|
const tabs = this.element.querySelector('nav.tabs[data-application-part="tabs"]')
|
||||||
|
if ( mainTab && tabs && getComputedStyle(mainTab).display === "none" ) {
|
||||||
|
const existing = tabs.parentNode.querySelector('.sheet-header-toggle[data-moved]')
|
||||||
|
if (existing) existing.remove()
|
||||||
|
const toggle = mainTab.querySelector(".sheet-header-toggle")
|
||||||
|
if (toggle) {
|
||||||
|
toggle.dataset.moved = "true"
|
||||||
|
tabs.parentNode.insertBefore(toggle, tabs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.#dragDrop.forEach(d => d.bind(this.element))
|
||||||
|
this.element.querySelectorAll(".rollable").forEach(el => {
|
||||||
|
el.addEventListener("click", this._onRoll.bind(this))
|
||||||
|
})
|
||||||
|
// Auto-fill empty number inputs on change to prevent validation errors
|
||||||
|
this.element.addEventListener("change", e => {
|
||||||
|
const input = e.target
|
||||||
|
if (input?.type === "number" && !input.value && input.name && input !== document.activeElement) {
|
||||||
|
input.value = "0"
|
||||||
|
}
|
||||||
|
}, { capture: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _onDropItem(event, item) {
|
||||||
|
const doc = item instanceof foundry.abstract.Document ? item : await fromUuid(item.uuid)
|
||||||
|
if (!doc) return
|
||||||
|
const itemData = doc.toObject()
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onToggleSheet() {
|
||||||
|
const modes = this.constructor.SHEET_MODES
|
||||||
|
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onEditImage(event, target) {
|
||||||
|
const attr = target.dataset.edit ?? "img"
|
||||||
|
const current = foundry.utils.getProperty(this.document, attr)
|
||||||
|
const fp = new FilePicker({
|
||||||
|
current,
|
||||||
|
type: "image",
|
||||||
|
callback: (path) => this.document.update({ [attr]: path }),
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10
|
||||||
|
})
|
||||||
|
return fp.browse()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onItemEdit(event, target) {
|
||||||
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||||
|
const uuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
|
||||||
|
let item
|
||||||
|
if (uuid) item = await fromUuid(uuid)
|
||||||
|
if (!item) item = this.document.items.get(id)
|
||||||
|
item?.sheet.render(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onItemDelete(event, target) {
|
||||||
|
const itemUuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
|
||||||
|
if (itemUuid) {
|
||||||
|
const item = await fromUuid(itemUuid)
|
||||||
|
await item?.deleteDialog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||||
|
const item = this.document.items.get(id)
|
||||||
|
await item?.deleteDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onItemCreate(event, target) {
|
||||||
|
const type = target.dataset.type
|
||||||
|
if (!type) return
|
||||||
|
const name = game.i18n.localize("ITEMS.new_" + type)
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [{ name, type }])
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRollItem(event, target) {
|
||||||
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||||
|
if (!id) return
|
||||||
|
const item = this.document.items.get(id)
|
||||||
|
item?.roll()
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onClickRadioHexa(event, target) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
const input = target
|
||||||
|
const update = {}
|
||||||
|
let current = this.document
|
||||||
|
const propTree = input.name.split(".")
|
||||||
|
for (const prop of propTree) {
|
||||||
|
current = current[prop]
|
||||||
|
}
|
||||||
|
if (current != input.value) {
|
||||||
|
update[input.name] = parseInt(input.value)
|
||||||
|
} else {
|
||||||
|
update[input.name] = parseInt(input.value) - 1
|
||||||
|
}
|
||||||
|
this.document.update(update)
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onEffectControl(event, target) {
|
||||||
|
onManageActiveEffect(event, this.document)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onChooseTotem(event, target) {
|
||||||
|
const { TotemPicker } = await import("../../system/applications.mjs")
|
||||||
|
new TotemPicker(target, this.document).render(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fiche de base pour tous les items Vermine 2047 (ApplicationV2).
|
||||||
|
*/
|
||||||
|
export class VermineBaseItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||||
|
|
||||||
|
// ── Mode édition ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||||
|
|
||||||
|
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||||
|
|
||||||
|
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
|
||||||
|
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
|
||||||
|
|
||||||
|
// ── Options par défaut ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["vermine2047", "item"],
|
||||||
|
position: { width: 560, height: "auto" },
|
||||||
|
form: { submitOnChange: true },
|
||||||
|
window: { resizable: true },
|
||||||
|
actions: {
|
||||||
|
editImage: VermineBaseItemSheet.#onEditImage,
|
||||||
|
toggleSheet: VermineBaseItemSheet.#onToggleSheet,
|
||||||
|
clickDamage: VermineBaseItemSheet.#onClickDamage,
|
||||||
|
openTraits: VermineBaseItemSheet.#onOpenTraits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Drag & Drop ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#dragDrop
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options)
|
||||||
|
this.#dragDrop = this.#createDragDropHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
#createDragDropHandlers() {
|
||||||
|
if (!this.options.dragDrop) return []
|
||||||
|
return this.options.dragDrop.map(d => {
|
||||||
|
d.permissions = {
|
||||||
|
dragstart: this._canDragStart.bind(this),
|
||||||
|
drop: this._canDragDrop.bind(this)
|
||||||
|
}
|
||||||
|
d.callbacks = {
|
||||||
|
dragover: this._onDragOver.bind(this),
|
||||||
|
drop: this._onDrop.bind(this)
|
||||||
|
}
|
||||||
|
return new foundry.applications.ux.DragDrop.implementation(d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_canDragStart() { return this.isEditable }
|
||||||
|
_canDragDrop() { return this.isEditable }
|
||||||
|
|
||||||
|
// ── Contexte commun ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
return {
|
||||||
|
fields: this.document.schema.fields,
|
||||||
|
systemFields: this.document.system.schema.fields,
|
||||||
|
item: this.document,
|
||||||
|
system: this.document.system,
|
||||||
|
source: this.document.toObject(),
|
||||||
|
config: CONFIG.VERMINE,
|
||||||
|
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
|
||||||
|
isEditMode: this.isEditMode,
|
||||||
|
isPlayMode: this.isPlayMode,
|
||||||
|
isEditable: this.isEditable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rendu ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_onRender(context, options) {
|
||||||
|
this.#dragDrop.forEach(d => d.bind(this.element))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sauvegarde ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_prepareSubmitData(event, form, formData, updateData) {
|
||||||
|
return super._prepareSubmitData(event, form, formData, updateData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static #onToggleSheet() {
|
||||||
|
const modes = this.constructor.SHEET_MODES
|
||||||
|
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onEditImage(event, target) {
|
||||||
|
const attr = target.dataset.edit ?? "img"
|
||||||
|
const current = foundry.utils.getProperty(this.document, attr)
|
||||||
|
const fp = new FilePicker({
|
||||||
|
current,
|
||||||
|
type: "image",
|
||||||
|
callback: (path) => this.document.update({ [attr]: path }),
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10
|
||||||
|
})
|
||||||
|
return fp.browse()
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onClickDamage(event, target) {
|
||||||
|
// Les radios de dégâts sont 1-based dans le template (value="{{@index}}" avec index 1..max)
|
||||||
|
// mais le stockage est 0-based. On soustrait 1 avant de sauvegarder.
|
||||||
|
const prop = target.name
|
||||||
|
const value = parseInt(target.value) - 1
|
||||||
|
this.document.update({ [prop]: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onOpenTraits(event, target) {
|
||||||
|
const { TraitSelector } = await import("../../system/applications.mjs")
|
||||||
|
new TraitSelector(this.document).render(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["character"],
|
||||||
|
position: { width: 860, height: 720 },
|
||||||
|
window: { contentClasses: ["character-content"] },
|
||||||
|
actions: {
|
||||||
|
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs" },
|
||||||
|
totem: { template: "systems/vermine2047/templates/actor/appv2/character-totem.hbs" },
|
||||||
|
equipment: { template: "systems/vermine2047/templates/actor/appv2/character-equipment.hbs" },
|
||||||
|
stories: { template: "systems/vermine2047/templates/actor/appv2/character-stories.hbs" },
|
||||||
|
combat: { template: "systems/vermine2047/templates/actor/appv2/character-combat.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "abilities" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
abilities: { id: "abilities", group: "sheet", icon: "fas fa-address-card", label: "VERMINE.tabs.abilities" },
|
||||||
|
totem: { id: "totem", group: "sheet", icon: "fas fa-star", label: "VERMINE.tabs.totem" },
|
||||||
|
equipment: { id: "equipment", group: "sheet", icon: "fas fa-hammer", label: "VERMINE.tabs.equipment" },
|
||||||
|
stories: { id: "stories", group: "sheet", icon: "fas fa-book-open-reader", label: "VERMINE.tabs.stories" },
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fas fa-medal", label: "VERMINE.tabs.combat" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
context.systemFields = doc.system.schema.fields
|
||||||
|
switch (partId) {
|
||||||
|
case "main": break
|
||||||
|
case "abilities":
|
||||||
|
context.tab = context.tabs.abilities
|
||||||
|
break
|
||||||
|
case "totem":
|
||||||
|
context.tab = context.tabs.totem
|
||||||
|
context.abilities = doc.itemTypes.ability.filter(i => i.system.type !== "totem")
|
||||||
|
context.totem_abilities = doc.itemTypes.ability.filter(i => i.system.type === "totem")
|
||||||
|
context.specialties = doc.itemTypes.specialty
|
||||||
|
context.backgrounds = doc.itemTypes.background
|
||||||
|
context.traumas = doc.itemTypes.trauma
|
||||||
|
context.evolutions = doc.itemTypes.evolution
|
||||||
|
break
|
||||||
|
case "equipment":
|
||||||
|
context.tab = context.tabs.equipment
|
||||||
|
context.gear = doc.itemTypes.item
|
||||||
|
context.weapons = doc.itemTypes.weapon
|
||||||
|
context.defenses = doc.itemTypes.defense
|
||||||
|
context.vehicles = doc.itemTypes.vehicle
|
||||||
|
break
|
||||||
|
case "stories":
|
||||||
|
context.tab = context.tabs.stories
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||||
|
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTab(tab, group, options = {}) {
|
||||||
|
super.changeTab(tab, group, options)
|
||||||
|
if (group === "sheet") {
|
||||||
|
const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]')
|
||||||
|
if (main) main.classList.add("active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onAddSpecialty(event, target) {
|
||||||
|
const skillKey = target.dataset.skill
|
||||||
|
const name = game.i18n.localize("ITEMS.new_specialty")
|
||||||
|
const itemData = { name, type: "specialty" }
|
||||||
|
if (skillKey) itemData.system = { skill: skillKey }
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [itemData])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["creature"],
|
||||||
|
position: { width: 700, height: 650 },
|
||||||
|
window: { contentClasses: ["creature-content"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/creature-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
info: { template: "systems/vermine2047/templates/actor/appv2/creature-info.hbs" },
|
||||||
|
stats: { template: "systems/vermine2047/templates/actor/appv2/creature-stats.hbs" },
|
||||||
|
combat: { template: "systems/vermine2047/templates/actor/appv2/creature-combat.hbs" },
|
||||||
|
effects: { template: "systems/vermine2047/templates/actor/appv2/creature-effects.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "info" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
info: { id: "info", group: "sheet", icon: "fas fa-info-circle", label: "VERMINE.information" },
|
||||||
|
stats: { id: "stats", group: "sheet", icon: "fas fa-chart-bar", label: "VERMINE.stats" },
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" },
|
||||||
|
effects: { id: "effects", group: "sheet", icon: "fas fa-magic", label: "UI.effects.name" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTab(tab, group, options = {}) {
|
||||||
|
super.changeTab(tab, group, options)
|
||||||
|
if (group === "sheet") {
|
||||||
|
const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]')
|
||||||
|
if (main) main.classList.add("active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main":
|
||||||
|
context.patternOptions = CONFIG.VERMINE.creaturePatternLevels
|
||||||
|
context.roleOptions = CONFIG.VERMINE.creatureRoleLevels
|
||||||
|
context.sizeOptions = CONFIG.VERMINE.creatureSizeLevels
|
||||||
|
context.packOptions = CONFIG.VERMINE.creaturePackLevels
|
||||||
|
break
|
||||||
|
case "info":
|
||||||
|
context.tab = context.tabs.info
|
||||||
|
break
|
||||||
|
case "stats":
|
||||||
|
context.tab = context.tabs.stats
|
||||||
|
context.patternLabel = doc.system.pattern?.value ? CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label : ""
|
||||||
|
context.sizeLabel = doc.system.size?.value || ""
|
||||||
|
context.roleLabel = doc.system.role?.value ? CONFIG.VERMINE.creatureRoleLevels[doc.system.role.value]?.label : ""
|
||||||
|
context.packLabel = doc.system.pack?.value || game.i18n.localize("VERMINE.none")
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
break
|
||||||
|
case "effects":
|
||||||
|
context.tab = context.tabs.effects
|
||||||
|
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||||
|
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["group"],
|
||||||
|
position: { width: 700, height: 600 },
|
||||||
|
window: { contentClasses: ["group-content"] },
|
||||||
|
actions: {
|
||||||
|
chooseActor: VermineGroupSheetV2.#onChooseActor,
|
||||||
|
deleteMember: VermineGroupSheetV2.#onDeleteMember,
|
||||||
|
deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter,
|
||||||
|
deleteObjective: VermineGroupSheetV2.#onDeleteObjective,
|
||||||
|
addObjective: VermineGroupSheetV2.#onAddObjective
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/group-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
info: { template: "systems/vermine2047/templates/actor/appv2/group-info.hbs" },
|
||||||
|
gear: { template: "systems/vermine2047/templates/actor/appv2/group-gear.hbs" },
|
||||||
|
road: { template: "systems/vermine2047/templates/actor/appv2/group-road.hbs" },
|
||||||
|
reserve: { template: "systems/vermine2047/templates/actor/appv2/group-reserve.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "info" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
info: { id: "info", group: "sheet", icon: "fas fa-star", label: "VERMINE.information" },
|
||||||
|
gear: { id: "gear", group: "sheet", icon: "fas fa-gear", label: "VERMINE.gear" },
|
||||||
|
road: { id: "road", group: "sheet", icon: "fas fa-route", label: "VERMINE.road" },
|
||||||
|
reserve: { id: "reserve", group: "sheet", icon: "fas fa-users", label: "VERMINE.reserve" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
// Résoudre les IDs des membres/encounters en données acteur
|
||||||
|
context.resolvedMembers = {}
|
||||||
|
if (this.document.system.members?.length > 0) {
|
||||||
|
for (const memberId of this.document.system.members) {
|
||||||
|
const a = game.actors.get(memberId)
|
||||||
|
if (a) context.resolvedMembers[memberId] = { name: a.name, id: a.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.resolvedEncounters = {}
|
||||||
|
if (this.document.system.encounters?.length > 0) {
|
||||||
|
for (const encId of this.document.system.encounters) {
|
||||||
|
const a = game.actors.get(encId)
|
||||||
|
if (a) context.resolvedEncounters[encId] = { name: a.name, id: a.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main": break
|
||||||
|
case "info":
|
||||||
|
context.tab = context.tabs.info
|
||||||
|
context.abilities = doc.itemTypes.ability
|
||||||
|
context.specialties = doc.itemTypes.specialty
|
||||||
|
context.backgrounds = doc.itemTypes.background
|
||||||
|
context.traumas = doc.itemTypes.trauma
|
||||||
|
context.evolutions = doc.itemTypes.evolution
|
||||||
|
break
|
||||||
|
case "gear":
|
||||||
|
context.tab = context.tabs.gear
|
||||||
|
context.gear = doc.itemTypes.item
|
||||||
|
context.weapons = doc.itemTypes.weapon
|
||||||
|
context.defenses = doc.itemTypes.defense
|
||||||
|
break
|
||||||
|
case "road":
|
||||||
|
context.tab = context.tabs.road
|
||||||
|
context.vehicles = doc.itemTypes.vehicle
|
||||||
|
break
|
||||||
|
case "reserve":
|
||||||
|
context.tab = context.tabs.reserve
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker
|
||||||
|
static async #onChooseTotem(event, target) {
|
||||||
|
const { TotemPicker } = await import("../../system/applications.mjs")
|
||||||
|
new TotemPicker(target, this.document).render(true)
|
||||||
|
}
|
||||||
|
static async #onChooseActor(event, target) {
|
||||||
|
const { ActorPicker } = await import("../../system/applications.mjs")
|
||||||
|
new ActorPicker(target, this.document).render(true)
|
||||||
|
}
|
||||||
|
static #onDeleteMember(event, target) {
|
||||||
|
const li = target.closest("li.actor")
|
||||||
|
if (!li) return
|
||||||
|
const actorId = li.dataset.actorId
|
||||||
|
const idx = this.document.system.members.indexOf(actorId)
|
||||||
|
if (idx !== -1) {
|
||||||
|
const members = [...this.document.system.members]
|
||||||
|
members.splice(idx, 1)
|
||||||
|
this.document.update({ "system.members": members })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static #onDeleteEncounter(event, target) {
|
||||||
|
const li = target.closest("li.actor")
|
||||||
|
if (!li) return
|
||||||
|
const actorId = li.dataset.actorId
|
||||||
|
const idx = this.document.system.encounters.indexOf(actorId)
|
||||||
|
if (idx !== -1) {
|
||||||
|
const encounters = [...this.document.system.encounters]
|
||||||
|
encounters.splice(idx, 1)
|
||||||
|
this.document.update({ "system.encounters": encounters })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static #onDeleteObjective(event, target) {
|
||||||
|
const type = target.dataset.type
|
||||||
|
const index = parseInt(target.dataset.index)
|
||||||
|
if (isNaN(index)) return
|
||||||
|
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: [], minor: [] })
|
||||||
|
objectives[type].splice(index, 1)
|
||||||
|
this.document.update({ "system.objectives": objectives })
|
||||||
|
}
|
||||||
|
static #onAddObjective(event, target) {
|
||||||
|
const type = target.dataset.type === "major_objective" ? "major" : "minor"
|
||||||
|
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: [], minor: [] })
|
||||||
|
objectives[type].push("")
|
||||||
|
this.document.update({ "system.objectives": objectives })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { VermineBaseItemSheet } from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
// ── Item générique ────────────────────────────────────────────────────
|
||||||
|
export class VermineItemSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["item-gear"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-item-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Arme ──────────────────────────────────────────────────────────────
|
||||||
|
export class VermineWeaponSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["weapon"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-weapon-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Défense ───────────────────────────────────────────────────────────
|
||||||
|
export class VermineDefenseSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["defense"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-defense-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Véhicule ──────────────────────────────────────────────────────────
|
||||||
|
export class VermineVehicleSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["vehicle"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-vehicle-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Capacité ──────────────────────────────────────────────────────────
|
||||||
|
export class VermineAbilitySheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["ability"], position: { width: 560 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-ability-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Spécialité ────────────────────────────────────────────────────────
|
||||||
|
export class VermineSpecialtySheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["specialty"], position: { width: 400 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-specialty-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Historique ────────────────────────────────────────────────────────
|
||||||
|
export class VermineBackgroundSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["background"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-background-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Traumatisme ───────────────────────────────────────────────────────
|
||||||
|
export class VermineTraumaSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["trauma"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-trauma-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Évolution ─────────────────────────────────────────────────────────
|
||||||
|
export class VermineEvolutionSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["evolution"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-evolution-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rumeur ────────────────────────────────────────────────────────────
|
||||||
|
export class VermineRumorSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["rumor"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-rumor-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cible ─────────────────────────────────────────────────────────────
|
||||||
|
export class VermineTargetSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["target"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-target-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rite ──────────────────────────────────────────────────────────────
|
||||||
|
export class VermineRiteSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["rite"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-rite-sheet.hbs" } }
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export default class VermineNpcSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["npc"],
|
||||||
|
position: { width: 750, height: 680 },
|
||||||
|
window: { contentClasses: ["npc-content"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/npc-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
characteristics: { template: "systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs" },
|
||||||
|
skills: { template: "systems/vermine2047/templates/actor/appv2/npc-skills.hbs" },
|
||||||
|
threat: { template: "systems/vermine2047/templates/actor/appv2/npc-threat.hbs" },
|
||||||
|
combat: { template: "systems/vermine2047/templates/actor/appv2/npc-combat.hbs" },
|
||||||
|
notes: { template: "systems/vermine2047/templates/actor/appv2/npc-notes.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "characteristics" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
characteristics: { id: "characteristics", group: "sheet", icon: "fas fa-dice", label: "VERMINE.abilities" },
|
||||||
|
skills: { id: "skills", group: "sheet", icon: "fas fa-brain", label: "VERMINE.skills" },
|
||||||
|
threat: { id: "threat", group: "sheet", icon: "fas fa-exclamation-triangle", label: "ADVERSITY.threat" },
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" },
|
||||||
|
notes: { id: "notes", group: "sheet", icon: "fas fa-sticky-note", label: "IDENTITY.notes" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main":
|
||||||
|
context.npcThreatOptions = CONFIG.VERMINE.npcThreatLevels
|
||||||
|
context.npcExperienceOptions = CONFIG.VERMINE.npcExperienceLevels
|
||||||
|
context.npcRoleOptions = CONFIG.VERMINE.npcRoleLevels
|
||||||
|
context.totemOptions = CONFIG.VERMINE.totems
|
||||||
|
context.originOptions = CONFIG.VERMINE.origins
|
||||||
|
break
|
||||||
|
case "characteristics":
|
||||||
|
context.tab = context.tabs.characteristics
|
||||||
|
break
|
||||||
|
case "skills":
|
||||||
|
context.tab = context.tabs.skills
|
||||||
|
break
|
||||||
|
case "threat":
|
||||||
|
context.tab = context.tabs.threat
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||||
|
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||||
|
break
|
||||||
|
case "notes":
|
||||||
|
context.tab = context.tabs.notes
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Module de ré-export des classes de documents
|
||||||
|
* Compatible avec Foundry V2
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { default as VermineActor } from "./actor.mjs";
|
||||||
|
export { default as VermineItem } from "./item.mjs";
|
||||||
@@ -3,30 +3,7 @@
|
|||||||
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
|
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
|
||||||
* @extends {Actor}
|
* @extends {Actor}
|
||||||
*/
|
*/
|
||||||
export class VermineActor extends Actor {
|
export default class VermineActor extends Actor {
|
||||||
|
|
||||||
/** @override */
|
|
||||||
prepareData() {
|
|
||||||
// Prepare data for the actor. Calling the super version of this executes
|
|
||||||
// the following, in order: data reset (to clear active effects),
|
|
||||||
// prepareBaseData(), prepareEmbeddedDocuments() (including active effects),
|
|
||||||
// prepareDerivedData().
|
|
||||||
super.prepareData();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
prepareBaseData() {
|
|
||||||
// Data modifications in this step occur before processing embedded
|
|
||||||
// documents or derived data.
|
|
||||||
|
|
||||||
if (this.type == 'character') {
|
|
||||||
this._setAgeType();
|
|
||||||
this._setCharacterEffort();
|
|
||||||
this._setCharacterSelfControl();
|
|
||||||
this._setCharacterThresholds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
@@ -38,42 +15,55 @@ export class VermineActor extends Actor {
|
|||||||
* is queried and has a roll executed directly from it).
|
* is queried and has a roll executed directly from it).
|
||||||
*/
|
*/
|
||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData();
|
||||||
const actorData = this;
|
const actorData = this;
|
||||||
const systemData = actorData.system;
|
const systemData = actorData.system;
|
||||||
const flags = actorData.flags.vermine2047 || {};
|
const flags = actorData.flags.vermine2047 || {};
|
||||||
|
|
||||||
// Make separate methods for each Actor type (character, npc, etc.) to keep
|
// Make separate methods for each Actor type (character, npc, etc.) to keep
|
||||||
// things organized.
|
// things organized.
|
||||||
|
switch (this.type) {
|
||||||
|
case "character":
|
||||||
this._prepareCharacterData(actorData);
|
this._prepareCharacterData(actorData);
|
||||||
|
break;
|
||||||
|
case "npc":
|
||||||
this._prepareNpcData(actorData);
|
this._prepareNpcData(actorData);
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
this._prepareGroupData(actorData);
|
||||||
|
break;
|
||||||
|
case "creature":
|
||||||
|
this._prepareCreatureData(actorData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare Character type specific data
|
* Prepare Character type specific data
|
||||||
*/
|
*/
|
||||||
_prepareCharacterData(actorData) {
|
_prepareCharacterData(actorData) {
|
||||||
if (actorData.type !== 'character') return;
|
// Character derived data is computed by VermineCharacterData.prepareDerivedData()
|
||||||
|
|
||||||
// Make modifications to data here. For example:
|
|
||||||
const systemData = actorData.system;
|
|
||||||
|
|
||||||
// Loop through ability scores, and add their modifiers to our sheet output.
|
|
||||||
for (let [key, ability] of Object.entries(systemData.abilities)) {
|
|
||||||
// Calculate the modifier using d20 rules.
|
|
||||||
ability.mod = Math.floor((ability.value - 10) / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare NPC type specific data.
|
* Prepare NPC type specific data.
|
||||||
*/
|
*/
|
||||||
_prepareNpcData(actorData) {
|
_prepareNpcData(actorData) {
|
||||||
if (actorData.type !== 'npc') return;
|
// NPC derived data is computed by VermineNpcData.prepareDerivedData()
|
||||||
|
}
|
||||||
|
|
||||||
// Make modifications to data here. For example:
|
/**
|
||||||
const systemData = actorData.system;
|
* Prepare Group type specific data.
|
||||||
systemData.xp = (systemData.cr * systemData.cr) * 100;
|
*/
|
||||||
|
_prepareGroupData(actorData) {
|
||||||
|
// Group derived data is computed by VermineGroupData.prepareDerivedData()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare Creature type specific data.
|
||||||
|
*/
|
||||||
|
_prepareCreatureData(actorData) {
|
||||||
|
// Creature derived data is computed by VermineCreatureData.prepareDerivedData()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,53 +108,6 @@ export class VermineActor extends Actor {
|
|||||||
// Process additional NPC data here.
|
// Process additional NPC data here.
|
||||||
}
|
}
|
||||||
|
|
||||||
_setCharacterSelfControl() {
|
// Character derived-data methods removed — handled by VermineCharacterData.prepareDerivedData()
|
||||||
this.system.attributes.self_control.max = 0 + Object.values(this.system.abilities).filter(i => i.category === "mental" || i.category === "social").map((i) => i.value).reduce((acc, curr) => acc + curr, 0) + this.modFromAgeSelfControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setCharacterEffort() {
|
|
||||||
this.system.attributes.effort.max = 0 + Object.values(this.system.abilities).filter(i => i.category === "physical" || i.category === "manual").map((i) => i.value).reduce((acc, curr) => acc + curr, 0) + this.modFromAgeEffort;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setCharacterThresholds() {
|
|
||||||
const health = this.system.abilities.health.value;
|
|
||||||
|
|
||||||
this.system.minorWound.threshold = health;
|
|
||||||
this.system.majorWound.threshold = health + 3;
|
|
||||||
this.system.deadlyWound.threshold = (health + 7 < 11) ? health + 7 : 10;
|
|
||||||
|
|
||||||
this.system.minorWound.max = 4 + this.modFromAgeWounds.l;
|
|
||||||
this.system.majorWound.max = 3 + this.modFromAgeWounds.h;
|
|
||||||
this.system.deadlyWound.max = 2 + this.modFromAgeWounds.d;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_setAgeType() {
|
|
||||||
Object.keys(CONFIG.VERMINE.AgeTypes).forEach((type) => {
|
|
||||||
if (this.system.identity.age >= parseInt(CONFIG.VERMINE.AgeTypes[type].beginning, 10)) {
|
|
||||||
this.system.identity.ageType = type;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get ageType() {
|
|
||||||
return this.system.identity.ageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modFromAgeSelfControl() {
|
|
||||||
return this.ageType == 1 ? -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modFromAgeEffort() {
|
|
||||||
if (this.ageType == 1) return -1;
|
|
||||||
if (this.ageType == 3) return -2;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modFromAgeWounds() {
|
|
||||||
if (this.ageType == 1) return { l: 0, h: 0, d: -1 };
|
|
||||||
if (this.ageType == 3) return { l: -1, h: -1, d: -1 };
|
|
||||||
return { l: 0, h: 0, d: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* Extend the basic Item with some very simple modifications.
|
* Extend the basic Item with some very simple modifications.
|
||||||
* @extends {Item}
|
* @extends {Item}
|
||||||
*/
|
*/
|
||||||
export class VermineItem extends Item {
|
export default class VermineItem extends Item {
|
||||||
/**
|
/**
|
||||||
* Augment the basic Item data model with additional dynamic data.
|
* Augment the basic Item data model with additional dynamic data.
|
||||||
*/
|
*/
|
||||||
@@ -13,38 +13,41 @@ export class VermineItem extends Item {
|
|||||||
}
|
}
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
const actorType = (this.actor !== null) ? this.actor.type : 'character';
|
const actorType = (this.actor !== null) ? this.actor.type : 'character';
|
||||||
|
const itemType = this.type;
|
||||||
|
|
||||||
switch (this.type) {
|
// Vérifie si une méthode spécifique au type existe
|
||||||
case 'ability':
|
if (typeof this[`prepare${itemType.charAt(0).toUpperCase() + itemType.slice(1)}Data`] === 'function') {
|
||||||
if (this.system.type == "") {
|
this[`prepare${itemType.charAt(0).toUpperCase() + itemType.slice(1)}Data`]();
|
||||||
// console.log('je suis une capacité, avec pour sous-type', this.system.type, actorType);
|
|
||||||
this.system.type = actorType;
|
|
||||||
}
|
|
||||||
if (this.system.totem == "" && this.actor !== null && this.actor.system.identity.totem != "") {
|
|
||||||
// console.log('je suis une capacité, avec pour sous-type', this.system.type, actorType);
|
|
||||||
this.system.totem = this.actor.system.identity.totem;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// si dégats sur l'item, application du damage label et damage icon
|
||||||
if (this.system.damages?.value) {
|
if (this.system.damages?.value) {
|
||||||
this.damagedLabel = this.system.damages.state[parseInt(this.system.damages?.value) - 1];
|
this.damagedLabel = this.system.damages.state[parseInt(this.system.damages?.value) - 1];
|
||||||
switch (this.damagedLabel) {
|
switch (this.damagedLabel) {
|
||||||
case "endommagé":
|
case "endommagé":
|
||||||
this.damagedIcon = '<i class="fas fa-exclamation-circle" style:"color="yellow"></i>';
|
this.damagedIcon = '<i class="fas fa-exclamation-circle" style="color=yellow"></i>';
|
||||||
break;
|
break;
|
||||||
case "défectueux":
|
case "défectueux":
|
||||||
this.damagedIcon = '<i class="fas fa-exclamation-triangle" style:"color="orange"></i>';
|
this.damagedIcon = '<i class="fas fa-exclamation-triangle" style="color=orange"></i>';
|
||||||
break;
|
break;
|
||||||
case "hors d'usage":
|
case "hors d'usage":
|
||||||
this.damagedIcon = '<i class="fas fa-star-exclamation" style:"color="red"></i>';
|
this.damagedIcon = '<i class="fas fa-star-exclamation" style="color=red"></i>';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareAbilityData() {
|
||||||
|
// ability data
|
||||||
|
const actorType = (this.actor !== null) ? this.actor.type : 'character';
|
||||||
|
|
||||||
|
if (this.system.type == "") {
|
||||||
|
this.system.type = actorType;
|
||||||
|
}
|
||||||
|
if (this.system.totem == "" && this.actor !== null && this.actor.system.identity.totem != "") {
|
||||||
|
this.system.totem = this.actor.system.identity.totem;
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Prepare a data object which is passed to any Roll formulas which are created related to this Item
|
* Prepare a data object which is passed to any Roll formulas which are created related to this Item
|
||||||
* @private
|
* @private
|
||||||
@@ -65,9 +68,6 @@ export class VermineItem extends Item {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async roll() {
|
async roll() {
|
||||||
if (this.type == "weapon") {
|
|
||||||
this.rollWeapon()
|
|
||||||
}
|
|
||||||
const item = this;
|
const item = this;
|
||||||
|
|
||||||
// Initialize chat data.
|
// Initialize chat data.
|
||||||
@@ -75,30 +75,13 @@ export class VermineItem extends Item {
|
|||||||
const rollMode = game.settings.get('core', 'rollMode');
|
const rollMode = game.settings.get('core', 'rollMode');
|
||||||
const label = `[${item.type}] ${item.name}`;
|
const label = `[${item.type}] ${item.name}`;
|
||||||
|
|
||||||
// If there's no roll data, send a chat message.
|
let mess = {
|
||||||
if (!this.system.formula) {
|
|
||||||
ChatMessage.create({
|
|
||||||
speaker: speaker,
|
speaker: speaker,
|
||||||
rollMode: rollMode,
|
rollMode: rollMode,
|
||||||
flavor: label,
|
flavor: label,
|
||||||
content: item.system.description ?? ''
|
};
|
||||||
});
|
mess.content = await foundry.applications.handlebars.renderTemplate(`systems/vermine2047/templates/item/chatCards/${this.type}.hbs`, { item: this, message: mess, config: CONFIG.VERMINE }) ?? null;
|
||||||
|
ChatMessage.create(mess)
|
||||||
}
|
}
|
||||||
// Otherwise, create a roll and send a chat message from it.
|
|
||||||
else {
|
|
||||||
// Retrieve roll data.
|
|
||||||
const rollData = this.getRollData();
|
|
||||||
|
|
||||||
// Invoke the roll and submit it to chat.
|
|
||||||
const roll = new Roll(rollData.item.formula, rollData);
|
|
||||||
// If you need to store the value first, uncomment the next line.
|
|
||||||
// let result = await roll.roll({async: true});
|
|
||||||
roll.toMessage({
|
|
||||||
speaker: speaker,
|
|
||||||
rollMode: rollMode,
|
|
||||||
flavor: label,
|
|
||||||
});
|
|
||||||
return roll;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export { default as VermineCharacterData } from "./character.mjs"
|
||||||
|
export { default as VermineNpcData } from "./npc.mjs"
|
||||||
|
export { default as VermineGroupData } from "./group.mjs"
|
||||||
|
export { default as VermineCreatureData } from "./creature.mjs"
|
||||||
|
export { default as VermineItemData } from "./item.mjs"
|
||||||
|
export { default as VermineWeaponData } from "./weapon.mjs"
|
||||||
|
export { default as VermineDefenseData } from "./defense.mjs"
|
||||||
|
export { default as VermineVehicleData } from "./vehicle.mjs"
|
||||||
|
export { default as VermineAbilityData } from "./ability.mjs"
|
||||||
|
export { default as VermineSpecialtyData } from "./specialty.mjs"
|
||||||
|
export { default as VermineBackgroundData } from "./background.mjs"
|
||||||
|
export { default as VermineTraumaData } from "./trauma.mjs"
|
||||||
|
export { default as VermineEvolutionData } from "./evolution.mjs"
|
||||||
|
export { default as VermineRumorData } from "./rumor.mjs"
|
||||||
|
export { default as VermineTargetData } from "./target.mjs"
|
||||||
|
export { default as VermineRiteData } from "./rite.mjs"
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
/**
|
||||||
|
* Schémas partagés pour les DataModels Vermine 2047.
|
||||||
|
* Fonctions factory retournant des objets SchemaField réutilisables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
/** NumberField qui accepte les strings vides en les remplaçant par `initial`. */
|
||||||
|
class LooseNumberField extends fields.NumberField {
|
||||||
|
clean(value, options) {
|
||||||
|
if (value === "" || value === null || value === undefined) {
|
||||||
|
return this.initial ?? 0
|
||||||
|
}
|
||||||
|
return super.clean(value, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne un schema pour une blessure (minor/major/deadly)
|
||||||
|
* @param {number} defaultThreshold
|
||||||
|
* @param {number} defaultMax
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function woundSchema(defaultThreshold = 1, defaultMax = 5) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
threshold: new fields.NumberField({ ...reqInt, initial: defaultThreshold, min: 0 }),
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema des 3 types de blessures présents sur tous les acteurs.
|
||||||
|
*/
|
||||||
|
export function woundsSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statut de combat (offensif/actif/passif).
|
||||||
|
*/
|
||||||
|
export function combatStatusSchema(defaultDifficulty = "7") {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
difficulty: new fields.StringField({ required: true, nullable: false, initial: defaultDifficulty })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description d'équipement.
|
||||||
|
*/
|
||||||
|
export function equipmentSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
description: new fields.HTMLField({ required: true, initial: "", textSearch: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribut avec value/min/max.
|
||||||
|
* @param {number} defaultVal
|
||||||
|
* @param {number} defaultMin
|
||||||
|
* @param {number} defaultMax
|
||||||
|
*/
|
||||||
|
export function attributeSchema(defaultVal = 0, defaultMin = 0, defaultMax = 10) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new LooseNumberField({ ...reqInt, initial: defaultVal, min: defaultMin }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caractéristique (capacité) avec catégorie.
|
||||||
|
* @param {string} category - physical, manual, mental, social
|
||||||
|
*/
|
||||||
|
export function abilityField(category) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 1, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
category: new fields.StringField({ required: true, nullable: false, initial: category })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Les 8 caractéristiques communes à character et npc.
|
||||||
|
*/
|
||||||
|
export function abilitiesSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
vigor: abilityField("physical"),
|
||||||
|
health: abilityField("physical"),
|
||||||
|
precision: abilityField("manual"),
|
||||||
|
reflexes: abilityField("manual"),
|
||||||
|
knowledge: abilityField("mental"),
|
||||||
|
perception: abilityField("mental"),
|
||||||
|
will: abilityField("social"),
|
||||||
|
empathy: abilityField("social")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Une compétence individuelle.
|
||||||
|
* @param {string} category
|
||||||
|
* @param {number} rarity - 0, 1, ou 2
|
||||||
|
*/
|
||||||
|
export function skillField(category, rarity = 0) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
category: new fields.StringField({ required: true, nullable: false, initial: category }),
|
||||||
|
rarity: new fields.NumberField({ ...reqInt, initial: rarity, min: 0, max: 2 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Les 30 compétences (character et npc).
|
||||||
|
*/
|
||||||
|
export function skillsSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
// man
|
||||||
|
arts: skillField("man", 1),
|
||||||
|
civilization: skillField("man", 2),
|
||||||
|
psychology: skillField("man", 1),
|
||||||
|
rumors: skillField("man", 0),
|
||||||
|
healing: skillField("man", 1),
|
||||||
|
// animal
|
||||||
|
animalism: skillField("animal", 1),
|
||||||
|
dissection: skillField("animal", 2),
|
||||||
|
wildlife: skillField("animal", 1),
|
||||||
|
repulsion: skillField("animal", 0),
|
||||||
|
tracks: skillField("animal", 0),
|
||||||
|
// tool
|
||||||
|
crafting: skillField("tool", 2),
|
||||||
|
diy: skillField("tool", 0),
|
||||||
|
mecanical: skillField("tool", 2),
|
||||||
|
piloting: skillField("tool", 1),
|
||||||
|
technology: skillField("tool", 2),
|
||||||
|
// weapon
|
||||||
|
firearms: skillField("weapon", 2),
|
||||||
|
archery: skillField("weapon", 0),
|
||||||
|
armory: skillField("weapon", 2),
|
||||||
|
throwing: skillField("weapon", 0),
|
||||||
|
melee: skillField("weapon", 0),
|
||||||
|
// survival
|
||||||
|
alertness: skillField("survival", 0),
|
||||||
|
atletics: skillField("survival", 0),
|
||||||
|
food: skillField("survival", 0),
|
||||||
|
stealth: skillField("survival", 0),
|
||||||
|
close: skillField("survival", 0),
|
||||||
|
// world
|
||||||
|
environment: skillField("world", 1),
|
||||||
|
flora: skillField("world", 1),
|
||||||
|
road: skillField("world", 0),
|
||||||
|
toxics: skillField("world", 2),
|
||||||
|
ruins: skillField("world", 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catégories de compétences avec domaine de prédilection.
|
||||||
|
*/
|
||||||
|
export function skillCategoriesSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
preferred: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
man: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.man" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
animal: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.animal" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
tool: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.tool" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
weapon: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.weapon" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
survival: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.survival" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
world: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.world" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Item shared schemas ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rareté avec handicap.
|
||||||
|
*/
|
||||||
|
export function raritySchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }),
|
||||||
|
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dégâts des items (hors arme).
|
||||||
|
*/
|
||||||
|
export function itemDamagesSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
pannes: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: ["mineure", "mineure", "grave", "grave", "critique"]
|
||||||
|
}),
|
||||||
|
state: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: ["endommagé", "endommagé", "défectueux", "défectueux", "hors d'usage"]
|
||||||
|
}),
|
||||||
|
effect: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: ["bonus annulé", "bonus annulé", "malus 1D", "malus 1D", "inutilisable"]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base commune à tous les items (template "base" dans l'ancien template.json).
|
||||||
|
*/
|
||||||
|
export function baseItemSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
description: new fields.HTMLField({ required: true, initial: "", textSearch: true }),
|
||||||
|
rarity: raritySchema(),
|
||||||
|
reliability: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }),
|
||||||
|
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
quantity: new fields.NumberField({ ...reqInt, initial: 1, min: 1 }),
|
||||||
|
weight: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
traits: new fields.ObjectField({ required: true, initial: {} }),
|
||||||
|
damages: itemDamagesSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template "list" pour les items abstraits (ability, background, trauma, evolution, rumor, target).
|
||||||
|
* Version légère avec seulement description.
|
||||||
|
*/
|
||||||
|
export function listItemSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
description: new fields.HTMLField({ required: true, initial: "", textSearch: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schéma d'apprentissage pour les abilities.
|
||||||
|
*/
|
||||||
|
export function learnSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
threshold: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
hindrance: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Niveau générique (value/min/max).
|
||||||
|
*/
|
||||||
|
export function levelSchema(defaultVal = 1, defaultMin = 1, defaultMax = 5) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: defaultVal, min: defaultMin }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { listItemSchema, learnSchema, levelSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "ability" (capacités, pouvoirs).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineAbilityData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.ability"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
type: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
totem: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
learn: learnSchema(),
|
||||||
|
level: levelSchema(1, 1, 3),
|
||||||
|
effects: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { listItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "background" (historiques, origines).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineBackgroundData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.background"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
cost: new fields.NumberField({ ...reqInt, initial: 1, min: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "character" (personnage).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema,
|
||||||
|
abilitiesSchema,
|
||||||
|
skillCategoriesSchema,
|
||||||
|
skillsSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineCharacterData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.character"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base)
|
||||||
|
combatStatus: combatStatusSchema("7"),
|
||||||
|
|
||||||
|
// Adaptation (totems humain/adapté)
|
||||||
|
adaptation: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 5, min: 0 }),
|
||||||
|
totems: new fields.SchemaField({
|
||||||
|
human: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 3 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3, min: 0 })
|
||||||
|
}),
|
||||||
|
adapted: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 3 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3, min: 0 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Identité
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
height: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
weight: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
age: new fields.StringField({ required: true, nullable: false, initial: "15" }),
|
||||||
|
ageType: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2 }),
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
instincts: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
prohibits: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
objectives: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
relations: new fields.HTMLField({ required: true, initial: "" }),
|
||||||
|
biography: new fields.HTMLField({ required: true, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema(),
|
||||||
|
|
||||||
|
// Attributs (XP, réputation, sang-froid, effort)
|
||||||
|
attributes: new fields.SchemaField({
|
||||||
|
xp: attributeSchema(0, 0, 10),
|
||||||
|
reputation: attributeSchema(0, 0, 10),
|
||||||
|
self_control: attributeSchema(0, 0, 5),
|
||||||
|
effort: attributeSchema(0, 0, 5)
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Rencontres
|
||||||
|
encounters: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
|
||||||
|
// Caractéristiques (8)
|
||||||
|
abilities: abilitiesSchema(),
|
||||||
|
|
||||||
|
// Catégories de compétences
|
||||||
|
skill_categories: skillCategoriesSchema(),
|
||||||
|
|
||||||
|
// Compétences (30)
|
||||||
|
skills: skillsSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
this._setAgeType()
|
||||||
|
this._setSelfControlMax()
|
||||||
|
this._setEffortMax()
|
||||||
|
this._setWoundThresholds()
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détermine la tranche d'âge (1=jeune, 2=adulte, 3=vieux)
|
||||||
|
* à partir de l'âge et de la config VERMINE.AgeTypes.
|
||||||
|
*/
|
||||||
|
_setAgeType() {
|
||||||
|
const age = this.identity.age
|
||||||
|
const ageTypes = CONFIG.VERMINE.AgeTypes
|
||||||
|
const entries = Object.entries(ageTypes).reverse()
|
||||||
|
for (const [type, cfg] of entries) {
|
||||||
|
if (age >= parseInt(cfg.beginning, 10)) {
|
||||||
|
this.identity.ageType = parseInt(type, 10)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule le max de sang-froid :
|
||||||
|
* somme des caractéristiques mentales + sociales + modificateur d'âge.
|
||||||
|
*/
|
||||||
|
_setSelfControlMax() {
|
||||||
|
const abilities = Object.values(this.abilities)
|
||||||
|
const modFromAge = this._getModFromAgeSelfControl()
|
||||||
|
const sum = abilities
|
||||||
|
.filter(a => a.category === "mental" || a.category === "social")
|
||||||
|
.reduce((acc, a) => acc + a.value, 0)
|
||||||
|
this.attributes.self_control.max = sum + modFromAge
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule le max d'effort :
|
||||||
|
* somme des caractéristiques physiques + manuelles + modificateur d'âge.
|
||||||
|
*/
|
||||||
|
_setEffortMax() {
|
||||||
|
const abilities = Object.values(this.abilities)
|
||||||
|
const modFromAge = this._getModFromAgeEffort()
|
||||||
|
const sum = abilities
|
||||||
|
.filter(a => a.category === "physical" || a.category === "manual")
|
||||||
|
.reduce((acc, a) => acc + a.value, 0)
|
||||||
|
this.attributes.effort.max = sum + modFromAge
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les seuils de blessures à partir de la Santé.
|
||||||
|
*/
|
||||||
|
_setWoundThresholds() {
|
||||||
|
const health = this.abilities.health.value
|
||||||
|
const ageMods = this._getModFromAgeWounds()
|
||||||
|
|
||||||
|
this.minorWound.threshold = health
|
||||||
|
this.majorWound.threshold = health + 3
|
||||||
|
this.deadlyWound.threshold = (health + 7 < 11) ? health + 7 : 10
|
||||||
|
|
||||||
|
this.minorWound.max = 4 + ageMods.l
|
||||||
|
this.majorWound.max = 3 + ageMods.h
|
||||||
|
this.deadlyWound.max = 2 + ageMods.d
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Modificateurs liés à l'âge ──────────────────────────────────────
|
||||||
|
|
||||||
|
/** @returns {number} Modificateur de sang-froid selon l'âge. */
|
||||||
|
_getModFromAgeSelfControl() {
|
||||||
|
return this.identity.ageType === 1 ? -1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {number} Modificateur d'effort selon l'âge. */
|
||||||
|
_getModFromAgeEffort() {
|
||||||
|
if (this.identity.ageType === 1) return -1
|
||||||
|
if (this.identity.ageType === 3) return -2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {{l: number, h: number, d: number}} Modificateurs de blessures selon l'âge. */
|
||||||
|
_getModFromAgeWounds() {
|
||||||
|
if (this.identity.ageType === 1) return { l: 0, h: 0, d: -1 }
|
||||||
|
if (this.identity.ageType === 3) return { l: -1, h: -1, d: -1 }
|
||||||
|
return { l: 0, h: 0, d: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "creature" (créature).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineCreatureData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.creature"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base)
|
||||||
|
combatStatus: combatStatusSchema(),
|
||||||
|
|
||||||
|
// Identité
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
notes: new fields.HTMLField({ required: true, initial: "" }),
|
||||||
|
biography: new fields.HTMLField({ required: true, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Compétences (description libre)
|
||||||
|
skills: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
|
||||||
|
// Modes de jeu actifs
|
||||||
|
modes: new fields.SchemaField({
|
||||||
|
survival: new fields.BooleanField({ required: true, initial: true }),
|
||||||
|
nightmare: new fields.BooleanField({ required: true, initial: true }),
|
||||||
|
apocalypse: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Niveaux de créature (patron, taille, rôle, meute)
|
||||||
|
pattern: attributeSchema(1, 1, 4),
|
||||||
|
size: attributeSchema(1, 1, 3),
|
||||||
|
role: attributeSchema(1, 1, 4),
|
||||||
|
pack: attributeSchema(0, 0, 3),
|
||||||
|
|
||||||
|
// Valeurs calculées (dérivées de pattern/size/role/pack)
|
||||||
|
computed: new fields.SchemaField({
|
||||||
|
attack: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
damage: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
vigor: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
reaction: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
reactionBonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
pools: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
gear: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 9 }),
|
||||||
|
gearHindrance: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
protection: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1 })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
// 1. Calculer les valeurs dérivées (attaque, dégâts, vigueur, etc.)
|
||||||
|
this._calculateCreatureComputedValues()
|
||||||
|
|
||||||
|
// 2. Calculer les seuils de blessures
|
||||||
|
this._calculateCreatureWoundThresholds()
|
||||||
|
|
||||||
|
// 3. Mettre à jour le statut de combat
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les valeurs dérivées à partir des niveaux de patron, taille, rôle et meute.
|
||||||
|
* Utilise les configs CONFIG.VERMINE.creaturePatternLevels, .creatureSizeLevels,
|
||||||
|
* .creatureRoleLevels, .creaturePackLevels.
|
||||||
|
*
|
||||||
|
* Règles :
|
||||||
|
* - Attaque = pattern.attack + size.attack + pack.attack + role.reaction
|
||||||
|
* - Dégâts = pattern.damage + size.vigor + pack.damage
|
||||||
|
* - Vigueur = size.vigor + pack.damage
|
||||||
|
* - Réaction = role.reaction + role.reaction_bonus
|
||||||
|
*/
|
||||||
|
_calculateCreatureComputedValues() {
|
||||||
|
const patternLevel = this.pattern?.value || 1
|
||||||
|
const sizeLevel = this.size?.value || 1
|
||||||
|
const roleLevel = this.role?.value || 1
|
||||||
|
const packLevel = this.pack?.value || 0
|
||||||
|
|
||||||
|
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
|
||||||
|
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
|
||||||
|
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}
|
||||||
|
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
|
||||||
|
|
||||||
|
// Attaque : patron + taille + meute + réaction du rôle
|
||||||
|
this.computed.attack = (patternConfig.attack || 0)
|
||||||
|
+ (sizeConfig.attack || 0)
|
||||||
|
+ (packConfig.attack || 0)
|
||||||
|
+ (roleConfig.reaction || 0)
|
||||||
|
|
||||||
|
// Dégâts : patron + vigueur de taille + meute
|
||||||
|
this.computed.damage = (patternConfig.damage || 0)
|
||||||
|
+ (sizeConfig.vigor || 0)
|
||||||
|
+ (packConfig.damage || 0)
|
||||||
|
|
||||||
|
// Vigueur : taille + meute
|
||||||
|
this.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0)
|
||||||
|
|
||||||
|
// Réaction : rôle
|
||||||
|
this.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0)
|
||||||
|
this.computed.reactionBonus = roleConfig.reaction_bonus || 0
|
||||||
|
|
||||||
|
// Réserves
|
||||||
|
this.computed.pools = roleConfig.pools || 0
|
||||||
|
|
||||||
|
// Équipement et handicap
|
||||||
|
this.computed.gear = roleConfig.gear || 9
|
||||||
|
this.computed.gearHindrance = roleConfig.gear_hindrance || 0
|
||||||
|
|
||||||
|
// Protection
|
||||||
|
this.computed.protection = roleConfig.protection || 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les seuils de blessures à partir du patron, de la taille et de la meute.
|
||||||
|
* Les seuils sont la somme des valeurs correspondantes des trois sources.
|
||||||
|
*/
|
||||||
|
_calculateCreatureWoundThresholds() {
|
||||||
|
const patternLevel = this.pattern?.value || 1
|
||||||
|
const sizeLevel = this.size?.value || 1
|
||||||
|
const packLevel = this.pack?.value || 0
|
||||||
|
|
||||||
|
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
|
||||||
|
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
|
||||||
|
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
|
||||||
|
|
||||||
|
this.minorWound.threshold = (patternConfig.minorWound || 0)
|
||||||
|
+ (sizeConfig.minorWound || 0)
|
||||||
|
+ (packConfig.minorWound || 0)
|
||||||
|
this.majorWound.threshold = (patternConfig.majorWound || 0)
|
||||||
|
+ (sizeConfig.majorWound || 0)
|
||||||
|
+ (packConfig.majorWound || 0)
|
||||||
|
this.deadlyWound.threshold = (patternConfig.deadlyWound || 0)
|
||||||
|
+ (sizeConfig.deadlyWound || 0)
|
||||||
|
+ (packConfig.deadlyWound || 0)
|
||||||
|
|
||||||
|
// Max de blessures
|
||||||
|
this.minorWound.max = Math.min(5, this.minorWound.threshold + 2)
|
||||||
|
this.majorWound.max = Math.min(4, this.majorWound.threshold + 1)
|
||||||
|
this.deadlyWound.max = Math.min(2, this.deadlyWound.threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { baseItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "defense" (protections, armures, boucliers).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineDefenseData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.defense"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
level: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
specificLevel: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
level: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
}),
|
||||||
|
mobility: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
isShield: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { listItemSchema, levelSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "evolution" (évolutions du personnage).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineEvolutionData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.evolution"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
level: levelSchema(1, 1, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "group" (groupe).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema,
|
||||||
|
levelSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineGroupData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.group"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base)
|
||||||
|
combatStatus: combatStatusSchema(),
|
||||||
|
|
||||||
|
// Identité du groupe
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
instincts: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
prohibits: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
notes: new fields.HTMLField({ required: true, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema(),
|
||||||
|
|
||||||
|
// Niveau du groupe (1-10)
|
||||||
|
level: levelSchema(1, 1, 10),
|
||||||
|
|
||||||
|
// Réputation
|
||||||
|
reputation: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: true, integer: true, initial: 10, min: 2 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 10, min: 0 })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Moral
|
||||||
|
morale: new fields.SchemaField({
|
||||||
|
level: new fields.StringField({ required: true, nullable: false, initial: "high" }),
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 7, min: 0, max: 7 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 7, min: 0 })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Réserve
|
||||||
|
reserve: attributeSchema(0, 0, 10),
|
||||||
|
|
||||||
|
// Objectifs (majeurs et mineurs)
|
||||||
|
objectives: new fields.SchemaField({
|
||||||
|
major: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
minor: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" }))
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Capacités de groupe
|
||||||
|
groupAbilities: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
|
||||||
|
// Membres (IDs d'acteurs)
|
||||||
|
members: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
|
||||||
|
// Rencontres
|
||||||
|
encounters: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
// 1. Initialiser les données de groupe si absentes
|
||||||
|
this._initGroupData()
|
||||||
|
|
||||||
|
// 2. Calculer la réserve max selon le niveau
|
||||||
|
this._calculateGroupReserve()
|
||||||
|
|
||||||
|
// 3. Mettre à jour le moral selon la valeur de dés
|
||||||
|
this._updateGroupMorale()
|
||||||
|
|
||||||
|
// 4. Mettre à jour le statut de combat
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise les champs optionnels du groupe s'ils ne sont pas présents.
|
||||||
|
*/
|
||||||
|
_initGroupData() {
|
||||||
|
if (!this.objectives) {
|
||||||
|
this.objectives = { major: [], minor: [] }
|
||||||
|
}
|
||||||
|
if (!this.groupAbilities) {
|
||||||
|
this.groupAbilities = []
|
||||||
|
}
|
||||||
|
if (!this.reserve) {
|
||||||
|
this.reserve = { value: 0, min: 0, max: 10 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la réserve max en fonction du niveau du groupe.
|
||||||
|
* Règle simplifiée : niveau × 2, plafonné à 10.
|
||||||
|
*/
|
||||||
|
_calculateGroupReserve() {
|
||||||
|
const level = this.level?.value || 1
|
||||||
|
this.reserve.max = Math.min(10, level * 2)
|
||||||
|
|
||||||
|
if (this.reserve.value > this.reserve.max) {
|
||||||
|
this.reserve.value = this.reserve.max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le niveau de moral en fonction de la valeur de dés.
|
||||||
|
* Règles : 7D+ = Haut, 6-3D = Normal, 2-1D = Bas, 0D = Crise.
|
||||||
|
*/
|
||||||
|
_updateGroupMorale() {
|
||||||
|
const moraleValue = this.morale?.value || 0
|
||||||
|
|
||||||
|
// Ne pas écraser un niveau explicitement défini (sauf "high" qui est la valeur par défaut)
|
||||||
|
if (this.morale.level && this.morale.level !== "high") return
|
||||||
|
|
||||||
|
if (moraleValue >= 7) {
|
||||||
|
this.morale.level = "high"
|
||||||
|
} else if (moraleValue >= 3) {
|
||||||
|
this.morale.level = "normal"
|
||||||
|
} else if (moraleValue >= 1) {
|
||||||
|
this.morale.level = "low"
|
||||||
|
} else {
|
||||||
|
this.morale.level = "crisis"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les items de type "item" (équipement générique).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineItemData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.item"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
needSkill: new fields.SchemaField({
|
||||||
|
value: new fields.BooleanField({ required: true, initial: false }),
|
||||||
|
skill: new fields.StringField({ required: true, initial: "" })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import partagé — après la déclaration de classe car defineSchema est statique
|
||||||
|
import { baseItemSchema } from './_shared.mjs'
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "npc" (PNJ).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*
|
||||||
|
* Note : le champ libre de compétences (texte descriptif) est nommé "freeSkills"
|
||||||
|
* pour éviter le conflit avec le SchemaField "skills" qui contient les 30 compétences.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema,
|
||||||
|
abilitiesSchema,
|
||||||
|
skillCategoriesSchema,
|
||||||
|
skillsSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineNpcData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.npc"]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration des données avant traitement par le schéma.
|
||||||
|
* Avant DataModel, template.json définissait "skills" comme un champ texte libre
|
||||||
|
* pour les PNJ. Le DataModel réserve "skills" pour les 30 compétences individuelles
|
||||||
|
* (SchemaField) et utilise "freeSkills" pour le texte libre.
|
||||||
|
* @param {Object} source Données brutes avant validation du schéma
|
||||||
|
* @returns {Object} Données migrées
|
||||||
|
*/
|
||||||
|
static migrateData(source) {
|
||||||
|
if (typeof source.skills === "string") {
|
||||||
|
source.freeSkills = source.skills
|
||||||
|
}
|
||||||
|
return super.migrateData(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base, difficulté par défaut 9 pour PNJ)
|
||||||
|
combatStatus: combatStatusSchema("9"),
|
||||||
|
|
||||||
|
// Identité
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
notes: new fields.HTMLField({ required: true, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Attributs (XP, réputation, sang-froid, effort)
|
||||||
|
attributes: new fields.SchemaField({
|
||||||
|
xp: attributeSchema(0, 0, 10),
|
||||||
|
reputation: attributeSchema(0, 0, 10),
|
||||||
|
self_control: attributeSchema(0, 0, 5),
|
||||||
|
effort: attributeSchema(0, 0, 5)
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Niveaux PNJ (menace, expérience, rôle)
|
||||||
|
threat: attributeSchema(1, 1, 4),
|
||||||
|
experience: attributeSchema(1, 1, 4),
|
||||||
|
role: attributeSchema(1, 1, 4),
|
||||||
|
|
||||||
|
// Compétences (les 30 compétences individuelles)
|
||||||
|
skills: skillsSchema(),
|
||||||
|
|
||||||
|
// Description libre des compétences (champ texte PNJ)
|
||||||
|
freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
|
||||||
|
// Catégories de compétences
|
||||||
|
skill_categories: skillCategoriesSchema(),
|
||||||
|
|
||||||
|
// Caractéristiques (8)
|
||||||
|
abilities: abilitiesSchema(),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
// 1. Calculer les seuils de blessures selon le niveau de menace
|
||||||
|
this._setNpcWoundThresholds()
|
||||||
|
|
||||||
|
// 2. Calculer les réserves selon le niveau de rôle
|
||||||
|
this._setNpcAttributes()
|
||||||
|
|
||||||
|
// 3. Définir les libellés des caractéristiques
|
||||||
|
this._setAbilityLabels()
|
||||||
|
|
||||||
|
// 4. Mettre à jour le statut de combat
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les seuils de blessures à partir du niveau de menace.
|
||||||
|
* Utilise CONFIG.VERMINE.npcThreatLevels.
|
||||||
|
*/
|
||||||
|
_setNpcWoundThresholds() {
|
||||||
|
const health = this.abilities?.health?.value || 1
|
||||||
|
const threatLevel = this.threat?.value || 1
|
||||||
|
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
|
||||||
|
|
||||||
|
this.minorWound.threshold = health
|
||||||
|
this.majorWound.threshold = health + 3
|
||||||
|
this.deadlyWound.threshold = Math.min(health + 7, 10)
|
||||||
|
|
||||||
|
this.minorWound.max = threatConfig.minorWound || 1
|
||||||
|
this.majorWound.max = threatConfig.majorWound || 1
|
||||||
|
this.deadlyWound.max = threatConfig.deadlyWound || 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définit les attributs dérivés (effort, sang-froid) selon le niveau de rôle.
|
||||||
|
* Utilise CONFIG.VERMINE.npcRoleLevels.
|
||||||
|
*/
|
||||||
|
_setNpcAttributes() {
|
||||||
|
const roleLevel = this.role?.value || 1
|
||||||
|
const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {}
|
||||||
|
|
||||||
|
this.attributes.effort.max = roleConfig.pools || 0
|
||||||
|
this.attributes.self_control.max = roleConfig.reaction_bonus || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définit les libellés localisés des caractéristiques.
|
||||||
|
*/
|
||||||
|
_setAbilityLabels() {
|
||||||
|
for (const [k, v] of Object.entries(this.abilities)) {
|
||||||
|
v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { baseItemSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "rite" (rites, rituels).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineRiteData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.rite"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
rituel: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
transe: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
ability: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
effect: new fields.StringField({ required: true, initial: "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { listItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "rumor" (rumeurs).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineRumorData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.rumor"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les items de type "specialty" (spécialités de compétence).
|
||||||
|
* Modèle minimal sans base partagée.
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineSpecialtyData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.specialty"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
skill: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { listItemSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "target" (cibles, objectifs).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineTargetData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.target"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
level: new fields.StringField({ required: true, initial: "minor" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { listItemSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "trauma" (traumatismes, séquelles).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineTraumaData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.trauma"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
type: new fields.StringField({ required: true, initial: "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||