61 Commits

Author SHA1 Message Date
ea6f267f8f Migration datamodels !
All checks were successful
Release Creation / build (release) Successful in 55s
2026-01-11 22:45:43 +01:00
cfda525f7c Migration datamodels ! 2026-01-11 22:44:49 +01:00
1afb1d0769 Migration datamodels ! 2026-01-11 22:40:24 +01:00
b282427406 Migration datamodels ! 2026-01-11 22:40:18 +01:00
fc7c51e369 Migration datamodels ! 2026-01-11 22:40:06 +01:00
8d3fdbd009 Fix version 2026-01-10 18:20:10 +01:00
bb3a4fc5f7 Foundry v13 migration 2025-05-02 00:00:24 +02:00
ee7f37878f Fix v11/v12 compat 2024-05-02 09:24:23 +02:00
85e0249822 Cleanup et preparation Foundry v12 2024-05-01 09:33:34 +02:00
8f0bf91464 Enhance stats 2024-02-08 12:50:51 +01:00
2c79743009 Sync 2023-05-25 15:13:41 +02:00
ecd164c3e5 Fix furor 2023-02-13 15:55:40 +01:00
ecdb85455d Merge pull request 'fix calculation of furor usage' (#5) from Lasth31/fvtt-yggdrasill:fix/v10-furor-calculation into v10
Reviewed-on: #5
2023-02-13 15:54:39 +01:00
8d56fed614 fix calculation of furor usage 2023-02-13 14:38:20 +01:00
63d1c5847b Fix carac secondaires 2022-11-13 10:29:36 +01:00
866a079c69 Allow items links in editors 2022-09-28 15:44:25 +02:00
fa0b989c86 Allow items links in editors 2022-09-28 15:43:48 +02:00
895a722f4c Allow items links in editors 2022-09-28 15:42:45 +02:00
4442050b11 First v10 migration 2022-09-21 11:42:58 +02:00
aee6b2feae First v10 migration 2022-09-21 11:42:09 +02:00
a613571d7e Fix yggdrasil 2022-09-21 11:19:30 +02:00
545807d533 Remove warnings 2022-08-04 21:06:32 +02:00
42736e02a7 Sync 2022-05-21 14:21:55 +02:00
0e3c5ef749 Merge branch 'Archonoir-main-patch-99586' into 'main'
Update template.json

See merge request LeRatierBretonnien/fvtt-yggdrasill!2
2022-02-21 22:38:03 +00:00
Archonoir-dev
4b3b59516a Update template.json 2022-02-21 22:35:18 +00:00
6b42e9dc3f Merge branch 'Archonoir-main-patch' into 'main'
Update template.json

See merge request LeRatierBretonnien/fvtt-yggdrasill!1
2022-02-21 22:25:35 +00:00
Archonoir-dev
9886ebc85b Update system.json 2022-02-21 22:12:48 +00:00
Archonoir-dev
b67fffb26c Update templates/actor-sheet.html 2022-02-21 21:59:04 +00:00
Archonoir-dev
d3ffc93738 Update modules/yggdrasill-actor-sheet.js 2022-02-21 21:57:10 +00:00
Archonoir-dev
d4a005fafb Update modules/yggdrasill-actor-sheet.js 2022-02-21 21:54:45 +00:00
Archonoir-dev
7f029f67f2 Update modules/yggdrasill-actor.js 2022-02-21 21:51:38 +00:00
Archonoir-dev
6ea56b85e6 sheet bouclier 2022-02-21 21:47:35 +00:00
Archonoir-dev
3ca4a1c844 Update template.json 2022-02-21 21:43:30 +00:00
583cc2b87a Use another server for measures 2022-02-19 09:30:57 +01:00
85030243fa Sync 2022-02-05 12:37:04 +01:00
6506023e2d Compendium update 2022-02-03 20:57:02 +01:00
3c684ca7b9 Sync yggdrasill 2022-01-30 21:10:44 +01:00
95337a35c2 Sync yggdrasill 2022-01-30 21:07:51 +01:00
e371d57963 Sync yggdrasill 2022-01-30 21:01:59 +01:00
d1da5bfe23 Sync yggdrasill 2022-01-30 21:01:10 +01:00
1dc804697e Sync 2022-01-23 22:01:13 +01:00
e7c67fe479 Do not add carac value 2022-01-23 21:41:13 +01:00
57afc103c6 Do not add carac value 2022-01-23 21:39:30 +01:00
24f03af66e Fix CSS 2022-01-23 14:53:09 +01:00
26491b5594 Mod carac 2022-01-23 14:05:01 +01:00
af38cb1e7c Sync carrières 2022-01-23 10:48:19 +01:00
f5557f46a6 Biodatas 2022-01-23 10:07:25 +01:00
da5b2bf785 Fix rolls and various glitches 2022-01-23 00:07:47 +01:00
26a5cdcffb Fix blesse 2022-01-16 10:09:16 +01:00
bafaaad6b4 Fix v9 2022-01-12 12:09:36 +01:00
45922a1277 Fix v9 2022-01-12 11:58:17 +01:00
35aab26384 Fix v9 2022-01-12 11:58:05 +01:00
e4f8505cc7 Fix v9 2022-01-12 11:32:08 +01:00
c28c7d8c0d Fix v9 2022-01-12 09:19:08 +01:00
9a94798102 Fix v9 2022-01-12 09:13:47 +01:00
96e02cdb7a Fix infinite loop + dices 2022-01-11 18:19:13 +01:00
1eb5c5ecb3 Sync 2022-01-10 22:31:22 +01:00
42904b1391 Initial import 2022-01-09 10:01:15 +01:00
51fb0deba2 Initial import 2022-01-09 09:54:41 +01:00
ba05e0485f Initial import 2022-01-09 09:52:43 +01:00
4cff41d648 Initial import 2022-01-09 09:51:00 +01:00
278 changed files with 17918 additions and 2659 deletions

View File

@@ -0,0 +1,63 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the 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/public/fvtt-yggdrasill/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-yggdrasill.zip
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./fvtt-yggdrasill.zip system.json README.md fonts/ images/ lang/ less/ modules/ packs/ styles/ templates/ template.json
- 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: |-
./fvtt-yggdrasill.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: 'fvtt-yggdrasill'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-yggdrasill/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-yggdrasill.zip'
compatibility-minimum: '13'
compatibility-verified: '13'

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.history/
node_modules/
packs/_source/

View File

@@ -17,7 +17,7 @@ test:
build:
stage: build
script:
- zip fvtt-yggdrasill.zip -r *.js *.json *.md module styles templates -x ".*"
- zip fvtt-yggdrasill.zip -r *.js *.json *.md fonts images packs module styles templates -x ".*"
artifacts:
name: fvtt-yggdrasill
when: on_success

28
LICENSE.md Normal file
View File

@@ -0,0 +1,28 @@
# Licence
## Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)
Ce système Foundry VTT pour Yggdrasill est sous licence Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International.
### Vous êtes autorisé à :
- **Partager** — copier et redistribuer le matériel sous quelque support ou format que ce soit
### Selon les conditions suivantes :
- **Attribution** — Vous devez créditer l'œuvre, fournir un lien vers la licence et indiquer si des modifications ont été effectuées. Vous devez indiquer ces informations par tous les moyens raisonnables, sans toutefois suggérer que l'offrant vous soutient ou soutient la façon dont vous avez utilisé son œuvre.
- **Pas d'Utilisation Commerciale** — Vous n'êtes pas autorisé à faire un usage commercial de cette œuvre, tout ou partie du matériel la composant.
- **Pas de modifications** — Dans le cas où vous effectuez un remix, que vous transformez, ou créez à partir du matériel composant l'œuvre originale, vous n'êtes pas autorisé à distribuer ou mettre à disposition l'œuvre modifiée.
- **Pas de restrictions complémentaires** — Vous n'êtes pas autorisé à appliquer des conditions légales ou des mesures techniques qui restreindraient légalement autrui à utiliser l'œuvre dans les conditions décrites par la licence.
### Texte complet de la licence
Pour consulter une copie complète de cette licence, visitez :
https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.fr
---
**Note** : Ce système est un projet non officiel et n'est pas affilié à l'éditeur du jeu de rôle Yggdrasill.

View File

@@ -1,8 +1,11 @@
# Système Yggdrasill pour FoundryVTT
# Yggdrasill (7ième Cercle) pour FoundryVTT
Ce système non-officiel est le portage du JdR Yggdrasill (7ième Cercle) pour le système FoundryVTT.
Ce système est l'implémentation du jeu de rôle Yggdrasill (7ième Cercle) pour FoundryVTT.
https://www.7emecercle.com/7C_site/jeux-de-roles/yggdrasill/
Ce système n'est pas officiel, et nécessite le livre de base pour jouer.
Le livre de base d'Yggdrasill, disponible sur le site de 7ième cercle est nécessaire pour jouer.
Yggdrasill le jeu de rôle est la propriété de 7ième Cercle.
The Vinque font used in this system is properly licensed for Web usage. The proof of the license agreement can be provided thru mail from here or on the French Discord (user @Thelvyn).
Yggdrasill le JDR est la propriété de 7ième Cercle.

35
gulpfile.js Normal file
View File

@@ -0,0 +1,35 @@
const gulp = require('gulp');
const less = require('gulp-less');
const sourcemaps = require('gulp-sourcemaps');
// Paths
const paths = {
styles: {
src: 'less/**/*.less',
dest: 'styles/'
}
};
// Compile LESS to CSS
function styles() {
return gulp.src('less/yggdrasill.less')
.pipe(sourcemaps.init())
.pipe(less())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paths.styles.dest));
}
// Watch files
function watchFiles() {
gulp.watch(paths.styles.src, styles);
}
// Define complex tasks
const build = gulp.series(styles);
const watch = gulp.series(build, watchFiles);
// Export tasks
exports.styles = styles;
exports.build = build;
exports.watch = watch;
exports.default = build;

27
lang/fr.json Normal file
View File

@@ -0,0 +1,27 @@
{
"TYPES": {
"Item": {
"competence": "Compétence",
"don": "Don",
"faiblesse": "Faiblesse",
"blessure": "Blessure",
"maladie": "Maladie",
"poison": "Poison",
"prouesse": "Prouesse",
"sortsejdr": "Sort Sejdr",
"sortgaldr": "Sort Galdr",
"rune": "Rune",
"armecc": "Arme de corps à corps",
"armedist": "Arme de distance",
"armure": "Armure",
"bouclier": "Bouclier",
"equipement": "Équipement",
"monnaie": "Monnaie",
"effetmagique": "Effet magique"
},
"Actor": {
"personnage": "Personnage",
"figurant": "Figurant"
}
}
}

View File

@@ -0,0 +1,534 @@
/* ========================================
CHAT MESSAGE STYLES - Viking Theme
======================================== */
.ygg-chat-card {
font-family: "Vinque", serif;
background: linear-gradient(135deg, rgba(245, 235, 220, 0.95) 0%, rgba(230, 220, 205, 0.95) 100%);
border: 3px solid #4a0404;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), inset 0 0 20px rgba(218, 165, 32, 0.1);
overflow: hidden;
margin: 0.3rem 0;
/* Header Section */
.ygg-chat-header {
background: linear-gradient(135deg, #4a0404 0%, #6b0505 100%);
color: #f5ead3;
padding: 0.5rem 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
border-bottom: 2px solid #daa520;
.header-decorative-border {
flex: 1;
height: 2px;
background: linear-gradient(90deg, transparent 0%, #daa520 50%, transparent 100%);
}
.actor-portrait {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid #daa520;
box-shadow: 0 0 8px rgba(218, 165, 32, 0.5);
object-fit: cover;
}
.header-content {
flex-shrink: 0;
text-align: center;
}
.actor-name {
margin: 0;
font-family: "CaslonAntique", serif;
font-size: 1.1rem;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
color: #f5ead3;
}
.roll-type {
font-size: 0.75rem;
font-weight: normal;
opacity: 0.9;
margin-top: -0.1rem;
i {
margin-right: 0.2rem;
}
}
}
/* Roll Description */
.ygg-roll-description {
padding: 0.5rem 0.75rem;
background: rgba(255, 255, 255, 0.4);
border-bottom: 1px solid rgba(74, 4, 4, 0.2);
font-size: 0.9rem;
strong {
color: #4a0404;
font-size: 1rem;
}
.dice-formula,
.skill-level,
.carac-detail,
.skill-detail {
color: #555;
font-size: 0.8rem;
}
}
/* Dice Section */
.ygg-dice-section {
padding: 0.5rem 0.75rem;
background: rgba(255, 250, 240, 0.6);
border-bottom: 1px solid rgba(74, 4, 4, 0.2);
.dice-results,
.furor-results {
margin-bottom: 0.4rem;
&:last-child {
margin-bottom: 0;
}
label {
display: block;
font-weight: bold;
color: #4a0404;
margin-bottom: 0.3rem;
font-size: 0.85rem;
i {
margin-right: 0.25rem;
}
}
}
.dice-list {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
}
.die-result {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 32px;
padding: 0 0.4rem;
background: linear-gradient(135deg, #fff 0%, #f5f5f5 100%);
border: 2px solid #8b4513;
border-radius: 5px;
font-weight: bold;
font-size: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.5);
&.high {
background: linear-gradient(135deg, #90ee90 0%, #7ad87a 100%);
border-color: #228b22;
color: #004d00;
}
&.low {
background: linear-gradient(135deg, #ffcccb 0%, #ffb3b3 100%);
border-color: #8b0000;
color: #4d0000;
}
&.furor {
background: linear-gradient(135deg, #ff6b35 0%, #ff4500 100%);
border-color: #8b0000;
color: #fff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
}
}
/* Calculation Breakdown */
.ygg-calculation {
padding: 0.5rem 0.75rem;
background: rgba(255, 255, 255, 0.3);
border-bottom: 1px solid rgba(74, 4, 4, 0.2);
.calc-row {
display: flex;
justify-content: space-between;
padding: 0.2rem 0;
border-bottom: 1px dashed rgba(74, 4, 4, 0.15);
font-size: 0.85rem;
&:last-child {
border-bottom: none;
}
&.furor-row .calc-label i {
color: #ff4500;
}
}
.calc-label {
font-weight: 600;
color: #4a0404;
}
.calc-value {
font-weight: bold;
font-family: "MedievalSharp", serif;
&.negative {
color: #8b0000;
}
&.positive {
color: #228b22;
}
}
}
/* Final Result */
.ygg-result {
padding: 0.75rem;
background: linear-gradient(135deg, rgba(240, 230, 210, 0.8) 0%, rgba(230, 220, 200, 0.8) 100%);
border-top: 3px double #4a0404;
&.critical-success {
background: linear-gradient(135deg, rgba(144, 238, 144, 0.3) 0%, rgba(122, 216, 122, 0.3) 100%);
border-top-color: #228b22;
}
&.critical-failure {
background: linear-gradient(135deg, rgba(255, 99, 71, 0.3) 0%, rgba(220, 20, 60, 0.3) 100%);
border-top-color: #8b0000;
}
&.success {
background: linear-gradient(135deg, rgba(173, 216, 230, 0.2) 0%, rgba(135, 206, 235, 0.2) 100%);
}
.result-total {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.4rem;
padding-bottom: 0.4rem;
border-bottom: 2px solid rgba(74, 4, 4, 0.3);
.result-label {
font-size: 1.1rem;
font-weight: bold;
color: #4a0404;
font-family: "CaslonAntique", serif;
}
.result-value {
font-size: 1.7rem;
font-weight: bold;
font-family: "MedievalSharp", serif;
color: #4a0404;
text-shadow: 2px 2px 4px rgba(218, 165, 32, 0.3);
}
}
.result-vs {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
font-size: 0.85rem;
.vs-label {
color: #666;
}
.vs-value {
font-weight: bold;
color: #4a0404;
}
}
.result-status {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.5);
border-radius: 5px;
border: 2px solid rgba(74, 4, 4, 0.2);
.status-icon {
width: 28px;
height: 28px;
flex-shrink: 0;
font-size: 24px;
}
.status-text {
font-size: 1rem;
font-weight: bold;
font-family: "CaslonAntique", serif;
&.critical {
color: #228b22;
text-shadow: 0 0 8px rgba(34, 139, 34, 0.5);
}
&.success {
color: #4682b4;
}
&.failure {
color: #8b0000;
}
}
}
}
/* Damage Section */
.ygg-damage {
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, rgba(139, 0, 0, 0.1) 0%, rgba(178, 34, 34, 0.1) 100%);
border-top: 2px solid #8b0000;
.damage-header {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.4rem;
i {
color: #8b0000;
font-size: 1.1rem;
}
.damage-label {
font-weight: bold;
font-size: 0.9rem;
color: #4a0404;
}
}
.damage-value {
font-size: 1.5rem;
font-weight: bold;
color: #8b0000;
font-family: "MedievalSharp", serif;
text-shadow: 2px 2px 4px rgba(139, 0, 0, 0.2);
margin-bottom: 0.2rem;
}
.damage-detail {
font-size: 0.75rem;
color: #666;
font-style: italic;
}
.damage-note {
margin-top: 0.4rem;
padding: 0.3rem;
background: rgba(255, 255, 255, 0.6);
border-left: 3px solid #daa520;
font-size: 0.8rem;
color: #4a0404;
}
}
/* Weapon Details */
.ygg-weapon-details {
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, rgba(139, 69, 19, 0.08) 0%, rgba(160, 82, 45, 0.08) 100%);
border-top: 2px solid #8b4513;
.weapon-header {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.5rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid rgba(139, 69, 19, 0.3);
&.collapsible {
cursor: pointer;
user-select: none;
transition: background 0.2s ease;
margin: -0.5rem -0.75rem 0;
padding: 0.5rem 0.75rem 0.4rem;
border-bottom: none;
&:hover {
background: rgba(139, 69, 19, 0.1);
}
.toggle-icon {
margin-left: auto;
transition: transform 0.3s ease;
font-size: 0.8rem;
}
&.expanded .toggle-icon {
transform: rotate(180deg);
}
}
i {
color: #8b4513;
font-size: 1.1rem;
}
span {
font-weight: bold;
font-size: 0.9rem;
color: #4a0404;
font-family: "CaslonAntique", serif;
}
}
.weapon-content {
max-height: 500px;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
opacity: 1;
&.collapsed {
max-height: 0;
opacity: 0;
}
}
.weapon-properties {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.3rem 0.5rem;
}
.weapon-property {
display: flex;
gap: 0.4rem;
font-size: 0.85rem;
.property-label {
font-weight: 600;
color: #4a0404;
white-space: nowrap;
}
.property-value {
color: #555;
}
}
.weapon-description {
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.6);
border-radius: 5px;
border-left: 3px solid #8b4513;
font-size: 0.85rem;
line-height: 1.4;
color: #333;
}
}
/* Magic Details */
.ygg-magic-details {
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, rgba(138, 43, 226, 0.08) 0%, rgba(148, 0, 211, 0.08) 100%);
border-top: 2px solid #8a2be2;
.magic-header {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.5rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid rgba(138, 43, 226, 0.3);
&.collapsible {
cursor: pointer;
user-select: none;
transition: background 0.2s ease;
margin: -0.5rem -0.75rem 0;
padding: 0.5rem 0.75rem 0.4rem;
border-bottom: none;
&:hover {
background: rgba(138, 43, 226, 0.1);
}
.toggle-icon {
margin-left: auto;
transition: transform 0.3s ease;
font-size: 0.8rem;
}
&.expanded .toggle-icon {
transform: rotate(180deg);
}
}
i {
color: #8a2be2;
font-size: 1.1rem;
}
span {
font-weight: bold;
font-size: 0.9rem;
color: #4a0404;
font-family: "CaslonAntique", serif;
}
}
.magic-content {
max-height: 500px;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
opacity: 1;
&.collapsed {
max-height: 0;
opacity: 0;
}
}
.magic-property {
display: flex;
gap: 0.4rem;
margin-bottom: 0.3rem;
font-size: 0.85rem;
.property-label {
font-weight: 600;
color: #4a0404;
min-width: 110px;
}
.property-value {
color: #555;
}
}
.rune-details {
background: rgba(255, 255, 255, 0.4);
padding: 0.4rem;
border-radius: 4px;
margin-top: 0.4rem;
}
.magic-description {
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.6);
border-radius: 5px;
border-left: 3px solid #8a2be2;
font-size: 0.85rem;
line-height: 1.4;
color: #333;
}
}
}

