Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ef0de45ff | ||
|
|
c7e6660d9d | ||
|
|
9f88a66b72 | ||
|
|
61b7815381 | ||
|
|
44fbc592ad | ||
|
|
ef01651b1d | ||
|
|
8dd5d02994 | ||
|
|
ef86b2637c | ||
|
|
2f78d1a1ea | ||
|
|
7ec3d73915 | ||
|
|
88e2902068 | ||
|
|
70a97796a3 | ||
|
|
d499f60386 | ||
|
|
1c7e05bae6 | ||
|
|
06603ceff7 | ||
|
|
ca74ff4a88 | ||
|
|
b0a3aff648 | ||
|
|
3cb96410b9 | ||
|
|
ad4daf0b00 | ||
|
|
c96e8d69d7 | ||
|
|
282fb2794a | ||
|
|
46b9512256 | ||
|
|
8316112741 | ||
|
|
75f39551f8 | ||
|
|
ef76ae1116 | ||
|
|
18180a8445 | ||
|
|
3235017a4c | ||
|
|
b2a044640c | ||
|
|
a13b0fc2d6 | ||
|
|
0ad240bd03 | ||
|
|
9d36b92fa9 | ||
|
|
d0373fab5a | ||
|
|
cf905cdcfb | ||
|
|
ea3322e843 | ||
|
|
6247f65590 | ||
|
|
8d8428cad3 | ||
|
|
9cf7d9ac68 | ||
|
|
f2c8cfe246 | ||
|
|
31f2f4530c | ||
|
|
9c7ebc6df9 | ||
|
|
05f668acf0 | ||
|
|
10b01614e6 | ||
|
|
89d9d71395 | ||
|
|
1e298ed809 | ||
|
|
4ffaebf52b | ||
|
|
de7691c4dc | ||
|
|
c87b25fbaf | ||
|
|
6ce51ed8cf | ||
|
|
df852f17d1 | ||
|
|
8916600bf6 | ||
|
|
0189f62734 | ||
|
|
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 | ||
|
|
9a4a3553d4 | ||
|
|
1f1198b2d5 | ||
|
|
b478fd7b54 | ||
|
|
299fbcd50d | ||
|
|
3a53644213 | ||
|
|
742f20874c | ||
|
|
15bb1756ef | ||
|
|
4e654d1855 | ||
|
|
3345fcf0f9 | ||
|
|
c5c41a2920 | ||
|
|
2a5c0a2445 | ||
|
|
a4c07715a0 | ||
|
|
2af7b06f9a | ||
|
|
d0326bfe30 | ||
|
|
d592e7c221 | ||
|
|
caea20665a | ||
|
|
80277f822f | ||
|
|
6568eb067f | ||
|
|
56a8e30348 | ||
|
|
daf56b15e7 |
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* 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',
|
||||
fromUuid: '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-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-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-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 },
|
||||
FunctionExpression: { body: 1, parameters: 1 },
|
||||
StaticBlock: { body: 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,73 @@
|
||||
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
|
||||
|
||||
# Recompile les compendiums LevelDB depuis les sources YAML (src/packs/ → packs/)
|
||||
- name: Compile Compendiums
|
||||
run: npm run pullYAMLtoLDB
|
||||
|
||||
# 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
|
||||
run: npm ci
|
||||
|
||||
# Build CSS from LESS
|
||||
- name: Build CSS
|
||||
run: npm run build:less
|
||||
|
||||
# Pull YAML to LDB packs
|
||||
- name: Build Packs
|
||||
run: npm run pullYAMLtoLDB
|
||||
|
||||
# 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 asset/ 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
|
||||
- name: Update Release with Files
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# IDE
|
||||
.idea/
|
||||
.vsode
|
||||
.vscode/
|
||||
.github/
|
||||
|
||||
# Node Modules
|
||||
node_modules
|
||||
@@ -13,3 +14,15 @@ packs/*/*
|
||||
*.lock
|
||||
jsconfig.json
|
||||
foundry
|
||||
.history/
|
||||
|
||||
# Ancien dossier SCSS (archivé)
|
||||
scss_old/
|
||||
|
||||
# Outils d'audit local
|
||||
.swival/
|
||||
|
||||
# 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,56 +1,142 @@
|
||||
# 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
|
||||
- [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
|
||||
Interface bilingue **FR/EN**.
|
||||
|
||||
### fiche de groupe
|
||||
- [ ] pas encore penché dessus
|
||||
---
|
||||
|
||||
#### Members et encounters
|
||||
|
||||
- [ ] faire une classe GroupLink pour avoir les actors en objets dans les array group.members[], group.encounters[], et character.encounters[],
|
||||
- [ ] faire une fonction sur le Hook.onUpdateActor => update des groupes dans characters, update des encounters et members dans groups
|
||||
## Types d'Acteurs
|
||||
|
||||
### fiche de pnj créature
|
||||
- [ ] à faire,
|
||||
- [ ] lister les gabarit/taille/roles(creatures) et menace/experience/role(pnj)... stocker les modifs dans CONFIG.VERMINE,
|
||||
| Type | Fiche | Description |
|
||||
|------|-------|-------------|
|
||||
| **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 |
|
||||
|
||||
## Types d'Objets
|
||||
|
||||
### les jets 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
|
||||
| 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) |
|
||||
|
||||
### le combat
|
||||
faut s'y pencher
|
||||
- [ ] modifier la difficulté en fonction de l'état du combatant /offensif/actif/passif/
|
||||
## Système de Dés
|
||||
|
||||
- **Comptage de succès sur d10** : `Xd10cs>=N`
|
||||
- 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
|
||||
|
||||
### les items
|
||||
- [-] gérer les rolls d'items dans le chat
|
||||
- [-] repasser sur les différents itemTypes et sheets
|
||||
- [X] construire une selecteur de traits, traits= CONFIG.VERMINE.traits
|
||||
traits:[
|
||||
key:{
|
||||
name:string,
|
||||
description:string,
|
||||
value:number
|
||||
}
|
||||
]
|
||||
- [X] ajouter des dégats sur chaques fiches item
|
||||
### Initiative
|
||||
Formule : `(@abilities.reflexes.value + @skills.alertness.value)d10cs>=@combatStatus.difficulty`
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
module/
|
||||
├── vermine2047.mjs # Point d'entrée (Hooks.once init)
|
||||
├── models/ # DataModels (Foundry v2 TypeDataModel)
|
||||
│ ├── actor/ # character.mjs, npc.mjs, group.mjs, creature.mjs
|
||||
│ └── item/ # Les 12 types d'objets
|
||||
├── documents/ # VermineActor, VermineItem
|
||||
├── applications/
|
||||
│ └── sheets/ # Fiches AppV2 par type
|
||||
├── system/
|
||||
│ ├── config.mjs # CONFIG.VERMINE (totems, traits, compétences…)
|
||||
│ ├── roll.mjs # VermineUtils.roll()
|
||||
│ ├── 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
|
||||
|
||||
VERMINE 2047, Un jeu de Julien Blondel
|
||||
|
||||
© 2023 Studio Agate
|
||||
TOTEM System
|
||||
|
||||
© 2023 Julien Blondel
|
||||
Tous droits réservés
|
||||
@@ -0,0 +1,145 @@
|
||||
# Plan de test — Combat Vermine2047 (rejouable)
|
||||
|
||||
Base : règles de combat (Kit de Survie p.44-52, `regles/2047_KitDeSurvie-DEF.txt`) et moteur `VermineExchange`/`CombatDialog`/`VermineCombatTracker`.
|
||||
Ce plan décrit le **setup reproductible**, les **scénarios**, les **valeurs attendues** (déterministes : pools, difficultés, formules) et la **procédure** de validation. Les jets de dés restent aléatoires ; on valide la mécanique, pas un résultat chiffré précis.
|
||||
|
||||
## Prérequis
|
||||
- Monde Vermine2047 sur `https://localhost:31000`, connecté en **Gamemaster**.
|
||||
- Scène « AA » (`49tnX0ynTzztVkxy`) avec tokens PJ / PNJ (+ token Créature ajouté).
|
||||
- Dés en main : chaque jet via l'UI crée une carte de chat (animations 3D possibles).
|
||||
|
||||
## Acteurs (setup reproductible)
|
||||
|
||||
| Acteur | Type | ID | Réglages |
|
||||
|--------|------|----|----------|
|
||||
| **Personnage** | PJ (character) | `f6qo5ubEnrSrtcfM` | Vigueur 3, Précision 2, Réflexes 2 · Mêlée 3, Corps-à-corps 2, Armes à feu 3 · Equipé : TEST Epée (lame 3), TEST Fusil (balle 4, mun. 10, 5/50 m), TEST Gilet (Protection 2, Mobilité 1) |
|
||||
| **PNJ** | PNJ (npc) | `EsksoLF7SV6yx7ZM` | Menace 2 (Attaque 4), Expérience 3 (contact « 5,7 ou 9 », action 4), Rôle 3 (Protection 3) · Equipé : TEST Hache (lame 3), TEST Armure (Protection 3) |
|
||||
| **Créature** | Créature | `4CCdIWBHSqgApmlR` | Attaque 7, Dommages 5, Vigueur 4, Protection 2, Statut combat Difficulté 7 · Equipée : TEST Morsure (choc 4 + Vigueur) |
|
||||
|
||||
### Pools de dés attendus (SkillLevels : lvl2→pool1/reroll1, lvl3→pool2/reroll1)
|
||||
| Jet | Pool | Difficulté |
|
||||
|-----|------|-----------|
|
||||
| PJ contact (Épée, Mêlée 3) | 3 + 2 = **5D** | 5 / 7 / 9 |
|
||||
| PJ distance (Fusil, AF 3) | 2 + 2 = **4D** | Portée : courte 5 / moyenne 7 / longue 9 |
|
||||
| PJ esquive | 2 + 1 = **3D** | = difficulté attaque |
|
||||
| PJ parade | 3 + 2 = **5D** | = difficulté attaque |
|
||||
| PNJ attaque (Menace 2) | **4D** | 5 / 7 / 9 |
|
||||
| PNJ défense | **4D** (action) | = difficulté attaque |
|
||||
| Créature attaque (Morsure) | **7D** | = statut combat (7) |
|
||||
| Créature défense | **passive** (pas de jet, difficulté verrouillée 7) | — |
|
||||
|
||||
### Seuils de blessure (netDamage)
|
||||
| Acteur | Légère | Grave | Mortelle |
|
||||
|--------|--------|-------|----------|
|
||||
| PJ | ≥ 1 | ≥ 4 | ≥ 8 |
|
||||
| PNJ | ≥ 2 | ≥ 5 | ≥ 8 |
|
||||
| Créature | ≥ 2 | ≥ 4 | ≥ 6 |
|
||||
|
||||
### Formules de résolution (vérifiées par le moteur)
|
||||
- Dégâts bruts = **Dommages de base + réussites de l'attaquant**.
|
||||
- Dégâts nets = max(0, bruts − Protection du défenseur). La Protection est la somme des **défenses équipées** (PNJ : TEST Armure 3 ; la « Protection » de rôle n'est PAS une défense équipée). Parade : seule la protection utilisée compte.
|
||||
- Blessure : catégorie dont le seuil est ≤ dégâts nets (la plus haute).
|
||||
- Défense : ≥ réussites attaquant → paré/esquivé (0 dégât) ; 1+ de plus → touché.
|
||||
|
||||
---
|
||||
|
||||
## Scénarios
|
||||
|
||||
### S1 — Création du combat et initiative
|
||||
1. Créer un combat « TEST COMBAT » ; ajouter les combatants PJ + PNJ + Créature.
|
||||
2. `rollAll()` : toutes les initiatives sont jetées (formule `Xd10cs>=7`).
|
||||
- **Attendu** : combatantCount = 3 ; chaque combatant a une initiative non nulle ; `turn`/`round` cohérents après `startCombat`.
|
||||
- **Validation** : ⬜
|
||||
|
||||
### S2 — Attaque PJ → PNJ au contact (Épée)
|
||||
1. Cibler le token PNJ. Ouvrir l'attaque via l'icône « Attaque » de la fiche PJ (équipement → TEST Epée).
|
||||
2. Vérifier le pool **5D** (Vigueur 3 + Mêlée) et les difficultés **5/7/9**.
|
||||
3. Lancer (difficulté 7). Défense PNJ : **Esquive** (4D).
|
||||
4. « Résoudre » : dégâts bruts = 3 + réussites PJ ; nets = bruts − 3 (Protection PNJ).
|
||||
- **Attendu** : pool affiché 5D, difficulté 7 ; résolution = formules ci-dessus ; si touché → blessure Légère (≥2) / Grave (≥5) / Mortelle (≥8).
|
||||
- **Validation** : ⬜
|
||||
|
||||
### S3 — Attaque PJ → Créature à distance (Fusil)
|
||||
1. Cibler le token Créature. Attaque via TEST Fusil.
|
||||
2. Vérifier le pool **4D**, difficulté de Portée (choisir Moyenne = 7).
|
||||
3. Lancer. La créature **ne peut pas défendre** (défense passive ; pas de bouton Esquive/Parade sur sa ligne).
|
||||
- **Attendu** : pool 4D, difficulté 7 ; carte créature = « Défense passive » ; résolution = 4 + réussites − 2 (Protection créature) ; seuils créature 2/4/6.
|
||||
- **Validation** : ⬜
|
||||
|
||||
### S4 — Attaque PNJ → PJ (Hache)
|
||||
1. Cibler le token PJ. Attaque via la Hache du PNJ.
|
||||
2. Vérifier le pool **4D** (Menace 2) et les difficultés **5/7/9**.
|
||||
3. Défense PJ : **Parade** (5D, Vigueur + Mêlée) — choisir Parade.
|
||||
- **Attendu** : pool 4D ; parade 5D ; si touché → 3 + réussites − 2 (TEST Gilet) ; seuils PJ 1/4/8.
|
||||
- **Validation** : ⬜
|
||||
|
||||
### S5 — Attaque Créature → PJ (Morsure)
|
||||
1. Cibler le token PJ. Attaque via TEST Morsure de la créature.
|
||||
2. Vérifier le pool **7D**, difficulté **verrouillée 7** (statut combat).
|
||||
3. Défense PJ : **Esquive** (3D).
|
||||
- **Attendu** : pool 7D, difficulté 7 verrouillée ; dégâts de base = 4 + Vigueur 4 = **8** ; nets = 8 + réussites − 2 ; seuils PJ 1/4/8.
|
||||
- **Validation** : ⬜
|
||||
|
||||
### S6 — Traits de combat
|
||||
- **Rafale (3)** : case « Tir en rafale » sur le Fusil → difficulté +2 (7→9) ; si jet réussi, +3 réussites affichées (« réussites automatiques ») ; si échec, pas de bonus.
|
||||
- **Rapide (2)** : arme avec Rapide → note « le défenseur subit 2 Handicap(s) » ; la défense du PNJ s'ouvre avec handicap **II** pré-rempli.
|
||||
- **Lourd (4)** : équiper un objet Lourd 4 au PJ (Vigueur 3 < 4) → malus **-1D** dans le pool (dialogue + ligne « Malus Lourd »).
|
||||
- **Portée (8)** : arme Portée 8, Vigueur 4 → « Portée effective : 32 m » affichée.
|
||||
- **Incapacitant (7)** : sur la carte d'échange, bouton « Jet de Santé (7) » → ouvre un jet de Santé vs 7.
|
||||
- **Zone (6)** : note « Zone (6 m) » + bouton « Appliquer aux cibles de la zone ».
|
||||
- **Validation** : ⬜
|
||||
|
||||
### S7 — Tracker (gestion)
|
||||
1. Bouton **+** (createCombat) : crée un nouveau combat.
|
||||
2. Clic droit sur un combatant → menu contextuel (Mettre à jour / Effacer / Relancer / **Supprimer**) ; le bouton poubelle par combatant supprime aussi.
|
||||
3. Poubelle d'en-tête (deleteCombat) : supprime le combat.
|
||||
4. Footer : **Commencer** (startCombat), tours (←/→), rounds (←/→), **Terminer** (endCombat).
|
||||
- **Validation** : ⬜
|
||||
|
||||
### S8 — Nettoyage
|
||||
- Supprimer les combats de test, les items « TEST * », restaurer les compétences PJ (0), retirer le token Créature.
|
||||
|
||||
---
|
||||
|
||||
## Rapport d'exécution
|
||||
Chaque scénario : note **PASS** si les valeurs attendues sont vérifiées, **FAIL** sinon (avec l'écart). Les chiffres aléatoires (dés) sont relevés tels quels.
|
||||
|
||||
### S1 — Combat & initiative — **PASS**
|
||||
3 combatants ajoutés ; initiatives jetées (PJ 1, PNJ 0, Créature 0) ; `startCombat` → round 1 ; ordre du tour = initiative décroissante (Personnage, PNJ, Créature).
|
||||
|
||||
### S2 — PJ→PNJ contact (Épée) — **PASS**
|
||||
- Dialogue : pool **5D** (Vigueur 3 + Mêlée 3), difficultés **[5,7,9]**, dégâts de base 3.
|
||||
- Attaque (difficulté 7) → 2 réussites. Défense PNJ **Esquive 4D** (action) → 0 réussite.
|
||||
- Résolution : **Touché**, bruts 3+2=5, nets 5 (PNJ sans défense équipée → 0 protection) → **Blessure grave** (seuil 5).
|
||||
- Application : `majorWound` 0→1, « Blessure appliquée ».
|
||||
- Note : la « Protection 3 » du rôle PNJ n'est pas une défense équipée → non déduite.
|
||||
|
||||
### S3 — PJ→Créature distance (Fusil) — **PASS**
|
||||
- Dialogue : pool **4D** (Précision 2 + AF 3), difficulté **verrouillée 7** (cible créature).
|
||||
- Créature : pas de bouton défense → « Défense passive ».
|
||||
- Résolution : attaque 3 réussites → **Touché**, bruts 4+3=7 − protection 2 = **5** → **Blessure grave** (seuil créature 4).
|
||||
|
||||
### S4 — PNJ→PJ (Hache) — **PASS**
|
||||
- Dialogue PNJ : pool **4D** (Menace 2), difficultés **[5,7,9]**.
|
||||
- Défense PJ **Parade** : pool **5D** (Vigueur 3 + Mêlée 3), difficulté 7, compétence Mêlée.
|
||||
- Résolution : attaque 2, défense 2 → **Paré, aucun dégât** (réussites défense ≥ attaque).
|
||||
|
||||
### S5 — Créature→PJ (Morsure) — **PASS**
|
||||
- Dialogue : pool **7D** (attaque), difficulté **verrouillée 7** (statut combat), dégâts de base **8** (4 + Vigueur 4).
|
||||
- Défense PJ **Esquive** : pool **3D** (Réflexes 2 + Corps-à-corps 2), difficulté 7.
|
||||
- Résolution (défense 0) : **Touché**, bruts 8+4=12 − protection 2 = **10** → **Blessure mortelle** (seuil 8).
|
||||
- Bonus : le **malus de blessure** d'une créature blessée est bien appliqué (deadlyWound=1 → −3D, pool 7→4).
|
||||
|
||||
### S6 — Traits de combat — **PASS**
|
||||
- **Rafale (3)** : case → difficulté +2 (7→9) ; jet raté → **aucun bonus** (total 0) ; jet réussi → **+3 réussites** (total = brut + 3, ligne « réussites automatiques »).
|
||||
- **Rapide (2)** : note sur la carte ; dialogue de défense → handicap **II** pré-rempli.
|
||||
- **Lourd (4)** : Vigueur 3 < 4 → malus **−1D** (pool breakdown « Malus Lourd »).
|
||||
- **Portée (8)** : portée effective = 8 × Vigueur 3 = **24 m**.
|
||||
- **Incapacitant (7)** / **Zone (6)** : boutons « Jet de Santé » / « Appliquer aux cibles de la zone » présents sur la carte.
|
||||
|
||||
### S7 — Tracker — **PASS** (onglet combat actif requis)
|
||||
Bouton **+** → nouveau combat ; suppression combatant (bouton poubelle + menu contextuel clic-droit) ; footer **Commencer** → round 1 ; boutons tours/rounds ; **Terminer** → round 0 ; poubelle d'en-tête → suppression du combat.
|
||||
|
||||
### S8 — Nettoyage — **PASS**
|
||||
Combats de test supprimés, items « TEST * » retirés, compétences PJ restaurées, token Créature retiré.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Plan d'automatisation des Traits — Suivi
|
||||
|
||||
Base : `regles/regles_traits.txt` (et KitDeSurvie p.70-71, 39).
|
||||
Implémenté déjà : `cassant`, `coque` (absorption), `etanche` (immersion) dans `module/system/reliability.mjs`.
|
||||
Décisions utilisateur : Fétiche +1D sur les **deux** Réserves · Malus/Durée = **saisie manuelle** · Handicap de Mobilité = **note de rappel** · Items génériques = **flag équipé OK**.
|
||||
|
||||
## Légende
|
||||
- [x] fait
|
||||
- [ ] à faire
|
||||
- [~] en cours
|
||||
|
||||
## Tâche 0 — Prérequis communs
|
||||
- [x] `item.mjs` : champ `equipped` sur items génériques + toggle fiche `item-item-sheet.hbs`
|
||||
- [x] nouveau `module/system/traits.mjs` : helpers partagés (`equippedItems`, `lourdMalus`, `mobilityNote`, `feticheItem`…)
|
||||
|
||||
## Phase 1 — Réparations (reliability.mjs + dialogue)
|
||||
- [x] **Bien conçu** : succès auto en réparation classique (restauration complète) + 1ʳᵉ réparation de fortune rend +1 Fiabilité (compteur `traits.bien_conçu.value`)
|
||||
- [x] **Pratique** : Handicap +I aux réparations (required +1, note sur la carte)
|
||||
- [x] **Coque réparable** : épuisement → `{ value: 0, max: n }` ; mode « Réparer la coque » dans le dialogue (jet vs `coque.max`) ; affichage `(0/n)` sur la fiche
|
||||
- [x] **Étanche** : rien de plus (immersion gérée ; gaz/maladie hors scope)
|
||||
|
||||
## Phase 2 — Combat (combatDialog.mjs, exchange.mjs)
|
||||
- [x] **Lourd** : malus auto (`lourdMalus()`) dans dialogue combat + pool breakdown (ligne « Malus Lourd »)
|
||||
- [x] **Rapide (n)** : handicap (n) défenseur pré-rempli (locks.handicap) + note sur carte d'échange
|
||||
- [x] **Rafale (n)** : case « Tir en rafale » → difficulté +2, +n réussites si réussi (`bonusOnSuccess`)
|
||||
- [x] **Portée** : portée effective × Vigueur (n × vigor) affichée (dialogue + carte)
|
||||
- [x] **Maniable / Mobilité** : note de rappel dialogue combat + carte (handicap de mobilité cumulé, Maniable l'annule)
|
||||
- [x] **Incapacitant (n)** : bouton « Jet de Santé (n) » sur carte d'échange (RollDialog santé vs n)
|
||||
- [x] `getLabel()` : libellés caractéristiques/compétences localisés dans le tchat (bonus du jet de Santé)
|
||||
|
||||
## Phase 3 — Jets d'action (rollDialog.mjs + roll-dialog.hbs)
|
||||
- [x] **Ponctuel (n)** : outil sélectionné → annule jusqu'à n Handicaps ; consommé au jet (décrément, Trait supprimé à 0) ; note carte
|
||||
- [x] **Pratique (jets)** : outil sélectionné → +2D au lieu de +1D (badge « +2D » dans le dialogue)
|
||||
- [x] **Intimidant** : case « Brandit (X) » → −1D Psychologie si compétence psychologie, sinon Relance 1D ; note carte
|
||||
- [x] **Malus (n)** : champ « Malus temporaires » (saisie manuelle) déduit du pool + ligne carte
|
||||
- [x] **Durée (n)** : note « effet pendant n tours de combat » sur carte de jet d'arme
|
||||
- [x] Fix `roll()` : transmission des paramètres étendus (`ponctuelUsed`, `intimidant`, `effectiveRange`, `mobilityHandicap`) au message — corrige aussi l'affichage Portée/Mobilité en Phase 2
|
||||
|
||||
## Phase 4 — Acteur (character.mjs + fiche)
|
||||
- [x] **Fétiche** : config « ou »→« et » ; +1D Effort ET Sang-Froid (max dérivé) ; alerte 2+ fétiches (bandeau fiche) ; action « Perdre le fétiche » (−2D valeurs, +1 perte max persistant `experience.feticheLoss`, retire le Trait)
|
||||
- [x] **Zone (n)** : note carte d'échange + bouton « Appliquer aux cibles de la zone » (#onApplyZone : résout et applique la blessure à toutes les cibles)
|
||||
|
||||
## Transverse
|
||||
- [x] i18n fr/en pour la Phase 1
|
||||
- [x] ESLint (0 erreur sur les fichiers modifiés) + tests navigateur Phase 1 (coque, réparation coque, Bien conçu classique/fortune, Pratique, dialogue, affichage fiche)
|
||||
- [x] ESLint (0 erreur sur les fichiers modifiés) + tests navigateur Phase 2 (Lourd, Rapide+défense, Rafale conditionnel, Portée, Mobilité, Incapacitant, toggle equip)
|
||||
- [x] ESLint (0 erreur sur les fichiers modifiés) + tests navigateur Phase 3 (Pratique +2D, Ponctuel annulation+consommation, Intimidant psychologie/relance, Malus temporaires, Durée, transmission roll())
|
||||
- [x] ESLint (0 erreur sur les fichiers modifiés) + tests navigateur Phase 4 (Fétiche helpers + UI, Zone note/bouton + #onApplyZone) — modèle réserves + champ feticheLoss effectifs au reload
|
||||
|
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: 54 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 50 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`.
|
||||
@@ -0,0 +1 @@
|
||||
vermine2047.min.css
|
||||
@@ -0,0 +1,126 @@
|
||||
# Combat — Échanges de coups (assisté)
|
||||
|
||||
## Principe
|
||||
|
||||
Chaque échange de coups se déroule dans le chat : un jet d'attaque, un éventuel
|
||||
jet de défense, puis une résolution assistée. Tous les calculs (réussites,
|
||||
dégâts, protection, blessure) sont effectués automatiquement ; la blessure
|
||||
s'applique ensuite d'un simple clic.
|
||||
|
||||
```
|
||||
Attaque → (Défense) → Résoudre → Appliquer
|
||||
```
|
||||
|
||||
## 1. L'attaque
|
||||
|
||||
Ouvrir le dialogue d'attaque :
|
||||
|
||||
- **Personnage / PNJ** : cliquer l'icône cible (`fa-crosshairs`) sur une arme
|
||||
dans l'onglet **Combat** (liste des armes équipées) ou dans l'onglet
|
||||
**Matériel**.
|
||||
- **Créature** : cliquer la valeur « Attaque » de l'onglet **Stats**.
|
||||
|
||||
Le dialogue est prérempli selon le type d'acteur :
|
||||
|
||||
| Type | Pool | Dégâts de base | Difficulté |
|
||||
|------|------|----------------|------------|
|
||||
| **Personnage** | Caractéristique + compétence (verrouillées) | Arme (+ Vigueur si « Vigueur+ ») | Selon maîtrise / portée |
|
||||
| **PNJ** | Pool de menace + réserves de rôle | Arme équipée, sinon `Vigueur` de menace | Selon niveau d'expérience |
|
||||
| **Créature** | `Attaque` calculée | `Dégâts` calculés | Son statut de combat (verrouillée) |
|
||||
|
||||
### Difficulté
|
||||
|
||||
- **Contact** : dépend du niveau de compétence de l'attaquant.
|
||||
- Incompétent / Débutant : `7`
|
||||
- Confirmé : `5 ou 7`
|
||||
- Expert : `5, 7 ou 9`
|
||||
- Maître / Légende : `3, 5, 7 ou 9`
|
||||
- **Distance** : par portée.
|
||||
- Courte : `5` — Moyenne : `7` — Longue : `9`
|
||||
- **Créature ciblée** : la difficulté est forcée au statut de combat de la
|
||||
créature (toute réussite ≥ 1 touche).
|
||||
|
||||
### Options du dialogue
|
||||
|
||||
- **Deuxième action** : +2 à la difficulté.
|
||||
- **Handicap** : augmente les succès requis.
|
||||
- **Sang-Froid** : dés bonus (débités du personnage).
|
||||
- **Totems** : dés de totem (+ garder un totem si les deux sont actifs).
|
||||
- **Consommation de munitions** : pour les armes à distance (coche cochée par
|
||||
défaut, la munition est décomptée au jet).
|
||||
- Les **cibles** sélectionnées sur la carte (`Ctrl` + clic / outil de ciblage)
|
||||
sont affichées et reprises dans l'échange.
|
||||
|
||||
### La carte de chat
|
||||
|
||||
Le message d'attaque affiche l'arme (dégâts, portées, munitions) puis le bloc
|
||||
**Échange de coups** avec, **par cible**, les boutons **Esquive** et **Parade**
|
||||
(contact) ou **Esquive** seule (distance), ainsi que le bouton **Résoudre**.
|
||||
|
||||
## 2. La défense
|
||||
|
||||
Seuls les acteurs **capables de se défendre** (personnages et PNJ) ont des
|
||||
boutons de défense — pas les créatures (défense passive).
|
||||
|
||||
- Cliquer **Esquive** ou **Parade** sur la carte d'attaque.
|
||||
- Le dialogue de défense est **verrouillé** :
|
||||
- **Esquive** : `Réflexes` + `Corps-à-corps` (attaque au contact) ou
|
||||
`Vigilance` (attaque à distance).
|
||||
- **Parade** : `Vigueur` + `Mêlée` (ou `Corps-à-corps`).
|
||||
- **Difficulté = celle de l'attaque** (non modifiable).
|
||||
- Le jet de défense est lié au message d'attaque (même échange).
|
||||
|
||||
> Permission : Esquive / Parade ne sont utilisables que par le **propriétaire
|
||||
> de la cible** ou le **MJ**.
|
||||
|
||||
## 3. La résolution
|
||||
|
||||
Cliquer **Résoudre** (auteur du message ou MJ). Le système compare les
|
||||
**réussites** des jets d'attaque et de défense :
|
||||
|
||||
- **Attaquant > Défenseur** → **Touché** (parade insuffisante si parade).
|
||||
- **Attaquant ≤ Défenseur** → **Esquivé / Paré** : aucun dégât.
|
||||
- **Sans défense** (abstention ou créature) : toute réussite ≥ 1 touche.
|
||||
|
||||
Les totaux sont lus en direct : une **relance** de dé effectuée avant la
|
||||
résolution est prise en compte.
|
||||
|
||||
### Dégâts
|
||||
|
||||
```
|
||||
Dégâts = dégâts de base de l'arme (+ Vigueur si « Vigueur+ ») + réussites d'attaque
|
||||
```
|
||||
|
||||
### Protection
|
||||
|
||||
- **Parade** : seule la protection utilisée pour la parade réduit.
|
||||
- **Sinon** : somme des **protections équipées** de la cible
|
||||
(créature : protection de rôle).
|
||||
|
||||
### Blessure
|
||||
|
||||
La blessure correspond au **seuil le plus élevé** inférieur ou égal aux dégâts
|
||||
nets : **Légère**, **Grave** ou **Mortelle**. Un bloc de résultat est affiché
|
||||
**par cible** (attaque multi-cibles = un bloc par cible).
|
||||
|
||||
## 4. L'application de la blessure
|
||||
|
||||
Cliquer **« Appliquer la blessure »** sur le bloc de résultat (propriétaire de
|
||||
la cible ou MJ) : le cercle de blessure correspondant est coché sur l'acteur.
|
||||
|
||||
## Rappels et permissions
|
||||
|
||||
| Action | Qui peut la faire |
|
||||
|--------|-------------------|
|
||||
| Esquive / Parade | Propriétaire de la cible ou MJ |
|
||||
| Résoudre | Auteur du message d'attaque ou MJ |
|
||||
| Appliquer la blessure | Propriétaire de la cible ou MJ |
|
||||
|
||||
- Les **créatures** ne lancent pas de défense : leur difficulté d'attaque et de
|
||||
défense est leur **statut de combat**.
|
||||
- Les **PNJ** lancent leur défense comme un personnage (capacités + compétences).
|
||||
- Les **réserves de rôle** des PNJ sont à dépenser à la main (gérées par le MJ).
|
||||
|
||||
---
|
||||
|
||||
*Guide utilisateur — Vermine2047 pour FoundryVTT.*
|
||||
@@ -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';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const sass = require('gulp-sass')(require('sass'));
|
||||
var browserSync = require('browser-sync').create();
|
||||
|
||||
function buildStyles() {
|
||||
return gulp.src('./scss/vermine2047.scss')
|
||||
.pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError))
|
||||
.pipe(gulp.dest('./css'))
|
||||
.pipe(browserSync.stream());
|
||||
|
||||
|
||||
};
|
||||
function reloadTemplatesHTML() {
|
||||
|
||||
return browserSync.reload("templates/**/*.html")
|
||||
}
|
||||
|
||||
function reloadTemplatesHBS() {
|
||||
|
||||
return browserSync.reload("templates/**/*.hbs")
|
||||
}
|
||||
exports.buildStyles = buildStyles;
|
||||
exports.watch = function () {
|
||||
browserSync.init(
|
||||
{
|
||||
server: false,
|
||||
proxy: {
|
||||
target: "https://localhost:30000/",
|
||||
ws: true,
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
gulp.watch("./templates/**/*.html").on('change', reloadTemplatesHTML);
|
||||
gulp.watch("./templates/**/*.hbs").on('change', reloadTemplatesHBS);
|
||||
gulp.watch(['./scss/**/*.scss', './scss/*.scss'], buildStyles);
|
||||
|
||||
};
|
||||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
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();
|
||||
|
||||
// ============================================
|
||||
// LESS Tasks
|
||||
// ============================================
|
||||
|
||||
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(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() {
|
||||
return browserSync.reload("templates/**/*.html");
|
||||
}
|
||||
|
||||
function reloadTemplatesHBS() {
|
||||
return browserSync.reload("templates/**/*.hbs");
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Exports
|
||||
// ============================================
|
||||
|
||||
exports.buildLess = buildLess;
|
||||
exports.buildLessDev = buildLessDev;
|
||||
exports.buildCSS = gulp.series(buildLess);
|
||||
exports.buildAllCSS = gulp.series(buildLess, buildLessDev);
|
||||
exports.watch = function () {
|
||||
browserSync.init({
|
||||
server: false,
|
||||
proxy: {
|
||||
target: "https://localhost:30000/",
|
||||
ws: true,
|
||||
}
|
||||
});
|
||||
|
||||
// Watch templates
|
||||
gulp.watch("./templates/**/*.html").on('change', reloadTemplatesHTML);
|
||||
gulp.watch("./templates/**/*.hbs").on('change', reloadTemplatesHBS);
|
||||
|
||||
// Watch LESS files
|
||||
gulp.watch(['./less/**/*.less', './less/*.less'], buildLess);
|
||||
};
|
||||
|
||||
exports.default = buildLess;
|
||||
|
||||
@@ -13,6 +13,23 @@
|
||||
},
|
||||
"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.",
|
||||
"TYPES.Item.item": "Item",
|
||||
"TYPES.Item.weapon": "Weapon",
|
||||
"TYPES.Item.defense": "Defense",
|
||||
"TYPES.Actor.vehicle": "Vehicle",
|
||||
"TYPES.Item.ability": "Ability",
|
||||
"TYPES.Item.creaturecapacity": "Creature capacity",
|
||||
"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": {
|
||||
"heroic": {
|
||||
"name": "Heroic"
|
||||
@@ -22,6 +39,23 @@
|
||||
},
|
||||
"legendary": {
|
||||
"name": "Legendary"
|
||||
},
|
||||
"survival": "Survival",
|
||||
"nightmare": "Nightmare",
|
||||
"apocalypse": "Apocalypse"
|
||||
},
|
||||
"CREATURE_CAPACITY_TYPES": {
|
||||
"base": {
|
||||
"name": "Base"
|
||||
},
|
||||
"survival": {
|
||||
"name": "Survival"
|
||||
},
|
||||
"nightmare": {
|
||||
"name": "Nightmare"
|
||||
},
|
||||
"apocalypse": {
|
||||
"name": "Apocalypse"
|
||||
}
|
||||
},
|
||||
"ROLLS": {
|
||||
@@ -86,7 +120,8 @@
|
||||
"relations": "Relations",
|
||||
"morale": "Morale",
|
||||
"morale_level": "Morale level",
|
||||
"notes": "Notes"
|
||||
"notes": "Notes",
|
||||
"biography": "Biography"
|
||||
},
|
||||
"ADVERSITY": {
|
||||
"threat": "Threat",
|
||||
@@ -108,7 +143,11 @@
|
||||
"skills": "Skills",
|
||||
"pattern": "Pattern",
|
||||
"size": "Size",
|
||||
"pack": "Group"
|
||||
"pack": "Group",
|
||||
"gear_hindrance": "Gear Hindrance",
|
||||
"skills_placeholder": "Skills Placeholder",
|
||||
"threat_details": "Threat Details",
|
||||
"role_details": "Role Details"
|
||||
},
|
||||
"VERMINE": {
|
||||
"name": "Vermine",
|
||||
@@ -116,10 +155,48 @@
|
||||
"level": "Level",
|
||||
"experience": "Experience",
|
||||
"reputation": "Reputation",
|
||||
"chaman": "Shaman",
|
||||
"sheet": {
|
||||
"description": "Description"
|
||||
},
|
||||
"pool": "Reserve",
|
||||
"pools": "Reserves",
|
||||
"dice_pool": "Dice pool",
|
||||
"pool_malus": "Wound penalty",
|
||||
"pool_wound": "Wound penalty",
|
||||
"pool_lourd": "Heavy penalty",
|
||||
"lourd_malus_note": "Heavy penalty: -{malus}D (Vigor too low for equipped Heavy items)",
|
||||
"effective_range": "Effective range",
|
||||
"traits_mobility_note": "Mobility Handicap: +{handicap} (equipped non-Maniable protection)",
|
||||
"rafale_check": "Burst fire (Difficulty +2, +{rafaleValue} successes if successful)",
|
||||
"rapide_note": "Fast ({handicap}): the defender suffers {handicap} Handicap(s)",
|
||||
"incapacitant_roll": "Health roll",
|
||||
"intimidant": "Intimidating",
|
||||
"intimidant_check": "Wield ({intimidantName}) — Intimidating",
|
||||
"intimidant_psychology": "1D penalty (Psychology)",
|
||||
"intimidant_reroll": "1D reroll",
|
||||
"temporary_malus": "Temporary penalties",
|
||||
"pool_temp_malus": "Temporary penalties",
|
||||
"pool_psychology": "Psychology penalty",
|
||||
"ponctuel": "One-shot",
|
||||
"ponctuel_used": "cancels {value} Handicap(s)",
|
||||
"duree": "Duration",
|
||||
"duree_note": "effect for {value} combat turns",
|
||||
"fetiche": "Fetish",
|
||||
"fetiche_bonus": "Effort and Composure reserves +1D",
|
||||
"fetiche_multiple": "A character can only possess a single Fetish object",
|
||||
"fetiche_none": "No equipped Fetish object",
|
||||
"fetiche_lost": "Fetish lost: reserves −2D, max −1D",
|
||||
"fetiche_lost_penalty": "Fetish lost: reserves reduced by 1D",
|
||||
"lose_fetiche": "Lose the fetish",
|
||||
"zone_note": "Area ({value} m): everyone in the area suffers the damage",
|
||||
"zone_apply": "Apply to targets in the area",
|
||||
"zone_applied": "Area: {count} wound(s) applied",
|
||||
"auto_successes": "automatic successes",
|
||||
"self_control": "Self-control",
|
||||
"effort": "Effort",
|
||||
"equip": "Equip",
|
||||
"unequip": "Unequip",
|
||||
"group": "Group",
|
||||
"abilities": "Characteristics",
|
||||
"ability": "Characteristic",
|
||||
@@ -145,10 +222,65 @@
|
||||
"range": "Range",
|
||||
"ranges": "Ranges",
|
||||
"mobility": "Mobility",
|
||||
"vehicle": "Vehicle",
|
||||
"vehicle_specs": "Vehicle specifications",
|
||||
"kind": "Kind",
|
||||
"kind_vehicle": "Vehicle",
|
||||
"kind_mount": "Mount",
|
||||
"distance": "Distance",
|
||||
"speed": "Speed",
|
||||
"speed_duration_ph": "top speed duration (e.g. 10 min)",
|
||||
"effort_reserve": "Effort reserve",
|
||||
"maintenance": "Maintenance",
|
||||
"owner": "Owner",
|
||||
"no_vehicles": "No vehicles",
|
||||
"no_encounters": "No encounters",
|
||||
"travel_notes": "Travel notes",
|
||||
"rarity": "Rarity",
|
||||
"rarity_sm": "Rar.",
|
||||
"reliability": "Reliability",
|
||||
"reliability_sm": "Fiab.",
|
||||
"reliability_test": "Reliability test",
|
||||
"reliability_repair": "Repair",
|
||||
"reliability_severity": "Damage severity",
|
||||
"reliability_severity_mineurs": "Minor (1D)",
|
||||
"reliability_severity_graves": "Grave (3D)",
|
||||
"reliability_severity_critiques": "Critical (5D)",
|
||||
"reliability_immersion": "Prolonged immersion",
|
||||
"reliability_damages": "Damage taken",
|
||||
"reliability_state": "State",
|
||||
"reliability_destroyed": "OBJECT DESTROYED / UNUSABLE",
|
||||
"reliability_repair_mode": "Repair type",
|
||||
"reliability_repair_classic": "Classic",
|
||||
"reliability_repair_fortune": "Field repair",
|
||||
"reliability_skill": "Skill",
|
||||
"reliability_run": "Roll",
|
||||
"reliability_successes": "Successes",
|
||||
"reliability_result": "Result",
|
||||
"reliability_gained": "Reliability restored: +{gained}",
|
||||
"reliability_fixed": "Breakdown fixed (object functional)",
|
||||
"reliability_failed": "Failure — no reliability point restored",
|
||||
"reliability_need_actor": "Repair impossible: the object must belong to an actor",
|
||||
"reliability_already_destroyed": "Object already destroyed or unusable",
|
||||
"reliability_coque_absorbed": "Hull absorbs {absorbed} damage(s), {remaining} left",
|
||||
"reliability_coque_lost": "Hull: trait exhausted — repairable (Difficulty {max})",
|
||||
"reliability_coque_restored": "Hull repaired: {max} points",
|
||||
"reliability_repair_coque": "Repair the hull (Difficulty {max})",
|
||||
"reliability_repair_coque_label": "Hull",
|
||||
"reliability_bien_concu": "Well-made: automatic success — reliability fully restored",
|
||||
"reliability_bien_concu_fortune": "Well-made: +1 reliability point (first field repair)",
|
||||
"reliability_auto_success": "Automatic success (Well-made)",
|
||||
"reliability_pratique": "Pratique: +I Handicap",
|
||||
"reliability_cassant": "Fragile: reliability losses increased by 1 point",
|
||||
"reliability_etanche": "Watertight: immersion damage ignored",
|
||||
"reliability_fortune_skill_bonus": "+1D — the character has the required skill ({skill})",
|
||||
"reliability_fortune_skill_missing": "Required skill ({skill}) not mastered: rarity Handicap +{rarity}",
|
||||
"reliability_fortune_hint": "Field repair: +1D if the character has the required skill, otherwise the rarity Handicap of that skill applies.",
|
||||
"damages_state_1": "Damaged",
|
||||
"damages_state_2": "Damaged",
|
||||
"damages_state_3": "Faulty",
|
||||
"damages_state_4": "Faulty",
|
||||
"damages_state_5": "Out of service",
|
||||
"wounds": {
|
||||
"name": "Wounds",
|
||||
"threshold": "Threshold",
|
||||
@@ -171,16 +303,321 @@
|
||||
"boost": "Boost",
|
||||
"group_abilities": "Group abilities",
|
||||
"totem_abilities": "Totem abilities",
|
||||
"creature_capacities": "Creature capacities",
|
||||
"type_group": "Group",
|
||||
"type_character": "Character",
|
||||
"type_npc": "NPC",
|
||||
"type_creature": "Creature",
|
||||
"type_community": "Community",
|
||||
"type_vehicle": "Vehicle",
|
||||
"type": "Type",
|
||||
"group_members": "Members",
|
||||
"encounters": "Encounters",
|
||||
"road": "The Road",
|
||||
"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",
|
||||
"ProfilePlaceholder": "Profile",
|
||||
"none": "None",
|
||||
"all_characters": "All characters",
|
||||
"no_group": "No group",
|
||||
"include_in_phase": "Include in this phase",
|
||||
"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",
|
||||
"roll_success": "Success",
|
||||
"roll_failure": "Failure",
|
||||
"roll_context": "Roll context",
|
||||
"roll_pool": "Dice pool",
|
||||
"roll_dice": "Dice detail",
|
||||
"pool_ability": "Ability",
|
||||
"pool_skill": "Skill",
|
||||
"pool_specialty": "Specialty",
|
||||
"pool_help": "Help",
|
||||
"pool_tooling": "Equipment",
|
||||
"pool_group": "Group",
|
||||
"pool_selfControl": "Self-Control",
|
||||
"pool_pools": "Pools",
|
||||
"pool_total": "Total",
|
||||
"totem_used": "Totem",
|
||||
"totem_keep": "kept",
|
||||
"domain_bonus": "Domain bonus",
|
||||
"domain_penalty": "Domain penalty",
|
||||
"used": "used",
|
||||
"die_regular": "dice",
|
||||
"die_human": "human",
|
||||
"die_adapted": "adapted",
|
||||
"die_successes": "successes",
|
||||
"total": "Total",
|
||||
"totem_dice": "Totem Dice",
|
||||
"totem_hint": "Check to use totem dice (double success possible)",
|
||||
"totem_neutral": "Neutral",
|
||||
"totem_manage": "Manage Totem Dice (Instincts / Interdits)",
|
||||
"totem_serious": "Serious act (2 dice)",
|
||||
"totem_manage_hint": "Instincts: obeying the Human's Instincts gains 1 Human die; another Totem's Instincts gain 1 Adapted die. Interdit: breaking a Human Interdit loses 1 Human die (or gains 1 Adapted die if none). Max 3 dice of the same Totem and 5 dice total.",
|
||||
"totem_instinct_human": "Human Instinct (+1 Human die)",
|
||||
"totem_instinct_adapted": "Another Totem's Instinct (+1 Adapted die)",
|
||||
"totem_interdit_human": "Human Interdit (-1 Human die)",
|
||||
"totem_gained": "{totem} +1D",
|
||||
"totem_gained_adapted": "+1D Adapted",
|
||||
"totem_exchanged": "{totem} gained in exchange (1 opposite die removed)",
|
||||
"totem_replaced": "Opposite die removed instead (max 3D reached)",
|
||||
"totem_lost": "-1 Human die",
|
||||
"totem_limit_reached": "Totem dice limit reached.",
|
||||
"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",
|
||||
"attack": "Attack",
|
||||
"attack_contact": "Melee attack",
|
||||
"attack_ranged": "Ranged attack",
|
||||
"defense_esquive": "Dodge",
|
||||
"defense_parade": "Parry",
|
||||
"passive_defense": "Passive defense",
|
||||
"range_short": "Short",
|
||||
"range_mid": "Medium",
|
||||
"range_long": "Long",
|
||||
"second_action": "Second action (+2 difficulty)",
|
||||
"consume_ammo": "Consume ammo",
|
||||
"npc_pool": "Pools used",
|
||||
"exchange": "Combat exchange",
|
||||
"resolve": "Resolve",
|
||||
"resolve_result": "Exchange resolution",
|
||||
"attacker_successes": "Attack successes",
|
||||
"defender_successes": "Defense successes",
|
||||
"raw_damage": "Raw damage",
|
||||
"net_damage": "Net damage",
|
||||
"hit": "Hit",
|
||||
"hit_parade": "Hit (insufficient parry)",
|
||||
"missed_esquive": "Dodged",
|
||||
"missed_parade": "Parried",
|
||||
"no_damage": "No damage",
|
||||
"wound": "Wound",
|
||||
"no_wound": "No wound",
|
||||
"apply_wound": "Apply wound",
|
||||
"wound_applied": "Wound applied",
|
||||
"wound_escalated": "{to} wound (escalated from {from})",
|
||||
"wound_death": "All wounds checked — death",
|
||||
"error_creature_no_defense": "Creatures cannot defend (passive defense)",
|
||||
"error_no_permission": "Insufficient permissions",
|
||||
"error_no_target": "No target selected for this exchange",
|
||||
"experience_phase": "Experience phase",
|
||||
"experience_dice": "Experience dice",
|
||||
"experience_rewards": "Experience rewards",
|
||||
"collective": "collective",
|
||||
"collective_rewards": "Collective rewards",
|
||||
"individual_rewards": "Individual rewards",
|
||||
"collective_group": "Group (collective pool)",
|
||||
"danger": "Danger",
|
||||
"discoveries": "Discoveries",
|
||||
"duration": "Duration",
|
||||
"implication": "Implication",
|
||||
"influence": "Influence",
|
||||
"interpretation": "Interpretation",
|
||||
"reward_anecdotic": "Anecdotal",
|
||||
"reward_minor": "Minor",
|
||||
"reward_major": "Major",
|
||||
"reward_exceptionnal": "Exceptional",
|
||||
"objective_minor": "Minor objective",
|
||||
"objective_major": "Major objective",
|
||||
"trauma_reward": "Chosen trauma",
|
||||
"trauma_reward_5": "Trauma reward (+5D)",
|
||||
"evolution_dice": "Evolution dice",
|
||||
"buy_evolution": "Buy an Adaptation/Mutation",
|
||||
"evolution_name": "Name",
|
||||
"evolution_level": "Level (cost in Evolution dice)",
|
||||
"evolution_mutation": "Mutation",
|
||||
"evolution_adaptation": "Adaptation",
|
||||
"evolution_available_dice": "Accumulated Evolution dice",
|
||||
"evolution_total": "Total levels (after purchase)",
|
||||
"evolution_mode_max": "Game mode maximum",
|
||||
"game_mode": "Game mode",
|
||||
"mode_survie": "Survival",
|
||||
"mode_cauchemar": "Nightmare",
|
||||
"mode_apocalypse": "Apocalypse",
|
||||
"evolution_buy_hint": "The purchase consumes a number of Evolution dice equal to the chosen level (the remainder is kept). Limits: Survival 5D, Nightmare 6D, Apocalypse 7D total. Level 3/4 Mutations are forbidden in Survival; only one level 4 Mutation.",
|
||||
"buy_evolution_done": "Adaptation/Mutation acquired. Evolution dice erased.",
|
||||
"error_evolution_no_name": "Provide a name for the Adaptation/Mutation.",
|
||||
"error_evolution_invalid_level": "Invalid level (1 to 4).",
|
||||
"error_evolution_not_enough_dice": "Not enough Evolution dice for this level.",
|
||||
"error_evolution_mode_limit": "Game mode maximum Evolution dice reached.",
|
||||
"error_evolution_mutation_mode": "Level 3/4 Mutations require Nightmare or Apocalypse modes.",
|
||||
"error_evolution_mutation_4d": "The character already has a level 4 Mutation.",
|
||||
"error_evolution_create": "Could not create the Adaptation/Mutation.",
|
||||
"open_experience_phase": "Experience phase",
|
||||
"open_learning": "Learning",
|
||||
"validate_phase": "Validate phase",
|
||||
"validate_phase_done": "Experience phase validated: learning limits reset.",
|
||||
"error_not_editable": "You do not have permission to modify this element.",
|
||||
"vehicle_assign": "Attach vehicle",
|
||||
"vehicle_assign_confirm": "This vehicle already belongs to \"{group}\". Attach it to this group?",
|
||||
"vehicle_assign_failed": "Could not attach this vehicle to the group.",
|
||||
"vehicle_import_failed": "Could not import this vehicle from the compendium.",
|
||||
"learning_limits": "Learning limits (per phase)",
|
||||
"learned_skill": "Skill",
|
||||
"learned_specialty": "Specialty",
|
||||
"learned_reserve": "Reserve",
|
||||
"learned_characteristic": "Characteristic",
|
||||
"learned_evolution": "Evolution",
|
||||
"learned_ability": "Totem capacity",
|
||||
"learned_background": "Background",
|
||||
"learned_reputation": "Reputation",
|
||||
"no_character": "No character in this world.",
|
||||
"apply": "Apply",
|
||||
"characteristic": "Characteristic",
|
||||
"learn_skill": "Skill",
|
||||
"learn_specialty": "Specialty",
|
||||
"learn_characteristic": "Characteristic",
|
||||
"learn_reserve": "Reserve",
|
||||
"learn_evolution": "Evolution die",
|
||||
"learn_totem_ability": "Totem capacity",
|
||||
"learn_background": "Background element",
|
||||
"learn_reputation": "Reputation",
|
||||
"learn_adapted_die": "Replace 1 Experience die with an Adapted totem die (×2)",
|
||||
"adapted_die_x2": "1 Adapted totem die ×2",
|
||||
"learn_specialty_name": "Specialty name",
|
||||
"specialty_hint": "Prerequisite: skill at least Proficient (≥2).",
|
||||
"characteristic_hint": "Difficulty 9 + Handicap = current value. Max 3 (except Superhuman).",
|
||||
"reserve_hint": "Difficulty = current value + age Handicap. Max 10.",
|
||||
"evolution_hint": "Difficulty by age + Handicap = accumulated Evolution dice. Adapted totem +1D, Human -1D.",
|
||||
"totem_ability_hint": "Prerequisites: same Totem as the character, Group level ≥ Capacity level, game mode compatible (Survival N1, Nightmare N2, Apocalypse N3).",
|
||||
"background_hint": "Difficulty 9. Handicap (I) if the Background is marked with an asterisk (*). GM authorization required.",
|
||||
"reputation_hint": "Reduce = more famous (Difficulty 10 − target score, 1 pt max per phase). Increase = fade into obscurity (requires a major Objective, Difficulty = current Reputation, +1 per Success).",
|
||||
"reputation_reduce": "Become more famous (reduce)",
|
||||
"reputation_increase": "Fade into obscurity (increase)",
|
||||
"reputation_current": "Current Reputation",
|
||||
"dice_spent": "Dice spent",
|
||||
"learning_success": "Learning successful!",
|
||||
"error_no_experience_dice": "No Experience dice available.",
|
||||
"error_learning_limit": "Limit reached for this type of learning in this phase.",
|
||||
"error_skill_max": "This skill is already at maximum level.",
|
||||
"error_characteristic_max": "Characteristic already at maximum (3).",
|
||||
"error_specialty_requirement": "Specialty requires: skill at least Proficient (≥2).",
|
||||
"error_reserve_max": "Reserve already at maximum (10).",
|
||||
"no_totem_ability": "No Totem capacity available to learn.",
|
||||
"no_background": "No background element to acquire.",
|
||||
"error_totem_mode": "Capacity level incompatible with the game mode.",
|
||||
"error_totem_totem": "The character must share the Totem of the Capacity.",
|
||||
"error_totem_group_level": "Group level insufficient for this Capacity.",
|
||||
"error_totem_limit": "Totem capacity limit reached (3× Level 1, 1× Levels 2 and 3, max 5).",
|
||||
"error_reputation_min": "Reputation already at minimum (0).",
|
||||
"error_reputation_max": "Reputation already at maximum (10).",
|
||||
"error_evolution_no_adapted": "The character must have strictly more Adapted than Human totem dice.",
|
||||
"learn_level": "Group level",
|
||||
"learn_group_reserve": "Group reserve",
|
||||
"learn_ability": "Group capacity",
|
||||
"group_level_hint": "Difficulty = number of members. Handicap = current Group level. Max 10.",
|
||||
"group_reserve_hint": "Difficulty = current Reserve value. Handicap II. Max 10.",
|
||||
"group_ability_hint": "Difficulty = Learning. Handicap = Capacity level. Requires Group level ≥ Capacity level.",
|
||||
"no_group_ability": "No Group capacity available to develop.",
|
||||
"error_group_level_max": "Group level already at maximum (10).",
|
||||
"error_group_reserve_max": "Group reserve already at maximum.",
|
||||
"error_group_level_requirement": "Group level insufficient for this Capacity.",
|
||||
"experience_transfer": "Experience dice",
|
||||
"experience_pull": "Pull from group",
|
||||
"experience_give": "Give 1D to group",
|
||||
"pool_experience": "Experience",
|
||||
"error_no_group": "No group associated with this character.",
|
||||
"error_no_group_dice": "Not enough group pool dice.",
|
||||
"tabs": {
|
||||
"abilities": "Abilities",
|
||||
"combat": "Combat",
|
||||
"equipment": "Equipment",
|
||||
"stories": "Stories",
|
||||
"totem": "Totem",
|
||||
"experience": "Experience"
|
||||
},
|
||||
"Sheet": {
|
||||
"character": "Character",
|
||||
"npc": "NPC",
|
||||
"group": "Group",
|
||||
"creature": "Creature",
|
||||
"community": "Community",
|
||||
"vehicle": "Vehicle"
|
||||
},
|
||||
"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",
|
||||
"major_objective_placeholder": "eg: Reach a distant goal",
|
||||
"minor_objective_placeholder": "eg: Find a medic, scavenge",
|
||||
"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",
|
||||
"close": "Close",
|
||||
"playMode": "Play Mode",
|
||||
"editMode": "Edit Mode"
|
||||
},
|
||||
"UI": {
|
||||
"add": "Add",
|
||||
@@ -195,9 +632,100 @@
|
||||
"temporary": "Temporary effects",
|
||||
"passive": "Passive effects",
|
||||
"inactive": "Inactive effects"
|
||||
}
|
||||
},
|
||||
"effects.inactive": "Inactive",
|
||||
"effects.passive": "Passive",
|
||||
"effects.temporary": "Temporary"
|
||||
},
|
||||
"COMMUNITY": {
|
||||
"tabs": {
|
||||
"identity": "Identity",
|
||||
"domains": "Domains",
|
||||
"resources": "Resources"
|
||||
},
|
||||
"identity": "Identity",
|
||||
"name_placeholder": "Community name",
|
||||
"choose_totem": "Choose totem",
|
||||
"origin": "Origin",
|
||||
"theme": "Theme",
|
||||
"age_label": "Community age",
|
||||
"age": {
|
||||
"recent": "A few days",
|
||||
"established": "A few months",
|
||||
"old": "A few years"
|
||||
},
|
||||
"instincts": "Instincts",
|
||||
"prohibits": "Prohibitions",
|
||||
"notes": "Notes",
|
||||
"location": "Location",
|
||||
"sector": "Sector",
|
||||
"placeType": "Place type",
|
||||
"landmarks": "Landmarks",
|
||||
"territory": "Territory",
|
||||
"history": "History",
|
||||
"founding": "Founding",
|
||||
"events": "Key events",
|
||||
"history_relations": "Relations",
|
||||
"objectives": "Objectives",
|
||||
"objective_major": "Major objective",
|
||||
"objectives_minor": "Minor objectives",
|
||||
"add_objective": "Add objective",
|
||||
"size_title": "Size & Population",
|
||||
"size_label": "Size",
|
||||
"sizeLevels": {
|
||||
"1": "Size 1",
|
||||
"2": "Size 2",
|
||||
"3": "Size 3"
|
||||
},
|
||||
"population": "Population",
|
||||
"population_range": "Recommended population",
|
||||
"figures": "Figures",
|
||||
"companies": "Companies",
|
||||
"domains": {
|
||||
"resources": "Resources",
|
||||
"security": "Security",
|
||||
"dynamism": "Dynamism",
|
||||
"relations": "Relations"
|
||||
},
|
||||
"sections": {
|
||||
"vivre": "Food & Water",
|
||||
"supplies": "Supplies",
|
||||
"defense": "Defense",
|
||||
"care": "Care",
|
||||
"cohesion": "Cohesion",
|
||||
"productivity": "Productivity",
|
||||
"human": "Human",
|
||||
"ecology": "Ecology"
|
||||
},
|
||||
"states": {
|
||||
"crisis": "Crisis",
|
||||
"low": "Low",
|
||||
"stable": "Stable",
|
||||
"opulent": "Opulent"
|
||||
},
|
||||
"reserves": "Reserves",
|
||||
"group_reserve": "Group reserve",
|
||||
"material_dice": "Material dice",
|
||||
"experience_dice": "Experience dice",
|
||||
"morale": "Morale",
|
||||
"morale_value": "Value",
|
||||
"morale_state": "State",
|
||||
"morale_levels": {
|
||||
"high": "High",
|
||||
"normal": "Normal",
|
||||
"low": "Low",
|
||||
"crisis": "Crisis"
|
||||
},
|
||||
"morale_blocked": "All four Domains are Low: the Morale bonus is inactive.",
|
||||
"habitants_title": "Inhabitants",
|
||||
"habitants_coming": "Figures and Companies management comes in a future version.",
|
||||
"linked_group": "Linked group",
|
||||
"profile_hint": "Community profile"
|
||||
},
|
||||
"ITEMS": {
|
||||
"item": "Item",
|
||||
"items": "Items",
|
||||
"new_item": "New item",
|
||||
"defense": "Protection",
|
||||
"defenses": "Protections",
|
||||
"new_defense": "New protection",
|
||||
@@ -214,8 +742,16 @@
|
||||
"ability": "Ability",
|
||||
"new_ability": "New ability",
|
||||
"abilities": "Abilities",
|
||||
"creaturecapacity": "Creature capacity",
|
||||
"new_creaturecapacity": "New creature capacity",
|
||||
"specialties": "Specialties",
|
||||
"new_specialty": "New specialty",
|
||||
"target": "Target",
|
||||
"targets": "Targets",
|
||||
"new_target": "New target",
|
||||
"rite": "Rite",
|
||||
"rites": "Rites",
|
||||
"new_rite": "New rite",
|
||||
"evolution": "Adaptation",
|
||||
"new_evolution": "New adaptation",
|
||||
"evolutions": "Adaptations",
|
||||
@@ -227,7 +763,8 @@
|
||||
"rituel": "Ritual",
|
||||
"transe": "Trance",
|
||||
"effects": "Effects",
|
||||
"details": "Details"
|
||||
"details": "Details",
|
||||
"shield": "Shield"
|
||||
},
|
||||
"ABILITIES": {
|
||||
"vigor": {
|
||||
@@ -281,6 +818,11 @@
|
||||
"dog": "Dog",
|
||||
"bear": "Bear"
|
||||
},
|
||||
"PACK_LEVELS": {
|
||||
"small": "Small",
|
||||
"large": "Large",
|
||||
"giant": "Giant"
|
||||
},
|
||||
"TOTEMS": {
|
||||
"human": {
|
||||
"name": "Human",
|
||||
@@ -434,7 +976,7 @@
|
||||
"name": "Repulsion"
|
||||
},
|
||||
"tracks": {
|
||||
"name": "Tracks"
|
||||
"name": "Tracking"
|
||||
},
|
||||
"crafting": {
|
||||
"name": "Crafting"
|
||||
@@ -464,7 +1006,7 @@
|
||||
"name": "Melee"
|
||||
},
|
||||
"archery": {
|
||||
"name": "Ranged"
|
||||
"name": "Ranged Weapons"
|
||||
},
|
||||
"throwing": {
|
||||
"name": "Throwing"
|
||||
@@ -498,10 +1040,27 @@
|
||||
},
|
||||
"ruins": {
|
||||
"name": "Ruins"
|
||||
},
|
||||
"athletics": {
|
||||
"name": "Athletics"
|
||||
},
|
||||
"fauna": {
|
||||
"name": "Fauna"
|
||||
}
|
||||
},
|
||||
"SEXES": {
|
||||
"male": "Masculine",
|
||||
"female": "Feminine"
|
||||
},
|
||||
"COMBAT": {
|
||||
"Encounter": "Encounter",
|
||||
"None": "None",
|
||||
"Round": "Round"
|
||||
},
|
||||
"SIZE_LEVELS": {
|
||||
"tiny": "Insect",
|
||||
"small": "Small",
|
||||
"medium": "Medium",
|
||||
"large": "Large"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,27 @@
|
||||
{
|
||||
"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.Actor.vehicle": "Véhicule",
|
||||
"TYPES.Item.ability": "Capacité",
|
||||
"TYPES.Item.creaturecapacity": "Capacité de créature",
|
||||
"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": {
|
||||
"world": {
|
||||
"game_mode": {
|
||||
@@ -22,6 +45,23 @@
|
||||
},
|
||||
"legendary": {
|
||||
"name": "Légendaire"
|
||||
},
|
||||
"survival": "Survie",
|
||||
"nightmare": "Cauchemar",
|
||||
"apocalypse": "Apocalypse"
|
||||
},
|
||||
"CREATURE_CAPACITY_TYPES": {
|
||||
"base": {
|
||||
"name": "Base"
|
||||
},
|
||||
"survival": {
|
||||
"name": "Survie"
|
||||
},
|
||||
"nightmare": {
|
||||
"name": "Cauchemar"
|
||||
},
|
||||
"apocalypse": {
|
||||
"name": "Apocalypse"
|
||||
}
|
||||
},
|
||||
"ROLLS": {
|
||||
@@ -68,9 +108,10 @@
|
||||
"AGE_TYPES": {
|
||||
"young": "Jeune",
|
||||
"adult": "Adulte",
|
||||
"old": "Vieux"
|
||||
"old": "Ancien"
|
||||
},
|
||||
"IDENTITY": {
|
||||
"biography": "Biographie",
|
||||
"name": "Nom",
|
||||
"height": "Taille",
|
||||
"weight": "Poids",
|
||||
@@ -98,17 +139,21 @@
|
||||
"vigor": "Vigueur",
|
||||
"wounds": "Blessures",
|
||||
"action": "Action",
|
||||
"specialties": "Spécialité",
|
||||
"specialties": "Spécialités",
|
||||
"rerolls": "Relances",
|
||||
"contact": "Contact",
|
||||
"reaction": "Réaction",
|
||||
"pools": "Réserves",
|
||||
"gear": "Matériel",
|
||||
"gear_hindrance": "Handicap Matériel",
|
||||
"protection": "Protection",
|
||||
"skills": "Compétences",
|
||||
"skills_placeholder": "ex: Armes à feu, Soins, Discrétion",
|
||||
"pattern": "Gabarit",
|
||||
"size": "Taille",
|
||||
"pack": "Groupe"
|
||||
"pack": "Groupe",
|
||||
"threat_details": "Détails de Menace",
|
||||
"role_details": "Détails de Rôle"
|
||||
},
|
||||
"VERMINE": {
|
||||
"name": "Vermine",
|
||||
@@ -116,14 +161,125 @@
|
||||
"level": "Niveau",
|
||||
"experience": "Expérience",
|
||||
"reputation": "Réputation",
|
||||
"chaman": "Chaman",
|
||||
"sheet": {
|
||||
"description": "Description"
|
||||
},
|
||||
"pool": "Réserve",
|
||||
"pools": "Réserves",
|
||||
"reserves": "Réserves",
|
||||
"dice_pool": "Pool de dés",
|
||||
"pool_malus": "Malus Blessure",
|
||||
"pool_wound": "Malus Blessure",
|
||||
"pool_lourd": "Malus Lourd",
|
||||
"lourd_malus_note": "Malus Lourd : -{malus}D (Vigueur insuffisante pour les objets Lourds équipés)",
|
||||
"effective_range": "Portée effective",
|
||||
"traits_mobility_note": "Handicap de Mobilité : +{handicap} (protections équipées non Maniables)",
|
||||
"rafale_check": "Tir en rafale (Difficulté +2, +{rafaleValue} Réussites si réussi)",
|
||||
"rapide_note": "Rapide ({handicap}) : le défenseur subit {handicap} Handicap(s)",
|
||||
"incapacitant_roll": "Jet de Santé",
|
||||
"intimidant": "Intimidant",
|
||||
"intimidant_check": "Brandit ({intimidantName}) — Intimidant",
|
||||
"intimidant_psychology": "Malus 1D (Psychologie)",
|
||||
"intimidant_reroll": "Relance 1D",
|
||||
"temporary_malus": "Malus temporaires",
|
||||
"pool_temp_malus": "Malus temporaires",
|
||||
"pool_psychology": "Malus Psychologie",
|
||||
"ponctuel": "Ponctuel",
|
||||
"ponctuel_used": "annule {value} Handicap(s)",
|
||||
"duree": "Durée",
|
||||
"duree_note": "effet pendant {value} tours de combat",
|
||||
"fetiche": "Fétiche",
|
||||
"fetiche_bonus": "Réserves d'Effort et de Sang-Froid +1D",
|
||||
"fetiche_multiple": "Un même personnage ne peut posséder qu'un seul objet Fétiche",
|
||||
"fetiche_none": "Aucun objet Fétiche équipé",
|
||||
"fetiche_lost": "Fétiche perdu : Réserves −2D, max −1D",
|
||||
"fetiche_lost_penalty": "Fétiche perdu : Réserves réduites de 1D",
|
||||
"lose_fetiche": "Perdre le fétiche",
|
||||
"zone_note": "Zone ({value} m) : tous les présents dans la zone subissent les Dommages",
|
||||
"zone_apply": "Appliquer aux cibles de la zone",
|
||||
"zone_applied": "Zone : {count} blessure(s) appliquée(s)",
|
||||
"auto_successes": "réussites automatiques",
|
||||
"self_control": "Sang-Froid",
|
||||
"effort": "Effort",
|
||||
"equip": "Équiper",
|
||||
"unequip": "Déséquiper",
|
||||
"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",
|
||||
"roll_success": "Réussite",
|
||||
"roll_failure": "Échec",
|
||||
"roll_context": "Contexte du jet",
|
||||
"roll_pool": "Composition du jet",
|
||||
"roll_dice": "Détail des dés",
|
||||
"pool_ability": "Caractéristique",
|
||||
"pool_skill": "Compétence",
|
||||
"pool_specialty": "Spécialité",
|
||||
"pool_help": "Entraide",
|
||||
"pool_tooling": "Équipement",
|
||||
"pool_group": "Groupe",
|
||||
"pool_selfControl": "Sang-Froid",
|
||||
"pool_pools": "Réserves",
|
||||
"pool_total": "Total",
|
||||
"totem_used": "Totem",
|
||||
"totem_keep": "gardé",
|
||||
"domain_bonus": "Bonus de domaine",
|
||||
"domain_penalty": "Malus de domaine",
|
||||
"used": "utilisé",
|
||||
"die_regular": "dés",
|
||||
"die_human": "humains",
|
||||
"die_adapted": "adaptés",
|
||||
"die_successes": "succès",
|
||||
"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",
|
||||
"abilities": "Caractéristiques",
|
||||
"ability": "Caractéristique",
|
||||
"skills": "Compétences",
|
||||
"skills_title": "Compétences",
|
||||
"skill": "Compétence",
|
||||
"skill_title": "Compétence",
|
||||
"skill_mastery": "Maîtrise",
|
||||
"bonus": "Bonus",
|
||||
@@ -131,6 +287,8 @@
|
||||
"reroll": "Relance",
|
||||
"equipment": "Equipement",
|
||||
"specialty": "Spécialité",
|
||||
"specificLevel": "Niveau spécifique",
|
||||
"competence": "Compétence",
|
||||
"technique": "Technique",
|
||||
"techniques": "Techniques",
|
||||
"difficulty": "Difficulté",
|
||||
@@ -145,49 +303,335 @@
|
||||
"range": "Portée",
|
||||
"ranges": "Portées",
|
||||
"mobility": "Mobilité",
|
||||
"vehicle": "Véhicule",
|
||||
"vehicle_specs": "Caractéristiques du véhicule",
|
||||
"kind": "Type",
|
||||
"kind_vehicle": "Véhicule",
|
||||
"kind_mount": "Monture",
|
||||
"distance": "Distance",
|
||||
"speed": "Vitesse",
|
||||
"speed_duration_ph": "durée de pointe (ex: 10 min)",
|
||||
"effort_reserve": "Réserve d'Effort",
|
||||
"maintenance": "Entretien",
|
||||
"owner": "Propriétaire",
|
||||
"no_vehicles": "Aucun véhicule",
|
||||
"no_encounters": "Aucune rencontre",
|
||||
"travel_notes": "Notes de voyage",
|
||||
"rarity": "Rareté",
|
||||
"rarity_sm": "Rar.",
|
||||
"reliability": "Fiabilité",
|
||||
"reliability_sm": "Fiab.",
|
||||
"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"
|
||||
"reliability_test": "Test de Fiabilité",
|
||||
"reliability_repair": "Réparer",
|
||||
"reliability_severity": "Gravité des Dommages",
|
||||
"reliability_severity_mineurs": "Mineurs (1D)",
|
||||
"reliability_severity_graves": "Graves (3D)",
|
||||
"reliability_severity_critiques": "Critiques (5D)",
|
||||
"reliability_immersion": "Immersion prolongée",
|
||||
"reliability_damages": "Dommages subis",
|
||||
"reliability_state": "État",
|
||||
"reliability_destroyed": "OBJET DÉTRUIT / INUTILISABLE",
|
||||
"reliability_repair_mode": "Type de réparation",
|
||||
"reliability_repair_classic": "Classique",
|
||||
"reliability_repair_fortune": "De fortune",
|
||||
"reliability_skill": "Compétence",
|
||||
"reliability_run": "Lancer",
|
||||
"reliability_successes": "Réussites",
|
||||
"reliability_result": "Résultat",
|
||||
"reliability_gained": "Fiabilité restaurée : +{gained}",
|
||||
"reliability_fixed": "Panne réparée (objet fonctionnel)",
|
||||
"reliability_failed": "Échec — aucun point de Fiabilité rendu",
|
||||
"reliability_need_actor": "Réparation impossible : l'objet doit appartenir à un acteur",
|
||||
"reliability_already_destroyed": "Objet déjà détruit ou inutilisable",
|
||||
"reliability_coque_absorbed": "Coque absorbe {absorbed} Dommage(s), {remaining} restant(s)",
|
||||
"reliability_coque_lost": "Coque : Trait épuisé — réparable (Difficulté {max})",
|
||||
"reliability_coque_restored": "Coque restaurée : {max} points",
|
||||
"reliability_repair_coque": "Réparer la coque (Difficulté {max})",
|
||||
"reliability_repair_coque_label": "Coque",
|
||||
"reliability_bien_concu": "Bien conçu : Réussite automatique — Fiabilité entièrement restaurée",
|
||||
"reliability_bien_concu_fortune": "Bien conçu : +1 point de Fiabilité (1ʳᵉ réparation de fortune)",
|
||||
"reliability_auto_success": "Réussite automatique (Bien conçu)",
|
||||
"reliability_pratique": "Pratique : Handicap +I",
|
||||
"reliability_cassant": "Cassant : pertes de Fiabilité majorées d'1 point",
|
||||
"reliability_etanche": "Étanche : Dommages d'immersion ignorés",
|
||||
"reliability_fortune_skill_bonus": "+1D — le personnage possède la Compétence requise ({skill})",
|
||||
"reliability_fortune_skill_missing": "Compétence requise ({skill}) non maîtrisée : Handicap de Rareté +{rarity}",
|
||||
"reliability_fortune_hint": "Réparation de fortune : +1D si le personnage possède la Compétence requise, sinon Handicap de Rareté de cette Compétence.",
|
||||
"damages_state_1": "Endommagé",
|
||||
"damages_state_2": "Endommagé",
|
||||
"damages_state_3": "Défectueux",
|
||||
"damages_state_4": "Défectueux",
|
||||
"damages_state_5": "Hors d'usage",
|
||||
"choose_ability": "Choisissez une caractéristique",
|
||||
"choose_skill": "Choisissez une compétence",
|
||||
"none": "Aucun",
|
||||
"all_characters": "Tous les personnages",
|
||||
"no_group": "Sans groupe",
|
||||
"include_in_phase": "Inclure dans cette phase",
|
||||
"total": "Total",
|
||||
"bonuses": "Bonus",
|
||||
"handicap": "Handicap",
|
||||
"score": "Score",
|
||||
"totem_dice": "Dés de totem",
|
||||
"keep_totem": "Garder le totem",
|
||||
"totem_hint": "Cochez pour utiliser les dés de totem (double réussite possible)",
|
||||
"totem_neutral": "Neutre",
|
||||
"totem_manage": "Gérer les Dés de Totems (Instincts / Interdits)",
|
||||
"totem_serious": "Acte grave (2 dés)",
|
||||
"totem_manage_hint": "Instincts : obéir aux Instincts de l'Humain gagne 1D Humain ; ceux d'un autre Totem gagnent 1D Adapté. Interdit : briser un Interdit de l'Humain fait perdre 1D Humain (ou gagner 1D Adapté si aucun). Max 3 Dés du même Totem et 5 Dés au total.",
|
||||
"totem_instinct_human": "Instinct de l'Humain (+1D Humain)",
|
||||
"totem_instinct_adapted": "Instinct d'un autre Totem (+1D Adapté)",
|
||||
"totem_interdit_human": "Interdit de l'Humain (-1D Humain)",
|
||||
"totem_gained": "{totem} +1D",
|
||||
"totem_gained_adapted": "+1D Adapté",
|
||||
"totem_exchanged": "{totem} gagné en échange (1D opposé retiré)",
|
||||
"totem_replaced": "Dé opposé retiré à la place (max 3D atteint)",
|
||||
"totem_lost": "-1D Humain",
|
||||
"totem_limit_reached": "Limite de Dés de Totems atteinte.",
|
||||
"attack": "Attaque",
|
||||
"attack_contact": "Attaque au contact",
|
||||
"attack_ranged": "Attaque à distance",
|
||||
"defense_esquive": "Esquive",
|
||||
"defense_parade": "Parade",
|
||||
"passive_defense": "Défense passive",
|
||||
"range_short": "Courte",
|
||||
"range_mid": "Moyenne",
|
||||
"range_long": "Longue",
|
||||
"second_action": "Deuxième action (+2 difficulté)",
|
||||
"consume_ammo": "Consommer des munitions",
|
||||
"npc_pool": "Réserves utilisées",
|
||||
"exchange": "Échange de coups",
|
||||
"resolve": "Résoudre",
|
||||
"resolve_result": "Résolution de l'échange",
|
||||
"attacker_successes": "Réussites d'attaque",
|
||||
"defender_successes": "Réussites de défense",
|
||||
"raw_damage": "Dégâts bruts",
|
||||
"net_damage": "Dégâts nets",
|
||||
"hit": "Touché",
|
||||
"hit_parade": "Touché (parade insuffisante)",
|
||||
"missed_esquive": "Esquivé",
|
||||
"missed_parade": "Paré",
|
||||
"no_damage": "Aucun dégât",
|
||||
"wound": "Blessure",
|
||||
"no_wound": "Aucune blessure",
|
||||
"apply_wound": "Appliquer la blessure",
|
||||
"wound_applied": "Blessure appliquée",
|
||||
"wound_escalated": "Blessure {to} (montée depuis {from})",
|
||||
"wound_death": "Toutes les blessures sont cochées — mort",
|
||||
"error_creature_no_defense": "Les créatures ne peuvent pas se défendre (défense passive)",
|
||||
"error_no_permission": "Permissions insuffisantes",
|
||||
"error_no_target": "Aucune cible sélectionnée pour cet échange",
|
||||
"experience_phase": "Phase d'Expérience",
|
||||
"experience_dice": "Dés d'Expérience",
|
||||
"experience_rewards": "Récompenses d'Expérience",
|
||||
"collective": "collectifs",
|
||||
"collective_rewards": "Récompenses collectives",
|
||||
"individual_rewards": "Récompenses individuelles",
|
||||
"collective_group": "Groupe (réserve collective)",
|
||||
"danger": "Danger",
|
||||
"discoveries": "Découvertes",
|
||||
"duration": "Durée",
|
||||
"implication": "Implication",
|
||||
"influence": "Influence",
|
||||
"interpretation": "Interprétation",
|
||||
"reward_anecdotic": "Anecdotique",
|
||||
"reward_minor": "Mineure",
|
||||
"reward_major": "Majeure",
|
||||
"reward_exceptionnal": "Exceptionnelle",
|
||||
"objective_minor": "Objectif mineur",
|
||||
"objective_major": "Objectif majeur",
|
||||
"trauma_reward": "Traumatisme choisi",
|
||||
"trauma_reward_5": "Récompense de Traumatisme (+5D)",
|
||||
"evolution_dice": "Dés d'Évolution",
|
||||
"buy_evolution": "Acheter une Adaptation/Mutation",
|
||||
"evolution_name": "Nom",
|
||||
"evolution_level": "Niveau (coût en Dés d'Évolution)",
|
||||
"evolution_mutation": "Mutation",
|
||||
"evolution_adaptation": "Adaptation",
|
||||
"evolution_available_dice": "Dés d'Évolution accumulés",
|
||||
"evolution_total": "Total des niveaux (après achat)",
|
||||
"evolution_mode_max": "Maximum du Mode de jeu",
|
||||
"game_mode": "Mode de jeu",
|
||||
"mode_survie": "Survie",
|
||||
"mode_cauchemar": "Cauchemar",
|
||||
"mode_apocalypse": "Apocalypse",
|
||||
"evolution_buy_hint": "L'achat consomme un nombre de Dés d'Évolution égal au niveau choisi (le reste est conservé). Limites : Survie 5D, Cauchemar 6D, Apocalypse 7D au total. Mutations 3D/4D interdites en Mode Survie ; une seule Mutation à 4D.",
|
||||
"buy_evolution_done": "Adaptation/Mutation acquise. Dés d'Évolution effacés.",
|
||||
"error_evolution_no_name": "Indiquez un nom pour l'Adaptation/Mutation.",
|
||||
"error_evolution_invalid_level": "Niveau invalide (1 à 4).",
|
||||
"error_evolution_not_enough_dice": "Dés d'Évolution insuffisants pour ce niveau.",
|
||||
"error_evolution_mode_limit": "Maximum de Dés d'Évolution du Mode de jeu atteint.",
|
||||
"error_evolution_mutation_mode": "Les Mutations de Niveau 3/4 nécessitent les Modes Cauchemar ou Apocalypse.",
|
||||
"error_evolution_mutation_4d": "Le personnage possède déjà une Mutation à 4D.",
|
||||
"error_evolution_create": "Impossible de créer l'Adaptation/Mutation.",
|
||||
"open_experience_phase": "Phase d'Expérience",
|
||||
"open_learning": "Apprentissage",
|
||||
"validate_phase": "Valider la phase",
|
||||
"validate_phase_done": "Phase d'Expérience validée : limites d'apprentissage réinitialisées.",
|
||||
"error_not_editable": "Vous n'avez pas la permission de modifier cet élément.",
|
||||
"vehicle_assign": "Attacher le véhicule",
|
||||
"vehicle_assign_confirm": "Ce véhicule appartient déjà à « {group} ». L'attacher à ce Groupe ?",
|
||||
"vehicle_assign_failed": "Impossible d'attacher ce véhicule au Groupe.",
|
||||
"vehicle_import_failed": "Impossible d'importer ce véhicule depuis le compendium.",
|
||||
"learning_limits": "Limites d'apprentissage (par phase)",
|
||||
"learned_skill": "Compétence",
|
||||
"learned_specialty": "Spécialité",
|
||||
"learned_reserve": "Réserve",
|
||||
"learned_characteristic": "Caractéristique",
|
||||
"learned_evolution": "Évolution",
|
||||
"learned_ability": "Capacité de Totem",
|
||||
"learned_background": "Historique",
|
||||
"learned_reputation": "Réputation",
|
||||
"no_character": "Aucun personnage dans ce monde.",
|
||||
"apply": "Appliquer",
|
||||
"characteristic": "Caractéristique",
|
||||
"learn_skill": "Compétence",
|
||||
"learn_specialty": "Spécialité",
|
||||
"learn_characteristic": "Caractéristique",
|
||||
"learn_reserve": "Réserve",
|
||||
"learn_evolution": "Dé d'Évolution",
|
||||
"learn_totem_ability": "Capacité de Totem",
|
||||
"learn_background": "Élément d'Historique",
|
||||
"learn_reputation": "Réputation",
|
||||
"learn_adapted_die": "Remplacer 1D d'Expérience par un Dé de Totem Adapté (×2)",
|
||||
"adapted_die_x2": "1 Dé de Totem Adapté ×2",
|
||||
"learn_specialty_name": "Nom de la spécialité",
|
||||
"specialty_hint": "Prérequis : compétence au moins Confirmée (≥2).",
|
||||
"characteristic_hint": "Difficulté 9 + Handicap = valeur actuelle. Max 3 (Surhumain excepté).",
|
||||
"reserve_hint": "Difficulté = valeur actuelle + Handicap d'âge. Max 10.",
|
||||
"evolution_hint": "Difficulté selon l'âge + Handicap = Dés d'Évolution accumulés. Totem Adapté +1D, Humain -1D.",
|
||||
"totem_ability_hint": "Prérequis : même Totem que le personnage, Niveau de Groupe ≥ Niveau de la Capacité, Mode de jeu compatible (Survie N1, Cauchemar N2, Apocalypse N3).",
|
||||
"background_hint": "Difficulté 9. Handicap (I) si l'Historique est marqué d'un astérisque (*). Autorisation du MJ requise.",
|
||||
"reputation_hint": "Réduire = plus célèbre (Difficulté 10 − score visé, 1 pt max/phase). Augmenter = se faire oublier (exige un Objectif majeur, Difficulté = Réputation actuelle, +1 par Réussite).",
|
||||
"reputation_reduce": "Devenir plus célèbre (réduire)",
|
||||
"reputation_increase": "Se faire oublier (augmenter)",
|
||||
"reputation_current": "Réputation actuelle",
|
||||
"dice_spent": "Dés dépensés",
|
||||
"learning_success": "Apprentissage réussi !",
|
||||
"error_no_experience_dice": "Pas de Dés d'Expérience disponibles.",
|
||||
"error_learning_limit": "Limite atteinte pour ce type d'apprentissage dans cette phase.",
|
||||
"error_skill_max": "Cette compétence est déjà au niveau maximal.",
|
||||
"error_characteristic_max": "Caractéristique déjà au maximum (3).",
|
||||
"error_specialty_requirement": "Spécialité requise : compétence au moins Confirmée (≥2).",
|
||||
"error_reserve_max": "Réserve déjà au maximum (10).",
|
||||
"no_totem_ability": "Aucune Capacité de Totem disponible à apprendre.",
|
||||
"no_background": "Aucun élément d'Historique à acquérir.",
|
||||
"error_totem_mode": "Niveau de Capacité incompatible avec le Mode de jeu.",
|
||||
"error_totem_totem": "Le personnage doit être du même Totem que la Capacité.",
|
||||
"error_totem_group_level": "Niveau de Groupe insuffisant pour cette Capacité.",
|
||||
"error_totem_limit": "Limite de Capacités de Totem atteinte (3× Niveau 1, 1× Niveaux 2 et 3, max 5).",
|
||||
"error_reputation_min": "Réputation déjà au minimum (0).",
|
||||
"error_reputation_max": "Réputation déjà au maximum (10).",
|
||||
"error_evolution_no_adapted": "Il faut strictement plus de Dés du Totem Adapté que Humains.",
|
||||
"learn_level": "Niveau de Groupe",
|
||||
"learn_group_reserve": "Réserve de Groupe",
|
||||
"learn_ability": "Capacité de Groupe",
|
||||
"group_level_hint": "Difficulté = nombre de membres. Handicap = Niveau actuel du Groupe. Max 10.",
|
||||
"group_reserve_hint": "Difficulté = valeur actuelle de la Réserve. Handicap II. Max 10.",
|
||||
"group_ability_hint": "Difficulté = Apprentissage. Handicap = Niveau de la Capacité. Nécessite un Niveau de Groupe ≥ Niveau de la Capacité.",
|
||||
"no_group_ability": "Aucune Capacité de Groupe disponible à développer.",
|
||||
"error_group_level_max": "Niveau de Groupe déjà au maximum (10).",
|
||||
"error_group_reserve_max": "Réserve de Groupe déjà au maximum.",
|
||||
"error_group_level_requirement": "Niveau de Groupe insuffisant pour cette Capacité.",
|
||||
"experience_transfer": "Dés d'Expérience",
|
||||
"experience_pull": "Puiser du Groupe",
|
||||
"experience_give": "Donner 1D au Groupe",
|
||||
"pool_experience": "Expérience",
|
||||
"error_no_group": "Aucun Groupe associé à ce personnage.",
|
||||
"error_no_group_dice": "Réserve de Groupe insuffisante.",
|
||||
"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": {
|
||||
"abilities": "Caractéristiques et compétences",
|
||||
"totem": "Totem et ajustements",
|
||||
"combat": "Combat et reserves",
|
||||
"equipment": "Matériel",
|
||||
"stories": "Histoire"
|
||||
"stories": "Histoire",
|
||||
"experience": "Expérience"
|
||||
},
|
||||
"ammo": "Munitions",
|
||||
"ammo_sm": "Mun",
|
||||
"trait": "Trait",
|
||||
"traits": "Traits",
|
||||
"traits_selector": "Sélecteur de traits",
|
||||
"clew": "Indice",
|
||||
"combat": "Combat",
|
||||
"stories": "Histoires",
|
||||
"gear": "Matériel",
|
||||
"information": "Informations",
|
||||
"editMode": "Mode édition",
|
||||
"playMode": "Mode jeu",
|
||||
"modes": "Modes de jeu",
|
||||
"boost": "boost",
|
||||
"group_abilities": "Capacités de groupe",
|
||||
"totem_abilities": "Capacités de totem",
|
||||
"creature_capacities": "Capacités de créature",
|
||||
"type_group": "Groupe",
|
||||
"type_character": "Personnage",
|
||||
"type_npc": "PNJ",
|
||||
"type_creature": "Créature",
|
||||
"type_community": "Communauté",
|
||||
"type_vehicle": "Véhicule",
|
||||
"type": "Type",
|
||||
"group_members": "Membres",
|
||||
"encounters": "Rencontres",
|
||||
"road": "La Route",
|
||||
"reserve": "Réserve",
|
||||
"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...",
|
||||
"major_objective_placeholder": "Objectif majeur...",
|
||||
"minor_objective_placeholder": "Objectif mineur...",
|
||||
"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",
|
||||
"ProfilePlaceholder": "Profil",
|
||||
"sexes": {
|
||||
"female": "Féminin",
|
||||
"male": "Masculin"
|
||||
},
|
||||
"RollTool": "Outil de lancer de dés",
|
||||
"roll": "Lancer",
|
||||
"cancel": "Annuler",
|
||||
"close": "Fermer",
|
||||
"Sheet": {
|
||||
"character": "Personnage",
|
||||
"npc": "PNJ",
|
||||
"group": "Groupe",
|
||||
"creature": "Créature",
|
||||
"community": "Communauté",
|
||||
"vehicle": "Véhicule"
|
||||
}
|
||||
},
|
||||
"UI": {
|
||||
"add": "Ajouter",
|
||||
@@ -204,7 +648,95 @@
|
||||
"inactive": "Effets inactifs"
|
||||
}
|
||||
},
|
||||
"COMMUNITY": {
|
||||
"tabs": {
|
||||
"identity": "Identité",
|
||||
"domains": "Domaines",
|
||||
"resources": "Ressources"
|
||||
},
|
||||
"identity": "Identité",
|
||||
"name_placeholder": "Nom de la Communauté",
|
||||
"choose_totem": "Choisir le totem",
|
||||
"origin": "Origine",
|
||||
"theme": "Thème",
|
||||
"age_label": "Âge de la Communauté",
|
||||
"age": {
|
||||
"recent": "Quelques jours",
|
||||
"established": "Quelques mois",
|
||||
"old": "Quelques années"
|
||||
},
|
||||
"instincts": "Instincts",
|
||||
"prohibits": "Interdits",
|
||||
"notes": "Notes",
|
||||
"location": "Localisation",
|
||||
"sector": "Secteur",
|
||||
"placeType": "Type de lieu",
|
||||
"landmarks": "Éléments marquants",
|
||||
"territory": "Territoire",
|
||||
"history": "Histoire",
|
||||
"founding": "Fondation",
|
||||
"events": "Événements marquants",
|
||||
"history_relations": "Relations",
|
||||
"objectives": "Objectifs",
|
||||
"objective_major": "Objectif majeur",
|
||||
"objectives_minor": "Objectifs mineurs",
|
||||
"add_objective": "Ajouter un objectif",
|
||||
"size_title": "Taille & Effectif",
|
||||
"size_label": "Taille",
|
||||
"sizeLevels": {
|
||||
"1": "Taille 1",
|
||||
"2": "Taille 2",
|
||||
"3": "Taille 3"
|
||||
},
|
||||
"population": "Effectif",
|
||||
"population_range": "Population recommandée",
|
||||
"figures": "Figures",
|
||||
"companies": "Compagnies",
|
||||
"domains": {
|
||||
"resources": "Ressources",
|
||||
"security": "Sécurité",
|
||||
"dynamism": "Dynamisme",
|
||||
"relations": "Rapports"
|
||||
},
|
||||
"sections": {
|
||||
"vivre": "Vivres",
|
||||
"supplies": "Fournitures",
|
||||
"defense": "Défense",
|
||||
"care": "Soins",
|
||||
"cohesion": "Cohésion",
|
||||
"productivity": "Productivité",
|
||||
"human": "Humain",
|
||||
"ecology": "Écologie"
|
||||
},
|
||||
"states": {
|
||||
"crisis": "Crise",
|
||||
"low": "Bas",
|
||||
"stable": "Stable",
|
||||
"opulent": "Opulent"
|
||||
},
|
||||
"reserves": "Réserves",
|
||||
"group_reserve": "Réserve de Groupe",
|
||||
"material_dice": "Dés de Matériel",
|
||||
"experience_dice": "Dés d'Expérience",
|
||||
"morale": "Moral",
|
||||
"morale_value": "Valeur",
|
||||
"morale_state": "État",
|
||||
"morale_levels": {
|
||||
"high": "Haut",
|
||||
"normal": "Normal",
|
||||
"low": "Bas",
|
||||
"crisis": "Crise"
|
||||
},
|
||||
"morale_blocked": "Les quatre Domaines sont Bas : le Bonus de Moral est inactif.",
|
||||
"habitants_title": "Habitants",
|
||||
"habitants_coming": "La gestion des Figures et des Compagnies arrive dans une prochaine version.",
|
||||
"linked_group": "Groupe rattaché",
|
||||
"profile_hint": "Profil de la Communauté"
|
||||
},
|
||||
"ITEMS": {
|
||||
"item": "Objet",
|
||||
"items": "Objets",
|
||||
"new_item": "Nouvel objet",
|
||||
"defense": "Protection",
|
||||
"defenses": "Protections",
|
||||
"new_defense": "Nouvelle protection",
|
||||
@@ -221,8 +753,17 @@
|
||||
"ability": "Capacité",
|
||||
"new_ability": "Nouvelle capacité",
|
||||
"abilities": "Capacités",
|
||||
"creaturecapacity": "Capacité de créature",
|
||||
"new_creaturecapacity": "Nouvelle capacité de créature",
|
||||
"specialties": "Spécialités",
|
||||
"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",
|
||||
"new_evolution": "Nouvelle adaptation",
|
||||
"evolutions": "Adaptations",
|
||||
@@ -282,12 +823,23 @@
|
||||
"important": "Important",
|
||||
"major": "Majeur"
|
||||
},
|
||||
"SIZE_LEVELS": {
|
||||
"tiny": "Insecte",
|
||||
"small": "Petit",
|
||||
"medium": "Moyen",
|
||||
"large": "Grand"
|
||||
},
|
||||
"PATTERN_LEVELS": {
|
||||
"insect": "Insecte",
|
||||
"rat": "Rat",
|
||||
"dog": "Chien",
|
||||
"bear": "Ours"
|
||||
},
|
||||
"PACK_LEVELS": {
|
||||
"small": "Petit",
|
||||
"large": "Grand",
|
||||
"giant": "Géant"
|
||||
},
|
||||
"TOTEMS": {
|
||||
"human": {
|
||||
"name": "L'Humain",
|
||||
@@ -400,7 +952,7 @@
|
||||
"name": "L'Animal"
|
||||
},
|
||||
"tool": {
|
||||
"name": "L'Outil"
|
||||
"name": "La Machine"
|
||||
},
|
||||
"weapon": {
|
||||
"name": "L'Arme"
|
||||
@@ -417,7 +969,7 @@
|
||||
"name": "Arts"
|
||||
},
|
||||
"civilization": {
|
||||
"name": "Civilisations"
|
||||
"name": "Civilisation"
|
||||
},
|
||||
"psychology": {
|
||||
"name": "Psychologie"
|
||||
@@ -434,7 +986,7 @@
|
||||
"dissection": {
|
||||
"name": "Dissection"
|
||||
},
|
||||
"wildlife": {
|
||||
"fauna": {
|
||||
"name": "Faune"
|
||||
},
|
||||
"repulsion": {
|
||||
@@ -461,42 +1013,39 @@
|
||||
"firearms": {
|
||||
"name": "Armes à feu"
|
||||
},
|
||||
"archery": {
|
||||
"name": "Armes de Tir"
|
||||
},
|
||||
"armory": {
|
||||
"name": "Armurerie"
|
||||
},
|
||||
"shield": {
|
||||
"name": "Bouclier"
|
||||
},
|
||||
"close": {
|
||||
"name": "Corps-à-corps"
|
||||
},
|
||||
"archery": {
|
||||
"name": "Armes de tir"
|
||||
},
|
||||
"throwing": {
|
||||
"name": "Lancer"
|
||||
},
|
||||
"melee": {
|
||||
"name": "Mêlée"
|
||||
},
|
||||
"atletics": {
|
||||
"name": "Atlétisme"
|
||||
},
|
||||
"stealth": {
|
||||
"name": "Discrétion"
|
||||
},
|
||||
"alertness": {
|
||||
"name": "Vigilance"
|
||||
},
|
||||
"flora": {
|
||||
"name": "Flore"
|
||||
"athletics": {
|
||||
"name": "Athlétisme"
|
||||
},
|
||||
"food": {
|
||||
"name": "Alimentation"
|
||||
},
|
||||
"stealth": {
|
||||
"name": "Discrétion"
|
||||
},
|
||||
"close": {
|
||||
"name": "Corps-à-corps"
|
||||
},
|
||||
"environment": {
|
||||
"name": "Environnement"
|
||||
},
|
||||
"flora": {
|
||||
"name": "Flore"
|
||||
},
|
||||
"road": {
|
||||
"name": "Route"
|
||||
},
|
||||
@@ -505,10 +1054,28 @@
|
||||
},
|
||||
"ruins": {
|
||||
"name": "Vestiges"
|
||||
},
|
||||
"shield": {
|
||||
"name": "Bouclier"
|
||||
},
|
||||
"wildlife": {
|
||||
"name": "Faune"
|
||||
},
|
||||
"atletics": {
|
||||
"name": "Athlétisme"
|
||||
}
|
||||
},
|
||||
"SEXES": {
|
||||
"male": "Masculin",
|
||||
"female": "Féminin"
|
||||
},
|
||||
"COMBAT": {
|
||||
"Begin": "Commencer",
|
||||
"Encounter": "Rencontre",
|
||||
"End": "Terminer",
|
||||
"None": "Aucun",
|
||||
"NotStarted": "Non commencé",
|
||||
"Round": "Tour",
|
||||
"TurnEnd": "Fin de tour"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,842 @@
|
||||
@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: #333;
|
||||
line-height: 1.2;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.char-header {
|
||||
h1 label {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.char-name-value {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
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);
|
||||
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stacked layout for creature sheets (header + tabs + content) ───
|
||||
&.creature .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.8rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-shadow: 0 0 10px @theme-color-highlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fiche personnage (ApplicationV2 : l'élément fiche EST le <form>, le
|
||||
// sélecteur "form div.hexa" ci-dessus ne s'y applique donc pas).
|
||||
&.character 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)); }
|
||||
}
|
||||
}
|
||||
|
||||
// Fiche de Groupe : mêmes dés de Réserve cliquables (AppV2).
|
||||
&.group 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)); }
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
padding: 0.25rem;
|
||||
|
||||
li {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.resource-label {
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.major-totem {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.6rem;
|
||||
background-color: rgba(0, 0, 0, 0.12);
|
||||
border: 2px groove #eeede0;
|
||||
|
||||
.flexcol {
|
||||
gap: 0.3rem;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.img-totem {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
filter: drop-shadow(0px 0px 10px rgb(0, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Onglet Histoire : doubler la hauteur des zones Notes et Relations ──
|
||||
.tab.stories {
|
||||
prose-mirror.editor {
|
||||
min-height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.fetiche-block {
|
||||
gap: 12px;
|
||||
margin: 6px 0;
|
||||
padding: 6px 8px;
|
||||
border: 1px dashed @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
border-radius: 3px;
|
||||
|
||||
.fetiche-info {
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-family: "DistressBlack";
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
color: @theme-color-dark;
|
||||
}
|
||||
|
||||
.fetiche-bonus {
|
||||
margin: 0;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
color: @color-acid-green;
|
||||
}
|
||||
|
||||
.fetiche-warning {
|
||||
margin: 0;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
font-weight: 700;
|
||||
color: @color-hemolymph;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
flex: none;
|
||||
font-family: "DistressBlack";
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-hemolymph, 20%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
border-radius: 3px;
|
||||
.transition();
|
||||
|
||||
&:hover {
|
||||
color: @theme-color-dark;
|
||||
background: @color-hemolymph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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; }
|
||||
}
|
||||
|
||||
.evolution-badge {
|
||||
margin-left: 6px;
|
||||
padding: 1px 6px;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
text-transform: uppercase;
|
||||
border-radius: 3px;
|
||||
|
||||
&.adaptation {
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-acid-green, 25%);
|
||||
border: 1px solid @color-acid-green;
|
||||
}
|
||||
|
||||
&.mutation {
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-hemolymph, 30%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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-hemolymph;
|
||||
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: @color-honeycomb;
|
||||
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,152 @@
|
||||
@import "../variables";
|
||||
@import "../utilities";
|
||||
|
||||
.system-vermine2047 .vermine2047.actor.community {
|
||||
.char-header {
|
||||
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||
max-height: 130px;
|
||||
|
||||
.community-portrait {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.char-name,
|
||||
.char-vermine2047 {
|
||||
border-bottom: none;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.char-vermine2047 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.profile,
|
||||
.origin,
|
||||
.level,
|
||||
.population {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.level input,
|
||||
.population input {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.community-section {
|
||||
padding: 8px;
|
||||
border: 1px solid @color-border-dark-4;
|
||||
background: fade(@color-chitin-dark, 20%);
|
||||
|
||||
h3 {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-13;
|
||||
color: @theme-color-dark;
|
||||
margin: 0 0 8px;
|
||||
text-align: left;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.community-linked-group {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 8px;
|
||||
border: 1px dashed @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
|
||||
.label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
|
||||
.community-size-info {
|
||||
margin: 6px 0 0;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.community-warning {
|
||||
margin: 6px 0 0;
|
||||
padding: 4px 8px;
|
||||
font-size: @font-size-11;
|
||||
font-weight: 700;
|
||||
color: @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 12%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
}
|
||||
|
||||
.community-hint {
|
||||
margin: 0;
|
||||
font-size: @font-size-11;
|
||||
font-style: italic;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
// Jauges de Domaines
|
||||
.gauge {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 6px;
|
||||
border: 1px solid @color-border-dark-4;
|
||||
border-left: 4px solid @color-border-dark-4;
|
||||
|
||||
label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
width: 48px;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.gauge-value {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
font-size: @font-size-16;
|
||||
color: @color-text-dark-header;
|
||||
}
|
||||
|
||||
.gauge-state {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.crisis { border-left-color: @color-hemolymph; .gauge-state { color: @color-hemolymph; } }
|
||||
&.low { border-left-color: @color-honeycomb; .gauge-state { color: @color-honeycomb; } }
|
||||
&.stable { border-left-color: @color-acid-green; .gauge-state { color: @color-acid-green; } }
|
||||
&.opulent { border-left-color: @theme-color-accent; .gauge-state { color: @theme-color-accent; } }
|
||||
}
|
||||
|
||||
.morale-level {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-12;
|
||||
font-weight: 700;
|
||||
|
||||
&.high { color: @color-acid-green; }
|
||||
&.normal { color: @color-text-dark-secondary; }
|
||||
&.low { color: @color-honeycomb; }
|
||||
&.crisis { color: @color-hemolymph; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
@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;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.header-fields { flex: 1; }
|
||||
.resources { margin-bottom: 10px; }
|
||||
|
||||
.resource {
|
||||
.card-style();
|
||||
|
||||
// Le mixin card-style ajoute un margin-bottom (15px) : inutile dans le
|
||||
// header, on le neutralise pour garder une hauteur compacte.
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.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;
|
||||
height: auto;
|
||||
font-size: @font-size-18;
|
||||
line-height: 1.2;
|
||||
|
||||
span,
|
||||
input {
|
||||
font-size: @font-size-18;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
// ── Cases de blessures (hexagones) ──────────────────────────────────
|
||||
// Le radio est masqué et recouvre l'hexagone : cliquer sur la case la coche.
|
||||
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)); }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Capacités de créature ───────────────────────────────────────────
|
||||
.creature-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 6px;
|
||||
border-bottom: 1px solid @color-border-dark-3;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { box-shadow: 0 0 10px @theme-color-highlight inset; }
|
||||
}
|
||||
|
||||
.item-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.item-roll { cursor: pointer; }
|
||||
}
|
||||
|
||||
.capacity-type {
|
||||
flex: 0 0 auto;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.item-controls {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
a.item-control {
|
||||
color: inherit;
|
||||
opacity: 0.85;
|
||||
|
||||
&:hover { opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Titres de section : uniforme (centré, scotch, majuscules) ───────
|
||||
h4 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-14;
|
||||
text-align: center;
|
||||
padding: 2px 8px;
|
||||
margin: 0 0 8px;
|
||||
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
opacity: 0.85;
|
||||
|
||||
&:hover { opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Sous-titres (ex. Objectifs majeurs / mineurs) ───────────────────
|
||||
h5 {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
// Brun foncé lisible sur le fond beige de la fiche (le jaune était illisible).
|
||||
color: @color-chitin-dark;
|
||||
margin: 8px 0 4px;
|
||||
text-align: left;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
// ── Liste uniforme (objectifs, capacités, membres, rencontres) ──────
|
||||
.group-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
> li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 6px;
|
||||
border-bottom: 1px solid @color-border-dark-3;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover { box-shadow: 0 0 10px @theme-color-highlight inset; }
|
||||
}
|
||||
|
||||
> li.group-list-header {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
border-bottom: 2px solid @color-border-dark-3;
|
||||
box-shadow: none;
|
||||
|
||||
> div { text-align: center; }
|
||||
}
|
||||
|
||||
.item-name, .actor-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-controls {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Champs (Origine, Concept, Instincts, Interdits…) ────────────────
|
||||
.group-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
label {
|
||||
flex: 0 0 auto;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
input[type="text"] { width: 100%; }
|
||||
}
|
||||
|
||||
// ── Colonnes des tableaux (Rencontres) ──────────────────────────────
|
||||
.gcol-name { flex: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gcol-profile { flex: 3; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gcol-totem { flex: 2; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.totem-display {
|
||||
.card-style();
|
||||
flex: 1;
|
||||
|
||||
span { font-weight: bold; }
|
||||
}
|
||||
|
||||
.level input,
|
||||
.reputation input {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
@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;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.charname {
|
||||
margin: 0;
|
||||
height: auto;
|
||||
font-size: @font-size-18;
|
||||
line-height: 1.2;
|
||||
|
||||
span,
|
||||
input {
|
||||
font-size: @font-size-18;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.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; }
|
||||
}
|
||||
}
|
||||
|
||||
.npc-trait-list {
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid @color-border-dark-3;
|
||||
font-size: @font-size-12;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
strong { flex: 1; }
|
||||
}
|
||||
|
||||
.trait-value {
|
||||
min-width: 30px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.npc-roll-btn {
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.75;
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: @theme-color-dark;
|
||||
text-shadow: 0 0 6px @theme-color-highlight;
|
||||
}
|
||||
}
|
||||
|
||||
.npc-wounds {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.wound-cat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-weight: bold;
|
||||
|
||||
.wound-label {
|
||||
font-size: @font-size-12;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.wound-boxes {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
|
||||
i {
|
||||
font-size: 8px;
|
||||
color: @color-border-dark-2;
|
||||
}
|
||||
|
||||
.hexa {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.npc-free-skills {
|
||||
margin-top: 15px;
|
||||
.form-group-style();
|
||||
|
||||
label {
|
||||
font-size: @font-size-12;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
font-size: @font-size-12;
|
||||
padding: 3px 5px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.npc-profile-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.editor {
|
||||
min-height: 100px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.editor-content {
|
||||
min-height: 80px;
|
||||
font-size: @font-size-12;
|
||||
}
|
||||
}
|
||||
|
||||
.hexa {
|
||||
.hexa-style();
|
||||
.transition();
|
||||
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)); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
@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%;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle « Chaman » de l'onglet Totem : ne pas étirer sur toute la largeur.
|
||||
.chaman-toggle {
|
||||
width: fit-content;
|
||||
margin: 0.5rem auto;
|
||||
gap: 0.5rem;
|
||||
|
||||
label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
color: @theme-color-dark;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
div.minor-totems {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
gap: 0.25rem;
|
||||
min-height: 8.5rem;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(146, 156, 111, 0.5215686275);
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.totem-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h5 {
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
|
||||
img {
|
||||
max-width: 1.5rem;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&.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 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.human-dice,
|
||||
.adapted-dice {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.human-dice i,
|
||||
.adapted-dice i {
|
||||
color: @totem-human-color;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.adapted-dice {
|
||||
transform: rotate(180deg);
|
||||
|
||||
i {
|
||||
transform: rotate(180deg);
|
||||
color: @totem-adapted-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.totem-influence-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
margin-top: 2px;
|
||||
font-size: 0.75rem;
|
||||
|
||||
.totem-influence-label {
|
||||
color: @color-text-dark-header;
|
||||
|
||||
strong.totem-human { color: @totem-human-color; }
|
||||
strong.totem-adapted { color: @totem-adapted-color; }
|
||||
}
|
||||
|
||||
.totem-manage-btn {
|
||||
flex: none;
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-text-light-0, 30%);
|
||||
border: 1px solid @color-text-dark-secondary;
|
||||
border-radius: 3px;
|
||||
.transition();
|
||||
|
||||
&:hover {
|
||||
color: @theme-color-dark;
|
||||
background: @color-hemolymph;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
@import "../variables";
|
||||
@import "../utilities";
|
||||
|
||||
.system-vermine2047 .vermine2047.actor.vehicle {
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-header {
|
||||
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||
padding: 10px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.header-fields { flex: 1; }
|
||||
|
||||
.resource {
|
||||
.card-style();
|
||||
|
||||
// Le mixin card-style ajoute un margin-bottom (15px) : inutile dans le
|
||||
// header, on le neutralise pour garder une hauteur compacte.
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.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: 100px;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.charname {
|
||||
margin: 0;
|
||||
height: auto;
|
||||
font-size: @font-size-18;
|
||||
line-height: 1.2;
|
||||
|
||||
span,
|
||||
input {
|
||||
font-size: @font-size-18;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.vehicle-owner {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 8px;
|
||||
border: 1px dashed @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
|
||||
.label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
|
||||
.vehicle-section {
|
||||
padding: 8px;
|
||||
border: 1px solid @color-border-dark-4;
|
||||
background: fade(@color-chitin-dark, 20%);
|
||||
margin-bottom: 8px;
|
||||
|
||||
h3 {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-13;
|
||||
color: @theme-color-dark;
|
||||
margin: 0 0 8px;
|
||||
text-align: left;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.resource-label {
|
||||
display: block;
|
||||
margin: 0 0 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.unit {
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.maintenance {
|
||||
margin-top: 8px;
|
||||
|
||||
.flexrow label {
|
||||
gap: 4px;
|
||||
margin-right: 12px;
|
||||
font-size: @font-size-12;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* ============================================
|
||||
Combat & Combat Tracker (Vermine2047)
|
||||
Styles custom par-dessus le CSS core Foundry v14
|
||||
============================================ */
|
||||
|
||||
// ── Combat tracker sidebar ──────────────────────────────────────────
|
||||
.combat-sidebar {
|
||||
|
||||
// Nom des combatants coloré : PJ (vert acide) / PNJ (hémolymphe)
|
||||
.combat-tracker .name.player {
|
||||
color: @color-acid-green;
|
||||
}
|
||||
|
||||
.combat-tracker .name.npc {
|
||||
color: @color-hemolymph;
|
||||
}
|
||||
|
||||
// Boutons d'attitude (offensif / actif / passif)
|
||||
.combatant-attitudes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--element-spacing-half);
|
||||
|
||||
.status {
|
||||
color: var(--color-text-subtle);
|
||||
}
|
||||
|
||||
.status.active {
|
||||
color: var(--color-warm-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Footer des dialogues de jet (combat & action) ───────────────────
|
||||
// Pin le footer en bas de la fenêtre : visible même si le contenu dépasse.
|
||||
.application.vermine-roll {
|
||||
.roll-dialog-content .sheet-footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
margin-top: auto;
|
||||
padding-top: 8px;
|
||||
background: @theme-color-dark;
|
||||
border-top: 1px solid fade(@color-hemolymph, 50%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,840 @@
|
||||
@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: @theme-color-dark;
|
||||
|
||||
.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-text-light-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
select {
|
||||
min-height: 28px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ability-score {
|
||||
gap: 4px;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-dark-secondary;
|
||||
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-chitin-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @color-acid-green;
|
||||
}
|
||||
}
|
||||
|
||||
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-dark-secondary;
|
||||
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-dark-primary;
|
||||
}
|
||||
|
||||
.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-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @color-acid-green;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-bonus,
|
||||
.tool-ponctuel {
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.tool-bonus {
|
||||
color: @color-acid-green;
|
||||
background: fade(@color-acid-green, 12%);
|
||||
}
|
||||
|
||||
.tool-ponctuel {
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-hemolymph, 18%);
|
||||
}
|
||||
}
|
||||
|
||||
&.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-dark-primary;
|
||||
font-size: @font-size-12;
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @color-acid-green;
|
||||
}
|
||||
|
||||
small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.totem-hint {
|
||||
width: 100%;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Combat dialog (attaque) ──────────────────────────────────────
|
||||
.combat-mode-label {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-size: @font-size-14;
|
||||
color: @color-hemolymph;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.traits-reminder {
|
||||
margin: 4px 0 0;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
font-style: italic;
|
||||
color: @theme-color-accent;
|
||||
|
||||
i { color: @color-hemolymph; }
|
||||
}
|
||||
|
||||
.locked-value {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 8px;
|
||||
font-size: @font-size-12;
|
||||
color: @color-chitin-dark;
|
||||
background: @color-text-light-0;
|
||||
border-radius: 3px;
|
||||
.custom-select-style();
|
||||
}
|
||||
|
||||
.combat-dialog-weapon {
|
||||
border: 1px solid @color-hemolymph;
|
||||
padding: 6px 8px;
|
||||
background: fade(@color-chitin-dark, 40%);
|
||||
|
||||
.weapon-block-header {
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.weapon-icon { color: @color-hemolymph; }
|
||||
|
||||
.weapon-name {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
font-weight: 900;
|
||||
color: @theme-color-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.weapon-details {
|
||||
gap: 4px 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.weapon-detail {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-dark-primary;
|
||||
|
||||
strong { text-transform: uppercase; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.combat-difficulty-options {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.difficulty-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
input[type="radio"] { flex: none; }
|
||||
|
||||
&.locked {
|
||||
opacity: 0.8;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.combat-targets {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 4px 2px;
|
||||
|
||||
.label {
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
.combat-target-tag {
|
||||
padding: 2px 8px;
|
||||
font-size: @font-size-11;
|
||||
font-family: "Roboto", sans-serif;
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-hemolymph, 20%);
|
||||
border: 1px solid fade(@color-hemolymph, 50%);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase d'Expérience ───────────────────────────────────────────
|
||||
.exp-phase-characters {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
|
||||
details > div:not(summary) {
|
||||
padding: 8px;
|
||||
background: fade(@color-chitin-dark, 30%);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dialogue d'apprentissage ─────────────────────────────────────
|
||||
.learning-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 10%);
|
||||
|
||||
.label {
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
.exp-value {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
font-size: @font-size-20;
|
||||
color: @color-acid-green;
|
||||
}
|
||||
}
|
||||
|
||||
.learning-type {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 6px 2px;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
input[type="radio"] { flex: none; }
|
||||
|
||||
&:hover { color: @color-acid-green; }
|
||||
}
|
||||
}
|
||||
|
||||
.learning-targets {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
border: 1px solid @color-border-dark-4;
|
||||
background: fade(@color-chitin-dark, 30%);
|
||||
|
||||
.learning-hint {
|
||||
margin: 0;
|
||||
font-size: @font-size-11;
|
||||
font-style: italic;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.learning-result {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
padding: 8px;
|
||||
border: 1px solid @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
|
||||
.context-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.context-label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
.context-value {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-13;
|
||||
font-weight: 700;
|
||||
color: @color-text-dark-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Transfert de Dés d'Expérience ─────────────────────────────────
|
||||
.experience-section {
|
||||
.experience-controls {
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
|
||||
.experience-group {
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
|
||||
#exp-group {
|
||||
font-weight: 700;
|
||||
color: @color-acid-green;
|
||||
}
|
||||
}
|
||||
|
||||
button[data-action="giveToGroup"] {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
padding: 3px 12px;
|
||||
cursor: pointer;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-acid-green, 15%);
|
||||
border: 1px solid @color-acid-green;
|
||||
border-radius: 3px;
|
||||
.transition();
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: @theme-color-dark;
|
||||
background: @color-acid-green;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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-text-light-0;
|
||||
text-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Achat d'Adaptation/Mutation ───────────────────────────────────────
|
||||
.application.buy-evolution .bonus-item {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
> label.label {
|
||||
flex: none;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
flex: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Gestion des Dés de Totems ─────────────────────────────────────────
|
||||
.application.totem-dice .totem-dice-state {
|
||||
gap: 12px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
|
||||
.totem-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.totem-human .label { color: @color-text-light-2; }
|
||||
&.totem-adapted .label { color: @color-text-light-2; }
|
||||
|
||||
.value {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-14;
|
||||
font-weight: 700;
|
||||
color: @color-text-dark-header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.application.totem-dice .totem-influence {
|
||||
font-size: @font-size-16;
|
||||
}
|
||||
|
||||
.application.totem-dice .totem-actions {
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
flex: 1 1 100%;
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-hemolymph, 15%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
border-radius: 3px;
|
||||
.transition();
|
||||
|
||||
&:hover {
|
||||
color: @theme-color-dark;
|
||||
background: @color-hemolymph;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
@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-dark-primary;
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
|
||||
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%;
|
||||
}
|
||||
|
||||
// Icônes des cartes d'item envoyées au chat (ApplicationV2).
|
||||
// Le template des cartes (templates/item/chatCards/parts/base.hbs) rend
|
||||
// une <img> pleine taille : on la plafonne à 64×64.
|
||||
ol#chat-log .chat-card-img,
|
||||
ol.chat-log .chat-card-img {
|
||||
max-width: 64px;
|
||||
max-height: 64px;
|
||||
object-fit: contain;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
@@ -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,258 @@
|
||||
// ============================================
|
||||
// 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;
|
||||
flex: 0 0 auto;
|
||||
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,174 @@
|
||||
// ============================================
|
||||
// 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: #3f9b55;
|
||||
@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: #1e7d32;
|
||||
@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,42 @@
|
||||
// ============================================
|
||||
// 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";
|
||||
@import "actor/community";
|
||||
@import "actor/vehicle";
|
||||
|
||||
// 6. Styles des items
|
||||
@import "items";
|
||||
|
||||
// 7. Styles des dialogs
|
||||
@import "dialogs";
|
||||
|
||||
// 8. Styles du combat et du combat tracker
|
||||
@import "combat";
|
||||
|
||||
// ============================================
|
||||
// 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,22 @@
|
||||
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 { default as VermineCommunitySheetV2 } from "./community-sheet.mjs"
|
||||
export { default as VermineVehicleSheetV2 } from "./vehicle-sheet.mjs"
|
||||
export {
|
||||
VermineItemSheetV2,
|
||||
VermineWeaponSheetV2,
|
||||
VermineDefenseSheetV2,
|
||||
VermineAbilitySheetV2,
|
||||
VermineCreatureCapacitySheetV2,
|
||||
VermineSpecialtySheetV2,
|
||||
VermineBackgroundSheetV2,
|
||||
VermineTraumaSheetV2,
|
||||
VermineEvolutionSheetV2,
|
||||
VermineRumorSheetV2,
|
||||
VermineTargetSheetV2,
|
||||
VermineRiteSheetV2
|
||||
} from "./item-sheets.mjs"
|
||||
@@ -0,0 +1,372 @@
|
||||
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,
|
||||
toggleEquip: VermineBaseActorSheet.#onToggleEquip,
|
||||
create: VermineBaseActorSheet.#onItemCreate,
|
||||
createVehicle: VermineBaseActorSheet.#onCreateVehicle,
|
||||
editVehicle: VermineBaseActorSheet.#onEditVehicle,
|
||||
deleteVehicle: VermineBaseActorSheet.#onDeleteVehicle,
|
||||
roll: VermineBaseActorSheet.#onRollItem,
|
||||
attack: VermineBaseActorSheet.#onAttack,
|
||||
clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
|
||||
clickWound: VermineBaseActorSheet.#onClickWound,
|
||||
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"),
|
||||
enrichedEquipmentNotes: await enrich("equipment.description")
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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 }])
|
||||
}
|
||||
|
||||
// ── Véhicules (acteurs) ──────────────────────────────────────────────
|
||||
|
||||
/** Crée un acteur "vehicle" rattaché à cet acteur (groupe ou personnage). */
|
||||
static async #onCreateVehicle(event, target) {
|
||||
const name = game.i18n.localize("ITEMS.new_vehicle")
|
||||
const vehicle = await Actor.create({
|
||||
name,
|
||||
type: "vehicle",
|
||||
system: { ownerId: this.document.id },
|
||||
// Le véhicule est un bien partagé : reprendre la propriété du propriétaire
|
||||
ownership: foundry.utils.deepClone(this.document.ownership)
|
||||
})
|
||||
vehicle?.sheet.render(true)
|
||||
}
|
||||
|
||||
static async #onEditVehicle(event, target) {
|
||||
const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId
|
||||
const vehicle = game.actors.get(id)
|
||||
vehicle?.sheet.render(true)
|
||||
}
|
||||
|
||||
static async #onDeleteVehicle(event, target) {
|
||||
const id = target.closest("[data-vehicle-id]")?.dataset?.vehicleId
|
||||
const vehicle = game.actors.get(id)
|
||||
await vehicle?.deleteDialog()
|
||||
}
|
||||
|
||||
static async #onRollItem(event, target) {
|
||||
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||
if (!id) return
|
||||
const item = this.document.items.get(id)
|
||||
if (!item) return
|
||||
// Armes : PNJ et créatures → dialogue de combat (valeur d'Attaque) ;
|
||||
// personnage → dialogue de compétence. PNJ/créatures n'ont ni
|
||||
// caractéristiques ni compétences.
|
||||
if (item.type === "weapon" && (this.document.type === 'npc' || this.document.type === 'creature')) {
|
||||
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
|
||||
const dialog = await CombatDialog.create({ actorId: this.document.id, weapon: item })
|
||||
if (dialog) dialog.render(true)
|
||||
return
|
||||
}
|
||||
if (item.type === "weapon" && this.document.system.abilities && this.document.system.skills) {
|
||||
const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
|
||||
const dialog = await RollDialog.create({
|
||||
actorId: this.document.id,
|
||||
rolltype: "skill",
|
||||
label: item.system.skill,
|
||||
weapon: item
|
||||
})
|
||||
if (dialog) dialog.render(true)
|
||||
return
|
||||
}
|
||||
item?.roll()
|
||||
}
|
||||
|
||||
static async #onAttack(event, target) {
|
||||
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||
if (!id) return
|
||||
const item = this.document.items.get(id)
|
||||
if (!item) return
|
||||
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
|
||||
const dialog = await CombatDialog.create({ actorId: this.document.id, weapon: item })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
static async #onToggleEquip(event, target) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||
if (!id) return
|
||||
const item = this.document.items.get(id)
|
||||
if (!item) return
|
||||
await item.update({ "system.equipped": !item.system.equipped })
|
||||
}
|
||||
|
||||
static #onClickRadioHexa(event, target) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
// Évite une erreur de permission pour un utilisateur non éditeur.
|
||||
if (!this.isEditable) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.error_not_editable"))
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Coche/décoche les cases de Blessure en respectant l'ordre de gravité
|
||||
* (règles p.39) : on ne peut cocher que la case suivante dans l'ordre
|
||||
* (une blessure s'applique toujours au Seuil égal ou inférieur le plus
|
||||
* élevé), et on ne peut pas en sauter. Décocher une case retire toutes
|
||||
* celles situées après elle.
|
||||
*/
|
||||
static #onClickWound(event, target) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const input = target
|
||||
const box = parseInt(input.value, 10) || 0 // numéro de case (1-based)
|
||||
let current = this.document
|
||||
const propTree = input.name.split(".")
|
||||
for (const prop of propTree) {
|
||||
current = current[prop]
|
||||
}
|
||||
const currentValue = parseInt(current, 10) || 0
|
||||
let next = currentValue
|
||||
if (box === currentValue + 1) {
|
||||
// Cocher la case suivante dans l'ordre.
|
||||
next = box
|
||||
} else if (box <= currentValue) {
|
||||
// Décocher : retire la case cliquée et toutes celles après.
|
||||
next = box - 1
|
||||
}
|
||||
// Si box > currentValue + 1 : on ne permet pas de sauter des cases.
|
||||
this.document.update({ [input.name]: next })
|
||||
}
|
||||
|
||||
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,137 @@
|
||||
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,
|
||||
reliabilityTest: VermineBaseItemSheet.#onReliabilityTest,
|
||||
reliabilityRepair: VermineBaseItemSheet.#onReliabilityRepair
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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) {
|
||||
super._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)
|
||||
}
|
||||
|
||||
static async #onReliabilityTest() {
|
||||
const { ReliabilityDialog } = await import("../../system/dialogs/reliabilityDialog.mjs")
|
||||
const dialog = await ReliabilityDialog.create({ item: this.document, mode: "test" })
|
||||
await dialog.render(true)
|
||||
}
|
||||
|
||||
static async #onReliabilityRepair() {
|
||||
const { ReliabilityDialog } = await import("../../system/dialogs/reliabilityDialog.mjs")
|
||||
const dialog = await ReliabilityDialog.create({ item: this.document, mode: "repair" })
|
||||
await dialog.render(true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
||||
|
||||
export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["character"],
|
||||
position: { width: 920, height: 720 },
|
||||
window: { contentClasses: ["character-content"] },
|
||||
actions: {
|
||||
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty,
|
||||
openExperiencePhase: VermineCharacterSheetV2.#onOpenExperiencePhase,
|
||||
openLearning: VermineCharacterSheetV2.#onOpenLearning,
|
||||
validatePhase: VermineCharacterSheetV2.#onValidatePhase,
|
||||
buyEvolution: VermineCharacterSheetV2.#onBuyEvolution,
|
||||
openTotemDice: VermineCharacterSheetV2.#onOpenTotemDice,
|
||||
loseFetiche: VermineCharacterSheetV2.#onLoseFetiche
|
||||
}
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs", scrollable: [""] },
|
||||
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||
abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs", scrollable: [""] },
|
||||
totem: { template: "systems/vermine2047/templates/actor/appv2/character-totem.hbs", scrollable: [""] },
|
||||
equipment: { template: "systems/vermine2047/templates/actor/appv2/character-equipment.hbs", scrollable: [""] },
|
||||
stories: { template: "systems/vermine2047/templates/actor/appv2/character-stories.hbs", scrollable: [""] },
|
||||
combat: { template: "systems/vermine2047/templates/actor/appv2/character-combat.hbs", scrollable: [""] },
|
||||
experience: { template: "systems/vermine2047/templates/actor/appv2/character-experience.hbs", scrollable: [""] }
|
||||
}
|
||||
|
||||
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" },
|
||||
experience: { id: "experience", group: "sheet", icon: "fas fa-graduation-cap", label: "VERMINE.tabs.experience" }
|
||||
}
|
||||
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
|
||||
context.specialties = doc.itemTypes.specialty
|
||||
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
|
||||
context.rites = doc.itemTypes.rite
|
||||
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 = game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === doc.id)
|
||||
break
|
||||
case "stories":
|
||||
context.tab = context.tabs.stories
|
||||
break
|
||||
case "combat": {
|
||||
context.tab = context.tabs.combat
|
||||
context.equippedWeapons = doc.itemTypes.weapon.filter(i => i.system.equipped)
|
||||
context.equippedDefenses = doc.itemTypes.defense.filter(i => i.system.equipped)
|
||||
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||
const { VermineTraits } = await import("../../system/traits.mjs")
|
||||
const feticheItems = VermineTraits.equippedItems(doc, 'fetiche')
|
||||
context.feticheItems = feticheItems
|
||||
context.fetiche = feticheItems[0] ?? null
|
||||
context.feticheMultiple = feticheItems.length > 1
|
||||
const loss = doc.system.experience?.feticheLoss
|
||||
context.feticheLost = Boolean(loss?.self_control || loss?.effort)
|
||||
break
|
||||
}
|
||||
case "experience":
|
||||
context.tab = context.tabs.experience
|
||||
context.specialtyCount = doc.itemTypes.specialty.length
|
||||
const ex = doc.system.experience
|
||||
context.experience = ex
|
||||
context.collectiveTotal = (ex.collective.danger + ex.collective.discoveries + ex.collective.duration)
|
||||
context.individualTotal = (ex.individual.implication + ex.individual.influence + ex.individual.interpretation)
|
||||
context.currentPhase = ex.phase
|
||||
context.learnedCurrent = ex.learned.phase === ex.phase
|
||||
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])
|
||||
}
|
||||
|
||||
static async #onOpenExperiencePhase(event, target) {
|
||||
const { default: ExperiencePhaseDialog } = await import("../../system/dialogs/experiencePhaseDialog.mjs")
|
||||
const dialog = await ExperiencePhaseDialog.create()
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
static async #onOpenLearning(event, target) {
|
||||
const { default: LearningDialog } = await import("../../system/dialogs/learningDialog.mjs")
|
||||
const dialog = await LearningDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
/** Valide la phase d'Expérience courante et réinitialise les limites. */
|
||||
static async #onValidatePhase() {
|
||||
const { VermineExperience } = await import("../../system/experience.mjs")
|
||||
await VermineExperience.resetPhaseLimits(this.document)
|
||||
ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done"))
|
||||
}
|
||||
|
||||
/** Ouvre le dialogue d'achat d'une Adaptation/Mutation (Dés d'Évolution). */
|
||||
static async #onBuyEvolution(event, target) {
|
||||
const { default: BuyEvolutionDialog } = await import("../../system/dialogs/buyEvolutionDialog.mjs")
|
||||
const dialog = await BuyEvolutionDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
/** Ouvre le dialogue de gestion des Dés de Totems. */
|
||||
static async #onOpenTotemDice(event, target) {
|
||||
const { default: TotemDiceDialog } = await import("../../system/dialogs/totemDiceDialog.mjs")
|
||||
const dialog = await TotemDiceDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Action « Perdre le fétiche » : applique -2D dans chaque Réserve et
|
||||
* -1D de max persistant (expérience.feticheLoss), puis retire le Trait
|
||||
* Fétiche de l'objet équipé.
|
||||
*/
|
||||
static async #onLoseFetiche() {
|
||||
const doc = this.document
|
||||
if (!game.user.isGM && !doc.isOwner) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_permission"))
|
||||
return
|
||||
}
|
||||
const { VermineTraits } = await import("../../system/traits.mjs")
|
||||
const items = VermineTraits.equippedItems(doc, 'fetiche')
|
||||
if (!items.length) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.fetiche_none"))
|
||||
return
|
||||
}
|
||||
const loss = doc.system.experience?.feticheLoss
|
||||
const sc = Math.max(0, (doc.system.attributes.self_control.value || 0) - 2)
|
||||
const ef = Math.max(0, (doc.system.attributes.effort.value || 0) - 2)
|
||||
await doc.update({
|
||||
"system.attributes.self_control.value": sc,
|
||||
"system.attributes.effort.value": ef,
|
||||
"system.experience.feticheLoss.self_control": (loss?.self_control || 0) + 1,
|
||||
"system.experience.feticheLoss.effort": (loss?.effort || 0) + 1
|
||||
})
|
||||
await items[0].update({ "system.traits.fetiche": null })
|
||||
ui.notifications.info(game.i18n.localize("VERMINE.fetiche_lost"))
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
||||
|
||||
export default class VermineCommunitySheetV2 extends VermineBaseActorSheet {
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["community"],
|
||||
position: { width: 760, height: 640 },
|
||||
window: { contentClasses: ["community-content"] },
|
||||
actions: {
|
||||
addObjective: VermineCommunitySheetV2.#onAddObjective,
|
||||
deleteObjective: VermineCommunitySheetV2.#onDeleteObjective
|
||||
}
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/vermine2047/templates/actor/appv2/community-main.hbs", scrollable: [""] },
|
||||
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||
identity: { template: "systems/vermine2047/templates/actor/appv2/community-identity.hbs", scrollable: [""] },
|
||||
domains: { template: "systems/vermine2047/templates/actor/appv2/community-domains.hbs", scrollable: [""] },
|
||||
resources: { template: "systems/vermine2047/templates/actor/appv2/community-resources.hbs", scrollable: [""] }
|
||||
}
|
||||
|
||||
tabGroups = { sheet: "identity" }
|
||||
|
||||
#getTabs() {
|
||||
const tabs = {
|
||||
identity: { id: "identity", group: "sheet", icon: "fas fa-info-circle", label: "COMMUNITY.tabs.identity" },
|
||||
domains: { id: "domains", group: "sheet", icon: "fas fa-chart-simple", label: "COMMUNITY.tabs.domains" },
|
||||
resources: { id: "resources", group: "sheet", icon: "fas fa-boxes-stacked", label: "COMMUNITY.tabs.resources" }
|
||||
}
|
||||
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()
|
||||
// Groupe de personnages rattaché
|
||||
context.resolvedGroup = null
|
||||
if (this.document.system.groupId) {
|
||||
const g = game.actors.get(this.document.system.groupId)
|
||||
if (g) context.resolvedGroup = { id: g.id, name: g.name }
|
||||
}
|
||||
// Enrichissement HTML des champs longs (même logique que la fiche PNJ)
|
||||
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 }) : ""
|
||||
}
|
||||
context.enrichedNotes = await enrich("identity.notes")
|
||||
context.enrichedLandmarks = await enrich("location.landmarks")
|
||||
context.enrichedTerritory = await enrich("location.territory")
|
||||
context.enrichedEvents = await enrich("history.events")
|
||||
context.enrichedRelations = await enrich("history.relations")
|
||||
return context
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
const doc = this.document
|
||||
switch (partId) {
|
||||
case "main": break
|
||||
case "identity":
|
||||
context.tab = context.tabs.identity
|
||||
context.ageOptions = Object.entries(CONFIG.VERMINE.communityAges).map(([key, label]) => ({ key, label: game.i18n.localize(label) }))
|
||||
break
|
||||
case "domains":
|
||||
context.tab = context.tabs.domains
|
||||
context.sizeOptions = CONFIG.VERMINE.communitySizeLevels
|
||||
context.sizeLabel = doc.system.size?.value ? game.i18n.localize(CONFIG.VERMINE.communitySizeLevels[doc.system.size.value]?.label ?? "") : ""
|
||||
context.sizeInfo = doc.system.size?.value ? CONFIG.VERMINE.communitySizeLevels[doc.system.size.value] : null
|
||||
// Préparer les 4 Domaines avec leurs 2 sections (jauges) et leur état
|
||||
context.domains = []
|
||||
for (const [dkey, dcfg] of Object.entries(CONFIG.VERMINE.communityDomains)) {
|
||||
const domain = doc.system.domains?.[dkey]
|
||||
context.domains.push({
|
||||
key: dkey,
|
||||
label: game.i18n.localize(dcfg.label),
|
||||
sections: Object.entries(dcfg.sections).map(([skey, label]) => {
|
||||
const gauge = domain?.[skey] ?? { value: 0 }
|
||||
const state = gauge.state || "crisis"
|
||||
return {
|
||||
domainKey: dkey,
|
||||
key: skey,
|
||||
label: game.i18n.localize(label),
|
||||
gauge,
|
||||
state,
|
||||
stateLabel: game.i18n.localize(CONFIG.VERMINE.communityGaugeStates[state]?.label ?? "COMMUNITY.states.crisis")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
break
|
||||
case "resources":
|
||||
context.tab = context.tabs.resources
|
||||
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 #onDeleteObjective(event, target) {
|
||||
const index = parseInt(target.dataset.index)
|
||||
if (isNaN(index)) return
|
||||
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: "", minor: [] })
|
||||
objectives.minor.splice(index, 1)
|
||||
this.document.update({ "system.objectives": objectives })
|
||||
}
|
||||
|
||||
static #onAddObjective(event, target) {
|
||||
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: "", minor: [] })
|
||||
objectives.minor.push("")
|
||||
this.document.update({ "system.objectives": objectives })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import VermineBaseActorSheet from "./base-actor-sheet.mjs"
|
||||
|
||||
export default class VermineCreatureSheetV2 extends VermineBaseActorSheet {
|
||||
|
||||
// La fiche créature s'ouvre en mode édition : Gabarit, Taille, Rôle et Meute
|
||||
// se règlent dès l'ouverture (le mode jeu reste accessible via le toggle).
|
||||
_sheetMode = this.constructor.SHEET_MODES.EDIT
|
||||
|
||||
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", scrollable: [""] },
|
||||
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||
info: { template: "systems/vermine2047/templates/actor/appv2/creature-info.hbs", scrollable: [""] },
|
||||
stats: { template: "systems/vermine2047/templates/actor/appv2/creature-stats.hbs", scrollable: [""] },
|
||||
combat: { template: "systems/vermine2047/templates/actor/appv2/creature-combat.hbs", scrollable: [""] },
|
||||
effects: { template: "systems/vermine2047/templates/actor/appv2/creature-effects.hbs", scrollable: [""] }
|
||||
}
|
||||
|
||||
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
|
||||
context.sizeLabel = doc.system.size?.value !== undefined
|
||||
? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "")
|
||||
: ""
|
||||
context.packLabel = doc.system.pack?.value
|
||||
? game.i18n.localize(CONFIG.VERMINE.creaturePackLevels[doc.system.pack.value]?.label ?? "")
|
||||
: ""
|
||||
break
|
||||
case "info":
|
||||
context.tab = context.tabs.info
|
||||
context.capacities = doc.itemTypes.creaturecapacity
|
||||
break
|
||||
case "stats":
|
||||
context.tab = context.tabs.stats
|
||||
context.patternLabel = doc.system.pattern?.value ? game.i18n.localize(CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label ?? "") : ""
|
||||
context.sizeLabel = doc.system.size?.value !== undefined ? game.i18n.localize(CONFIG.VERMINE.creatureSizeLevels[doc.system.size.value]?.label ?? "") : ""
|
||||
context.roleLabel = doc.system.role?.value ? game.i18n.localize(CONFIG.VERMINE.creatureRoleLevels[doc.system.role.value]?.label ?? "") : ""
|
||||
context.packLabel = doc.system.pack?.value
|
||||
? `${doc.system.pack.value} (${game.i18n.localize(CONFIG.VERMINE.creaturePackLevels[doc.system.pack.value]?.label ?? "")})`
|
||||
: game.i18n.localize("VERMINE.none")
|
||||
// Contribution cumulée de la Taille (les modificateurs se cumulent : Taille N = somme 1..N)
|
||||
const sizeLevels = CONFIG.VERMINE.creatureSizeLevels || {}
|
||||
const sizeLevel = doc.system.size?.value ?? 0
|
||||
context.sizeCumulative = { attack: 0, vigor: 0, minorWound: 0, majorWound: 0, deadlyWound: 0 }
|
||||
for (let i = 1; i <= sizeLevel; i++) {
|
||||
const cfg = sizeLevels[i] || {}
|
||||
context.sizeCumulative.attack += cfg.attack || 0
|
||||
context.sizeCumulative.vigor += cfg.vigor || 0
|
||||
context.sizeCumulative.minorWound += cfg.minorWound || 0
|
||||
context.sizeCumulative.majorWound += cfg.majorWound || 0
|
||||
context.sizeCumulative.deadlyWound += cfg.deadlyWound || 0
|
||||
}
|
||||
break
|
||||
case "combat":
|
||||
context.tab = context.tabs.combat
|
||||
context.weapons = doc.itemTypes.weapon
|
||||
context.equippedWeapons = doc.itemTypes.weapon.filter(i => i.system.equipped)
|
||||
context.equippedDefenses = doc.itemTypes.defense.filter(i => i.system.equipped)
|
||||
break
|
||||
case "effects":
|
||||
context.tab = context.tabs.effects
|
||||
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||
break
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
/** @override - Les créatures n'ont pas de caractéristiques/compétences : on ouvre le dialogue d'attaque (pool calculé) ou de Réaction. */
|
||||
async _onRoll(event) {
|
||||
event.preventDefault()
|
||||
const el = event.currentTarget
|
||||
const type = el.dataset.type
|
||||
if (type === "creature-reaction") {
|
||||
const { default: NpcRollDialog } = await import("../../system/dialogs/npcRollDialog.mjs")
|
||||
const dialog = await NpcRollDialog.create({ actorId: this.document.id, rolltype: "reaction" })
|
||||
if (dialog) dialog.render(true)
|
||||
return
|
||||
}
|
||||
if (type !== "creature-attack") return super._onRoll(event)
|
||||
const { default: CombatDialog } = await import("../../system/dialogs/combatDialog.mjs")
|
||||
const dialog = await CombatDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
}
|
||||