View File

@@ -0,0 +1,2 @@
/* Chat Message Styles - Viking Theme */
@import url("yggdrasill-chat-viking.less");

2684
less/yggdrasill-main.less Normal file

File diff suppressed because it is too large Load Diff

4
less/yggdrasill.less Normal file
View File

@@ -0,0 +1,4 @@
// Main LESS file for Yggdrasill system
// Importing all component styles
@import "yggdrasill-main";

View File

@@ -0,0 +1,27 @@
/**
* Index des applications AppV2 pour Yggdrasill
* Ce fichier centralise tous les exports des applications
*/
// Applications de feuilles d'acteurs
export { default as YggdrasillPersonnageSheet } from './yggdrasill-personnage-sheet.mjs';
export { default as YggdrasillFigurantSheet } from './yggdrasill-figurant-sheet.mjs';
// Applications de feuilles d'items
export { default as YggdrasillCompetenceSheet } from './yggdrasill-competence-sheet.mjs';
export { default as YggdrasillDonSheet } from './yggdrasill-don-sheet.mjs';
export { default as YggdrasillFaiblesseSheet } from './yggdrasill-faiblesse-sheet.mjs';
export { default as YggdrasillBlessureSheet } from './yggdrasill-blessure-sheet.mjs';
export { default as YggdrasillMaladieSheet } from './yggdrasill-maladie-sheet.mjs';
export { default as YggdrasillPoisonSheet } from './yggdrasill-poison-sheet.mjs';
export { default as YggdrasillProuesseSheet } from './yggdrasill-prouesse-sheet.mjs';
export { default as YggdrasillSortsejdrSheet } from './yggdrasill-sortsejdr-sheet.mjs';
export { default as YggdrasillSortgaldrSheet } from './yggdrasill-sortgaldr-sheet.mjs';
export { default as YggdrasillRuneSheet } from './yggdrasill-rune-sheet.mjs';
export { default as YggdrasillArmeccSheet } from './yggdrasill-armecc-sheet.mjs';
export { default as YggdrasillArmedistSheet } from './yggdrasill-armedist-sheet.mjs';
export { default as YggdrasillArmureSheet } from './yggdrasill-armure-sheet.mjs';
export { default as YggdrasillBouclierSheet } from './yggdrasill-bouclier-sheet.mjs';
export { default as YggdrasillEquipementSheet } from './yggdrasill-equipement-sheet.mjs';
export { default as YggdrasillMonnaieSheet } from './yggdrasill-monnaie-sheet.mjs';
export { default as YggdrasillEffetmagiqueSheet } from './yggdrasill-effetmagique-sheet.mjs';

View File

@@ -0,0 +1,449 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { YggdrasillUtility } from "../../yggdrasill-utility.js"
export default class YggdrasillActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
/**
* Different sheet modes.
* @enum {number}
*/
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
this._sheetMode = this.constructor.SHEET_MODES.PLAY
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "sheet", "actor"],
position: {
width: 750,
height: 720,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "principal",
},
],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
actions: {
editImage: YggdrasillActorSheet.#onEditImage,
toggleSheet: YggdrasillActorSheet.#onToggleSheet,
editItem: YggdrasillActorSheet.#onEditItem,
deleteItem: YggdrasillActorSheet.#onDeleteItem,
createItem: YggdrasillActorSheet.#onCreateItem,
equipItem: YggdrasillActorSheet.#onEquipItem,
rollCarac: YggdrasillActorSheet.#onRollCarac,
rollCompetence: YggdrasillActorSheet.#onRollCompetence,
rollArme: YggdrasillActorSheet.#onRollArme,
rollSort: YggdrasillActorSheet.#onRollSort,
rollProuesse: YggdrasillActorSheet.#onRollProuesse,
rollDamage: YggdrasillActorSheet.#onRollDamage,
lockUnlock: YggdrasillActorSheet.#onLockUnlock,
incrementPV: YggdrasillActorSheet.#onIncrementPV,
decrementPV: YggdrasillActorSheet.#onDecrementPV,
updateCompetence: YggdrasillActorSheet.#onUpdateCompetence,
},
}
/**
* Is the sheet currently in 'Play' mode?
* @type {boolean}
*/
get isPlayMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
/**
* Is the sheet currently in 'Edit' mode?
* @type {boolean}
*/
get isEditMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.yggdrasill.config,
editScore: this.isEditMode,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group], nav.sheet-tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
const activeTab = this.tabGroups[group] || "principal"
nav.querySelectorAll('[data-tab]').forEach(link => {
const tab = link.dataset.tab
link.classList.toggle('active', tab === activeTab)
link.addEventListener('click', (event) => {
event.preventDefault()
this.tabGroups[group] = tab
this.render()
})
})
// Show/hide tab content
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
// Add change listener for competence niveau selects
this.element.querySelectorAll('select.competence-niveau').forEach(select => {
select.addEventListener('change', async (event) => {
const itemId = event.target.dataset.itemId
const item = this.document.items.get(itemId)
if (item) {
const newNiveau = parseInt(event.target.value)
await item.update({ "system.niveau": newNiveau })
}
})
})
}
/**
* Creates drag-and-drop handlers for this application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return []
}
/**
* Handle changing a Document's image
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => {
this.document.update({ [attr]: path })
},
})
return fp.browse()
}
/**
* Toggle sheet mode between Edit and Play
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onToggleSheet(event, target) {
this._sheetMode = this.isEditMode
? this.constructor.SHEET_MODES.PLAY
: this.constructor.SHEET_MODES.EDIT
this.render()
}
/**
* Handle item editing
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onEditItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Handle item deletion
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onDeleteItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item) {
await item.delete()
}
}
/**
* Handle item creation
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.itemType
const itemData = {
name: `Nouveau ${itemType}`,
type: itemType,
}
await this.document.createEmbeddedDocuments("Item", [itemData])
}
/**
* Handle item equip toggle
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEquipItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item && item.system.equipe !== undefined) {
await item.update({ "system.equipe": !item.system.equipe })
}
}
/**
* Handle characteristic roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollCarac(event, target) {
const caracCateg = target.dataset.caracCateg
const caracKey = target.dataset.caracKey
this.document.rollCarac(caracCateg, caracKey)
}
/**
* Handle competence roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollCompetence(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollCompetence(itemId)
}
/**
* Handle weapon roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollArme(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollArme(itemId)
}
/**
* Handle lock/unlock toggle
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onLockUnlock(event, target) {
this._sheetMode = this.isEditMode
? this.constructor.SHEET_MODES.PLAY
: this.constructor.SHEET_MODES.EDIT
this.render()
}
/**
* Handle incrementing PV
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onIncrementPV(event, target) {
const currentPV = this.document.system.caracsecondaire.pv.value || 0
const maxPV = this.document.system.caracsecondaire.pv.max || 0
const newPV = Math.min(currentPV + 1, maxPV)
await this.document.update({ "system.caracsecondaire.pv.value": newPV })
}
/**
* Handle decrementing PV
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onDecrementPV(event, target) {
const currentPV = this.document.system.caracsecondaire.pv.value || 0
const newPV = Math.max(currentPV - 1, 0)
await this.document.update({ "system.caracsecondaire.pv.value": newPV })
}
/**
* Handle competence niveau update
* @this {YggdrasillActorSheet}
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The select element
*/
static async #onUpdateCompetence(event, target) {
const itemId = target.dataset.itemId
const item = this.document.items.get(itemId)
if (!item) return
const newNiveau = parseInt(target.value)
await item.update({ "system.niveau": newNiveau })
}
/**
* Handle sort roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollSort(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const sortType = target.dataset.sortType || "sejdr"
this.document.rollSort(itemId, sortType)
}
/**
* Handle prouesse roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollProuesse(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollProuesse(itemId)
}
/**
* Handle damage roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollDamage(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const weapon = this.document.items.get(itemId)
if (weapon) {
this.document.rollDamage(weapon, 'damage')
}
}
/**
* Handle beginning of a drag operation
* @param {DragEvent} event - The originating drag event
* @protected
*/
_onDragStart(event) {
const li = event.currentTarget
const itemId = li.dataset.itemId
const item = this.document.items.get(itemId)
if (!item) return
const dragData = item.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Handle a drop event
* @param {DragEvent} event - The originating drop event
* @protected
*/
async _onDrop(event) {
const data = TextEditor.getDragEventData(event)
const actor = this.document
// Handle different data types
switch (data.type) {
case "Item":
return this._onDropItem(event, data)
case "ActiveEffect":
return this._onDropActiveEffect(event, data)
}
}
/**
* Handle dropping an Item on the sheet
* @param {DragEvent} event - The originating drop event
* @param {object} data - The dropped data
* @protected
*/
async _onDropItem(event, data) {
if (!this.isEditable) return false
const item = await Item.implementation.fromDropData(data)
const itemData = item.toObject()
// Handle item from same actor
if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData)
// Create the item
return this._onDropItemCreate(itemData)
}
/**
* Handle creating an owned item from drop data
* @param {object} itemData - The item data to create
* @protected
*/
async _onDropItemCreate(itemData) {
itemData = itemData instanceof Array ? itemData : [itemData]
return this.document.createEmbeddedDocuments("Item", itemData)
}
/**
* Handle sorting items
* @param {DragEvent} event - The originating drop event
* @param {object} itemData - The item data being sorted
* @protected
*/
_onSortItem(event, itemData) {
// Implement sorting logic if needed
return Promise.resolve()
}
/**
* Handle dropping an ActiveEffect on the sheet
* @param {DragEvent} event - The originating drop event
* @param {object} data - The dropped data
* @protected
*/
async _onDropActiveEffect(event, data) {
const effect = await ActiveEffect.implementation.fromDropData(data)
if (!this.isEditable || !effect) return false
if (this.document.uuid === effect.parent?.uuid) return false
return ActiveEffect.create(effect.toObject(), { parent: this.document })
}
}

View File

@@ -0,0 +1,178 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class YggdrasillItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "item"],
position: {
width: 620,
height: 600,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "description",
},
],
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
editImage: YggdrasillItemSheet.#onEditImage,
postItem: YggdrasillItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
// Import config
const YGGDRASILL_CONFIG = game.system.yggdrasill?.config || game.system.config || {};
// Create options for niveau 0-5
const optionsNiveaux4 = {};
for (let i = 0; i <= 5; i++) {
optionsNiveaux4[`${i}`] = `${i}`;
}
// Create options for base (0-20)
const optionsBase = {};
for (let i = 0; i <= 20; i++) {
optionsBase[`${i}`] = `${i}`;
}
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
data: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description || "", { async: true }),
enrichedEffet: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effet || "", { async: true }),
isEditMode: true,
isEditable: this.isEditable,
editable: this.isEditable,
isGM: game.user.isGM,
config: YGGDRASILL_CONFIG,
optionsBase: optionsBase,
optionsNiveaux4: optionsNiveaux4,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
const activeTab = this.tabGroups[group] || "description"
nav.querySelectorAll('[data-tab]').forEach(link => {
const tab = link.dataset.tab
link.classList.toggle('active', tab === activeTab)
link.addEventListener('click', (event) => {
event.preventDefault()
this.tabGroups[group] = tab
this.render()
})
})
// Show/hide tab content
const body = this.element.querySelector('section.sheet-body')
if (body) {
body.querySelectorAll('[data-tab]').forEach(content => {
const tab = content.dataset.tab
content.style.display = tab === activeTab ? 'block' : 'none'
})
}
}
}
/**
* Creates drag-and-drop handlers for this application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return []
}
/**
* Handle changing a Document's image
* @this {YggdrasillItemSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
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 })
},
})
return fp.browse()
}
/**
* Handle posting item to chat
* @this {YggdrasillItemSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onPostItem(event, target) {
const item = this.document
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker(),
content: await foundry.applications.handlebars.renderTemplate("systems/fvtt-yggdrasill/templates/chat-item-card.hbs", {
item: item,
system: item.system,
description: await TextEditor.enrichHTML(item.system.description || "", { async: true })
})
}
ChatMessage.create(chatData)
}
/**
* Handle beginning of a drag operation
* @param {DragEvent} event - The originating drag event
* @protected
*/
_onDragStart(event) {
const dragData = this.document.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Handle a drop event
* @param {DragEvent} event - The originating drop event
* @protected
*/
async _onDrop(event) {
// Items generally don't handle drops, but method exists for extensibility
return false
}
}

View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Liste des types d'items restants à créer
items=(
"poison:Poison"
"prouesse:Prouesse"
"sortsejdr:Sortsejdr"
"sortgaldr:Sortgaldr"
"rune:Rune"
"armecc:Armecc"
"armedist:Armedist"
"armure:Armure"
"bouclier:Bouclier"
"equipement:Equipement"
"monnaie:Monnaie"
"effetmagique:Effetmagique"
)
for item in "${items[@]}"; do
type="${item%%:*}"
class="${item##*:}"
cat > "yggdrasill-${type}-sheet.mjs" << EOF
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class Yggdrasill${class}Sheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "${type}"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-${type}-sheet.hbs",
},
}
}
EOF
done
echo "Created all item sheets"
ls -1 yggdrasill-*-sheet.mjs | wc -l

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmeccSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armecc"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armecc-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmedistSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armedist"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armedist-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmureSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armure"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armure-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillBlessureSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "blessure"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-blessure-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillBouclierSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "bouclier"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-bouclier-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,21 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillCompetenceSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "competence"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-competence-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillDonSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "don"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-don-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillEffetmagiqueSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "effetmagique"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-effetmagique-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillEquipementSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "equipement"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-equipement-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillFaiblesseSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "faiblesse"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-faiblesse-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,40 @@
import YggdrasillActorSheet from "./base-actor-sheet.mjs"
export default class YggdrasillFigurantSheet extends YggdrasillActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "figurant"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Figurant",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/actor-figurant-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// System fields for formInput helpers
context.systemFields = actor.system.schema.fields
// Enrich HTML fields
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.notes || "", { async: true })
return context
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillMaladieSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "maladie"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-maladie-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillMonnaieSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "monnaie"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-monnaie-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,99 @@
import YggdrasillActorSheet from "./base-actor-sheet.mjs"
export default class YggdrasillPersonnageSheet extends YggdrasillActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "personnage"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Personnage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/actor-personnage-sheet-new.hbs",
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Prepare items by type
context.competencesGenerales = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'generale')
context.competencesMartiales = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'martiale')
context.competencesMagiques = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'magique')
context.dons = actor.items.filter(i => i.type === 'don')
context.faiblesses = actor.items.filter(i => i.type === 'faiblesse')
context.blessures = actor.items.filter(i => i.type === 'blessure')
context.maladies = actor.items.filter(i => i.type === 'maladie')
context.poisons = actor.items.filter(i => i.type === 'poison')
context.prouesses = actor.items.filter(i => i.type === 'prouesse')
context.sortsSejdr = actor.items.filter(i => i.type === 'sortsejdr')
context.sortsGaldr = actor.items.filter(i => i.type === 'sortgaldr')
context.runes = actor.items.filter(i => i.type === 'rune')
context.armesCC = actor.items.filter(i => i.type === 'armecc')
context.armesDist = actor.items.filter(i => i.type === 'armedist')
context.armures = actor.items.filter(i => i.type === 'armure')
context.boucliers = actor.items.filter(i => i.type === 'bouclier')
context.equipements = actor.items.filter(i => i.type === 'equipement')
context.monnaies = actor.items.filter(i => i.type === 'monnaie')
context.effetsMagiques = actor.items.filter(i => i.type === 'effetmagique')
// Prepare equipped items
context.armesEquipees = actor.items.filter(i => (i.type === 'armecc' || i.type === 'armedist') && i.system.equipe)
context.armureEquipee = actor.items.find(i => i.type === 'armure' && i.system.equipe)
context.bouclierEquipe = actor.items.find(i => i.type === 'bouclier' && i.system.equipe)
// Calculate total protection from equipped armors
context.protectionTotal = context.armures
.filter(a => a.system.equipe)
.reduce((sum, a) => sum + (parseInt(a.system.protection) || 0), 0)
// Calculate shield defense bonus from equipped shield
context.dpBouclier = context.boucliers
.filter(b => b.system.equipe)
.reduce((sum, b) => sum + (parseInt(b.system.defensebonus) || 0), 0)
// Calculate total encumbrance from equipements
context.encTotal = context.equipements
.reduce((sum, e) => sum + ((parseInt(e.system.quantite) || 0) * (parseInt(e.system.enc) || 0)), 0)
// Options for selects
context.optionsCarac = {}
for (let i = 0; i <= 10; i++) {
context.optionsCarac[i] = i.toString()
}
context.optionsBase = {}
for (let i = 0; i <= 5; i++) {
context.optionsBase[i] = i.toString()
}
// Options for bonus/malus (-10 to +10)
context.optionsDMDP = []
for (let i = -10; i <= 10; i++) {
context.optionsDMDP.push({ value: i, text: i >= 0 ? `+${i}` : i.toString() })
}
// System fields for formInput helpers
context.systemFields = actor.system.schema.fields
// Enrich HTML fields
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.notes || "", { async: true })
context.enrichedGMNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.gmnotes || "", { async: true })
context.enrichedTirageRunes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.tiragerunes || "", { async: true })
return context
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillPoisonSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "poison"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-poison-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillProuesseSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "prouesse"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-prouesse-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillRuneSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "rune"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-rune-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillSortgaldrSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "sortgaldr"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-sortgaldr-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillSortsejdrSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "sortsejdr"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-sortsejdr-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,334 @@
import { YggdrasillUtility } from "../yggdrasill-utility.js"
const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15}
const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15}
const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15}
/**
* Dialogue de jet de dé pour Yggdrasill - Version DialogV2
*/
export class YggdrasillRollDialog {
/**
* Create and display the roll dialog
* @param {YggdrasillActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise}
*/
static async create(actor, rollData) {
// Initialiser attackData pour les armes si nécessaire
if ((rollData.mode === "armecc" || rollData.mode === "armetir" || rollData.mode === "armedist") && rollData.attackDef) {
// Les données sont déjà calculées dans attackDef par getAttaqueData/getTirData
// On les copie simplement dans attackData pour le template
rollData.attackData = { ...rollData.attackDef }
}
// Calculer srTotal pour les sorts
if (rollData.mode === "galdr") {
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
} else if (rollData.mode === "sejdr") {
rollData.srTotal = 14 + (rollData.bonusdefense || 0)
} else if (rollData.mode === "rune") {
this._updateRuneData(rollData, actor)
}
// Préparer le contexte pour le template
const context = {
...rollData,
img: actor.img,
name: actor.name,
config: game.system.yggdrasill.config,
}
// Titre selon le mode
let title = "Test"
if (rollData.mode === "competence") title = "Test de Compétence"
else if (rollData.mode === "carac") title = "Test de Caractéristique"
else if (rollData.mode === "attribut") title = "Test d'Attribut"
else if (rollData.mode === "armecc") title = "Attaque au Corps à Corps"
else if (rollData.mode === "armetir" || rollData.mode === "armedist") title = "Attaque à Distance"
else if (rollData.mode === "sejdr") title = "Sort Sejdr"
else if (rollData.mode === "galdr") title = "Sort Galdr"
else if (rollData.mode === "rune") title = "Rune"
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-yggdrasill/templates/roll-dialog-generic-new.hbs",
context
)
// Utiliser DialogV2.wait avec le HTML rendu
return foundry.applications.api.DialogV2.wait({
window: {
title: title,
icon: "fa-solid fa-dice-d20"
},
classes: ["yggdrasill-roll-dialog"],
position: { width: 600 },
modal: false,
content,
buttons: [
{
action: "roll",
label: "Lancer le Test",
icon: "fa-solid fa-dice",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
this._performRoll(rollData)
}
},
],
actions: {},
render: (event, dialog) => {
// Pour Galdr: recalculer srTotal quand durée, cibles ou zone changent
if (rollData.mode === "galdr") {
$("#dureeGaldr, #nbCibles, #zoneGaldr").on("change", () => {
rollData.dureeGaldr = $("#dureeGaldr").val()
rollData.nbCibles = $("#nbCibles").val()
rollData.zoneGaldr = $("#zoneGaldr").val()
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
$("#srTotal").text(rollData.srTotal)
})
}
// Pour Sejdr: recalculer srTotal quand DM change
if (rollData.mode === "sejdr") {
$("#bonusdefense").on("change", () => {
rollData.bonusdefense = parseInt($("#bonusdefense").val()) || 0
rollData.srTotal = 14 + rollData.bonusdefense
$("#srTotal").text(rollData.srTotal)
})
}
// Pour Rune: recalculer srTotal quand support ou puissance changent
if (rollData.mode === "rune") {
$("#supportRune, #puissanceRune").on("change", () => {
rollData.supportRune = $("#supportRune").val()
rollData.puissanceRune = parseInt($("#puissanceRune").val()) || 1
this._updateRuneData(rollData, actor)
$("#runeDuree").text(rollData.runeDuree)
$("#runeDureeVie").text(rollData.runeDureeVie)
$("#srTotal").text(rollData.srTotal)
})
}
},
rejectClose: false,
})
}
/**
* Perform the roll based on mode
* @param {Object} rollData - The roll data
* @private
*/
static _performRoll(rollData) {
if (rollData.mode === "attribut") {
YggdrasillUtility.rollAttribute(rollData)
} else {
YggdrasillUtility.rollYggdrasill(rollData)
}
}
/**
* Mettre à jour rollData avec les valeurs du formulaire
* @param {Object} rollData - L'objet rollData à mettre à jour
* @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire
* @param {YggdrasillActor} actor - L'acteur pour récupérer les données
* @private
*/
static _updateRollDataFromForm(rollData, formElements, actor) {
// Caractéristique (pour compétences)
if (formElements.caracName) {
const caracKey = formElements.caracName.value
rollData.caracName = caracKey
rollData.selectedCarac = this._findCaracByKey(actor, caracKey)
}
// Type d'attaque (pour armes)
if (formElements.typeAttack) {
rollData.attackDef = rollData.attackDef || {}
rollData.attackDef.typeAttack = formElements.typeAttack.value
this._updateAttackData(rollData, actor)
}
// Bonus/Malus
if (formElements.bonusMalus) {
rollData.bonusMalus = parseInt(formElements.bonusMalus.value) || 0
}
// Furor
if (formElements.furorUsage) {
rollData.furorUsage = parseInt(formElements.furorUsage.value) || 0
}
// Seuil de Réussite
if (formElements.sr) {
rollData.sr = parseInt(formElements.sr.value) || 9
}
// Galdr options
if (formElements.dureeGaldr) {
rollData.dureeGaldr = formElements.dureeGaldr.value
}
if (formElements.nbCibles) {
rollData.nbCibles = formElements.nbCibles.value
}
if (formElements.zoneGaldr) {
rollData.zoneGaldr = formElements.zoneGaldr.value
}
if (rollData.mode === "galdr") {
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
}
// Sejdr options
if (formElements.bonusdefense) {
rollData.bonusdefense = parseInt(formElements.bonusdefense.value) || 0
}
if (rollData.mode === "sejdr") {
rollData.srTotal = 14 + (rollData.bonusdefense || 0)
}
// Rune options
if (formElements.runeDuree) {
rollData.runeDuree = formElements.runeDuree.value
}
}
/**
* Find characteristic by key in actor
* @param {YggdrasillActor} actor
* @param {String} caracKey
* @returns {Object}
* @private
*/
static _findCaracByKey(actor, caracKey) {
for (let [categKey, categ] of Object.entries(actor.system.carac)) {
for (let [key, carac] of Object.entries(categ.carac)) {
if (key === caracKey) {
return { ...carac, categName: categKey, caracName: key }
}
}
}
return null
}
/**
* Update attack data based on attack type
* @param {Object} rollData
* @param {YggdrasillActor} actor
* @private
*/
static _updateAttackData(rollData, actor) {
const config = game.system.yggdrasill.config
const attackType = rollData.attackDef.typeAttack
const attackMode = config.attackMode?.[attackType]
if (attackMode) {
rollData.attackData = rollData.attackData || {}
rollData.attackData.categName = attackMode.categName
rollData.attackData.caracName = attackMode.caracName
rollData.attackData.malus = this._computeValue(attackMode.malus, actor)
rollData.attackData.bonusdegats = this._computeValue(attackMode.bonusdegats, actor)
rollData.attackData.protection = this._computeValue(attackMode.protection, actor)
rollData.attackData.label = attackMode.label
rollData.attackData.description = attackMode.description
}
}
/**
* Compute value from string formula like "puissance;3"
* @param {String|Number} value
* @param {YggdrasillActor} actor
* @returns {Number}
* @private
*/
static _computeValue(value, actor) {
if (typeof value === 'number') return value
if (!value || value === "0") return 0
const parts = String(value).split(';')
if (parts.length === 2) {
const caracName = parts[0]
const multiplier = parseInt(parts[1]) || 1
const caracValue = this._getCaracValue(actor, caracName)
return caracValue * multiplier
}
return 0
}
/**
* Get characteristic value by name
* @param {YggdrasillActor} actor
* @param {String} caracName
* @returns {Number}
* @private
*/
static _getCaracValue(actor, caracName) {
for (let categ of Object.values(actor.system.carac)) {
if (categ.carac && categ.carac[caracName]) {
return categ.carac[caracName].value || 0
}
}
return 0
}
/**
* Update rune data based on support and power
* @param {Object} rollData
* @param {YggdrasillActor} actor
* @private
*/
static _updateRuneData(rollData, actor) {
let support = 0
rollData.dureeRune = 6 - (rollData.agiliteCarac?.value || 0)
if (rollData.supportRune === "peau") {
support = 3
rollData.echelleDuree = "Actions"
rollData.echelleDureeVie = "Heures"
} else if (rollData.supportRune === "tissu") {
support = 6
rollData.echelleDuree = "Tours"
rollData.echelleDureeVie = "Jours"
} else if (rollData.supportRune === "cuir") {
support = 9
rollData.echelleDuree = "Minutes"
rollData.echelleDureeVie = "Semaines"
} else if (rollData.supportRune === "bois") {
support = 12
rollData.echelleDuree = "Heures"
rollData.echelleDureeVie = "Mois"
} else if (rollData.supportRune === "pierremetal") {
support = 15
rollData.echelleDuree = "Jours"
rollData.echelleDureeVie = "Années"
}
rollData.runeDuree = `${rollData.dureeRune} ${rollData.echelleDuree}`
rollData.runeDureeVie = `${rollData.competence?.system.niveau || 1} ${rollData.echelleDureeVie}`
rollData.srTotal = (rollData.puissanceRune || 1) + (Number(rollData.sort?.system.niveau || 0) * 3) + support
}
/**
* Update Galdr SD based on duration and targets
* @param {Object} rollData
* @private
*/
static _updateGaldrSD(rollData) {
let sdDuree = Number(dureeGaldrSD[rollData.dureeGaldr]) || 0
let sdVar = 0
if (rollData.sort?.system.voie === "illusion") {
sdVar = Number(zonesciblesGaldrSD[rollData.zoneGaldr]) || 0
} else {
sdVar = Number(ciblesGaldrSD[rollData.nbCibles]) || 0
}
rollData.sdGaldr = sdDuree + sdVar
}
}

17
modules/models/armecc.mjs Normal file
View File

@@ -0,0 +1,17 @@
/**
* Data model pour les armes de corps à corps
*/
export default class ArmeccDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
degat: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les armes à distance
*/
export default class ArmedistDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
degat: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
portee: new fields.StringField({ initial: "" }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

16
modules/models/armure.mjs Normal file
View File

@@ -0,0 +1,16 @@
/**
* Data model pour les armures
*/
export default class ArmureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
protection: new fields.StringField({ initial: "" }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les blessures
*/
export default class BlessureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les boucliers
*/
export default class BouclierDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
defensebonus: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
enccomb: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les compétences
*/
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
isspecialisation: new fields.BooleanField({ initial: false }),
categorie: new fields.StringField({ initial: "" }),
specialisation: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
niveauunrequis: new fields.BooleanField({ initial: false })
};
}
}

11
modules/models/don.mjs Normal file
View File

@@ -0,0 +1,11 @@
/**
* Data model pour les dons
*/
export default class DonDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,12 @@
/**
* Data model pour les effets magiques
*/
export default class EffetmagiqueDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
origine: new fields.StringField({ initial: "" }),
effet: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,15 @@
/**
* Data model pour l'équipement
*/
export default class EquipementDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
equipe: new fields.BooleanField({ initial: false }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les faiblesses
*/
export default class FaiblesseDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,87 @@
/**
* Data model pour les figurants
*/
export default class FigurantDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
biodata: new fields.SchemaField({
age: new fields.NumberField({ initial: 0, integer: true }),
taille: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
poids: new fields.NumberField({ initial: 0, integer: true }),
notes: new fields.HTMLField({ initial: "" })
}),
attributs: new fields.SchemaField({
conflit: new fields.SchemaField({
values: new fields.SchemaField({
offensif: new fields.SchemaField({
label: new fields.StringField({ initial: "Offensif" }),
value: new fields.NumberField({ initial: 0, integer: true }),
degats: new fields.BooleanField({ initial: true })
}),
defensif: new fields.SchemaField({
label: new fields.StringField({ initial: "Défensif" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Conflit" })
}),
relationnel: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Relationnel" })
}),
physique: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Physique" })
}),
mental: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Mental" })
}),
mystique: new fields.SchemaField({
values: new fields.SchemaField({
actif: new fields.SchemaField({
label: new fields.StringField({ initial: "Actif" }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
passif: new fields.SchemaField({
label: new fields.StringField({ initial: "Passif" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Mystique" })
}),
vitalite: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Vitalité" })
})
}),
etat: new fields.SchemaField({
etat: new fields.StringField({ initial: "fringant" })
})
};
}
}

27
modules/models/index.mjs Normal file
View File

@@ -0,0 +1,27 @@
/**
* Index des DataModels pour Yggdrasill
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as FigurantDataModel } from './figurant.mjs';
// Modèles d'items
export { default as CompetenceDataModel } from './competence.mjs';
export { default as DonDataModel } from './don.mjs';
export { default as FaiblesseDataModel } from './faiblesse.mjs';
export { default as BlessureDataModel } from './blessure.mjs';
export { default as MaladieDataModel } from './maladie.mjs';
export { default as PoisonDataModel } from './poison.mjs';
export { default as ProuesseDataModel } from './prouesse.mjs';
export { default as SortsejdrDataModel } from './sortsejdr.mjs';
export { default as SortgaldrDataModel } from './sortgaldr.mjs';
export { default as RuneDataModel } from './rune.mjs';
export { default as ArmeccDataModel } from './armecc.mjs';
export { default as ArmedistDataModel } from './armedist.mjs';
export { default as ArmureDataModel } from './armure.mjs';
export { default as BouclierDataModel } from './bouclier.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as MonnaieDataModel } from './monnaie.mjs';
export { default as EffetmagiqueDataModel } from './effetmagique.mjs';

View File

@@ -0,0 +1,13 @@
/**
* Data model pour les maladies
*/
export default class MaladieDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
periode: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* Data model pour la monnaie
*/
export default class MonnaieDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
quantite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,158 @@
/**
* Data model pour les personnages
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
biodata: new fields.SchemaField({
nom: new fields.StringField({ initial: "" }),
archetype: new fields.StringField({ initial: "" }),
profession: new fields.StringField({ initial: "" }),
royaume: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 0, integer: true }),
taille: new fields.NumberField({ initial: 0, integer: true }),
poids: new fields.NumberField({ initial: 0, integer: true }),
don: new fields.StringField({ initial: "" }),
faiblesse: new fields.StringField({ initial: "" }),
pointlegende: new fields.NumberField({ initial: 0, integer: true }),
renomee: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
tiragerunes: new fields.StringField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
carac: new fields.SchemaField({
corps: new fields.SchemaField({
label: new fields.StringField({ initial: "Corps" }),
carac: new fields.SchemaField({
puissance: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Puissance" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "pui" })
}),
vigueur: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Vigueur" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "vig" })
}),
agilite: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Agilité" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "agi" })
})
})
}),
esprit: new fields.SchemaField({
label: new fields.StringField({ initial: "Esprit" }),
carac: new fields.SchemaField({
intellect: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Intellect" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "int" })
}),
perception: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Perception" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "per" })
}),
tenacite: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Tenacité" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "ten" })
})
})
}),
ame: new fields.SchemaField({
label: new fields.StringField({ initial: "Ame" }),
carac: new fields.SchemaField({
charisme: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Charisme" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "cha" })
}),
communication: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Communication" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "com" })
}),
instinct: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Instinct" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "ins" })
})
})
})
}),
furor: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "Furor" })
}),
xp: new fields.SchemaField({
total: new fields.NumberField({ initial: 1, integer: true }),
current: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "XP" })
}),
renommee: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "Renommée" })
}),
status: new fields.SchemaField({
epuise: new fields.BooleanField({ initial: false }),
blesse: new fields.BooleanField({ initial: false }),
meurtri: new fields.BooleanField({ initial: false })
}),
caracsecondaire: new fields.SchemaField({
reaction: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Réaction" }),
abbrev: new fields.StringField({ initial: "rea" })
}),
defensephy: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonusmalus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Défense Physique" }),
abbrev: new fields.StringField({ initial: "dp" })
}),
defensemen: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonusmalus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Défense Mentale" }),
abbrev: new fields.StringField({ initial: "dm" })
}),
deplacement: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Déplacement" }),
abbrev: new fields.StringField({ initial: "dep" })
}),
capaenc: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Capacité d'Encombrement" }),
abbrev: new fields.StringField({ initial: "cpe" })
}),
pv: new fields.SchemaField({
value: new fields.NumberField({ initial: 10, integer: true }),
max: new fields.NumberField({ initial: 10, integer: true }),
min: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Points de Vie" }),
abbrev: new fields.StringField({ initial: "pv" })
})
})
};
}
}

13
modules/models/poison.mjs Normal file
View File

@@ -0,0 +1,13 @@
/**
* Data model pour les poisons
*/
export default class PoisonDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
type: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les prouesses
*/
export default class ProuesseDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
armes: new fields.StringField({ initial: "" }),
prerequis: new fields.StringField({ initial: "" }),
modificateur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

12
modules/models/rune.mjs Normal file
View File

@@ -0,0 +1,12 @@
/**
* Data model pour les runes
*/
export default class RuneDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
niveau: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* Data model pour les sorts Galdr
*/
export default class SortgaldrDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
voie: new fields.StringField({ initial: "" }),
sd: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,17 @@
/**
* Data model pour les sorts Sejdr
*/
export default class SortsejdrDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
forme: new fields.StringField({ initial: "" }),
preparation: new fields.StringField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
malus: new fields.NumberField({ initial: 0, integer: true }),
duree: new fields.StringField({ initial: "" }),
zone: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -6,16 +6,16 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
/* -------------------------------------------- */
export class YggdrasillActorSheet extends ActorSheet {
export class YggdrasillActorSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["yggdrasill", "sheet", "actor"],
template: "systems/fvtt-yggdrasill/templates/actor-sheet.html",
width: 640,
height: 720,
template: "systems/fvtt-yggdrasill/templates/actor-personnage-sheet.hbs",
width: 680,
height: 740,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
@@ -23,9 +23,9 @@ export class YggdrasillActorSheet extends ActorSheet {
}
/* -------------------------------------------- */
getData() {
const objectData = YggdrasillUtility.data(this.object);
async getData() {
const objectData = foundry.utils.duplicate(this.object)
let formData = {
title: this.title,
id: objectData.id,
@@ -34,7 +34,7 @@ export class YggdrasillActorSheet extends ActorSheet {
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(YggdrasillUtility.templateData(this.object)),
data: foundry.utils.deepClone(objectData.system),
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
isEpuise: this.actor.isEpuise(),
@@ -48,23 +48,31 @@ export class YggdrasillActorSheet extends ActorSheet {
blessures: this.actor.getBlessures(),
armes: this.actor.getArmes(),
armures: this.actor.getArmures(),
boucliers: this.actor.getBoucliers(),
prouessesMartiales: this.actor.getProuessesMartiales(),
equipements: this.actor.getToutEquipements(),
effetsmagiques: this.actor.getEffetsMagiques(),
effetsRunes: this.actor.getEffetsDeRunes(),
encTotal: this.actor.getEncTotal(),
protectionTotal: this.actor.getProtectionTotal(),
dpBouclier: this.actor.getDpBouclier(),
monnaies: this.actor.getMonnaies(),
sortsSejdr:this.actor.getSortsSejdr(),
sortsGaldr:this.actor.getSortsGaldr(),
runes: this.actor.getRunes(),
optionsDMDP: YggdrasillUtility.createDirectSortedOptionList(-10, +10),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsFuror: YggdrasillUtility.createDirectOptionList(0, 15),
optionsCarac: YggdrasillUtility.createDirectOptionList(0, 20),
optionsDMDP: YggdrasillUtility.createDirectSortedOptionList(-10, +10),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsFuror: YggdrasillUtility.createDirectOptionList(0, 15),
tiragerunes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.tiragesrunes, {async: true}),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, {async: true}),
notes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.notes, {async: true}),
gmnotes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.gmnotes, {async: true}),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM
isGM: game.user.isGM,
config: game.system.config
}
// Dynamic update some fields
this.updateDM(formData.data);
@@ -80,10 +88,10 @@ export class YggdrasillActorSheet extends ActorSheet {
dm.total = dm.max + Number(dm.bonusmalus);
}
/* -------------------------------------------- */
updateDP( data ) {
updateDP( data ) {
let dp = data.caracsecondaire.defensephy;
dp.total = dp.max + Number(dp.bonusmalus);
dp.total += (dp.bouclierequipe) ? 3 : 0;
dp.total += this.actor.getDpBouclier();
}
/* -------------------------------------------- */
@@ -97,7 +105,7 @@ export class YggdrasillActorSheet extends ActorSheet {
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
const item = this.actor.items.get(li.data("item-id"));
item.sheet.render(true);
});
// Delete Inventory Item
@@ -105,19 +113,19 @@ export class YggdrasillActorSheet extends ActorSheet {
const li = $(ev.currentTarget).parents(".item");
YggdrasillUtility.confirmDelete(this, li);
});
html.find('#isEpuise').click(event => {
this.actor.toggleEpuise( );
} );
html.find('.munition-moins').click(event => {
const li = $(event.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
const item = this.actor.items.get(li.data("item-id"));
this.actor.decrementeMunition( item );
} );
html.find('.munition-plus').click(event => {
const li = $(event.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
const item = this.actor.items.get(li.data("item-id"));
this.actor.incrementeMunition( item );
} );
html.find('.equipement-moins').click(event => {
@@ -151,32 +159,32 @@ export class YggdrasillActorSheet extends ActorSheet {
const li = $(event.currentTarget).parents(".item");
const sortId = li.data("item-id");
this.actor.rollSort(sortId, "sejdr");
});
});
html.find('.sort-galdr').click((event) => {
const li = $(event.currentTarget).parents(".item");
const sortId = li.data("item-id");
this.actor.rollSort(sortId, "galdr");
});
});
html.find('.sort-rune').click((event) => {
const li = $(event.currentTarget).parents(".item");
const sortId = li.data("item-id");
this.actor.rollSort(sortId, "rune");
});
});
html.find('.arme-label a').click((event) => {
const li = $(event.currentTarget).parents(".item");
const armeId = li.data("arme-id");
this.actor.rollArme(armeId);
});
});
html.find('.carac-roll').click((event) => {
const li = $(event.currentTarget).parents(".item");
let categ = li.data("carac-categ");
let carac = li.data("carac-key");
this.actor.rollCarac(categ, carac);
});
});
html.find('.weapon-damage').click((event) => {
const li = $(event.currentTarget).parents(".item");
const weapon = this.actor.getOwnedItem(li.data("item-id"));
const weapon = this.actor.items.get(li.data("item-id"));
this.actor.rollDamage(weapon, 'damage');
});
html.find('.competence-base').change((event) => {
@@ -187,12 +195,12 @@ export class YggdrasillActorSheet extends ActorSheet {
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
});
html.find('.item-link a').click((event) => {
const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.getOwnedItem(itemId);
const item = this.actor.items.get(itemId);
item.sheet.render(true);
});
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equiperObject( li.data("item-id") );

View File

@@ -1,6 +1,6 @@
/* -------------------------------------------- */
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillRoll } from "./yggdrasill-roll-dialog.js";
import { YggdrasillRollDialog } from "./applications/yggdrasill-roll-dialog.mjs";
/* -------------------------------------------- */
const statusEffects = [
@@ -175,15 +175,15 @@ export class YggdrasillActor extends Actor {
async prepareData() {
if ( this.type == "personnage") {
this.computeCaracSecondaire();
if (this.data.data.furor.value == 0)
if (this.system.furor.value == 0)
await this.setEpuise();
else
await this.cleanEpuise();
if ( this.data.data.caracsecondaire.pv.value < (this.data.data.caracsecondaire.pv.max/4) )
if ( this.system.caracsecondaire.pv.value < (this.system.caracsecondaire.pv.max/4) )
await this.setMeurtri();
else
await this.cleanMeurtri();
if ( this.data.data.caracsecondaire.pv.value < (this.data.data.caracsecondaire.pv.max/2) )
if ( this.system.caracsecondaire.pv.value < (this.system.caracsecondaire.pv.max/2) )
await this.setBlesse();
else
await this.cleanBlesse();
@@ -193,23 +193,24 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
_preUpdate(changed, options, user) {
if ( changed.data?.caracsecondaire?.pv?.value ) {
if ( changed.data.caracsecondaire.pv.value < 0 )
changed.data.caracsecondaire.pv.value = 0;
if ( changed.data.caracsecondaire.pv.value > this.data.data.caracsecondaire.pv.max )
changed.data.caracsecondaire.pv.value = this.data.data.caracsecondaire.pv.max;
if ( changed.system?.caracsecondaire?.pv?.value ) {
if ( changed.system.caracsecondaire.pv.value < 0 )
changed.system.caracsecondaire.pv.value = 0;
if ( changed.system.caracsecondaire.pv.value > this.system.caracsecondaire.pv.max )
changed.system.caracsecondaire.pv.value = this.system.caracsecondaire.pv.max;
}
if ( changed.data?.furor?.value ) {
if ( changed.data.furor.value < 0 )
changed.data.furor.value = 0;
if ( changed.data.furor.value > this.data.data.furor.max )
changed.data.furor.value = this.data.data.furor.max;
if ( changed.system?.furor?.value ) {
if ( changed.system.furor.value < 0 )
changed.system.furor.value = 0;
if ( changed.system.furor.value > this.system.furor.max )
changed.system.furor.value = this.system.furor.max;
}
super._preUpdate(changed, options, user);
}
/* -------------------------------------------- */
getCompetences() {
let comp = this.data.items.filter( item => item.type == 'competence');
let comp = this.items.filter( item => item.type == 'competence');
return comp;
}
/* -------------------------------------------- */
@@ -224,87 +225,97 @@ export class YggdrasillActor extends Actor {
}
/* -------------------------------------------- */
getInitiativeScore() {
return this.data.data.caracsecondaire.reaction.max;
if ( this.type == 'personnage') {
return this.system.caracsecondaire.reaction.max;
} else {
return this.system.attributs.physique.values.defaut.value;
}
}
/* -------------------------------------------- */
getCompetencesGenerales() {
let comp = this.data.items.filter( item => item.type == 'competence' && item.data.data.categorie == 'generale');
let comp = this.items.filter( item => item.type == 'competence' && item.system.categorie == 'generale');
return comp.sort( this.compareName );
}
/* -------------------------------------------- */
getCompetencesMartiales() {
let comp = this.data.items.filter( item => item.type == 'competence' && item.data.data.categorie == 'martiale');
let comp = this.items.filter( item => item.type == 'competence' && item.system.categorie == 'martiale');
return comp.sort( this.compareName );
}
/* -------------------------------------------- */
getCompetencesMagiques() {
let comp = this.data.items.filter( item => item.type == 'competence' && item.data.data.categorie == 'magique');
let comp = this.items.filter( item => item.type == 'competence' && item.system.categorie == 'magique');
return comp.sort( this.compareName );
}
/* -------------------------------------------- */
getDons( ) {
let dons = this.data.items.filter( item => item.type == 'don');
let dons = this.items.filter( item => item.type == 'don');
return dons.sort( this.compareName );
}
/* -------------------------------------------- */
getEffetsMagiques( ) {
let effets = this.data.items.filter( item => item.type == 'effetmagique');
let effets = this.items.filter( item => item.type == 'effetmagique');
return effets.sort( this.compareName );
}
/* -------------------------------------------- */
getEffetsDeRunes( ) {
let effets = this.data.items.filter( item => item.type == 'effetderune');
let effets = this.items.filter( item => item.type == 'effetderune');
return effets.sort( this.compareName );
}
/* -------------------------------------------- */
getMonnaies( ) {
let monnaies = this.data.items.filter( item => item.type == 'monnaie');
let monnaies = this.items.filter( item => item.type == 'monnaie');
return monnaies.sort( this.compareName );
}
/* -------------------------------------------- */
getFaiblesses( ) {
let faib = this.data.items.filter( item => item.type == 'faiblesse');
let faib = this.items.filter( item => item.type == 'faiblesse');
return faib.sort( this.compareName );
}
/* -------------------------------------------- */
getBlessures( ) {
return this.data.items.filter( item => item.type == 'blessure');
return this.items.filter( item => item.type == 'blessure');
}
/* -------------------------------------------- */
getToutEquipements() {
return this.data.items.filter( item => item.type == 'equipement' || item.type == 'armure' || item.type == 'armecc' || item.type == 'armedist');
return this.items.filter( item => item.type == 'equipement' || item.type == 'armure' || item.type == 'armecc' || item.type == 'armedist' || item.type == 'bouclier');
}
/* -------------------------------------------- */
getArmes() {
return this.data.items.filter( item => (item.type == 'armecc' || item.type == 'armedist') && item.data.data.equipe );
return this.items.filter( item => (item.type == 'armecc' || item.type == 'armedist') && item.system.equipe );
}
/* -------------------------------------------- */
getArmures() {
return this.data.items.filter( item => item.type == 'armure' && item.data.data.equipe );
return this.items.filter( item => item.type == 'armure' && item.system.equipe );
}
getBoucliers() {
return this.items.filter( item => item.type == 'bouclier' && item.system.equipe );
}
getProuessesMartiales() {
let prouesse = this.data.items.filter( item => item.type == 'prouesse' );
let prouesse = this.items.filter( item => item.type == 'prouesse' );
return prouesse.sort( this.compareName );
}
getSortsSejdr() {
let sort = this.data.items.filter( item => item.type == 'sortsejdr' );
let sort = this.items.filter( item => item.type == 'sortsejdr' );
return sort.sort( this.compareName );
}
getSortsGaldr() {
let sort = this.data.items.filter( item => item.type == 'sortgaldr' );
let sort = this.items.filter( item => item.type == 'sortgaldr' );
return sort.sort( this.compareName );
}
getRunes() {
let sort = this.data.items.filter( item => item.type == 'rune' );
let sort = this.items.filter( item => item.type == 'rune' );
return sort.sort( this.compareName );
}
/* -------------------------------------------- */
async setEpuise( ) {
await this.update({ 'data.status.epuise': true});
this.data.data.status.epuise = true;
if (!this.system.status.epuise) {
await this.update({ 'system.status.epuise': true});
this.system.status.epuise = true;
}
/*let effect = this.getEffectByLabel('Epuisé');
if ( !effect ) {
let effect = statusEffects.find( ef => ef.id == 'epuise');
@@ -313,8 +324,10 @@ export class YggdrasillActor extends Actor {
}
/* -------------------------------------------- */
async cleanEpuise() {
await this.update({ 'data.status.epuise': false});
this.data.data.status.epuise = false;
if (this.system.status.epuise) {
await this.update({ 'system.status.epuise': false});
this.system.status.epuise = false;
}
/*let effect = this.getEffectByLabel('Epuisé');
if ( effect ) {
await this.deleteEmbeddedDocuments("ActiveEffect", [ effect.id ]);
@@ -322,7 +335,7 @@ export class YggdrasillActor extends Actor {
}
/* -------------------------------------------- */
async toggleEpuise( ) {
if ( this.data.data.status.epuise ) {
if ( this.system.status.epuise ) {
await this.cleanEpuise();
} else {
await this.setEpuise();
@@ -330,14 +343,15 @@ export class YggdrasillActor extends Actor {
}
/* -------------------------------------------- */
isEpuise() {
return this.data.data.status.epuise;
return this.system.status.epuise;
}
/* -------------------------------------------- */
async setBlesse( ) {
await this.update({ 'data.status.blesse': true} );
this.data.data.status.blesse = true;
console.log("BLESSSE !!!!");
if (!this.system.status.blesse) {
await this.update({ 'system.status.blesse': true} );
this.system.status.blesse = true;
}
/*let effect = this.getEffectByLabel('Blessé');
if ( !effect ) {
let effect = statusEffects.find( ef => ef.id == 'blesse');
@@ -346,8 +360,10 @@ export class YggdrasillActor extends Actor {
}
/* -------------------------------------------- */
async cleanBlesse() {
await this.update({ 'data.status.blesse': false} );
this.data.data.status.blesse = false;
if (this.system.status.blesse) {
await this.update({ 'system.status.blesse': false} );
this.system.status.blesse = false;
}
/*let effect = this.getEffectByLabel('Blessé');
if ( effect ) {
await this.deleteEmbeddedDocuments("ActiveEffect", [ effect.id ]);
@@ -356,43 +372,46 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
isBlesse() {
return this.data.data.status.blesse;
return this.system.status.blesse;
//return this.getEffectByLabel('Blessé');
}
/* -------------------------------------------- */
async setMeurtri( ) {
await this.setBlesse();
await this.update({ 'data.status.meurtri': true});
this.data.data.status.meurtri = true;
if (!this.system.status.meurtri) {
await this.update({ 'system.status.meurtri': true});
this.system.status.meurtri = true;
}
}
/* -------------------------------------------- */
async cleanMeurtri() {
await this.update({ 'data.status.meurtri': false});
this.data.data.status.meurtri = false;
if (this.system.status.meurtri) {
await this.update({ 'system.status.meurtri': false});
this.system.status.meurtri = false;
}
}
/* -------------------------------------------- */
isMeurtri() {
return this.data.data.status.meurtri;
return this.system.status.meurtri;
}
/* -------------------------------------------- */
async decrementFuror( nbFuror) {
await this.update( { 'data.furor.value': this.data.data.furor.value - nbFuror } );
await this.update( { 'system.furor.value': this.system.furor.value - nbFuror } );
}
/* -------------------------------------------- */
getCurrentFuror() {
return this.data.data.furor.value;
return this.system.furor.value;
}
/* -------------------------------------------- */
getActiveEffects(matching = it => true) {
let array = Array.from(this.getEmbeddedCollection("ActiveEffect").values());
return Array.from(this.getEmbeddedCollection("ActiveEffect").values()).filter(it => matching(it));
}
/* -------------------------------------------- */
getEffectByLabel(label) {
return this.getActiveEffects().find(it => it.data.label == label);
return this.getActiveEffects().find(it => it.label == label);
}
/* -------------------------------------------- */
getEffectById(id) {
@@ -401,11 +420,11 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
getCarac( caracName ) {
for( let key in this.data.data.carac) {
let categ = this.data.data.carac[key];
for( let key in this.system.carac) {
let categ = this.system.carac[key];
for( let carac in categ.carac) {
if (carac.toLowerCase() == caracName.toLowerCase() ) {
return deepClone(categ.carac[carac]);
return foundry.utils.deepClone(categ.carac[carac]);
}
}
}
@@ -414,50 +433,64 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
computeCaracSecondaire( ) {
if ( this.type == "personnage") {
let basecorps = this.data.data.carac.corps.carac;
let basecorps = this.system.carac.corps.carac;
let sumcorps = basecorps.puissance.value + basecorps.agilite.value + basecorps.vigueur.value
let baseesprit = this.data.data.carac.esprit.carac;
let baseesprit = this.system.carac.esprit.carac;
let sumesprit = baseesprit.intellect.value + baseesprit.perception.value + baseesprit.tenacite.value
let baseame = this.data.data.carac.ame.carac;
let baseame = this.system.carac.ame.carac;
let sumame = baseame.charisme.value + baseame.communication.value + baseame.instinct.value
let newPV = (sumcorps*3) + (sumesprit *2) + sumame;
if ( newPV != this.data.data.caracsecondaire.pv.max) {
this.data.data.caracsecondaire.pv.max = newPV;
this.update( { 'data.caracsecondaire.pv.max': newPV });
this.system.caracsecondaire.pv.max = newPV;
this.system.caracsecondaire.reaction.value = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
let newReac = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
this.system.caracsecondaire.reaction.max = newReac
this.system.caracsecondaire.defensephy.value = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
let newDef = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
this.system.caracsecondaire.defensephy.max = newDef
// Initialize bonusmalus if not set
if (this.system.caracsecondaire.defensephy.bonusmalus === undefined) {
this.system.caracsecondaire.defensephy.bonusmalus = 0
}
this.system.caracsecondaire.defensephy.total = newDef + this.system.caracsecondaire.defensephy.bonusmalus
this.data.data.caracsecondaire.reaction.value = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
this.data.data.caracsecondaire.reaction.max = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
this.system.caracsecondaire.defensemen.value = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
newDef = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
this.system.caracsecondaire.defensemen.max = newDef
// Initialize bonusmalus if not set
if (this.system.caracsecondaire.defensemen.bonusmalus === undefined) {
this.system.caracsecondaire.defensemen.bonusmalus = 0
}
this.system.caracsecondaire.defensemen.total = newDef + this.system.caracsecondaire.defensemen.bonusmalus
this.data.data.caracsecondaire.defensephy.value = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
this.data.data.caracsecondaire.defensephy.max = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
this.system.caracsecondaire.deplacement.value = basecorps.agilite.value + basecorps.vigueur.value;
let depl = basecorps.agilite.value + basecorps.vigueur.value;
this.system.caracsecondaire.deplacement.max = depl
this.data.data.caracsecondaire.defensemen.value = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
this.data.data.caracsecondaire.defensemen.max = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
this.system.caracsecondaire.capaenc.value = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
let enc = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
this.system.caracsecondaire.capaenc.max = enc
this.data.data.caracsecondaire.deplacement.value = basecorps.agilite.value + basecorps.vigueur.value;
this.data.data.caracsecondaire.deplacement.max = basecorps.agilite.value + basecorps.vigueur.value;
this.data.data.caracsecondaire.capaenc.value = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
this.data.data.caracsecondaire.capaenc.max = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
//console.log("CARAC SEC", this.system.caracsecondaire)
}
}
/* -------------------------------------------- */
async equiperObject( equipementId ) {
let item = this.data.items.find( item => item.id == equipementId );
if (item && item.data.data) {
let update = { _id: item.id, "data.equipe": !item.data.data.equipe };
let item = this.items.find( item => item.id == equipementId );
if (item && item.system) {
let update = { _id: item.id, "system.equipe": !item.system.equipe };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async updateCompetence( compId, niveau) {
let comp = this.data.items.find( item => item.type == 'competence' && item.id == compId);
let comp = this.items.find( item => item.type == 'competence' && item.id == compId);
console.log("Comp updated!!!!", compId, niveau);
if (comp) {
const update = { _id: comp.id, 'data.niveau': niveau };
const update = { _id: comp.id, 'system.niveau': niveau };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
} else {
ui.notifications.warn("Compétence inconnue", compId)
@@ -471,9 +504,9 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
async rollAttribute( attrkey, subAttrKey = 'defaut') {
let attr = duplicate(this.data.data.attributs[attrkey]);
let subAttr = duplicate(this.data.data.attributs[attrkey].values[subAttrKey] );
console.log("ATTR : ", attr, subAttr);
let attr = foundry.utils.duplicate(this.system.attributs[attrkey]);
console.log("ATTR : ", attr, attrkey, subAttrKey);
let subAttr = foundry.utils.duplicate(this.system.attributs[attrkey].values[subAttrKey] );
if ( attr ) {
subAttr.label = subAttr.label || "";
let title = `Attribut : ${attr.label} ${subAttr.label} : ${subAttr.value}`;
@@ -483,28 +516,35 @@ export class YggdrasillActor extends Actor {
actorImg: this.img,
actorId: this.id,
attr: attr,
valuePhysique: this.data.data.attributs["physique"].values["defaut"].value,
valuePhysique: this.system.attributs["physique"].values["defaut"].value,
subAttr: subAttr,
rollMode: game.settings.get("core", "rollMode"),
title: title,
isBlesse: this.data.data.etat.etat == "blesse",
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
bonusMalus: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 0
isBlesse: this.system.etat.etat == "blesse",
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
this.addDefaultRoll(rollData)
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Attribut non trouvée");
}
}
/* -------------------------------------------- */
addDefaultRoll(rollData) {
rollData.optionsBonusMalus= YggdrasillUtility.createOptions(-15, 15)
rollData.bonusMalus= 0
rollData.optionsFuror= YggdrasillUtility.createOptions(0, this.getCurrentFuror())
rollData.furorUsage= 0
rollData.optionsBD= YggdrasillUtility.createOptions(0, +15)
rollData.sr= 0
rollData.puissanceRune = 1
rollData.optionsPuissanceRune= YggdrasillUtility.createOptions(1, +15)
rollData.supportRune= "peau"
rollData.config= game.system.config
}
/* -------------------------------------------- */
async rollCarac( categName, caracName) {
let carac = duplicate(this.data.data.carac[categName].carac[caracName]);
console.log("CARAC : ", carac, this.data.data.carac);
let carac = foundry.utils.duplicate(this.system.carac[categName].carac[caracName]);
if ( carac) {
let rollData = {
mode: "carac",
@@ -518,16 +558,10 @@ export class YggdrasillActor extends Actor {
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 0
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
this.addDefaultRoll(rollData)
console.log("CARAC : ", rollData, this.system.carac);
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Caractéristique non trouvée");
}
@@ -536,7 +570,7 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
async rollCompetence( competenceId ) {
let competence = this.data.items.find( item => item.type == 'competence' && item.id == competenceId);
let competence = this.items.find( item => item.type == 'competence' && item.id == competenceId);
if ( competence) {
let rollData = {
mode: "competence",
@@ -545,21 +579,14 @@ export class YggdrasillActor extends Actor {
actorId: this.id,
img: competence.img,
rollMode: game.settings.get("core", "rollMode"),
title: `Compétence ${competence.name} : ${competence.data.data.niveau}`,
competence: duplicate(competence),
title: `Compétence ${competence.name} : ${competence.system.niveau}`,
competence: foundry.utils.duplicate(competence),
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 0
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
this.addDefaultRoll(rollData)
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Compétence non trouvée");
}
@@ -567,21 +594,21 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
getAttaqueData( mode ) {
let attackData = duplicate(attackMode[mode]);
let attackData = foundry.utils.duplicate(attackMode[mode]);
if ( attackData){
attackData.mode = mode;
attackData.carac = duplicate(this.data.data.carac[attackData.categName].carac[attackData.caracName]);
attackData.carac = foundry.utils.duplicate(this.system.carac[attackData.categName].carac[attackData.caracName]);
if ( attackData.malus != 0) {
let malusTab = attackData.malus.split(';');
attackData.malus = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
attackData.malus = this.system.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.protection != 0) {
let malusTab = attackData.protection.split(';');
attackData.protection = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
attackData.protection = this.system.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.bonusdegats != 0) {
let malusTab = attackData.bonusdegats.split(';');
attackData.bonusdegats = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
attackData.bonusdegats = this.system.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
}
return attackData;
@@ -589,21 +616,21 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
getTirData( mode) {
let attackData = duplicate( tirMode[mode] );
let attackData = foundry.utils.duplicate( tirMode[mode] );
if ( attackData){
attackData.mode = mode;
attackData.carac = duplicate(this.data.data.carac[attackData.categName].carac[attackData.caracName]);
attackData.carac = foundry.utils.duplicate(this.system.carac[attackData.categName].carac[attackData.caracName]);
if ( attackData.malus != 0) {
let malusTab = attackData.malus.split(';');
attackData.malus = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
attackData.malus = this.system.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.protection != 0) {
let malusTab = attackData.protection.split(';');
attackData.protection = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
attackData.protection = this.system.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.bonusdegats != 0) {
let malusTab = attackData.bonusdegats.split(';');
attackData.bonusdegats = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
attackData.bonusdegats = this.system.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
}
return attackData;
@@ -611,23 +638,24 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
async rollSort( sortId, magie) {
let sort = this.data.items.find( item => item.id == sortId);
let competence = this.data.items.find( item => item.type == 'competence' && item.name.toLowerCase().includes(magie));
let sort = this.items.find( item => item.id == sortId);
let competence = this.items.find( item => item.type == 'competence' && item.name.toLowerCase().includes(magie));
console.log("SORT :", sortId, sort, competence );
let carac;
if ( magie == "sejdr") {
carac = duplicate(this.data.data.carac.ame.carac.instinct);
carac = foundry.utils.duplicate(this.system.carac.ame.carac.instinct);
} else if ( magie == "rune") {
carac = duplicate(this.data.data.carac.ame.carac.communication);
carac = foundry.utils.duplicate(this.system.carac.ame.carac.communication);
} else {
carac = duplicate(this.data.data.carac.ame.carac.charisme);
carac = foundry.utils.duplicate(this.system.carac.ame.carac.charisme);
}
if ( sort && competence) {
let rollData = {
mode: magie,
isMagie: true,
alias: this.name,
actorImg: this.img,
actorId: this.id,
@@ -635,10 +663,10 @@ export class YggdrasillActor extends Actor {
rollMode: game.settings.get("core", "rollMode"),
title: magie + " - " + sort.name,
selectedCarac: carac,
agiliteCarac: duplicate(this.data.data.carac.corps.carac.agilite),
instinctCarac: duplicate(this.data.data.carac.ame.carac.instinct),
sort: duplicate(sort),
competence: duplicate(competence),
agiliteCarac: foundry.utils.duplicate(this.system.carac.corps.carac.agilite),
instinctCarac: foundry.utils.duplicate(this.system.carac.ame.carac.instinct),
sort: foundry.utils.duplicate(sort),
competence: foundry.utils.duplicate(competence),
dureeGaldr: "1d5a",
nbCibles: "1",
zoneGaldr: "INS10cm3",
@@ -646,20 +674,10 @@ export class YggdrasillActor extends Actor {
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
optionsBD: YggdrasillUtility.buildListOptions(0, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 14,
puissanceRune: 1,
optionsPuissanceRune: YggdrasillUtility.buildListOptions(1, 15),
supportRune: "peau",
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
this.addDefaultRoll(rollData)
rollData.sr = 14
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Sortilège ou Compétence non trouvée !", sort, compName);
}
@@ -667,9 +685,9 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
async rollArme( armeId ) {
let arme = this.data.items.find( item => item.id == armeId);
let compName = armeCategorieToCompetence[arme.data.data.categorie];
let competence = this.data.items.find( item => item.type == 'competence' && item.name == compName);
let arme = this.items.find( item => item.id == armeId);
let compName = armeCategorieToCompetence[arme.system.categorie];
let competence = this.items.find( item => item.type == 'competence' && item.name == compName);
console.log("ARME :", armeId, arme, competence );
if ( arme && competence) {
@@ -690,24 +708,17 @@ export class YggdrasillActor extends Actor {
img: competence.img,
rollMode: game.settings.get("core", "rollMode"),
title: "Attaque !",
selectedCarac: duplicate(this.data.data.carac.corps.carac.agilite),
arme: duplicate(arme),
competence: duplicate(competence),
selectedCarac: foundry.utils.duplicate(this.system.carac.corps.carac.agilite),
arme: foundry.utils.duplicate(arme),
competence: foundry.utils.duplicate(competence),
bonusdefense: 0,
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
optionsBD: YggdrasillUtility.buildListOptions(0, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 14
isMeurtri: this.isMeurtri()
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
this.addDefaultRoll(rollData)
rollData.sr = 14
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Arme ou Compétence Martiale non trouvée !", arme, compName);
}
@@ -716,42 +727,58 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
getEncTotal( ) {
let encTotal = 0;
for( let item of this.data.items) {
if (item.type == "equipement" || item.type == "armecc"
|| item.type == "armedist" || item.type == "armure" || item.type == "monnaie") {
encTotal += item.data.data.enc;
for( let item of this.items) {
if (item.type == "equipement" || item.type == "armecc"
|| item.type == "armedist" || item.type == "armure" || item.type == "monnaie" || item.type == "bouclier") {
encTotal += (item.system.enc * item.system.quantite);
}
}
for( let item of this.items) {
if (item.type == "bouclier" && item.system.equipe) {
encTotal -= (item.system.enc * item.system.quantite);
encTotal += (item.system.enccomb * item.system.quantite);
}
}
return encTotal;
}
}
/* -------------------------------------------- */
getProtectionTotal( ) {
let protectionTotal = 0;
for( let item of this.data.items) {
if (item.type == "armure" && item.data.data.equipe) {
protectionTotal += Number(item.data.data.protection);
for( let item of this.items) {
if (item.type == "armure" && item.system.equipe) {
protectionTotal += Number(item.system.protection);
}
}
return protectionTotal;
}
/* -------------------------------------------- */
getDpBouclier( ) {
let dpBouclier = 0;
for( let item of this.items) {
if (item.type == "bouclier" && item.system.equipe) {
dpBouclier += Number(item.system.defensebonus);
}
}
return dpBouclier;
}
/* -------------------------------------------- */
async incrementeQuantite( objetId ) {
let objetQ = this.data.items.find( item => item.id == objetId );
let objetQ = this.items.find( item => item.id == objetId );
if (objetQ) {
let newQ = objetQ.data.data.quantite + 1;
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.quantite': newQ }]); // pdates one EmbeddedEntity
let newQ = objetQ.system.quantite + 1;
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantite': newQ }]); // pdates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async decrementeQuantite( objetId ) {
let objetQ = this.data.items.find( item => item.id == objetId );
let objetQ = this.items.find( item => item.id == objetId );
if (objetQ) {
let newQ = objetQ.data.data.quantite - 1;
let newQ = objetQ.system.quantite - 1;
newQ = (newQ <= 0) ? 0 : newQ;
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.quantite': newQ }]); // pdates one EmbeddedEntity
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantite': newQ }]); // pdates one EmbeddedEntity
}
}

View File

@@ -22,7 +22,7 @@ export class YggdrasillCombat extends Combat {
// Send a chat message
let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode");
let messageData = mergeObject(
let messageData = foundry.utils.mergeObject(
{
speaker: {
scene: canvas.scene._id,

View File

@@ -0,0 +1,111 @@
export const YGGDRASILL_CONFIG = {
optionsEtat: [
{ key: "fringant", label: "Fringant" },
{ key: "blesse", label: "Blessé" },
{ key: "mort", label: "Mort" }
],
optionsArme: [
{ key: "lutte", label: "Lutte" },
{ key: "improvisee", label: "Improvisée" },
{ key: "courte", label: "Courte" },
{ key: "longue", label: "Longue" },
{ key: "deuxmains", label: "A Deux Mains" },
{ key: "hast", label: "Hast" }
],
optionsArmeTir: [
{ key: "jet", label: "Jet" },
{ key: "tir", label: "Tir" }
],
optionsArmure: [
{ key: "armure", label: "Armure" },
{ key: "piecearmure", label: "Pièce d'Armure" }
],
optionsBouclier: [
{ key: "bouclier", label: "Bouclier" }
],
optionsCompetence: [
{ key: "generale", label: "Générale" },
{ key: "magique", label: "Magique" },
{ key: "martiale", label: "Martiale" }
],
optionsMaladie: [
{ key: "mineure", label: "Mineure" },
{ key: "moderee", label: "Modérée" },
{ key: "majeure", label: "Majeure" }
],
optionsProuesse: [
{ key: "attaque", label: "Attaque" },
{ key: "defensive", label: "Défensive" },
{ key: "utilitaire", label: "Utilitaire" }
]
,
optionsGaldr: [
{ key:"malediction", label:"Malédictions" },
{ key:"illusion", label:"Illusions" },
{ key:"charme", label:"Charme" }
],
optionsAttaque: [
{ key:"classique", label:"Attaque classique" },
{ key:"force", label:"Attaque en force" },
{ key:"devastatrice", label:"Attaque dévastatrice" },
{ key:"precise", label:"Attaque Précise" },
{ key:"visee", label:"Attaque Visée" }
],
optionsSR : [
{key: 0, label: "Aucun"},
{key: 5, label: "Très Simple (5)"},
{key: 7, label: "Simple (7)"},
{key: 10, label: "Aisé (10)"},
{key: 14, label: "Moyen (14)"},
{key: 19, label: "Difficile (19)"},
{key: 25, label: "Très Difficile (25)"},
{key: 32, label: "Exceptionnel (32)"},
{key: 40, label: "Légendaire (40)"},
{key: 49, label: "Divin (49)"}
],
optionsCarac: [
{ key: "puissance", label: "Puissance" },
{ key: "vigueur", label: "Vigueur" },
{ key: "agilite", label: "Agilité" },
{ key: "intellect", label: "Intellect" },
{ key: "perception", label: "Perception" },
{ key: "tenacite", label: "Tenacité" },
{ key: "charisme", label: "Charisme" },
{ key: "communication", label: "Communication" },
{ key: "instinct", label: "Instinct" }
],
optionsDureeGaldr: [
{ key:"1d5a", label:"1d5 Actions" },
{ key:"1d10t", label:"1d10 Tours" },
{ key:"1d10m", label:"1d10 Minutes" },
{ key:"1d10h", label:"1d10 Heures" },
{ key:"1d5j", label:"1d5 journées" }
],
optionsZoneGaldr: [
{ key:"INS10cm3", label:"INS x 10 cm3 (chat, balle, épée, ...)" },
{ key:"INS50cm3", label:"INS x 50 cm3 (tabouret, enfant, ...)" },
{ key:"INS1m3", label:"INS x 1 m3 (homme, 2 enfants, ...)" },
{ key:"INS5m3", label:"INS x 5 m3 (charrette, 2 cavaliers, ...)" },
{ key:"INS10m3", label:"INS x 10 m3 (maison, kraken, bateau, ...)" }
],
optionsNbCibles: [
{ key: "1", label: "1" },
{ key: "2_4", label: "2 à 4" },
{ key: "5_9", label: "5 à 9" },
{ key: "10_49", label: "10 à 49" },
{ key: "50plus", label: "50 et +" }
],
optionsSupportRunes: [
{ key: "peau", label: "Peau" },
{ key: "tissu", label: "Tissu" },
{ key: "cuir", label: "Cuir" },
{ key: "bois", label: "Bois" },
{ key: "pierremetal", label: "Pierre, Métal" }
]
}

View File

@@ -6,12 +6,12 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
/* -------------------------------------------- */
export class YggdrasillFigurantSheet extends ActorSheet {
export class YggdrasillFigurantSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["yggdrasill", "sheet", "actor"],
template: "systems/fvtt-yggdrasill/templates/figurant-sheet.html",
width: 640,
@@ -23,9 +23,9 @@ export class YggdrasillFigurantSheet extends ActorSheet {
}
/* -------------------------------------------- */
getData() {
const objectData = YggdrasillUtility.data(this.object);
async getData() {
const objectData = foundry.utils.duplicate(this.object)
let formData = {
title: this.title,
id: objectData.id,
@@ -34,18 +34,21 @@ export class YggdrasillFigurantSheet extends ActorSheet {
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(YggdrasillUtility.templateData(this.object)),
data: foundry.utils.deepClone(this.object.system),
limited: this.object.limited,
equipements: this.actor.getToutEquipements(),
effetsmagiques: this.actor.getEffetsMagiques(),
encTotal: this.actor.getEncTotal(),
monnaies: this.actor.getMonnaies(),
optionsAttr: new Array(21).fill('option'),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsAttr: Array.fromRange(41, 1),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, {async: true}),
notes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.notes, {async: true}),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM
isGM: game.user.isGM,
config: game.system.config
}
console.log("FIGURANT : ", formData);
@@ -63,7 +66,7 @@ export class YggdrasillFigurantSheet extends ActorSheet {
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
const item = this.actor.items.get(li.data("item-id"));
item.sheet.render(true);
});
// Delete Inventory Item
@@ -71,7 +74,7 @@ export class YggdrasillFigurantSheet extends ActorSheet {
const li = $(ev.currentTarget).parents(".item");
YggdrasillUtility.confirmDelete(this, li);
});
html.find('.equipement-moins').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.decrementeQuantite( li.data("item-id") );
@@ -86,16 +89,16 @@ export class YggdrasillFigurantSheet extends ActorSheet {
let attrKey = li.data("attr-key");
let attrSubKey = $(event.currentTarget).data("attr-sub-key");
this.actor.rollAttribute(attrKey, attrSubKey);
});
});
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
});
html.find('.item-link a').click((event) => {
const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.getOwnedItem(itemId);
const item = this.actor.items.get(itemId);
item.sheet.render(true);
});
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equiperObject( li.data("item-id") );

View File

@@ -1,101 +1,115 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YGGDRASILL_CONFIG } from "./yggdrasill-config.js";
const { HandlebarsApplicationMixin } = foundry.applications.api;
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
* @extends {ItemSheetV2}
*/
export class YggdrasillItemSheet extends ItemSheet {
export class YggdrasillItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options);
this.#dragDrop = this.#createDragDropHandlers();
}
#dragDrop;
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-yggdrasill", "sheet", "item"],
template: "systems/fvtt-yggdrasill/templates/item-sheet.html",
width: 550,
height: 550
//tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "item"],
position: {
width: 550,
height: 550,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
actions: {
editImage: YggdrasillItemSheet.#onEditImage,
},
};
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => {}
})
return buttons
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" };
/* -------------------------------------------- */
/** @override */
setPosition(options={}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
async getData() {
const objectData = YggdrasillUtility.data(this.object);
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(YggdrasillUtility.templateData(this.object)),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsNiveaux4: YggdrasillUtility.buildListOptions(1, 5),
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
isGM: game.user.isGM
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/item-{type}-sheet.hbs"
}
return formData;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
};
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
/** @override */
async _prepareContext() {
// Ensure config is always available with fallback to direct import
const config = game.system?.config || game.system?.yggdrasill?.config || YGGDRASILL_CONFIG || {};
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.object.options.actor.getOwnedItem(li.data("item-id"));
item.sheet.render(true);
});
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.object.options.actor.deleteOwnedItem( li.data("item-id") ).then( this.render(true));
});
// Create options for niveau 0-5
const optionsNiveaux4 = {};
for (let i = 0; i <= 5; i++) {
optionsNiveaux4[`${i}`] = `${i}`;
}
const optionsBase = YggdrasillUtility.createDirectOptionList(0, 20) || {};
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
data: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description || "", { async: true }),
enrichedEffet: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effet || "", { async: true }),
isEditMode: true,
isEditable: this.isEditable,
editable: this.isEditable,
isGM: game.user.isGM,
config: config,
optionsBase: optionsBase,
optionsNiveaux4: optionsNiveaux4,
};
return context;
}
/* -------------------------------------------- */
get template()
{
let type = this.item.type;
return `systems/fvtt-yggdrasill/templates/item-${type}-sheet.html`;
return context;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
_onRender(context, options) {
super._onRender(context, options);
this.#dragDrop.forEach((d) => d.bind(this.element));
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
*/
#createDragDropHandlers() {
return [];
}
// #region Actions
/**
* Handle editing the item image
* @param {Event} event - The triggering event
*/
static async #onEditImage(event) {
event.preventDefault();
const filePicker = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => {
this.document.update({ img: path });
},
});
filePicker.browse();
}
}

View File

@@ -9,11 +9,16 @@
/* -------------------------------------------- */
// Import Modules
import { YggdrasillActor } from "./yggdrasill-actor.js";
import { YggdrasillItemSheet } from "./yggdrasill-item-sheet.js";
import { YggdrasillActorSheet } from "./yggdrasill-actor-sheet.js";
import { YggdrasillFigurantSheet } from "./yggdrasill-figurant-sheet.js";
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillCombat } from "./yggdrasill-combat.js";
import { YGGDRASILL_CONFIG } from "./yggdrasill-config.js";
import { ClassCounter} from "https://www.uberwald.me/fvtt_appcount/count-class-ready.js"
// Import DataModels
import * as models from "./models/index.mjs";
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
@@ -28,12 +33,14 @@ Hooks.once("init", async function () {
YggdrasillUtility.preloadHandlebarsTemplates();
/* -------------------------------------------- */
// Set an initiative formula for the system
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1d20",
decimals: 0
};
game.system.config = YGGDRASILL_CONFIG
/* -------------------------------------------- */
game.socket.on("system.fvtt-yggdrasill", data => {
YggdrasillUtility.onSocketMesssage(data);
@@ -42,33 +49,82 @@ Hooks.once("init", async function () {
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Actor.documentClass = YggdrasillActor;
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
figurant: models.FigurantDataModel
};
CONFIG.Combat.documentClass = YggdrasillCombat;
CONFIG.Item.dataModels = {
competence: models.CompetenceDataModel,
don: models.DonDataModel,
faiblesse: models.FaiblesseDataModel,
blessure: models.BlessureDataModel,
maladie: models.MaladieDataModel,
poison: models.PoisonDataModel,
prouesse: models.ProuesseDataModel,
sortsejdr: models.SortsejdrDataModel,
sortgaldr: models.SortgaldrDataModel,
rune: models.RuneDataModel,
armecc: models.ArmeccDataModel,
armedist: models.ArmedistDataModel,
armure: models.ArmureDataModel,
bouclier: models.BouclierDataModel,
equipement: models.EquipementDataModel,
monnaie: models.MonnaieDataModel,
effetmagique: models.EffetmagiqueDataModel
};
CONFIG.Yggdrasill = {
}
game.system.yggdrasill = {
config: YGGDRASILL_CONFIG,
models,
sheets
}
/* -------------------------------------------- */
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("fvtt-yggdrasill", YggdrasillActorSheet, { types: ["personnage"], makeDefault: true });
Actors.registerSheet("fvtt-yggdrasill", YggdrasillFigurantSheet, { types: ["figurant"], makeDefault: false });
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("fvtt-yggdrasill", YggdrasillItemSheet, { makeDefault: true });
// Init/registers
Hooks.on('renderChatLog', (log, html, data) => {
//YggdrasillUtility.registerChatCallbacks(html);
});
// Register AppV2 Actor Sheets
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", sheets.YggdrasillPersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", sheets.YggdrasillFigurantSheet, { types: ["figurant"], makeDefault: true });
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillCompetenceSheet, { types: ["competence"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillDonSheet, { types: ["don"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillFaiblesseSheet, { types: ["faiblesse"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillBlessureSheet, { types: ["blessure"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillMaladieSheet, { types: ["maladie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillPoisonSheet, { types: ["poison"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillProuesseSheet, { types: ["prouesse"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillSortsejdrSheet, { types: ["sortsejdr"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillSortgaldrSheet, { types: ["sortgaldr"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillRuneSheet, { types: ["rune"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmeccSheet, { types: ["armecc"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmedistSheet, { types: ["armedist"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmureSheet, { types: ["armure"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillBouclierSheet, { types: ["bouclier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillMonnaieSheet, { types: ["monnaie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillEffetmagiqueSheet, { types: ["effetmagique"], makeDefault: true });
});
/* -------------------------------------------- */
function welcomeMessage() {
//ChatUtility.removeMyChatMessageContaining('<div id="welcome-message-sos">');
async function welcomeMessage() {
const templateData = {};
const html = await foundry.applications.handlebars.renderTemplate("systems/fvtt-yggdrasill/templates/chat-welcome-message.hbs", templateData);
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-yggdrasill"><span class="rdd-roll-part">Bienvenue !</div>
` });
content: html
});
}
/* -------------------------------------------- */
@@ -84,7 +140,9 @@ Hooks.once("ready", function () {
user: game.user._id
});
}
welcomeMessage();
ClassCounter.registerUsageCount()
welcomeMessage()
});
/* -------------------------------------------- */
@@ -98,3 +156,28 @@ Hooks.on("chatMessage", (html, content, msg) => {
}
return true;
});
/* -------------------------------------------- */
/* Chat Message Rendering */
/* -------------------------------------------- */
Hooks.on("renderChatMessageHTML", (message, html, data) => {
// Handle collapsible magic details
html.querySelector('.magic-header.collapsible')?.addEventListener('click', function() {
const header = this;
const content = header.parentElement.querySelector('.magic-content');
// Toggle collapsed state
content?.classList.toggle('collapsed');
header.classList.toggle('expanded');
});
// Handle collapsible weapon details
html.querySelector('.weapon-header.collapsible')?.addEventListener('click', function() {
const header = this;
const content = header.parentElement.querySelector('.weapon-content');
// Toggle collapsed state
content?.classList.toggle('collapsed');
header.classList.toggle('expanded');
});
});

View File

@@ -1,207 +0,0 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15};
const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15};
const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15};
export class YggdrasillRoll extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let html
let h = 440;
if ( rollData.mode == "competence") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-competence.html', rollData);
h = 340;
} else if (rollData.mode == "carac") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-carac.html', rollData);
h = 320;
} else if (rollData.mode == "attribut") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-attribut.html', rollData);
h = 320;
} else if (rollData.mode == "armecc") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armecc.html', rollData);
} else if (rollData.mode == "sejdr") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-sejdr.html', rollData);
} else if (rollData.mode == "rune") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-rune.html', rollData);
} else if (rollData.mode == "galdr") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-galdr.html', rollData);
} else {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armetir.html', rollData);
}
let options = { classes: ["yggdrasilldialog"], width: 600, height: h, 'z-index': 99999 };
return new YggdrasillRoll(actor, rollData, html, options );
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: (rollData.mode == "competence") ? "Compétence" : "Caractéristique",
content: html,
buttons: {
roll: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer le Test",
callback: () => { this.roll() }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
} },
default: "Roll",
close: close
}
super(conf, options);
this.actor = actor;
this.rollData = rollData;
}
/* -------------------------------------------- */
roll () {
if ( this.rollData.mode == "attribut") {
YggdrasillUtility.rollAttribute(this.rollData)
} else {
YggdrasillUtility.rollYggdrasill( this.rollData )
}
}
/* -------------------------------------------- */
updateGaldrSR( ) {
let sdDuree = Number(dureeGaldrSD[this.rollData.dureeGaldr]);
let sdVar = 0;
if ( this.rollData.sort.data.voie == "illusion") {
sdVar = Number(zonesciblesGaldrSD[this.rollData.zoneGaldr]);
} else {
sdVar = Number(ciblesGaldrSD[this.rollData.nbCibles]);
}
let SR = Number(this.rollData.sort.data.sd) + sdDuree + sdVar;
$("#srTotal").text(SR);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
updateRuneSR() {
let support = 0;
this.rollData.dureeRune = 6 - this.rollData.agiliteCarac.value;
if ( this.rollData.supportRune == "peau") {
support = 3;
this.rollData.echelleDuree = "Actions";
}
if ( this.rollData.supportRune == "tissu") {
support = 6;
this.rollData.echelleDuree = "Tours";
}
if ( this.rollData.supportRune == "cuir") {
support = 9;
this.rollData.echelleDuree = "Minutes";
}
if ( this.rollData.supportRune == "bois") {
support = 12;
this.rollData.echelleDuree = "Heures";
}
if ( this.rollData.supportRune == "pierremetal") {
support = 15;
this.rollData.echelleDuree = "Jours";
}
let SR = this.rollData.puissanceRune + (Number(this.rollData.sort.data.niveau)*3) + support;
$("#srTotal").text(SR);
$("#runeDuree").text( this.rollData.dureeRune + " " + this.rollData.echelleDuree);
$("#runeDureeVie").text( this.rollData.puissanceRune + " " + this.rollData.echelleDuree);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
if (dialog.rollData.mode == "competence") {
let carac = dialog.actor.getCarac( "Puissance" );
dialog.rollData.selectedCarac = carac;
} else if (dialog.rollData.mode == "armecc" || dialog.rollData.mode == "armedist" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
$("#attackDescr").text(dialog.rollData.attackDef.description);
} else if ( dialog.rollData.mode == "sejdr" || dialog.rollData.mode == "rune" || dialog.rollData.mode == "galdr" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
}
if (dialog.rollData.mode == "rune" ) {
dialog.updateRuneSR();
}
if (dialog.rollData.mode == "galdr" ) {
dialog.updateGaldrSR();
}
if (dialog.rollData.mode == "attribut") {
$("#attrValue").text("2d10+"+dialog.rollData.subAttr.value);
} else {
$("#caracValue").text(dialog.rollData.selectedCarac.value+"d10");
}
}
$(function () { onLoad(); });
html.find('#caracName').change((event) => {
let caracKey = event.currentTarget.value;
let carac = this.actor.getCarac( caracKey );
this.rollData.selectedCarac = carac;
$("#caracValue").text(carac.value+"d10");
});
html.find('#typeAttack').change((event) => {
let attackType = event.currentTarget.value;
let attackDef
if ( this.rollData.mode == 'armecc')
attackDef = this.actor.getAttaqueData( attackType);
else
attackDef = this.actor.getTirData( attackType);
this.rollData.attackDef = attackDef;
this.rollData.selectedCarac = attackDef.carac;
$("#caracValue").text(attackDef.carac.value+"d10");
$("#caracName").text(attackDef.carac.label);
$("#attackDescr").text(attackDef.description);
$("#malus").text(attackDef.malus);
});
html.find('#supportRune').change((event) => {
this.rollData.supportRune = event.currentTarget.value;
this.updateRuneSR();
});
html.find('#puissanceRune').change((event) => {
this.rollData.puissanceRune = Number(event.currentTarget.value);
this.updateRuneSR();
});
html.find('#dureeGaldr').change((event) => {
this.rollData.dureeGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#nbCibles').change((event) => {
this.rollData.nbCibles = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#zoneGaldr').change((event) => {
this.rollData.zoneGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#bonusMalus').change((event) => {
this.rollData.bonusMalus = Number(event.currentTarget.value);
});
html.find('#furorUsage').change((event) => {
this.rollData.furorUsage = Number(event.currentTarget.value);
});
html.find('#sr').change((event) => {
this.rollData.sr = Number(event.currentTarget.value);
});
html.find('#bonusdefense').change((event) => {
this.rollData.bonusdefense = Number(event.currentTarget.value);
});
}
}

View File

@@ -1,47 +1,38 @@
/* -------------------------------------------- */
//import { YggdrasillCombat } from "./yggdrasill-combat.js";
/* -------------------------------------------- */
/* -------------------------------------------- */
/* -------------------------------------------- */
const dureeGaldrText = { "1d5a": "Actions", "1d10t": "Tours", "1d10m": "Minutes", "1d10h": "Heures", "1d5j": "Jours"};
const ciblesGaldrText = { "1": "1", "2_4": "2 à 4", "5_9": "5 à 9", "10_49": "10 à 49", "50plus": "50 et plus"};
/* -------------------------------------------- */
/* -------------------------------------------- */
export class YggdrasillUtility {
/* -------------------------------------------- */
/* -------------------------------------------- */
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-yggdrasill/templates/actor-sheet.html',
'systems/fvtt-yggdrasill/templates/editor-notes-gm.html',
'systems/fvtt-yggdrasill/templates/hud-actor-attaque.html',
'systems/fvtt-yggdrasill/templates/hud-actor-sort.html'
'systems/fvtt-yggdrasill/templates/actor-personnage-sheet.hbs',
'systems/fvtt-yggdrasill/templates/editor-notes-gm.html'
]
return loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static templateData(it) {
return YggdrasillUtility.data(it)?.data ?? {}
}
/* -------------------------------------------- */
static data(it) {
if (it instanceof Actor || it instanceof Item || it instanceof Combatant) {
return it.data;
}
return it;
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static createDirectSortedOptionList( min, max) {
let options = [];
for(let i=min; i<=max; i++) {
for(let i=min; i<=max; i++) {
options.push( {value:i, text: `${i}` } );
}
return options;
}
/* -------------------------------------------- */
static createOptions( min, max) {
let options = [];
for(let i=min; i<=max; i++) {
options.push( {key:i, label: `${i}` } );
}
return options;
}
/* -------------------------------------------- */
static createDirectOptionList( min, max) {
let options = {};
@@ -59,23 +50,6 @@ export class YggdrasillUtility {
}
return options;
}
/* -------------------------------------------- */
static buildSROptions( ) {
let options = ""
options += `<option value="0">Aucun</option>`
options += `<option value="5">Très Simple (5)</option>`
options += `<option value="7">Simple (7)</option>`
options += `<option value="10">Aisé (10)</option>`
options += `<option value="14">Moyen (14)</option>`
options += `<option value="19">Difficile (19)</option>`
options += `<option value="25">Trés Difficile (25)</option>`
options += `<option value="32">Exceptionnel (32)</option>`
options += `<option value="40">Légendaire (40)</option>`
options += `<option value="49">Divin (49)</option>`
return options;
}
/* -------------------------------------------- */
static onSocketMesssage( msg ) {
@@ -98,41 +72,78 @@ export class YggdrasillUtility {
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static async specificYggRoll( nbDice, isFurorUsage = false) {
let rawDices = []
let rolls = []
let maxTab = []
let maxTabMaxIndex = isFurorUsage ? nbDice : 2;
for (let i=0; i<nbDice; i++) {
rolls[i] = await new Roll("1d10x10").roll( ) //+sumDice+"+"+rollData.furorUsage+"d10+"+niveauCompetence+"+"+rollData.finalBM).roll( { async: false} );
if ( i == nbDice-1 ) {
await this.showDiceSoNice(rolls[i], game.settings.get("core", "rollMode") );
} else {
this.showDiceSoNice(rolls[i], game.settings.get("core", "rollMode") );
}
rawDices.push({ 'result': rolls[i].total});
}
rolls.sort((a,b) => a.total-b.total);
rolls.reverse();
for (let i=0; i<maxTabMaxIndex; i++) {
maxTab[i] = {idx: 0, value: 0};
if (rolls[i]?.total != undefined) maxTab[i].value = rolls[i].total;
}
return { rawDices: rawDices, maxTab: maxTab, rolls: rolls }
}
/* -------------------------------------------- */
static async rollAttribute( rollData ) {
// Init stuff
let isCritical = false;
let isFailure = false;
let isSuccess = false;
let marge = 0;
let marge = 0;
let niveau = rollData.subAttr.value;
// Bonus/Malus total
rollData.finalBM = rollData.bonusMalus;
rollData.finalBM = Number(rollData.bonusMalus);
// Gestion cas blessé (malus de -3)
if ( rollData.isBlesse) { // Cas blesse : malus de -3
rollData.finalBM -= 3;
}
let myRoll = new Roll("2d10+"+niveau+"+"+rollData.finalBM).roll( { async: false} );
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode") );
let results = await this.specificYggRoll( 2 )
rollData.rawDices = results.rawDices
rollData.maxTab = results.maxTab
rollData.rolls = results.rolls
rollData.bonus = niveau + Number(rollData.finalBM)
rollData.finalTotal = Number(rollData.maxTab[0].value) + Number(rollData.maxTab[1].value);
rollData.finalTotal += Number(rollData.bonus)
// Compute total SR
rollData.srFinal = rollData.sr;
if ( rollData.srFinal > 0 ) {
isCritical = myRoll.total >= rollData.srFinal*2;
isSuccess = myRoll.total >= rollData.srFinal;
marge = myRoll.total - rollData.srFinal;
rollData.srFinal = Number(rollData.sr);
if ( rollData.bonusdefense ) {
rollData.srFinal += Number(rollData.bonusdefense);
}
if ( rollData.srFinal > 0 ) {
isCritical = rollData.finalTotal >= rollData.srFinal*2;
isSuccess = rollData.finalTotal >= rollData.srFinal;
marge = rollData.finalTotal - rollData.srFinal;
}
if (myRoll.dice[0].results[0].result == 1 && myRoll.dice[0].results[1].result == 1) {
if (rollData.rolls[0].dice[0].results[0].result == 1 && rollData.rolls[1].dice[0].results[0].result == 1) {
isFailure = true;
}
// Dégats
if ( isSuccess && rollData.subAttr.degats ) {
rollData.degatsExplain = `Marge(${marge}) + Physique(${rollData.valuePhysique}) + 1d10`;
rollData.rollDegats = new Roll("1d10+"+marge+"+"+rollData.valuePhysique).roll( { async: false} );
await this.showDiceSoNice(rollData.rollDegats, game.settings.get("core", "rollMode") );
rollData.rollDegats = await new Roll("1d10+"+marge+"+"+rollData.valuePhysique).roll( );
await this.showDiceSoNice(rollData.rollDegats, game.settings.get("core", "rollMode") );
rollData.degats = rollData.rollDegats.total;
}
@@ -141,83 +152,106 @@ export class YggdrasillUtility {
rollData.isSuccess = isSuccess;
rollData.isCritical = isCritical;
rollData.marge = marge;
rollData.roll = myRoll
console.log("ROLLLL ATTR!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result-new.hbs`, rollData)
});
//myRoll.toMessage();
}
/* -------------------------------------------- */
static async rollYggdrasill( rollData ) {
let sumDice = ( rollData.isEpuise | rollData.isMeurtri | rollData.isBlesse) ? 1 : 2;
let sumDice = ( rollData.isEpuise | rollData.isMeurtri) ? 1 : 2;
// Init stuff
let isCritical = false;
let isFailure = false;
let isSuccess = false;
let marge = 0;
let marge = 0;
let nbDice = rollData.selectedCarac.value;
let niveauCompetence = 0;
// Select niveau de competence/arme/carac
if ( rollData.mode != "carac" ) {
niveauCompetence = rollData.competence.data.niveau;
niveauCompetence = rollData.competence.system.niveau;
} else {
niveauCompetence = rollData.selectedCarac.value;
}
// Bonus/Malus total
rollData.finalBM = rollData.bonusMalus;
rollData.finalBM = Number(rollData.bonusMalus);
if ( rollData.attackDef) {
rollData.finalBM -= rollData.attackDef.malus;
}
if ( rollData.sort && rollData.sort.data.malus ) {
rollData.finalBM += rollData.sort.data.malus;
if ( rollData?.sort?.system?.malus ) {
rollData.finalBM += rollData.sort.system.malus;
}
// Gestion cas blessé (malus de -3)
if ( rollData.isBlesse) { // Cas blesse : malus de -3
rollData.finalBM -= 3;
}
if (sumDice > nbDice) sumDice = nbDice;
let myRoll = new Roll(nbDice+"d10x10kh"+sumDice+"+"+rollData.furorUsage+"d10+"+niveauCompetence+"+"+rollData.finalBM).roll( { async: false} );
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode") );
// Compute total SR
rollData.srFinal = rollData.sr;
if ( rollData.bonusdefense ) {
rollData.srFinal += rollData.bonusdefense;
}
if ( rollData.srFinal > 0 ) {
isCritical = myRoll.total >= rollData.srFinal*2;
isSuccess = myRoll.total >= rollData.srFinal;
marge = myRoll.total - rollData.srFinal;
}
rollData.rawDices = duplicate(myRoll.dice[0].results);
if (nbDice == 1 && myRoll.dice[0].results[0].result == 1) {
isFailure = true;
}
if (nbDice == 2 && myRoll.dice[0].results[0].result == 1 && myRoll.dice[0].results[1].result == 1) {
isFailure = true;
}
if (nbDice >= 3 ) {
let nbOnes = myRoll.dice[0].results.filter( dice => dice.result == 1);
isFailure = nbOnes.length >= 3;
}
if (sumDice > nbDice) sumDice = nbDice;
let results = await this.specificYggRoll( nbDice )
rollData.rawDices = results.rawDices
rollData.maxTab = results.maxTab
rollData.rolls = results.rolls
console.log("RES", results, nbDice, sumDice)
if ( rollData.furorUsage > 0 ) {
results = await this.specificYggRoll( rollData.furorUsage, true )
rollData.furorRawDices = results.rawDices
rollData.furorMaxTab = results.maxTab
rollData.furorRolls = results.rolls
let actor = game.actors.get(rollData.actorId);
actor.decrementFuror( rollData.furorUsage);
}
rollData.bonusTotal = niveauCompetence + rollData.finalBM
rollData.finalTotal = (sumDice ==1) ? rollData.maxTab[0].value : rollData.maxTab[0].value + rollData.maxTab[1].value;
rollData.furorResult = 0
for (let i=0; i<rollData.furorUsage; i++) {
rollData.furorResult += rollData.furorMaxTab[i].value
}
rollData.finalTotal += Number(rollData.furorResult) + Number(rollData.bonusTotal);
rollData.niveauCompetence = niveauCompetence
// Compute total SR
// Pour les sorts, utiliser srTotal si calculé, sinon sr + bonusdefense
if (rollData.srTotal !== undefined) {
rollData.srFinal = rollData.srTotal
} else {
rollData.srFinal = rollData.sr
if (rollData.bonusdefense) {
rollData.srFinal += rollData.bonusdefense
}
}
if ( rollData.srFinal > 0 ) {
isCritical = rollData.finalTotal >= rollData.srFinal*2;
isSuccess = rollData.finalTotal >= rollData.srFinal;
marge = rollData.finalTotal - rollData.srFinal;
}
if (nbDice == 1 && rollData.rolls[0].dice[0].results[0].result == 1) {
isFailure = true;
}
if (nbDice == 2 && rollData.rolls[0].dice[0].results[0].result == 1 && rollData.rolls[1].dice[0].results[0].result == 1) {
isFailure = true;
}
if (nbDice >= 3 ) {
let nbOnes = 0
for (let roll of rollData.rolls) {
if (roll.dice[0].results[0].result == 1 ) nbOnes++;
}
isFailure = nbOnes >= 3;
}
// Dégats
if ( isSuccess && (rollData.mode == "armecc" || rollData.mode == "armedist") ) {
rollData.degatsExplain = `Marge(${marge}) + Degats Arme(${rollData.arme.data.degat}) + Bonus Attaque(${rollData.attackDef.bonusdegats})`;
rollData.degats = marge + rollData.arme.data.degat + rollData.attackDef.bonusdegats;
rollData.degatsExplain = `Marge(${marge}) + Degats Arme(${rollData.arme.system.degat}) + Bonus Attaque(${rollData.attackDef.bonusdegats})`;
rollData.degats = marge + rollData.arme.system.degat + rollData.attackDef.bonusdegats;
}
// Stockage resultats
@@ -226,14 +260,13 @@ export class YggdrasillUtility {
rollData.isSuccess = isSuccess;
rollData.isCritical = isCritical;
rollData.marge = marge;
rollData.roll = myRoll
// Specific GALDR
if ( rollData.sort?.type == "sortgaldr" && rollData.isSuccess) {
let galdrRoll = new Roll( rollData.dureeGaldr.substring(0, rollData.dureeGaldr.length - 1) ).roll( { async: false} );
let galdrRoll = await new Roll( rollData.dureeGaldr.substring(0, rollData.dureeGaldr.length - 1) ).roll( );
await this.showDiceSoNice(galdrRoll, game.settings.get("core", "rollMode") );
rollData.dureeGaldrText = galdrRoll.total + " " + dureeGaldrText[rollData.dureeGaldr];
if ( rollData.sort.data.voie == "illusion") {
if ( rollData.sort.system.voie == "illusion") {
let volume = rollData.zoneGaldr.substring(3, rollData.zoneGaldr.length);
rollData.zoneGaldrText = rollData.instinctCarac.value + " x " + volume;
} else {
@@ -244,14 +277,13 @@ export class YggdrasillUtility {
console.log("ROLLLL!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result-new.hbs`, rollData)
});
//myRoll.toMessage();
}
/* -------------------------------------------- */
static getUsers(filter) {
return game.users.filter(filter).map(user => user.data._id);
return game.users.filter(filter).map(user => user.system._id);
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
@@ -271,7 +303,7 @@ export class YggdrasillUtility {
/* -------------------------------------------- */
static blindMessageToGM(chatOptions) {
let chatGM = duplicate(chatOptions);
let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Message aveugle de " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
@@ -304,7 +336,7 @@ export class YggdrasillUtility {
static createChatWithRollMode(name, chatOptions) {
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions);
}
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
@@ -333,7 +365,7 @@ export class YggdrasillUtility {
d.render(true);
}
/* -------------------------------------------- */
static async showDiceSoNice(roll, rollMode) {
if (game.modules.get("dice-so-nice")?.active) {

4971
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "fvtt-yggdrasill",
"version": "12.0.0",
"description": "Yggdrasill RPG for FoundryVTT - French",
"scripts": {
"build": "gulp build",
"watch": "gulp watch"
},
"author": "Uberwald/LeRatierBretonnien",
"license": "SEE LICENSE IN LICENCE.txt",
"devDependencies": {
"gulp": "^4.0.2",
"gulp-less": "^5.0.0",
"gulp-sourcemaps": "^3.0.0"
}
}

View File

@@ -1,7 +1,7 @@
[Dolphin]
SortOrder=1
HeaderColumnWidths=544,73,106,72
SortRole=modificationtime
Timestamp=2021,9,21,17,43,45.19
Timestamp=2022,2,5,12,44,17.076
Version=4
ViewMode=1
VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails

View File

@@ -1,21 +1,15 @@
{"name":"Casque","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":3,"enc":2,"valeur":2,"description":"<p>Le mod&egrave;le le plus fr&eacute;quent comporte un nasal et des &laquo; lunettes &raquo; qui prot&egrave;gent les yeux. Sur une base de bol en m&eacute;tal, des lamelles de renforts sont rivet&eacute;es afin d&rsquo;assurer la rigidit&eacute; de l&rsquo;ensemble. L&rsquo;int&eacute;rieur est doubl&eacute; de cuir et de tissu afin d&rsquo;amortir les chocs.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"8MoqC7EIc3SXtdsN"}
{"name":"Lance cc","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"hast","equipe":false,"degat":10,"solidite":11,"enc":2,"valeur":4,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"xMRAVbweHG0aMBNR":3},"flags":{"core":{"sourceId":"Item.KPPt0lyDyEac7SPi"}},"_id":"3VBHojfjdD504ibv"}
{"_id":"8aaaIqx6uT1sJ0RO","name":"Lance","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"jet","equipe":false,"degat":8,"solidite":11,"enc":4,"portee":"5/10/15/30","valeur":4,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Cuir lamellé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":6,"enc":3,"valeur":2,"description":"<p>Des lamelles de cuir plus rigides sont coll&eacute;es et cousues sur la version pr&eacute;c&eacute;dente. La poitrine, le ventre, les omoplates et les coudes sont particuli&egrave;rement renforc&eacute;s. Des pi&egrave;ces semi-rigides, bomb&eacute;es, enserrent parfois les &eacute;paules.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"B2yc8LDKTRl0Hjxt"}
{"name":"Hache de guerre","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"longue","equipe":false,"degat":9,"solidite":10,"enc":2,"valeur":1,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"BwmkCCiJWME4CpB0"}
{"name":"Epaullières de fourrure","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":2,"enc":1,"valeur":0.6,"description":"<p>Il s&rsquo;agit d&rsquo;un grand morceau de peau encerclant les &eacute;paules et le cou. En g&eacute;n&eacute;ral, cette fourrure est fix&eacute;e sur une cape. En plus de la protection qu&rsquo;elle apporte, sa nature (loup, ours, vison, etc.. ) donne une indication sur le statut du guerrier.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"C2mzwxiCPJWoY8kc"}
{"name":"Poignard cc","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"courte","equipe":false,"degat":3,"solidite":9,"enc":0,"valeur":0.5,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"xMRAVbweHG0aMBNR":3},"flags":{"core":{"sourceId":"Item.jLtQv1SAt8pkMGqa"}},"_id":"CwrTGdva6vQVz0aQ"}
{"name":"Epée courte","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"longue","equipe":false,"degat":5,"solidite":11,"enc":0,"valeur":2,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"FGIja8vhUOeGNC4x"}
{"name":"Longue hache","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"deuxmains","equipe":false,"degat":14,"solidite":12,"enc":3,"valeur":3,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"GqAbMhROpATanmH3"}
{"name":"Hache de jet","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"jet","equipe":false,"degat":4,"solidite":8,"enc":0,"portee":"3/6/9/15","valeur":0.3,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"IZRpi8jq9erpiyoy"}
{"_id":"MkEW9Ma5m9nlE0nr","name":"Poignard","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"jet","equipe":false,"degat":3,"solidite":9,"enc":2,"portee":"3/6/9/12","valeur":0.4,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":4,"enc":1,"valeur":1,"description":"<p>La veste devient plus &eacute;paisse et doubl&eacute;e de tissu. Des pi&egrave;ces de m&eacute;tal, des rivets plats en cuivre, en bronze ou en fer apportent une protection suppl&eacute;mentaire aux zones les plus expos&eacute;es. Le col monte parfois jusque sous le menton.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"P6dSkefY97UqdDj9"}
{"_id":"MkEW9Ma5m9nlE0nr","name":"Poignard","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"jet","equipe":false,"degat":3,"solidite":9,"enc":0,"portee":"3/6/9/12","valeur":0.5,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Arc de chasse","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"tir","equipe":false,"degat":6,"solidite":8,"enc":2,"portee":"10/20/40/80","valeur":1,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"PsqO2GFVTFIeyCmQ"}
{"name":"Bracelets de cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":1,"enc":0,"valeur":0.3,"description":"<p>Ils prot&egrave;gent l&rsquo;avant-bras, du dessus de la main jusqu&rsquo;au coude. Souvent grav&eacute;s de motifs d&eacute;coratifs, ils se composent de lamelles de cuir rigidifi&eacute;es et nou&eacute;es &agrave; l&rsquo;aide de gros lacets.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"RgYAFOOAh7GGzBaF"}
{"name":"Jambières de cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":2,"enc":1,"valeur":0.4,"description":"<p>Nou&eacute;es autour des cuisses ou des mollets, elles prot&egrave;gent le combattant des coups visant ses jambes. Ici aussi, les artisans essaient souvent d&rsquo;orner ces pi&egrave;ces d&rsquo;armure de d&eacute;corations cisel&eacute;es dans la mati&egrave;re.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"SpTt7wfW9qBKJDxX"}
{"name":"Veste de cuir","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":2,"enc":0,"valeur":0.3,"description":"<p>Il s&rsquo;agit autant d&rsquo;un v&ecirc;tement courant que d&rsquo;une armure. La veste recouvre le torse, le ventre et les hanches. Certaines poss&egrave;dent des manches, courtes ou longues. Le cuir souple et fin ne restreint gu&egrave;re la libert&eacute; de mouvement.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"WrJ7Q4e556LDolbu"}
{"name":"Hachette","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"courte","equipe":false,"degat":4,"solidite":8,"enc":0,"valeur":0.3,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"fjIHt6jNRAM25L9q"}
{"name":"Arc de guerre","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"tir","equipe":false,"degat":7,"solidite":10,"enc":2,"portee":"10/30/50/100","valeur":2,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"jSrZnzjI0QVWuXw8"}
{"name":"Javeline","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"jet","equipe":false,"degat":6,"solidite":7,"enc":2,"portee":"5/10/20/40","valeur":0.4,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"m6PjnSO3ACnzU0ie"}
{"name":"Cotte de mailles","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":9,"enc":5,"valeur":8,"description":"<p>Compos&eacute;e de centaines d&rsquo;anneaux de fer rivet&eacute;s, la cotte de mailles prot&egrave;ge les m&ecirc;mes parties du corps qu&rsquo;une veste. Elle ne descend jamais sur les jambes car cela restreint trop la mobilit&eacute; des combattants. Ceux-ci enfilent dessous une chemise molletonn&eacute;e ou une fine veste de cuir.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"n9eKutLHJZoBYFAD"}
{"name":"Epée à deux mains","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"deuxmains","equipe":false,"degat":12,"solidite":14,"enc":3,"valeur":5,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"ptcBNQhYLwWkBkc0"}
{"name":"Epée longue","type":"armecc","img":"icons/svg/item-bag.svg","data":{"categorie":"longue","equipe":false,"degat":7,"solidite":11,"enc":1,"valeur":4,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"rpY7aUxJelvsozvh"}
{"name":"Fronde","type":"armedist","img":"icons/svg/item-bag.svg","data":{"categorie":"tir","equipe":false,"degat":4,"solidite":12,"enc":0,"portee":"10/20/30/40","valeur":0.1,"description":""},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"x3aMYeKa73GxJ1Ge"}

BIN
packs/armes/000040.ldb Normal file

Binary file not shown.

0
packs/armes/000055.log Normal file
View File

1
packs/armes/CURRENT Normal file
View File

@@ -0,0 +1 @@
MANIFEST-000053

0
packs/armes/LOCK Normal file
View File

8
packs/armes/LOG Normal file
View File

@@ -0,0 +1,8 @@
2026/01/11-22:42:43.362261 7fd478fff6c0 Recovering log #51
2026/01/11-22:42:43.390767 7fd478fff6c0 Delete type=0 #51
2026/01/11-22:42:43.398607 7fd478fff6c0 Delete type=3 #49
2026/01/11-22:44:34.484930 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.484958 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.491990 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.498603 7fd4627fc6c0 Manual compaction at level-0 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.498641 7fd4627fc6c0 Manual compaction at level-1 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)

8
packs/armes/LOG.old Normal file
View File

@@ -0,0 +1,8 @@
2026/01/11-22:38:18.324856 7fd4637fe6c0 Recovering log #47
2026/01/11-22:38:18.334440 7fd4637fe6c0 Delete type=3 #45
2026/01/11-22:38:18.334494 7fd4637fe6c0 Delete type=0 #47
2026/01/11-22:39:21.508921 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.508963 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.514976 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.515163 7fd4627fc6c0 Manual compaction at level-0 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.515223 7fd4627fc6c0 Manual compaction at level-1 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)

BIN
packs/armes/MANIFEST-000053 Normal file

Binary file not shown.

View File

@@ -1,8 +1,8 @@
{"name":"Bracelets de cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":1,"enc":0,"valeur":0.3,"description":"<p>Ils prot&egrave;gent l&rsquo;avant-bras, du dessus de la main jusqu&rsquo;au coude. Souvent grav&eacute;s de motifs d&eacute;coratifs, ils se composent de lamelles de cuir rigidifi&eacute;es et nou&eacute;es &agrave; l&rsquo;aide de gros lacets.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"4YGPuZ813BWQaNbq"}
{"_id":"4YGPuZ813BWQaNbq","name":"Bracelets de cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"piecearmure","equipe":false,"protection":1,"enc":0,"valeur":0.3,"description":"<p>Ils prot&egrave;gent l&rsquo;avant-bras, du dessus de la main jusqu&rsquo;au coude. Souvent grav&eacute;s de motifs d&eacute;coratifs, ils se composent de lamelles de cuir rigidifi&eacute;es et nou&eacute;es &agrave; l&rsquo;aide de gros lacets.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Cuir lamellé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":6,"enc":3,"valeur":2,"description":"<p>Des lamelles de cuir plus rigides sont coll&eacute;es et cousues sur la version pr&eacute;c&eacute;dente. La poitrine, le ventre, les omoplates et les coudes sont particuli&egrave;rement renforc&eacute;s. Des pi&egrave;ces semi-rigides, bomb&eacute;es, enserrent parfois les &eacute;paules.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"9WReqm0CdcCGnsNc"}
{"name":"Veste de cuir","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":2,"enc":0,"valeur":0.3,"description":"<p>Il s&rsquo;agit autant d&rsquo;un v&ecirc;tement courant que d&rsquo;une armure. La veste recouvre le torse, le ventre et les hanches. Certaines poss&egrave;dent des manches, courtes ou longues. Le cuir souple et fin ne restreint gu&egrave;re la libert&eacute; de mouvement.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"JP6OeUflKfg4WiyJ"}
{"name":"Cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":4,"enc":1,"valeur":1,"description":"<p>La veste devient plus &eacute;paisse et doubl&eacute;e de tissu. Des pi&egrave;ces de m&eacute;tal, des rivets plats en cuivre, en bronze ou en fer apportent une protection suppl&eacute;mentaire aux zones les plus expos&eacute;es. Le col monte parfois jusque sous le menton.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"MFH09YNp4uGL8eCE"}
{"name":"Epaullières de fourrure","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":2,"enc":1,"valeur":0.6,"description":"<p>Il s&rsquo;agit d&rsquo;un grand morceau de peau encerclant les &eacute;paules et le cou. En g&eacute;n&eacute;ral, cette fourrure est fix&eacute;e sur une cape. En plus de la protection qu&rsquo;elle apporte, sa nature (loup, ours, vison, etc.. ) donne une indication sur le statut du guerrier.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"NK7IF0Cr4oOPCZVq"}
{"name":"Jambières de cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":2,"enc":1,"valeur":0.4,"description":"<p>Nou&eacute;es autour des cuisses ou des mollets, elles prot&egrave;gent le combattant des coups visant ses jambes. Ici aussi, les artisans essaient souvent d&rsquo;orner ces pi&egrave;ces d&rsquo;armure de d&eacute;corations cisel&eacute;es dans la mati&egrave;re.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"WergjG9QE8SxlAGr"}
{"name":"Casque","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":3,"enc":2,"valeur":2,"description":"<p>Le mod&egrave;le le plus fr&eacute;quent comporte un nasal et des &laquo; lunettes &raquo; qui prot&egrave;gent les yeux. Sur une base de bol en m&eacute;tal, des lamelles de renforts sont rivet&eacute;es afin d&rsquo;assurer la rigidit&eacute; de l&rsquo;ensemble. L&rsquo;int&eacute;rieur est doubl&eacute; de cuir et de tissu afin d&rsquo;amortir les chocs.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"ozRBKdYJtbnk87gO"}
{"_id":"NK7IF0Cr4oOPCZVq","name":"Epaulières de fourrure","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"piecearmure","equipe":false,"protection":2,"enc":1,"valeur":0.6,"description":"<p>Il s&rsquo;agit d&rsquo;un grand morceau de peau encerclant les &eacute;paules et le cou. En g&eacute;n&eacute;ral, cette fourrure est fix&eacute;e sur une cape. En plus de la protection qu&rsquo;elle apporte, sa nature (loup, ours, vison, etc.. ) donne une indication sur le statut du guerrier.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"_id":"WergjG9QE8SxlAGr","name":"Jambières de cuir renforcé","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"piecearmure","equipe":false,"protection":2,"enc":1,"valeur":0.4,"description":"<p>Nou&eacute;es autour des cuisses ou des mollets, elles prot&egrave;gent le combattant des coups visant ses jambes. Ici aussi, les artisans essaient souvent d&rsquo;orner ces pi&egrave;ces d&rsquo;armure de d&eacute;corations cisel&eacute;es dans la mati&egrave;re.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"_id":"ozRBKdYJtbnk87gO","name":"Casque","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"piecearmure","equipe":false,"protection":3,"enc":2,"valeur":2,"description":"<p>Le mod&egrave;le le plus fr&eacute;quent comporte un nasal et des &laquo; lunettes &raquo; qui prot&egrave;gent les yeux. Sur une base de bol en m&eacute;tal, des lamelles de renforts sont rivet&eacute;es afin d&rsquo;assurer la rigidit&eacute; de l&rsquo;ensemble. L&rsquo;int&eacute;rieur est doubl&eacute; de cuir et de tissu afin d&rsquo;amortir les chocs.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Cotte de mailles","type":"armure","img":"icons/svg/item-bag.svg","data":{"categorie":"armure","equipe":false,"protection":9,"enc":5,"valeur":8,"description":"<p>Compos&eacute;e de centaines d&rsquo;anneaux de fer rivet&eacute;s, la cotte de mailles prot&egrave;ge les m&ecirc;mes parties du corps qu&rsquo;une veste. Elle ne descend jamais sur les jambes car cela restreint trop la mobilit&eacute; des combattants. Ceux-ci enfilent dessous une chemise molletonn&eacute;e ou une fine veste de cuir.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"qUXBrstsh5Oo8FEx"}

BIN
packs/armures/000040.ldb Normal file

Binary file not shown.

0
packs/armures/000055.log Normal file
View File

1
packs/armures/CURRENT Normal file
View File

@@ -0,0 +1 @@
MANIFEST-000053

0
packs/armures/LOCK Normal file
View File

8
packs/armures/LOG Normal file
View File

@@ -0,0 +1,8 @@
2026/01/11-22:42:43.415431 7fd4637fe6c0 Recovering log #51
2026/01/11-22:42:43.448752 7fd4637fe6c0 Delete type=0 #51
2026/01/11-22:42:43.448972 7fd4637fe6c0 Delete type=3 #49
2026/01/11-22:44:34.511453 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.511476 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.517400 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.523930 7fd4627fc6c0 Manual compaction at level-0 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.523965 7fd4627fc6c0 Manual compaction at level-1 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)

8
packs/armures/LOG.old Normal file
View File

@@ -0,0 +1,8 @@
2026/01/11-22:38:18.336786 7fd462ffd6c0 Recovering log #47
2026/01/11-22:38:18.346917 7fd462ffd6c0 Delete type=3 #45
2026/01/11-22:38:18.347006 7fd462ffd6c0 Delete type=0 #47
2026/01/11-22:39:21.528136 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.528192 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.534692 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.541091 7fd4627fc6c0 Manual compaction at level-0 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.541131 7fd4627fc6c0 Manual compaction at level-1 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/blessures/000040.ldb Normal file

Binary file not shown.

View File

1
packs/blessures/CURRENT Normal file
View File

@@ -0,0 +1 @@
MANIFEST-000053

0
packs/blessures/LOCK Normal file
View File

8
packs/blessures/LOG Normal file
View File

@@ -0,0 +1,8 @@
2026/01/11-22:42:43.343711 7fd478fff6c0 Recovering log #51
2026/01/11-22:42:43.360165 7fd478fff6c0 Delete type=0 #51
2026/01/11-22:42:43.360231 7fd478fff6c0 Delete type=3 #49
2026/01/11-22:44:34.492077 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.492100 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.498484 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.498613 7fd4627fc6c0 Manual compaction at level-0 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.498635 7fd4627fc6c0 Manual compaction at level-1 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)

8
packs/blessures/LOG.old Normal file
View File

@@ -0,0 +1,8 @@
2026/01/11-22:38:18.312395 7fd463fff6c0 Recovering log #47
2026/01/11-22:38:18.322989 7fd463fff6c0 Delete type=3 #45
2026/01/11-22:38:18.323061 7fd463fff6c0 Delete type=0 #47
2026/01/11-22:39:21.501777 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.501807 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.508771 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.515149 7fd4627fc6c0 Manual compaction at level-0 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.515211 7fd4627fc6c0 Manual compaction at level-1 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{"name":"Superstition","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Le monde regorge de signes divins, de d&eacute;tails naturels qui refl&egrave;tent le sens cach&eacute; des choses. Le personnage sait rep&eacute;rer et interpr&eacute;ter ces &eacute;l&eacute;ments. Il s&rsquo;agit en g&eacute;n&eacute;ral d&rsquo;y voir des conditions favorables ou d&eacute;favorables pour le moment pr&eacute;sent, mais aussi des messages d&rsquo;avertissement envoy&eacute;s par les dieux. Les habitants de la Scandia sont des gens tr&egrave;s superstitieux et ils tendent &agrave; voir partout des pr&eacute;sages significatifs. Le Meneur de Jeu pourra en profiter pour glisser quelques indices obscurs sur la situation actuelle des h&eacute;ros qui, dans l&rsquo;id&eacute;al, r&eacute;v&egrave;leront leur plein sens une fois qu&rsquo;il sera trop tard.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"26CQEICWZs8Gw4Xi"}
{"name":"Intimidation","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Par l&rsquo;interm&eacute;diaire de la menace verbale ou physique, le personnage peut contraindre quelqu&rsquo;un &agrave; lui ob&eacute;ir. Une tentative d&rsquo;Intimidation n&eacute;cessite un test en opposition. La cible tente de r&eacute;sister gr&acirc;ce &agrave; sa T&eacute;nacit&eacute; et &agrave; sa D&eacute;fense mentale.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"3vspCnDuVMqQDuPe"}
{"name":"Négociation","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Il s&rsquo;agit de la capacit&eacute; &agrave; mener une discussion vers un compromis acceptable par les deux parties. N&eacute;gociation est une comp&eacute;tence utile aux diplomates de la Scandia, mais aussi aux marchands qui peuvent mener &agrave; bien leur marchandage lors de l&rsquo;achat et de la vente de leurs produits.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"4k4NLNlEG7jZEPK6"}
{"name":"Jeu","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>le personnage conna&icirc;t les r&egrave;gles et sait pratiquer la plupart des jeux pris&eacute;s par les habitants de la Scandia, comme les jeux de d&eacute;s ou le <em>hnefatafl</em>. Cette comp&eacute;tence s&rsquo;utilise g&eacute;n&eacute;ralement avec des tests en opposition mettant en concurrence les niveaux de ma&icirc;trise respectifs des joueurs. Mais elle permet &eacute;galement de tricher. Dans ce cas, les autres participants peuvent tenter un test en opposition de Perception + Vigilance. Si le tricheur est d&eacute;masqu&eacute;, attendezvous &agrave; des cons&eacute;quences&hellip; muscl&eacute;es.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"6OMBvnSFqVHqd36u"}
{"_id":"6OMBvnSFqVHqd36u","name":"Jeux","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>le personnage conna&icirc;t les r&egrave;gles et sait pratiquer la plupart des jeux pris&eacute;s par les habitants de la Scandia, comme les jeux de d&eacute;s ou le <em>hnefatafl</em>. Cette comp&eacute;tence s&rsquo;utilise g&eacute;n&eacute;ralement avec des tests en opposition mettant en concurrence les niveaux de ma&icirc;trise respectifs des joueurs. Mais elle permet &eacute;galement de tricher. Dans ce cas, les autres participants peuvent tenter un test en opposition de Perception + Vigilance. Si le tricheur est d&eacute;masqu&eacute;, attendezvous &agrave; des cons&eacute;quences&hellip; muscl&eacute;es.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Artisanat","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":true,"categorie":"generale","specialisation":"","description":"<p>Cette comp&eacute;tence regroupe l&rsquo;ensemble des activit&eacute;s manuelles permettant de confectionner les objets du quotidien. La plupart des habitants de la Scandia pratiquent plus ou moins ces techniques et se r&eacute;v&egrave;lent autonomes lorsqu&rsquo;il s&rsquo;agit de fabriquer ou de r&eacute;parer des ustensiles basiques. Un niveau Confirm&eacute; permet d&rsquo;exercer son artisanat en professionnel et de gagner sa vie avec cette activit&eacute;. Les meilleurs artisans b&eacute;n&eacute;ficient d&rsquo;une renomm&eacute;e et d&rsquo;une consid&eacute;ration importantes. L&rsquo;utilisation de cette comp&eacute;tence correspond en g&eacute;n&eacute;ral &agrave; un test &eacute;tendu. Sp&eacute;cialisations possibles : cordonnerie, ferronnerie, chaudronnerie, charpenterie, tissage, tannerie, corderie, taille de pierre, poterie, sellerie, etc&hellip;</p>\n<p>Il existe cependant deux sp&eacute;cialisations qui m&eacute;ritent que l&rsquo;on s&rsquo;y attarde, tant elles occupent une place importante dans la soci&eacute;t&eacute; des Terres du Nord.</p>\n<p><strong>Construction navale : </strong>cette sp&eacute;cialisation permet de mener &agrave; bien la construction des diff&eacute;rents types de navires sillonnant les flots de la Scandia. Le personnage ma&icirc;trise aussi bien les secrets de l&rsquo;architecture navale que les techniques de la charpenterie de marine, mais il sait tout autant diriger les artisans qui vont r&eacute;aliser le bateau selon ses instructions. Les meilleurs dans ce domaine sont tr&egrave;s recherch&eacute;s par tous les jarls et souverains des Royaumes Nordiques.</p>\n<p><strong>Forge : </strong>l&rsquo;art de la forge n&eacute;cessite des connaissances sp&eacute;cifiques dont certaines confinent au secret transmis uniquement d&rsquo;un ma&icirc;tre &agrave; son disciple. Un personnage Novice sait fabriquer des objets usuels, simples, utiles au quotidien (clous, t&ecirc;tes d&rsquo;outils, anneaux&hellip;) Une fois atteint le niveau Confirm&eacute;, il peut commencer &agrave; forger des pi&egrave;ces plus complexes (fer pour les chevaux, serrures, ferrures d&rsquo;ornements, etc&hellip;) ainsi que des armes et des armures. Si certains exercent leur profession de mani&egrave;re itin&eacute;rante, un forgeron doit tout de m&ecirc;me disposer d&rsquo;un atelier et d&rsquo;outils appropri&eacute;s afin de pouvoir mettre en oeuvre sa comp&eacute;tence. Un forgeron pratique l&rsquo;un des m&eacute;tiers les plus respect&eacute;s parmi les artisans.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"947IgofSqazA1CoD"}
{"name":"Survie","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Le personnage se sent &agrave; l&rsquo;aise dans les milieux naturels et sait comment y vivre en harmonie avec la nature. Cette comp&eacute;tence permet de trouver de quoi se nourrir et s&rsquo;abreuver, un endroit pour s&rsquo;abriter, de faire du feu et de se prot&eacute;ger des conditions climatiques extr&ecirc;mes comme le froid d&rsquo;une nuit d&rsquo;hiver. La Survie recouvre &eacute;galement la chasse et le pistage, mais aussi le sens de l&rsquo;orientation et les premiers soins.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"9AHUm9FLajXTkEZJ"}
{"name":"Traditions","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Gr&acirc;ce &agrave; cette comp&eacute;tence, le personnage se souvient des r&egrave;gles, des coutumes et des lois qui ont cours dans la r&eacute;gion, voire la cit&eacute;, o&ugrave; il se trouve. Si le syst&egrave;me juridique reste assez semblable dans toute la Scandia, il existe souvent quelques variations locales dans l&rsquo;application des peines encourues pour un m&ecirc;me crime. De m&ecirc;me, les us et coutumes de certains endroits changent subtilement par rapport aux habitudes des h&eacute;ros et peuvent provoquer des situations des plus embarrassantes. Un personnage Expert dans cette comp&eacute;tence peut endosser le r&ocirc;le de crieur des lois lors d&rsquo;un thing.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"CIfheBgF5plSgqYT"}
@@ -9,10 +9,10 @@
{"name":"Esquive","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Le personnage anticipe les attaques port&eacute;es contre lui et se met hors de port&eacute;e des coups ou des projectiles qui le visent. Bien qu&rsquo;elle s&rsquo;av&egrave;re surtout utile en combat, cette comp&eacute;tence sert &eacute;galement &agrave; &eacute;viter une collision qui menace le h&eacute;ros, comme un attelage lanc&eacute; au galop vers lui, ou un &eacute;boulement de gros rochers. Que l&rsquo;esquive soit r&eacute;ussie ou pas, le personnage se retrouve toujours &agrave; terre, &agrave; quelques pas de sa position initiale (le joueur choisit la direction).</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"MLCAKgi8wRLwlINc"}
{"name":"Commerce","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Il s&rsquo;agit de la connaissance des lois de l&rsquo;offre et de la demande. Le n&eacute;gociant sait &eacute;valuer les choses lors d&rsquo;un troc, ou d&rsquo;une transaction s&rsquo;il utilise de la monnaie. Il conna&icirc;t &eacute;galement les points d&rsquo;approvisionnement pour les diff&eacute;rentes marchandises produites dans la Scandia, et les endroits o&ugrave; il a le plus de chance d&rsquo;en tirer le meilleur profit. Les dates et les lieux o&ugrave; se d&eacute;roulent les foires annuelles n&rsquo;ont pas de secret pour lui. Enfin, cette comp&eacute;tence g&egrave;re tout l&rsquo;aspect intendance de cette activit&eacute;.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"OlELPb9hGkbQNnff"}
{"name":"Chercher","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Cette comp&eacute;tence entre en jeu lorsque le personnage fouille un endroit particulier. Elle permet de r&eacute;v&eacute;ler les &eacute;l&eacute;ments cach&eacute;s dans le d&eacute;cor ou les indices dissimul&eacute;s. Contrairement &agrave; la comp&eacute;tence Vigilance, Chercher n&eacute;cessite une d&eacute;marche active de la part du h&eacute;ros.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"QysZVyRFax7P0mko"}
{"name":"Médecine","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Cette comp&eacute;tence correspond &agrave; la connaissance de l&rsquo;ensemble des pratiques m&eacute;dicales de la Scandia. Elle permet d&rsquo;&eacute;tablir un diagnostic, de soigner les maladies les plus courantes, de traiter les blessures, de r&eacute;duire une fracture, d&rsquo;accompagner un accouchement, mais aussi de d&eacute;terminer les causes probables d&rsquo;une mort inexpliqu&eacute;e. Les premiers soins sont quant &agrave; eux couverts par la comp&eacute;tence Survie, mais un personnage plus comp&eacute;tent en M&eacute;decine peut dans ce cas substituer cette discipline &agrave; un test de Survie.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"SEeAKWRSKO0HWt6Q"}
{"_id":"SEeAKWRSKO0HWt6Q","name":"Médecine*","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Cette comp&eacute;tence correspond &agrave; la connaissance de l&rsquo;ensemble des pratiques m&eacute;dicales de la Scandia. Elle permet d&rsquo;&eacute;tablir un diagnostic, de soigner les maladies les plus courantes, de traiter les blessures, de r&eacute;duire une fracture, d&rsquo;accompagner un accouchement, mais aussi de d&eacute;terminer les causes probables d&rsquo;une mort inexpliqu&eacute;e. Les premiers soins sont quant &agrave; eux couverts par la comp&eacute;tence Survie, mais un personnage plus comp&eacute;tent en M&eacute;decine peut dans ce cas substituer cette discipline &agrave; un test de Survie.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Herboristerie","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Il s&rsquo;agit de la connaissance des plantes, des herbes et des produits naturels permettant de concocter des rem&egrave;des, mais aussi des teintures ou encore des poisons de toutes sortes. Un personnage vers&eacute; dans cette discipline sait &eacute;galement reconna&icirc;tre les plantes rencontr&eacute;es en pleine nature, ainsi que d&eacute;terminer l&rsquo;endroit o&ugrave; il a le plus de chance de trouver une esp&egrave;ce sp&eacute;cifique.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"SHnTbcIm0JZN7UqU"}
{"name":"Natation","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Cette comp&eacute;tence repr&eacute;sente l&rsquo;aptitude &agrave; se mouvoir &agrave; la surface de l&rsquo;eau et &agrave; y demeurer en cas de chute depuis un navire. Le Meneur de Jeu peut appliquer un malus au test de Natation &eacute;gal &agrave; la valeur de protection de l&rsquo;armure port&eacute;e par l&rsquo;infortun&eacute;. Le nageur peut &eacute;galement se d&eacute;placer sous l&rsquo;eau ou plonger depuis les hauteurs vertigineuses des falaises avec de bonnes chances de r&eacute;ussite.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"VcfHyyT169aId8Td"}
{"name":"Langues","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":true,"categorie":"generale","specialisation":"","description":"<p>Tous les personnages parlent couramment le norrois (<em>dansk tunga</em>), la langue commune aux diff&eacute;rents royaumes de la Scandia. Chaque sp&eacute;cialisation dans cette comp&eacute;tence correspond &agrave; une langue particuli&egrave;re et &eacute;trang&egrave;re, ou &agrave; un patois local issu d&rsquo;une autre racine linguistique comme le finnois parl&eacute; par les tribus sames. Il est toujours possible de comprendre un dialecte tir&eacute; du norrois en r&eacute;ussissant, g&eacute;n&eacute;ralement, un test d&rsquo;Intellect contre un Seuil de R&eacute;ussite Moyen (14). Exemples de sp&eacute;cialisation : saxon, finnois, balte, germain&hellip;</p>","niveau":0,"niveauunrequis":true},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"ZhIYWUKuDV5hyuUA"}
{"_id":"ZhIYWUKuDV5hyuUA","name":"Langues*","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":true,"categorie":"generale","specialisation":"","description":"<p>Tous les personnages parlent couramment le norrois (<em>dansk tunga</em>), la langue commune aux diff&eacute;rents royaumes de la Scandia. Chaque sp&eacute;cialisation dans cette comp&eacute;tence correspond &agrave; une langue particuli&egrave;re et &eacute;trang&egrave;re, ou &agrave; un patois local issu d&rsquo;une autre racine linguistique comme le finnois parl&eacute; par les tribus sames. Il est toujours possible de comprendre un dialecte tir&eacute; du norrois en r&eacute;ussissant, g&eacute;n&eacute;ralement, un test d&rsquo;Intellect contre un Seuil de R&eacute;ussite Moyen (14). Exemples de sp&eacute;cialisation : saxon, finnois, balte, germain&hellip;</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"name":"Discrétion","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Le personnage sait se d&eacute;placer en silence, tout en profitant du moindre couvert afin de passer inaper&ccedil;u. Cette comp&eacute;tence permet &eacute;galement de se cacher efficacement ou de camoufler un objet sur soi ou dans le d&eacute;cor environnant.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"cKowAJGvHnY0GSOZ"}
{"name":"Art","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":true,"categorie":"generale","specialisation":"","description":"<p>Choisissez le domaine de pr&eacute;dilection du personnage. Il peut s&rsquo;agir d&rsquo;un simple don naturel, d&rsquo;une activit&eacute; professionnelle (pour un scalde), d&rsquo;un loisir&hellip; Cette comp&eacute;tence permet de cr&eacute;er une oeuvre d&rsquo;art originale ou d&rsquo;en reproduire une existante. Sp&eacute;cialisations possibles : orf&egrave;vrerie, chant, musique, danse, gravure, po&eacute;sie d&eacute;clam&eacute;e, flatterie, peinture, etc&hellip;</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"fcTMIpIMVc1cQASZ"}
{"name":"Larcins","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"generale","specialisation":"","description":"<p>Cette comp&eacute;tence recouvre les activit&eacute;s des filous des terres du Nord. Elle permet de crocheter une serrure, mais aussi de couper une bourse ou de subtiliser discr&egrave;tement des clefs. Dans ce deuxi&egrave;me cas, Larcins s&rsquo;emploie dans un test en opposition contre la Perception et la Vigilance de la cible.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"ggoWPfYoTx7QG5ae"}

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000053

View File

View File

@@ -0,0 +1,8 @@
2026/01/11-22:42:43.240973 7fd463fff6c0 Recovering log #51
2026/01/11-22:42:43.255834 7fd463fff6c0 Delete type=0 #51
2026/01/11-22:42:43.255892 7fd463fff6c0 Delete type=3 #49
2026/01/11-22:44:34.459907 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.459930 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.466013 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.472607 7fd4627fc6c0 Manual compaction at level-0 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.472642 7fd4627fc6c0 Manual compaction at level-1 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,8 @@
2026/01/11-22:38:18.238976 7fd4637fe6c0 Recovering log #47
2026/01/11-22:38:18.249505 7fd4637fe6c0 Delete type=3 #45
2026/01/11-22:38:18.249563 7fd4637fe6c0 Delete type=0 #47
2026/01/11-22:39:21.489412 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.489449 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.495431 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.515115 7fd4627fc6c0 Manual compaction at level-0 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.515177 7fd4627fc6c0 Manual compaction at level-1 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@@ -1,3 +1,3 @@
{"name":"Galdr","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"magique","specialisation":"","description":"<p>Le <em>galdr</em> utilise la puissance divine de la voix pour la magie. Tout pratiquant de la magie peut l&rsquo;utiliser. Il n&rsquo;est pas besoin d&rsquo;avoir une belle voix ou de chanter juste, mais de lancer des incantations.</p>","niveau":0,"niveauunrequis":true},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"RAhkadJOfEnVBLpy"}
{"name":"Runes","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"magique","specialisation":"","description":"<p>Cette comp&eacute;tence permet de lire et de conna&icirc;tre le sens de chacune des vingt-quatre runes. Mais surtout elles donnent des indications occultes sur la r&eacute;alit&eacute; du monde et permettent de lancer des sorts de protection, mal&eacute;diction et gu&eacute;rison, &agrave; ceux qui savent s&rsquo;en servir.</p>","niveau":0,"niveauunrequis":true},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"UoWkSxLdXhZQinnc"}
{"name":"Sejdr","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"magique","specialisation":"","description":"<p>Il s&rsquo;agit l&agrave; de l&rsquo;antique art magique enseign&eacute; &agrave; Odhinn m&ecirc;me, par Freyja, la sorcellerie. Les femmes sont plus nombreuses &agrave; en pratiquer certains sorts car ils sont li&eacute;s &agrave; des travaux consid&eacute;r&eacute;s comme purement f&eacute;minins (filer de la laine, mettre des v&ecirc;tements de c&eacute;r&eacute;monie pour les visions, chevaucher un autre personnage). Certains hommes pratiquent le Sejdr sans s&rsquo;en soucier, d&rsquo;autres s&eacute;lectionnent les sorts les moins compromettants et les compl&egrave;tent avec les Runes ou le Galdr.</p>","niveau":0,"niveauunrequis":true},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{},"_id":"mIBiTKqfqiiepeyz"}
{"_id":"RAhkadJOfEnVBLpy","name":"Galdr*","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"magique","specialisation":"","description":"<p>Le <em>galdr</em> utilise la puissance divine de la voix pour la magie. Tout pratiquant de la magie peut l&rsquo;utiliser. Il n&rsquo;est pas besoin d&rsquo;avoir une belle voix ou de chanter juste, mais de lancer des incantations.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"_id":"UoWkSxLdXhZQinnc","name":"Runes*","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"magique","specialisation":"","description":"<p>Cette comp&eacute;tence permet de lire et de conna&icirc;tre le sens de chacune des vingt-quatre runes. Mais surtout elles donnent des indications occultes sur la r&eacute;alit&eacute; du monde et permettent de lancer des sorts de protection, mal&eacute;diction et gu&eacute;rison, &agrave; ceux qui savent s&rsquo;en servir.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}
{"_id":"mIBiTKqfqiiepeyz","name":"Sejdr*","type":"competence","img":"systems/fvtt-yggdrasill/images/icons/icon_skill.png","data":{"isspecialisation":false,"categorie":"magique","specialisation":"","description":"<p>Il s&rsquo;agit l&agrave; de l&rsquo;antique art magique enseign&eacute; &agrave; Odhinn m&ecirc;me, par Freyja, la sorcellerie. Les femmes sont plus nombreuses &agrave; en pratiquer certains sorts car ils sont li&eacute;s &agrave; des travaux consid&eacute;r&eacute;s comme purement f&eacute;minins (filer de la laine, mettre des v&ecirc;tements de c&eacute;r&eacute;monie pour les visions, chevaucher un autre personnage). Certains hommes pratiquent le Sejdr sans s&rsquo;en soucier, d&rsquo;autres s&eacute;lectionnent les sorts les moins compromettants et les compl&egrave;tent avec les Runes ou le Galdr.</p>","niveau":0,"niveauunrequis":false},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"feLeZcbqgHkVzfJP":3},"flags":{}}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More