Compare commits

..

32 Commits

Author SHA1 Message Date
c36f2d0116 Migration datamodels !
All checks were successful
Release Creation / build (release) Successful in 53s
2026-01-10 16:26:38 +01:00
690293c1c8 Migration datamodels ! 2026-01-10 16:23:45 +01:00
04e35228e4 Migration datamodels ! 2026-01-10 16:22:24 +01:00
ec2d5385c5 Migration datamodels ! 2026-01-10 16:06:12 +01:00
438caf3b1c Migration datamodels ! 2026-01-10 16:05:56 +01:00
627ccc707b Fouindry v13 support 2025-04-30 23:51:45 +02:00
fdf28c4978 Fix v12 Wasteland 2025-03-14 14:09:51 +01:00
e0eac58bc9 Fix v12 Wasteland 2025-03-14 14:08:17 +01:00
b463323fbe Add v12 support 2024-05-23 11:38:37 +02:00
bddf772c99 Add v12 support 2024-05-23 11:36:03 +02:00
a716a3b3d1 Enhance stats 2024-02-08 12:57:36 +01:00
5814ef41df First official release 2024-01-08 07:59:19 +01:00
c0fcbe278f First official release 2024-01-08 07:58:04 +01:00
94f7ef8f90 First official release 2024-01-08 07:56:51 +01:00
19409dd547 Import massif des compendiums 2024-01-06 14:05:47 +01:00
217df7ee10 Import massif des compendiums 2024-01-06 14:00:59 +01:00
9990545568 Update fiche creature 2023-12-06 10:14:26 +01:00
d2da332411 Add new translations 2023-12-05 11:12:36 +01:00
028e8bddac Add mutations + update compendiusm 2023-12-04 18:32:55 +01:00
5c889a5153 Fiche de creature 2023-12-02 09:03:58 +01:00
68689add33 Ajout pouvoirs et jets associés 2023-12-01 14:03:04 +01:00
4ed2bcd2ee Ajout pouvoirs et jets associés 2023-12-01 09:50:45 +01:00
0edf336d28 Ajout pouvoirs et jets associés 2023-11-30 22:17:31 +01:00
046cdf4fb2 Ajout pouvoirs et jets associés 2023-11-30 21:27:57 +01:00
cc0faec25e Ajout pouvoirs et jets associés 2023-11-30 18:50:32 +01:00
3419ddf8d6 Add charmes/tours management 2023-11-29 22:21:48 +01:00
5aa117b569 Add charmes/tours management 2023-11-29 22:21:29 +01:00
4b2dd20d49 Add charmes/tours management 2023-11-29 22:14:22 +01:00
3e394734ce Add charmes/tours management 2023-11-29 22:04:17 +01:00
06d0d9b24d Compendiums : Armes et protections 2023-11-28 22:27:34 +01:00
a3c5a9bfec Compendiums : Armes et protections 2023-11-28 22:27:31 +01:00
ca4b7f2803 Compendiums : Armes et protections 2023-11-28 21:42:31 +01:00
255 changed files with 17439 additions and 2869 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-wasteland/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-wasteland.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-wasteland.zip system.json README.md changelog.md assets/ lang/ 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-wasteland.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-wasteland'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-wasteland/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-wasteland.zip'
compatibility-minimum: '13'
compatibility-verified: '13'

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.history/ .history/
node_modules

View File

@@ -1,11 +1,14 @@
# Système Foundry pour Wasteland (French RPG, Titam France/Sombres Projets)
Système Foundry pour Wasteland (French RPG, Titam France/Sombres Projets)
## EN ## EN
Unofficial system for Wasteland (French version from Titam France). Unofficial system for Wasteland (French RPG from Titam France).
Books are mandatory to play and are available at : http://www.titam-france.fr Books are mandatory to play and are available at : http://www.titam-france.fr
`![alt text](https://www.lahiette.com/leratierbretonnien/wp-content/uploads/2024/01/screenshot-wasteland-01.webp "Wasteland")`
## FR ## FR
Système non-officiel pour le JDR Wasteland (Titam France). Système non-officiel pour le JDR Wasteland (Titam France).
@@ -16,8 +19,9 @@ Les livres du jeu sont nécessaires pour jouer, et sont disponibles ici : http:/
# Credits # Credits
Wasteland, le jeu de rôle de Sword & Sorcery, is a property of Titam France/Sombres Projets. Wasteland is a property of Titam France/Sombres Projets.
# Developmement # Developmement
LeRatierBretonnien Code, CSS and automations : LeRatierBretonnien
Compendiums : Pretre, LeRatierBretonnien

BIN
assets/icons/don.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
assets/icons/hubris.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

8
changelog.md Normal file
View File

@@ -0,0 +1,8 @@
# 12.0.1
- Fix v12 version
# 11.0.22
- Version initiale
-

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/wasteland.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;

View File

@@ -1,24 +1,27 @@
{ {
"ACTOR": { "TYPES": {
"TypePersonnage": "Personnage", "Item": {
"TypePNJ": "PNJ" "arme": "Arme",
}, "competence": "Compétence",
"protection": "Protection",
"ITEM": { "monnaie": "Monnaie",
"TypeArme": "Arme", "equipement": "Equipement",
"TypeCompetence": "Compétence", "don": "Don",
"TypeProtection": "Protection", "hubris": "Hubris",
"TypeMonnaie": "Monnaie", "capacite": "Capacité",
"TypeEquipement": "Equipement", "origine": "Origine",
"TypeCapacite": "Capacité", "heritage": "Héritage",
"TypeOrigine": "Origine", "metier": "Métier",
"TypeHeritage": "Héritage", "bouclier": "Bouclier",
"TypeMetier": "Métier", "pouvoir": "Pouvoir",
"TypeBouclier": "Bouclier", "artifex": "Artifex",
"TypePouvoir": "Pouvoir", "mutation": "Mutation",
"TypeArtifex": "Artifex", "charme": "Charme",
"TypeMutation": "Mutation", "peuple": "Peuple"
"TypeCharme": "Charme", },
"TypePeuple": "Peuple" "Actor": {
"personnage": "Personnage",
"creature": "Créature"
}
} }
} }

1201
less/actor-styles.less Normal file

File diff suppressed because it is too large Load Diff

859
less/chat-styles.less Normal file
View File

@@ -0,0 +1,859 @@
/* ============================================ */
/* WASTELAND CHAT MESSAGE STYLES */
/* Post-Apocalyptic Theme */
/* ============================================ */
.wasteland-chat-result {
background: linear-gradient(135deg, rgba(50, 40, 30, 0.95) 0%, rgba(30, 25, 20, 0.95) 100%);
border: 2px solid #8b7355;
border-radius: 2px;
overflow: hidden;
font-family: "Charlemagne", serif;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.1);
position: relative;
// Effet de texture sale/usée
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
opacity: 0.5;
}
.chat-result-header {
background: linear-gradient(135deg, #3d2f1f 0%, #2a1f15 100%);
border-bottom: 2px solid #8b7355;
padding: 0.5rem;
display: flex;
align-items: center;
gap: 0.625rem;
position: relative;
box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.4);
// Effet rouille sur le bord
&::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg,
transparent 0%,
#6a0606 20%,
#8b7355 40%,
#6a0606 60%,
transparent 100%
);
}
.actor-icon {
width: 48px;
height: 48px;
border-radius: 2px;
border: 2px solid #8b7355;
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
filter: contrast(1.1) saturate(0.9);
}
.header-info {
flex: 1;
.actor-name {
margin: 0;
color: #e8dcc4;
font-size: 1.1rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
font-family: "Charlemagne", serif;
letter-spacing: 0.5px;
}
.action-title {
color: #c9a86a;
font-size: 0.9rem;
margin-top: 0.125rem;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
font-style: italic;
i {
margin-right: 0.25rem;
color: #8b7355;
}
}
}
}
.result-main {
background: linear-gradient(180deg, rgba(230, 220, 200, 0.9) 0%, rgba(210, 200, 180, 0.9) 100%);
padding: 0.25rem 0.5rem;
border-bottom: 1px solid rgba(139, 115, 85, 0.5);
position: relative;
&.damage {
background: linear-gradient(180deg, rgba(200, 180, 160, 0.95) 0%, rgba(180, 160, 140, 0.95) 100%);
}
.result-display {
display: flex;
justify-content: space-around;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.1875rem;
.dice-result,
.total-result,
.difficulty {
text-align: center;
flex: 1;
background: linear-gradient(180deg, rgba(50, 40, 30, 0.7) 0%, rgba(40, 30, 20, 0.8) 100%);
padding: 0.25rem 0.375rem;
border-radius: 2px;
border: 2px solid #8b7355;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3);
i {
color: #c9a86a;
font-size: 1rem;
display: block;
margin-bottom: 0.125rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
span {
display: block;
font-weight: bold;
}
.dice-value,
.total-value,
.difficulty-value {
font-size: 1.5rem;
color: #e8dcc4;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
font-weight: bold;
line-height: 1.2;
}
.total-label,
.difficulty-label {
font-size: 0.75rem;
color: #c9a86a;
text-transform: uppercase;
font-weight: bold;
line-height: 1.1;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
}
}
.damage-display {
.damage-total {
text-align: center;
background: linear-gradient(180deg, rgba(60, 50, 40, 0.8) 0%, rgba(50, 40, 30, 0.9) 100%);
padding: 0.5rem;
border-radius: 2px;
border: 2px solid #8b7355;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6), 0 2px 4px rgba(0, 0, 0, 0.4);
i {
color: #c9a86a;
font-size: 1.2rem;
display: block;
margin-bottom: 0.25rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.damage-label {
display: block;
font-size: 0.85rem;
color: #c9a86a;
text-transform: uppercase;
font-weight: bold;
margin-bottom: 0.25rem;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.damage-value {
display: block;
font-size: 2rem;
color: #e8dcc4;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9), 0 0 8px rgba(200, 0, 0, 0.3);
font-weight: bold;
line-height: 1.2;
}
}
}
.result-badge-container {
display: flex;
justify-content: center;
margin-top: 0.25rem;
.result-badge {
padding: 0.25rem 0.75rem;
border-radius: 2px;
font-weight: bold;
font-size: 0.9rem;
text-transform: uppercase;
text-align: center;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.2);
border: 1px solid rgba(0, 0, 0, 0.4);
i {
margin-right: 0.375rem;
}
&.heroique {
background: linear-gradient(135deg, #c9a86a 0%, #8b7355 100%);
color: #1a1a1a;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5);
border-color: #8b7355;
}
&.success {
background: linear-gradient(135deg, #6b8e23 0%, #4a6017 100%);
color: #e8dcc4;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
border-color: #4a6017;
}
&.failure {
background: linear-gradient(135deg, #5a4a3a 0%, #3a2a1a 100%);
color: #c9a86a;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
border-color: #3a2a1a;
}
&.dramatique {
background: linear-gradient(135deg, #4a0404 0%, #2a0202 100%);
color: #e8dcc4;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
border-color: #6a0606;
}
}
}
}
.result-details {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(70, 60, 50, 0.6) 0%, rgba(60, 50, 40, 0.7) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.3);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
.details-section {
display: flex;
flex-direction: column;
gap: 0.1875rem;
.detail-row {
display: flex;
justify-content: space-between;
padding: 0.1875rem 0.375rem;
background: linear-gradient(90deg, rgba(230, 220, 200, 0.6) 0%, rgba(210, 200, 180, 0.5) 100%);
border-radius: 1px;
font-size: 0.85rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
border-left: 2px solid #8b7355;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
&.bonus {
background: linear-gradient(90deg, rgba(200, 220, 180, 0.7) 0%, rgba(180, 200, 160, 0.6) 100%);
border-left-color: #6b8e23;
}
&.malus {
background: linear-gradient(90deg, rgba(220, 180, 160, 0.7) 0%, rgba(200, 160, 140, 0.6) 100%);
border-left-color: #8b4513;
}
&.rune {
background: linear-gradient(90deg, rgba(190, 180, 210, 0.7) 0%, rgba(170, 160, 190, 0.6) 100%);
border-left-color: #6a5acd;
}
.detail-label {
color: #2a1f15;
font-weight: bold;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.detail-value {
color: #2a1f15;
font-weight: 500;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
}
}
}
.result-effects {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(80, 70, 60, 0.5) 0%, rgba(70, 60, 50, 0.6) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.4);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
.effect-success,
.effect-warning,
.effect-failure,
.effect-damage,
.effect-heroic {
padding: 0.3125rem 0.5rem;
margin-bottom: 0.3125rem;
background: linear-gradient(90deg, rgba(230, 220, 200, 0.8) 0%, rgba(210, 200, 180, 0.7) 100%);
border-radius: 1px;
border-left: 3px solid;
font-size: 0.85rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 2px rgba(0, 0, 0, 0.3);
i {
margin-right: 0.375rem;
}
&:last-child {
margin-bottom: 0;
}
}
.effect-success {
border-left-color: #6b8e23;
color: #2a1f15;
}
.effect-heroic {
border-left-color: #c9a86a;
color: #2a1f15;
font-weight: bold;
background: linear-gradient(90deg, rgba(201, 168, 106, 0.3) 0%, rgba(230, 220, 200, 0.8) 100%);
}
.effect-warning {
border-left-color: #d97706;
color: #2a1f15;
}
.effect-failure {
border-left-color: #8b4513;
color: #2a1f15;
}
.effect-damage {
border-left-color: #6a0606;
color: #2a1f15;
background: linear-gradient(90deg, rgba(200, 100, 100, 0.3) 0%, rgba(210, 200, 180, 0.7) 100%);
}
}
.damage-button-section {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(100, 70, 60, 0.5) 0%, rgba(80, 60, 50, 0.6) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.5);
.chat-card-button {
width: 100%;
padding: 0.5rem;
background: linear-gradient(to bottom, #6a0606 0%, #4a0404 100%);
border: 2px solid #8b7355;
border-radius: 2px;
color: #e8dcc4;
font-size: 0.9rem;
font-weight: bold;
font-family: "Charlemagne", serif;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
&:hover {
background: linear-gradient(to bottom, #8a0808 0%, #5a0505 100%);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2);
border-color: #c9a86a;
}
i {
margin-right: 0.5rem;
}
}
}
.predilection-reroll-section {
padding: 0.75rem;
background: linear-gradient(135deg, rgba(139, 115, 85, 0.3) 0%, rgba(100, 80, 60, 0.4) 100%);
border: 2px solid #8b7355;
border-radius: 2px;
margin: 0.5rem;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
// Cacher toute icône de dé qui pourrait apparaître par erreur
.fa-dice {
display: none;
}
.predilection-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(139, 115, 85, 0.5);
i {
color: #c9a86a;
font-size: 1.1rem;
}
.predilection-title {
color: #e8dcc4;
font-weight: bold;
font-size: 1rem;
font-family: "Charlemagne", serif;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
}
.predilection-results {
display: flex;
gap: 0.75rem;
margin-bottom: 0.75rem;
.predilection-roll {
flex: 1;
padding: 0.5rem;
background: linear-gradient(90deg, rgba(230, 220, 200, 0.4) 0%, rgba(210, 200, 180, 0.3) 100%);
border-radius: 2px;
border-left: 2px solid #8b7355;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
position: relative;
&.kept {
background: linear-gradient(90deg, rgba(200, 220, 180, 0.6) 0%, rgba(180, 200, 160, 0.5) 100%);
border-left-color: #6b8e23;
box-shadow: 0 0 8px rgba(107, 142, 35, 0.3);
}
.roll-label {
color: #2a1f15;
font-weight: bold;
font-size: 0.75rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.roll-value {
color: #2a1f15;
font-weight: 700;
font-size: 1rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
i.fa-check-circle {
color: #6b8e23;
font-size: 1.1rem;
margin-left: 0.25rem;
}
}
}
.predilection-kept {
padding: 0.5rem;
background: linear-gradient(90deg, rgba(200, 220, 180, 0.7) 0%, rgba(180, 200, 160, 0.6) 100%);
border: 1px solid #6b8e23;
border-radius: 2px;
text-align: center;
color: #2a1f15;
font-size: 0.95rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
i {
color: #6b8e23;
}
.kept-label {
font-weight: 600;
}
strong {
font-size: 1.1rem;
font-weight: 700;
}
}
}
.predilection-section {
padding: 0.5rem;
background: linear-gradient(180deg, rgba(100, 90, 70, 0.5) 0%, rgba(80, 70, 60, 0.6) 100%);
border-top: 1px solid rgba(139, 115, 85, 0.5);
.chat-card-button {
width: 100%;
padding: 0.5rem;
background: linear-gradient(to bottom, #5a4a3a 0%, #3a2a1a 100%);
border: 2px solid #8b7355;
border-radius: 2px;
color: #e8dcc4;
font-size: 0.9rem;
font-weight: bold;
font-family: "Charlemagne", serif;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
&:hover {
background: linear-gradient(to bottom, #6a5a4a 0%, #4a3a2a 100%);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2);
border-color: #c9a86a;
}
i {
margin-right: 0.5rem;
}
}
}
}
/* ============================================ */
/* WASTELAND ITEM CHAT POSTS */
/* ============================================ */
.wasteland-chat-item {
background: linear-gradient(135deg, rgba(50, 40, 30, 0.95) 0%, rgba(30, 25, 20, 0.95) 100%);
border: 2px solid #8b7355;
border-radius: 2px;
overflow: hidden;
font-family: "Charlemagne", serif;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.1);
position: relative;
// Effet de texture sale/usée
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
opacity: 0.5;
}
.chat-item-header {
background: linear-gradient(135deg, #3d2f1f 0%, #2a1f15 100%);
border-bottom: 2px solid #8b7355;
padding: 0.5rem;
display: flex;
align-items: center;
gap: 0.625rem;
position: relative;
box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.4);
// Effet rouille sur le bord
&::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg,
transparent 0%,
#6a0606 20%,
#8b7355 40%,
#6a0606 60%,
transparent 100%
);
}
.item-icon {
width: 48px;
height: 48px;
border-radius: 2px;
border: 2px solid #8b7355;
object-fit: cover;
flex-shrink: 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
filter: contrast(1.1) saturate(0.9);
}
.header-info {
flex: 1;
.item-name {
margin: 0;
color: #e8dcc4;
font-size: 1.1rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9);
font-weight: bold;
letter-spacing: 0.5px;
}
.item-type {
margin-top: 0.25rem;
color: #c9a86a;
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 0.375rem;
text-transform: capitalize;
i {
color: #8b7355;
font-size: 0.9rem;
}
}
}
}
.chat-item-body {
padding: 0.75rem;
position: relative;
z-index: 1;
.item-description {
color: #e8dcc4;
font-size: 0.9rem;
line-height: 1.5;
margin-bottom: 0.75rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
p {
margin: 0.5rem 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
.item-properties {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid rgba(139, 115, 85, 0.3);
.property {
background: linear-gradient(90deg, rgba(230, 220, 200, 0.6) 0%, rgba(210, 200, 180, 0.5) 100%);
padding: 0.375rem 0.5rem;
border-radius: 1px;
border-left: 2px solid #8b7355;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
.property-label {
color: #2a1f15;
font-weight: bold;
font-size: 0.85rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
.property-value {
color: #2a1f15;
font-weight: 600;
font-size: 0.9rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
}
}
}
}
/* ============================================ */
/* WASTELAND WELCOME MESSAGE */
/* ============================================ */
.wasteland-welcome-message {
background: linear-gradient(135deg, rgba(61, 47, 31, 0.15) 0%, rgba(42, 31, 21, 0.2) 100%);
border: 2px solid #6a0606;
border-radius: 8px;
padding: 0;
margin: 8px 0;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(201, 168, 106, 0.1);
font-family: "Charlemagne", serif;
.welcome-header {
background: linear-gradient(135deg, #6a0606 0%, #4a0404 100%);
padding: 10px;
text-align: center;
border-bottom: 2px solid #c9a86a;
position: relative;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
.welcome-icon {
font-size: 1.8rem;
color: #c9a86a;
margin-bottom: 4px;
text-shadow: 0 0 10px rgba(201, 168, 106, 0.5);
animation: pulse 2s ease-in-out infinite;
}
.welcome-title {
margin: 4px 0 2px 0;
font-size: 1.3rem;
font-weight: bold;
color: #e8dcc4;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
font-family: "Charlemagne", serif;
letter-spacing: 0.5px;
}
.welcome-subtitle {
font-size: 0.9rem;
color: #c9a86a;
font-style: italic;
margin-top: 2px;
line-height: 1.2;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
}
.welcome-content {
padding: 12px;
background: linear-gradient(180deg, rgba(230, 220, 200, 0.9) 0%, rgba(210, 200, 180, 0.85) 100%);
.welcome-section {
display: flex;
gap: 10px;
margin-bottom: 10px;
padding: 8px;
background: rgba(255, 255, 255, 0.5);
border-radius: 4px;
border: 1px solid #c9a86a;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
&:last-child {
margin-bottom: 0;
}
.section-icon {
flex-shrink: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #6a0606 0%, #4a0404 100%);
color: #c9a86a;
border-radius: 50%;
font-size: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
i {
line-height: 1;
}
}
.section-text {
flex: 1;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
strong {
display: block;
color: #3d2f1f;
margin-bottom: 4px;
font-size: 0.95rem;
font-weight: 700;
}
p {
margin: 0;
line-height: 1.4;
color: #2a1f15;
font-size: 0.9rem;
}
.welcome-link {
display: inline-block;
margin-top: 4px;
color: #6a0606;
font-weight: 600;
text-decoration: none;
transition: all 0.2s ease;
font-size: 0.9rem;
i {
margin-right: 4px;
}
&:hover {
color: #8b0606;
text-shadow: 0 0 4px rgba(106, 6, 6, 0.3);
}
}
}
}
}
.welcome-footer {
background: linear-gradient(135deg, #4a0404 0%, #6a0606 100%);
padding: 8px;
text-align: center;
color: #c9a86a;
font-style: italic;
font-size: 0.95rem;
border-top: 1px solid #8b7355;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
font-family: "Charlemagne", serif;
i {
margin: 0 8px;
opacity: 0.7;
font-size: 0.85rem;
}
span {
vertical-align: middle;
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.9;
}
}
}

569
less/item-styles.less Normal file
View File

@@ -0,0 +1,569 @@
/* ==================== Item Sheet Styles ==================== */
/* Item header with image and name */
.fvtt-wasteland.item {
/* Background pour toute la fiche d'item */
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
display: flex;
flex-direction: column;
height: 100%;
padding: 0;
margin: 0;
/* AppV2 - Remove window content padding */
.window-content {
padding: 0;
margin: 0;
}
/* AppV2 - Main section structure */
section {
background: url("../assets/ui/pc_sheet_bg.webp") repeat-y;
color: black;
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
padding: 0;
margin: 0;
}
/* AppV2 Item Sheets - Disabled inputs readability */
input:disabled,
select:disabled {
color: #000000;
opacity: 0.8;
background-color: rgba(255, 255, 255, 0.5);
}
/* Inputs and selects styling */
input[type="text"],
input[type="number"],
select {
color: #000000;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid #999999;
margin: 0;
padding: 2px 4px;
font-family: "Charlemagne", serif;
font-size: 0.85rem;
}
textarea {
margin: 0;
padding: 2px 4px;
}
input[type="checkbox"] {
width: auto;
height: auto;
margin: 0 4px;
align-self: center;
}
.header {
flex: 0 0 auto;
border-bottom: 1px solid #999;
margin: 0;
}
.sheet-header {
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
padding: 0.5rem;
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
flex: 0 0 auto;
}
.item-sheet-img {
flex: 0 0 100px;
height: 100px;
border: 2px solid #999;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.item-sheet-title {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
h1 {
margin: 0;
padding: 0;
font-size: 1.5rem;
border-bottom: none;
input {
background: none;
border: none;
font-size: 1.5rem;
font-family: "Charlemagne", serif;
font-weight: bold;
}
}
.item-subtitle {
font-size: 0.9rem;
color: #666;
font-style: italic;
}
}
/* Navigation tabs - Modern style */
nav.tabs {
display: flex;
border-bottom: 2px solid #403f3e;
margin: 0;
padding: 4px 8px;
background: linear-gradient(to bottom, #2a2520 0%, #1a1510 100%);
flex: 0 0 auto;
gap: 4px;
a.item {
padding: 8px 16px;
color: rgba(218, 218, 218, 0.85);
text-decoration: none;
border: 1px solid transparent;
border-radius: 6px 6px 0 0;
font-family: "Charlemagne", serif;
font-size: 0.9rem;
font-weight: normal;
transition: all 0.3s ease;
background: rgba(64, 63, 62, 0.3);
min-width: 80px;
text-align: center;
i {
display: none; // Hide icons for cleaner look
}
&:hover {
background: rgba(74, 4, 4, 0.4);
color: #f5f5f5;
border-color: rgba(218, 218, 218, 0.2);
}
&.active {
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border: 1px solid transparent;
color: #f5f5f5;
font-weight: bold;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
}
}
/* Tab content */
.tab {
display: none;
padding: 8px 12px;
overflow-y: auto;
flex: 1 1 auto;
&.active {
display: block;
}
}
/* Sheet body - scrollable content */
.sheet-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 1rem;
&[data-tab] {
display: none;
&.active {
display: block;
}
}
/* Dans l'onglet details, les form-group sont horizontaux par défaut */
&[data-tab="details"] {
.form-group {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
label {
font-weight: bold;
font-size: 0.9rem;
color: #464331;
flex: 0 0 auto;
min-width: 160px;
margin: 0;
}
input[type="text"],
input[type="number"],
select,
textarea {
flex: 1;
}
input[type="checkbox"] {
flex: 0 0 auto;
margin-right: 0.5rem;
}
/* Checkbox avec label après (pas avant) */
&:has(input[type="checkbox"]:first-child) {
label {
min-width: auto;
flex: 1;
order: 2;
}
input[type="checkbox"] {
order: 1;
}
}
/* Exception: quand le label contient lui-même le checkbox */
label:has(input[type="checkbox"]) {
min-width: auto;
flex: 1;
display: flex;
align-items: center;
gap: 0.5rem;
}
}
/* Pour les sections avec grilles, garder le comportement vertical */
.grid .form-group {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
label {
min-width: auto;
}
}
}
}
/* Form groups - comportement par défaut pour autres onglets */
.form-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-bottom: 0.5rem;
label {
font-weight: bold;
font-size: 0.9rem;
color: #464331;
}
&.horizontal {
flex-direction: row;
align-items: center;
gap: 0.5rem;
label {
flex: 0 0 auto;
min-width: 100px;
}
input, select, textarea {
flex: 1;
}
}
}
/* Grid layouts */
.grid {
display: grid;
gap: 0.5rem;
margin: 0.5rem 0;
&.grid-2col {
grid-template-columns: repeat(2, 1fr);
}
&.grid-3col {
grid-template-columns: repeat(3, 1fr);
}
&.grid-4col {
grid-template-columns: repeat(4, 1fr);
}
}
/* Editor content */
.editor-content {
min-height: 200px;
border: 1px solid #999;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.9);
border-radius: 4px;
}
/* Actions buttons */
.item-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
padding-top: 0.5rem;
border-top: 1px solid #999;
button {
flex: 1;
padding: 0.5rem 1rem;
background: rgba(74, 4, 4, 0.8);
color: white;
border: 1px solid #000;
border-radius: 4px;
cursor: pointer;
font-family: "Charlemagne", serif;
font-size: 0.9rem;
transition: all 0.2s;
&:hover {
background: rgba(74, 4, 4, 1);
box-shadow: 0 0 8px rgba(74, 4, 4, 0.6);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
/* Rollable elements */
.rollable {
cursor: pointer;
&:hover {
color: #ff6600;
text-shadow: 0 0 8px rgba(255, 102, 0, 0.8);
}
}
/* Item-specific sections */
.item-details {
display: flex;
flex-direction: column;
gap: 1rem;
.detail-section {
padding: 0.5rem;
background: rgba(255, 255, 255, 0.3);
border: 1px solid #999;
border-radius: 4px;
h3 {
margin: 0 0 0.5rem 0;
padding-bottom: 0.25rem;
border-bottom: 1px solid #999;
font-size: 1.1rem;
color: #2a1510;
font-family: "Charlemagne", serif;
font-weight: bold;
}
}
}
}
/* Specific item type styles */
.fvtt-wasteland.item {
&.arme-content,
&.bouclier-content {
.weapon-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
.stat-box {
padding: 0.5rem;
background: rgba(255, 255, 255, 0.5);
border: 1px solid #999;
border-radius: 4px;
text-align: center;
.stat-label {
font-size: 0.8rem;
color: #666;
margin-bottom: 0.25rem;
}
.stat-value {
font-size: 1.2rem;
font-weight: bold;
}
}
}
}
&.competence-content {
.predilections-container {
margin-top: 0.5rem;
.no-predilections {
text-align: center;
font-style: italic;
color: #999;
padding: 1rem;
margin: 0;
}
}
.predilections-list {
list-style: none;
padding: 0;
margin: 0 0 0.75rem 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
.predilection-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem;
background: rgba(42, 37, 32, 0.3);
border: 1px solid rgba(106, 6, 6, 0.3);
border-radius: 4px;
transition: background 0.2s;
&:hover {
background: rgba(42, 37, 32, 0.5);
border-color: rgba(106, 6, 6, 0.5);
}
.predilection-main {
flex: 1;
.predilection-name {
width: 100%;
padding: 0.375rem 0.5rem;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 3px;
background: rgba(255, 255, 255, 0.9);
font-family: inherit;
&::placeholder {
color: #999;
font-style: italic;
}
&:focus {
outline: none;
border-color: #6a0606;
box-shadow: 0 0 0 2px rgba(106, 6, 6, 0.2);
}
}
}
.predilection-controls {
display: flex;
align-items: center;
gap: 0.75rem;
flex-shrink: 0;
.predilection-used {
display: flex;
align-items: center;
gap: 0.375rem;
cursor: pointer;
user-select: none;
white-space: nowrap;
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
span {
font-size: 0.9rem;
color: rgba(218, 218, 218, 0.85);
}
&:hover span {
color: rgba(218, 218, 218, 1);
}
}
.predilection-delete {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
padding: 0;
background: rgba(106, 6, 6, 0.6);
border: 1px solid rgba(106, 6, 6, 0.8);
border-radius: 3px;
cursor: pointer;
transition: all 0.2s;
i {
color: rgba(255, 255, 255, 0.9);
font-size: 0.875rem;
}
&:hover {
background: rgba(106, 6, 6, 0.9);
border-color: #6a0606;
transform: scale(1.05);
i {
color: #fff;
}
}
}
}
}
}
.add-predilection-btn {
width: 100%;
padding: 0.5rem 1rem;
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border: 1px solid #6a0606;
border-radius: 4px;
color: rgba(218, 218, 218, 0.95);
font-family: "Charlemagne", serif;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
i {
font-size: 0.875rem;
}
&:hover {
background: linear-gradient(to bottom, #5a0505 0%, #4a0404 100%);
border-color: #8a0808;
color: #fff;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
&:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
}
}
}

View File

@@ -0,0 +1,311 @@
/* ============================================ */
/* WASTELAND ROLL DIALOG STYLES */
/* ============================================ */
.wasteland-roll-dialog {
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
.window-content {
background: transparent;
}
.dialog-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border-bottom: 2px solid #6a0606;
margin: -0.5rem -0.5rem 0.5rem -0.5rem;
.actor-icon {
width: 48px;
height: 48px;
border: 2px solid #6a0606;
border-radius: 4px;
object-fit: cover;
}
.dialog-title {
flex: 1;
color: #f5f5f5;
h3 {
margin: 0 0 0.15rem 0;
font-size: 1rem;
font-family: "Charlemagne", serif;
font-weight: bold;
text-transform: uppercase;
}
.competence-name {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.9);
font-style: italic;
.attribut-info {
color: rgba(255, 255, 255, 0.7);
font-size: 0.8rem;
}
}
}
}
.dialog-content {
padding: 0 0.5rem 0.5rem 0.5rem;
.form-group {
margin-bottom: 0.4rem;
label {
display: block;
margin-bottom: 0.2rem;
font-weight: bold;
font-size: 0.85rem;
color: #2a1510;
font-family: "Charlemagne", serif;
}
select,
input[type="number"] {
width: 100%;
background: rgba(255, 255, 255, 0.5);
border: 1px solid #6a0606;
border-radius: 3px;
padding: 0.3rem 0.4rem;
font-size: 0.85rem;
color: #1a1510;
&:hover {
background: rgba(255, 255, 255, 0.6);
}
&:focus {
outline: none;
background: rgba(255, 255, 255, 0.7);
border-color: #aa0a0a;
box-shadow: 0 0 4px rgba(170, 10, 10, 0.3);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
}
.modifiers-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.4rem;
margin-bottom: 0.4rem;
}
.attributes-section {
background: rgba(106, 6, 6, 0.1);
border: 1px solid #6a0606;
border-radius: 4px;
padding: 0.5rem;
margin-bottom: 0.5rem;
label {
color: #6a0606;
}
}
.special-option {
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(106, 6, 6, 0.05);
border: 2px solid #6a0606;
border-radius: 4px;
.checkbox-label {
display: flex;
align-items: center;
gap: 0.4rem;
margin: 0;
cursor: pointer;
&.highlight {
span {
font-weight: bold;
color: #6a0606;
font-family: "Charlemagne", serif;
font-size: 0.85rem;
}
}
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #6a0606;
}
span {
flex: 1;
font-size: 0.85rem;
color: #2a1510;
}
}
}
/* Weapon Section */
.weapon-section {
background: rgba(139, 101, 8, 0.1);
padding: 0.5rem;
border-radius: 4px;
border: 1px solid rgba(139, 101, 8, 0.4);
margin-bottom: 0.5rem;
.weapon-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.4rem 0.5rem;
background: rgba(139, 101, 8, 0.15);
border-radius: 3px;
margin-bottom: 0.5rem;
.weapon-label {
font-weight: bold;
font-size: 0.9rem;
color: #2a1510;
font-family: "Charlemagne", serif;
}
.weapon-bonus {
font-size: 0.85rem;
color: #8b0000;
font-weight: bold;
}
}
.defense-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.4rem 0.5rem;
background: rgba(0, 100, 0, 0.1);
border-radius: 3px;
margin-bottom: 0.5rem;
.defense-label {
font-size: 0.85rem;
color: #2a1510;
font-weight: bold;
}
.defense-value {
font-size: 1rem;
color: #006400;
font-weight: bold;
}
}
}
/* Combat Modifiers */
.combat-modifiers,
.ranged-combat-section {
background: rgba(139, 69, 19, 0.1);
padding: 0.5rem;
border-radius: 4px;
border: 1px solid rgba(139, 69, 19, 0.4);
margin-bottom: 0.5rem;
h4 {
margin: 0 0 0.5rem 0;
color: #2a1510;
font-size: 0.9rem;
font-weight: bold;
font-family: "Charlemagne", serif;
text-transform: uppercase;
text-shadow: 1px 1px 1px rgba(255, 255, 255, 0.3);
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.3rem 0.4rem;
cursor: pointer;
border-radius: 3px;
margin-bottom: 0.3rem;
transition: background 0.2s ease;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
input[type="checkbox"] {
width: auto;
margin: 0;
cursor: pointer;
accent-color: #8b4513;
}
span {
color: #2a1510;
font-size: 0.85rem;
user-select: none;
}
}
.info-message {
padding: 0.4rem 0.6rem;
background: rgba(201, 168, 106, 0.2);
border-left: 3px solid rgba(201, 168, 106, 0.8);
border-radius: 3px;
font-size: 0.85rem;
color: #2a1510;
margin-bottom: 0.5rem;
font-style: italic;
}
}
.modifiers-columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.3rem 0.6rem;
}
}
// Styles pour les boutons du dialog
.dialog-buttons {
display: flex;
gap: 0.4rem;
padding: 0.5rem;
border-top: 1px solid #6a0606;
margin: 0.5rem -0.5rem -0.5rem -0.5rem;
background: rgba(255, 255, 255, 0.1);
button {
flex: 1;
padding: 0.5rem 0.75rem;
background: linear-gradient(to bottom, #4a0404 0%, #3a0303 100%);
border: 1px solid #6a0606;
border-radius: 3px;
color: #f5f5f5;
font-size: 0.85rem;
font-weight: bold;
font-family: "Charlemagne", serif;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: linear-gradient(to bottom, #5a0505 0%, #4a0404 100%);
box-shadow: 0 0 6px rgba(106, 6, 6, 0.6);
}
&.default {
border-width: 2px;
border-color: #aa0a0a;
}
i {
margin-right: 0.4rem;
}
}
}
}

1365
less/simple-converted.less Normal file

File diff suppressed because it is too large Load Diff

8
less/wasteland.less Normal file
View File

@@ -0,0 +1,8 @@
// Main LESS file for Wasteland system
// Importing base styles and component-specific styles
@import "simple-converted";
@import "item-styles";
@import "actor-styles";
@import "roll-dialog-styles";
@import "chat-styles";

View File

@@ -0,0 +1,27 @@
/**
* Index des applications AppV2 pour Wasteland
* Ce fichier centralise tous les exports des applications
*/
// Applications de feuilles d'acteurs
export { default as WastelandPersonnageSheet } from './wasteland-personnage-sheet.mjs';
export { default as WastelandCreatureSheet } from './wasteland-creature-sheet.mjs';
// Applications de feuilles d'items
export { default as WastelandArmeSheet } from './wasteland-arme-sheet.mjs';
export { default as WastelandArtifexSheet } from './wasteland-artifex-sheet.mjs';
export { default as WastelandBouclierSheet } from './wasteland-bouclier-sheet.mjs';
export { default as WastelandCapaciteSheet } from './wasteland-capacite-sheet.mjs';
export { default as WastelandCharmeSheet } from './wasteland-charme-sheet.mjs';
export { default as WastelandCompetenceSheet } from './wasteland-competence-sheet.mjs';
export { default as WastelandDonSheet } from './wasteland-don-sheet.mjs';
export { default as WastelandEquipementSheet } from './wasteland-equipement-sheet.mjs';
export { default as WastelandHeritageSheet } from './wasteland-heritage-sheet.mjs';
export { default as WastelandHubrisSheet } from './wasteland-hubris-sheet.mjs';
export { default as WastelandMetierSheet } from './wasteland-metier-sheet.mjs';
export { default as WastelandMonnaieSheet } from './wasteland-monnaie-sheet.mjs';
export { default as WastelandMutationSheet } from './wasteland-mutation-sheet.mjs';
export { default as WastelandOrigineSheet } from './wasteland-origine-sheet.mjs';
export { default as WastelandPeupleSheet } from './wasteland-peuple-sheet.mjs';
export { default as WastelandPouvoirSheet } from './wasteland-pouvoir-sheet.mjs';
export { default as WastelandProtectionSheet } from './wasteland-protection-sheet.mjs';

View File

@@ -0,0 +1,382 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { WastelandUtility } from "../../wasteland-utility.js"
export default class WastelandActorSheet 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-wasteland", "sheet", "actor"],
position: {
width: 650,
height: 720,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "stats",
},
],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
actions: {
editImage: WastelandActorSheet.#onEditImage,
toggleSheet: WastelandActorSheet.#onToggleSheet,
editItem: WastelandActorSheet.#onEditItem,
deleteItem: WastelandActorSheet.#onDeleteItem,
createItem: WastelandActorSheet.#onCreateItem,
equipItem: WastelandActorSheet.#onEquipItem,
modifyQuantity: WastelandActorSheet.#onModifyQuantity,
incDecSante: WastelandActorSheet.#onIncDecSante,
rollAttribut: WastelandActorSheet.#onRollAttribut,
rollCompetence: WastelandActorSheet.#onRollCompetence,
rollCharme: WastelandActorSheet.#onRollCharme,
rollPouvoir: WastelandActorSheet.#onRollPouvoir,
rollArmeOffensif: WastelandActorSheet.#onRollArmeOffensif,
rollArmeDegats: WastelandActorSheet.#onRollArmeDegats,
resetPredilections: WastelandActorSheet.#onResetPredilections,
rollAssommer: WastelandActorSheet.#onRollAssommer,
rollFuir: WastelandActorSheet.#onRollFuir,
rollImmobiliser: WastelandActorSheet.#onRollImmobiliser,
},
}
/**
* 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: "stats" }
/** @override */
async _prepareContext() {
const actor = this.document
const context = {
actor: actor,
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: game.system.wasteland.config,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true }),
enrichedComportement: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.comportement || "", { async: true }),
enrichedHabitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.habitat || "", { async: true }),
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Handle edit-item-data changes
this.element.querySelectorAll('.edit-item-data').forEach(element => {
element.addEventListener('change', async (event) => {
const target = event.currentTarget
const itemElement = target.closest('[data-item-id]')
if (!itemElement) return
const itemId = itemElement.dataset.itemId
const itemType = itemElement.dataset.itemType
const itemField = target.dataset.itemField
const dataType = target.dataset.dtype
const value = target.value
await this.document.editItemField(itemId, itemType, itemField, dataType, value)
})
})
// 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] || "stats"
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)
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
*/
#createDragDropHandlers() {
return []
}
// #region Actions
/**
* Handle editing the actor image
* @param {Event} event - The triggering event
*/
static async #onEditImage(event) {
event.preventDefault()
const sheet = this
const filePicker = new FilePicker({
type: "image",
current: sheet.document.img,
callback: (path) => {
sheet.document.update({ img: path })
},
})
filePicker.browse()
}
/**
* Handle toggling the sheet mode
* @param {Event} event - The triggering event
*/
static async #onToggleSheet(event) {
event.preventDefault()
const sheet = this
sheet._sheetMode = sheet._sheetMode === sheet.constructor.SHEET_MODES.PLAY ? sheet.constructor.SHEET_MODES.EDIT : sheet.constructor.SHEET_MODES.PLAY
sheet.render()
}
/**
* Handle editing an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onEditItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const item = this.actor.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Handle deleting an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onDeleteItem(event, target) {
const li = target.closest(".item")
await WastelandUtility.confirmDelete(this, li)
}
/**
* Handle creating an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.type
await this.actor.createEmbeddedDocuments("Item", [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
}
/**
* Handle equipping an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onEquipItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.equipItem(itemId)
}
/**
* Handle modifying item quantity
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onModifyQuantity(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const item = this.actor.items.get(itemId)
if (!item) return
const qty = parseInt(target.dataset.qty) || 0
const currentQty = item.system.quantite || 0
const newQty = Math.max(0, currentQty + qty)
await item.update({ 'system.quantite': newQty })
}
/**
* Handle modifying santé/psyché
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onIncDecSante(event, target) {
const field = target.dataset.field
const value = parseInt(target.dataset.value) || 0
if (field === 'psyche') {
await this.actor.update({ 'system.psyche.value': this.actor.system.psyche.value + value })
} else if (field === 'nonletaux') {
await this.actor.update({ 'system.sante.nonletaux': this.actor.system.sante.nonletaux + value })
} else if (field === 'letaux') {
await this.actor.update({ 'system.sante.letaux': this.actor.system.sante.letaux + value })
}
}
/**
* Handle rolling an attribute
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollAttribut(event, target) {
const attrKey = target.dataset.attrKey
await this.actor.rollAttribut(attrKey)
}
/**
* Handle rolling a competence
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollCompetence(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
const attrKey = target.dataset.attrKey
if (!itemId) return
await this.actor.rollCompetence(attrKey, itemId)
}
/**
* Handle rolling a charme
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollCharme(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollCharme(itemId)
}
/**
* Handle rolling a pouvoir
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollPouvoir(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollPouvoir(itemId)
}
/**
* Handle rolling weapon attack
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollArmeOffensif(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollArmeOffensif(itemId)
}
/**
* Handle rolling weapon damage
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onRollArmeDegats(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.rollArmeDegats(itemId)
}
/**
* Handle resetting all predilections
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onResetPredilections(event, target) {
await this.actor.resetAllPredilections()
}
/**
* Handle Assommer roll
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onRollAssommer(event, target) {
await this.actor.rollAssommer()
}
/**
* Handle Fuir roll
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onRollFuir(event, target) {
await this.actor.rollFuir()
}
/**
* Handle Immobiliser roll
* @param {Event} event - The originating click event
* @param {HTMLElement} target - The target element
*/
static async #onRollImmobiliser(event, target) {
await this.actor.rollImmobiliser()
}
}

View File

@@ -0,0 +1,158 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class WastelandItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-wasteland", "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: WastelandItemSheet.#onEditImage,
postItem: WastelandItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
isEditMode: true,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.wasteland.config,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// 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
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
}
// #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()
}
/**
* Handle posting the item to chat
* @param {Event} event - The triggering event
*/
static async #onPostItem(event) {
event.preventDefault()
// Prepare chat data
const chatData = {
name: this.document.name,
img: this.document.img,
type: this.document.type,
system: this.document.system,
}
// Add actor reference if item is owned
if (this.document.actor) {
chatData.actor = { id: this.document.actor.id }
}
// Don't post any image for the item if the default image is used
if (chatData.img && chatData.img.includes("/blank.png")) {
chatData.img = null
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify({
compendium: "postedItem",
payload: chatData,
})
// Render the chat card template
const html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-wasteland/templates/post-item.hbs', chatData)
// Create the chat message
const chatOptions = {
user: game.user.id,
content: html,
speaker: ChatMessage.getSpeaker({ actor: this.document.actor })
}
ChatMessage.create(chatOptions)
}
}

View File

@@ -0,0 +1,49 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandArmeSheet extends WastelandItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["arme"],
position: {
width: 620,
},
window: {
contentClasses: ["arme-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-wasteland/templates/item-arme-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandArtifexSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["artifex"],
position: { width: 620 },
window: { contentClasses: ["artifex-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-artifex-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandBouclierSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["bouclier"],
position: { width: 620 },
window: { contentClasses: ["bouclier-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-bouclier-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandCapaciteSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["capacite"],
position: { width: 620 },
window: { contentClasses: ["capacite-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-capacite-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandCharmeSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["charme"],
position: { width: 620 },
window: { contentClasses: ["charme-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-charme-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,72 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandCompetenceSheet extends WastelandItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["competence"],
position: {
width: 620,
},
window: {
contentClasses: ["competence-content"],
},
actions: {
"add-predilection": this.#onAddPredilection,
"delete-predilection": this.#onDeletePredilection
}
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-wasteland/templates/item-competence-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
/**
* Handle adding a new predilection
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onAddPredilection(event, target) {
const predilections = foundry.utils.duplicate(this.document.system.predilections)
predilections.push({ name: "Nouvelle prédilection", used: false })
await this.document.update({ "system.predilections": predilections })
}
/**
* Handle deleting a predilection
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The delete button element
*/
static async #onDeletePredilection(event, target) {
const index = parseInt(target.dataset.index)
const predilections = foundry.utils.duplicate(this.document.system.predilections)
predilections.splice(index, 1)
await this.document.update({ "system.predilections": predilections })
}
}

View File

@@ -0,0 +1,40 @@
import WastelandActorSheet from "./base-actor-sheet.mjs"
export default class WastelandCreatureSheet extends WastelandActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "creature"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Créature",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-wasteland/templates/actor-creature-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "stats",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add creature-specific data
context.skills = actor.getSkills()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.protections = foundry.utils.duplicate(actor.getArmors())
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.combat = actor.getCombatValues()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandDonSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["don"],
position: { width: 620 },
window: { contentClasses: ["don-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-don-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandEquipementSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["equipement"],
position: { width: 620 },
window: { contentClasses: ["equipement-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-equipement-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandHeritageSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["heritage"],
position: { width: 620 },
window: { contentClasses: ["heritage-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-heritage-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandHubrisSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["hubris"],
position: { width: 620 },
window: { contentClasses: ["hubris-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-hubris-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandMetierSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["metier"],
position: { width: 620 },
window: { contentClasses: ["metier-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-metier-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandMonnaieSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["monnaie"],
position: { width: 620 },
window: { contentClasses: ["monnaie-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-monnaie-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandMutationSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["mutation"],
position: { width: 620 },
window: { contentClasses: ["mutation-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-mutation-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandOrigineSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["origine"],
position: { width: 620 },
window: { contentClasses: ["origine-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-origine-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,59 @@
import WastelandActorSheet from "./base-actor-sheet.mjs"
export default class WastelandPersonnageSheet extends WastelandActorSheet {
/** @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-wasteland/templates/actor-personnage-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "stats",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add personnage-specific data
context.skills = actor.getSkills()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.protections = foundry.utils.duplicate(actor.getArmors())
context.pouvoirs = foundry.utils.duplicate(actor.getPouvoirs())
context.dons = foundry.utils.duplicate(actor.getDons())
context.hubrises = foundry.utils.duplicate(actor.getHubris())
context.tours = foundry.utils.duplicate(actor.getTours())
context.artifex = foundry.utils.duplicate(actor.getArtifex())
context.charmes = foundry.utils.duplicate(actor.getCharmes())
context.peuple = foundry.utils.duplicate(actor.getPeuple() || {})
context.origine = foundry.utils.duplicate(actor.getOrigine() || {})
context.heritage = foundry.utils.duplicate(actor.getHeritage() || {})
context.metier = foundry.utils.duplicate(actor.getMetier() || {})
context.combat = actor.getCombatValues()
context.capacites = foundry.utils.duplicate(actor.getCapacites())
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.mutations = foundry.utils.duplicate(actor.getMutations())
// Enrich HTML fields for biodata
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.enrichedSequelles = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.sequelles || "", { async: true })
context.enrichedTraumatismes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.traumatismes || "", { async: true })
return context
}
}

View File

@@ -0,0 +1,32 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandPeupleSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["peuple"],
position: { width: 620 },
window: { contentClasses: ["peuple-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-peuple-sheet.hbs" },
}
tabGroups = { primary: "description" }
#getTabs() {
const tabs = {
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandPouvoirSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["pouvoir"],
position: { width: 620 },
window: { contentClasses: ["pouvoir-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-pouvoir-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,33 @@
import WastelandItemSheet from "./base-item-sheet.mjs"
export default class WastelandProtectionSheet extends WastelandItemSheet {
static DEFAULT_OPTIONS = {
classes: ["protection"],
position: { width: 620 },
window: { contentClasses: ["protection-content"] },
}
static PARTS = {
main: { template: "systems/fvtt-wasteland/templates/item-protection-sheet.hbs" },
}
tabGroups = { primary: "details" }
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
description: { id: "description", group: "primary", label: "Description" }
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
return context
}
}

View File

@@ -0,0 +1,156 @@
import { WastelandUtility } from "../wasteland-utility.js"
/**
* Dialogue de jet de dé pour Wasteland - Version DialogV2
*/
export class WastelandRollDialog {
/**
* Create and display the roll dialog
* @param {WastelandActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise<WastelandRollDialog>}
*/
static async create(actor, rollData) {
// Préparer le contexte pour le template
const context = {
...rollData,
difficulte: String(rollData.difficulte || 0),
img: actor.img,
name: actor.name,
config: game.system.wasteland.config,
}
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-wasteland/templates/roll-dialog-v2.hbs",
context
)
// Utiliser DialogV2.wait avec le HTML rendu
return foundry.applications.api.DialogV2.wait({
window: { title: "Test de Capacité", icon: "fa-solid fa-dice-d20" },
classes: ["wasteland-roll-dialog"],
position: { width: 500 },
modal: false,
content,
buttons: rollData.charme ? [
{
action: "roll",
label: "Lancer",
icon: "fa-solid fa-dice-d20",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
WastelandUtility.rollWasteland(rollData)
}
}
] : [
{
action: "rolld10",
label: "Lancer 1d10",
icon: "fa-solid fa-dice-d10",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
rollData.mainDice = "1d10"
WastelandUtility.rollWasteland(rollData)
}
},
{
action: "rolld20",
label: "Lancer 1d20",
icon: "fa-solid fa-dice-d20",
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
rollData.mainDice = "1d20"
WastelandUtility.rollWasteland(rollData)
}
},
],
rejectClose: false,
})
}
/**
* 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 {WastelandActor} actor - L'acteur pour récupérer les attributs
* @private
*/
static _updateRollDataFromForm(rollData, formElements, actor) {
// Attributs
if (formElements.attrKey) {
rollData.attrKey = formElements.attrKey.value
if (rollData.attrKey !== "tochoose" && actor) {
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
}
}
// Modificateurs de base
if (formElements.difficulte) {
rollData.difficulte = Number(formElements.difficulte.value)
}
if (formElements.modificateur) {
rollData.modificateur = Number(formElements.modificateur.value)
}
// Charme
if (formElements.charmeDice) {
rollData.charmeDice = String(formElements.charmeDice.value)
}
// Combat mêlée
if (formElements.typeAttaque) {
rollData.typeAttaque = String(formElements.typeAttaque.value)
rollData.typeAttaqueLabel = rollData.config.attaques[rollData.typeAttaque]
}
if (formElements.isMonte !== undefined) {
rollData.isMonte = formElements.isMonte.checked
}
// Combat distance
if (formElements.visee !== undefined) {
rollData.visee = formElements.visee.checked
}
if (formElements.cibleconsciente !== undefined) {
rollData.cibleconsciente = formElements.cibleconsciente.checked
}
if (formElements.ciblecourt !== undefined) {
rollData.ciblecourt = formElements.ciblecourt.checked
}
if (formElements.typeCouvert) {
rollData.typeCouvert = String(formElements.typeCouvert.value)
const couvert = rollData.config.couverts[rollData.typeCouvert]
if (rollData.typeCouvert !== "aucun" && couvert) {
rollData.typeCouvertLabel = couvert.name
rollData.typeCouvertValue = couvert.value
}
}
// Désavantages (avantages tactiques)
if (!rollData.desavantages) rollData.desavantages = {}
if (formElements.cibleausol !== undefined) {
rollData.desavantages.cibleausol = formElements.cibleausol.checked
}
if (formElements.cibledesarmee !== undefined) {
rollData.desavantages.cibledesarmee = formElements.cibledesarmee.checked
}
if (formElements.ciblerestreint !== undefined) {
rollData.desavantages.ciblerestreint = formElements.ciblerestreint.checked
}
if (formElements.cibleimmobilisée !== undefined) {
rollData.desavantages.cibleimmobilisée = formElements.cibleimmobilisée.checked
}
if (formElements.ciblesurplomb !== undefined) {
rollData.desavantages.ciblesurplomb = formElements.ciblesurplomb.checked
}
// Double D20
if (formElements.doubleD20 !== undefined) {
rollData.doubleD20 = formElements.doubleD20.checked
}
}
}

26
modules/models/arme.mjs Normal file
View File

@@ -0,0 +1,26 @@
/**
* Data model pour les armes
*/
export default class ArmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
typearme: new fields.StringField({ initial: "" }),
isdefense: new fields.BooleanField({ initial: false }),
bonusmaniementoff: new fields.NumberField({ initial: 0, integer: true }),
bonusmaniementdef: new fields.NumberField({ initial: 0, integer: true }),
nobonusdegats: new fields.BooleanField({ initial: false }),
degats: new fields.StringField({ initial: "" }),
nonletaux: new fields.BooleanField({ initial: false }),
deuxmains: new fields.BooleanField({ initial: false }),
courte: new fields.NumberField({ initial: 0, integer: true }),
moyenne: new fields.NumberField({ initial: 0, integer: true }),
longue: new fields.NumberField({ initial: 0, integer: true }),
tr: new fields.NumberField({ initial: 0, integer: true }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
};
}
}

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les artifex
*/
export default class ArtifexDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
artifextype: new fields.StringField({ initial: "vapeur" }),
competence: new fields.StringField({ initial: "" }),
complexite: new fields.NumberField({ initial: 0, integer: true }),
dureerealisation: new fields.StringField({ initial: "" }),
tempsmiseenroute: new fields.StringField({ initial: "" }),
defautcourant: new fields.StringField({ initial: "" }),
prix: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

@@ -0,0 +1,17 @@
/**
* Data model pour les boucliers
*/
export default class BouclierDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
bonusdefense: new fields.NumberField({ initial: 0, integer: true }),
degats: new fields.StringField({ initial: "" }),
nonletaux: new fields.BooleanField({ initial: false }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
};
}
}

View File

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

25
modules/models/charme.mjs Normal file
View File

@@ -0,0 +1,25 @@
/**
* Data model pour les charmes
*/
export default class CharmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
charmetype: new fields.StringField({ initial: "tour" }),
resultats: new fields.ArrayField(new fields.SchemaField({
value: new fields.NumberField({ initial: -1, integer: true }),
description: new fields.StringField({ initial: "" })
}), {
initial: [
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" },
{ value: -1, description: "" }
]
})
};
}
}

View File

@@ -0,0 +1,21 @@
/**
* Data model pour les compétences
*/
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
attribut1: new fields.StringField({ initial: "" }),
attribut2: new fields.StringField({ initial: "" }),
attribut3: new fields.StringField({ initial: "" }),
doublebonus: new fields.BooleanField({ initial: false }),
predilections: new fields.ArrayField(new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
description: new fields.StringField({ initial: "" }),
used: new fields.BooleanField({ initial: false })
}), { initial: [] })
};
}
}

View File

@@ -0,0 +1,99 @@
/**
* Data model pour les créatures
*/
export default class CreatureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 0, integer: true }),
alignement: new fields.StringField({ initial: "" }),
poids: new fields.StringField({ initial: "" }),
taille: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
habitat: new fields.StringField({ initial: "" }),
comportement: new fields.StringField({ initial: "" }),
psychemultiplier: new fields.NumberField({ initial: 1, integer: true }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
terreur: new fields.SchemaField({
value: new fields.NumberField({ initial: -1, integer: true })
}),
protection: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
ressource: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
attributs: new fields.SchemaField({
adr: new fields.SchemaField({
label: new fields.StringField({ initial: "Adresse" }),
labelnorm: new fields.StringField({ initial: "adresse" }),
abbrev: new fields.StringField({ initial: "adr" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pui: new fields.SchemaField({
label: new fields.StringField({ initial: "Puissance" }),
labelnorm: new fields.StringField({ initial: "puissance" }),
abbrev: new fields.StringField({ initial: "pui" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
cla: new fields.SchemaField({
label: new fields.StringField({ initial: "Clairvoyance" }),
labelnorm: new fields.StringField({ initial: "clairvoyance" }),
abbrev: new fields.StringField({ initial: "cla" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pre: new fields.SchemaField({
label: new fields.StringField({ initial: "Présence" }),
labelnorm: new fields.StringField({ initial: "presence" }),
abbrev: new fields.StringField({ initial: "pre" }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
tre: new fields.SchemaField({
label: new fields.StringField({ initial: "Trempe" }),
labelnorm: new fields.StringField({ initial: "trempe" }),
abbrev: new fields.StringField({ initial: "tre" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
bonneaventure: new fields.SchemaField({
base: new fields.NumberField({ initial: 0, integer: true }),
actuelle: new fields.NumberField({ initial: 0, integer: true })
}),
experience: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
eclat: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
sante: new fields.SchemaField({
base: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
nonletaux: new fields.NumberField({ initial: 0, integer: true }),
letaux: new fields.NumberField({ initial: 0, integer: true }),
sequelles: new fields.StringField({ initial: "" })
}),
psyche: new fields.SchemaField({
fullmax: new fields.NumberField({ initial: 0, integer: true }),
currentmax: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true }),
traumatismes: new fields.StringField({ initial: "" })
}),
combat: new fields.SchemaField({
initbonus: new fields.NumberField({ initial: 0, integer: true }),
vitessebonus: new fields.NumberField({ initial: 0, integer: true }),
bonusdegats: new fields.NumberField({ initial: 0, integer: true }),
defensebonus: new fields.NumberField({ initial: 0, integer: true })
})
};
}
}

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

@@ -0,0 +1,12 @@
/**
* 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: "" }),
coutpsyche: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

@@ -0,0 +1,14 @@
/**
* Data model pour les équipements
*/
export default class EquipementDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
quantite: new fields.NumberField({ initial: 1, integer: true, min: 0 }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

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

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

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

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

@@ -0,0 +1,27 @@
/**
* Index des DataModels pour Wasteland
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'items
export { default as ArmeDataModel } from './arme.mjs';
export { default as BouclierDataModel } from './bouclier.mjs';
export { default as CapaciteDataModel } from './capacite.mjs';
export { default as CompetenceDataModel } from './competence.mjs';
export { default as DonDataModel } from './don.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as HeritageDataModel } from './heritage.mjs';
export { default as MetierDataModel } from './metier.mjs';
export { default as MonnaieDataModel } from './monnaie.mjs';
export { default as OrigineDataModel } from './origine.mjs';
export { default as ProtectionDataModel } from './protection.mjs';
export { default as MutationDataModel } from './mutation.mjs';
export { default as PouvoirDataModel } from './pouvoir.mjs';
export { default as CharmeDataModel } from './charme.mjs';
export { default as ArtifexDataModel } from './artifex.mjs';
export { default as PeupleDataModel } from './peuple.mjs';
export { default as HubrisDataModel } from './hubris.mjs';
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as CreatureDataModel } from './creature.mjs';

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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,100 @@
/**
* Data model pour les personnages
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
name: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 0, integer: true }),
alignement: new fields.StringField({ initial: "" }),
poids: new fields.StringField({ initial: "" }),
taille: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
habitat: new fields.StringField({ initial: "" }),
comportement: new fields.StringField({ initial: "" }),
psychemultiplier: new fields.NumberField({ initial: 1, integer: true }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
terreur: new fields.SchemaField({
value: new fields.NumberField({ initial: -1, integer: true })
}),
protection: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
ressource: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
attributs: new fields.SchemaField({
adr: new fields.SchemaField({
label: new fields.StringField({ initial: "Adresse" }),
labelnorm: new fields.StringField({ initial: "adresse" }),
abbrev: new fields.StringField({ initial: "adr" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pui: new fields.SchemaField({
label: new fields.StringField({ initial: "Puissance" }),
labelnorm: new fields.StringField({ initial: "puissance" }),
abbrev: new fields.StringField({ initial: "pui" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
cla: new fields.SchemaField({
label: new fields.StringField({ initial: "Clairvoyance" }),
labelnorm: new fields.StringField({ initial: "clairvoyance" }),
abbrev: new fields.StringField({ initial: "cla" }),
value: new fields.NumberField({ initial: 1, integer: true })
}),
pre: new fields.SchemaField({
label: new fields.StringField({ initial: "Présence" }),
labelnorm: new fields.StringField({ initial: "presence" }),
abbrev: new fields.StringField({ initial: "pre" }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
tre: new fields.SchemaField({
label: new fields.StringField({ initial: "Trempe" }),
labelnorm: new fields.StringField({ initial: "trempe" }),
abbrev: new fields.StringField({ initial: "tre" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
bonneaventure: new fields.SchemaField({
base: new fields.NumberField({ initial: 0, integer: true }),
actuelle: new fields.NumberField({ initial: 0, integer: true })
}),
experience: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
eclat: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
sante: new fields.SchemaField({
base: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
nonletaux: new fields.NumberField({ initial: 0, integer: true }),
letaux: new fields.NumberField({ initial: 0, integer: true }),
sequelles: new fields.StringField({ initial: "" })
}),
psyche: new fields.SchemaField({
fullmax: new fields.NumberField({ initial: 0, integer: true }),
currentmax: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true }),
traumatismes: new fields.StringField({ initial: "" })
}),
combat: new fields.SchemaField({
initbonus: new fields.NumberField({ initial: 0, integer: true }),
vitessebonus: new fields.NumberField({ initial: 0, integer: true }),
bonusdegats: new fields.NumberField({ initial: 0, integer: true }),
defensebonus: new fields.NumberField({ initial: 0, integer: true }),
monte: new fields.BooleanField({ initial: false })
})
};
}
}

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

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

View File

@@ -0,0 +1,20 @@
/**
* Data model pour les pouvoirs
*/
export default class PouvoirDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
chemin: new fields.StringField({ initial: "force" }),
attribut1: new fields.StringField({ initial: "cla" }),
competence: new fields.StringField({ initial: "" }),
seuil: new fields.NumberField({ initial: 0, integer: true }),
coutpsyche: new fields.NumberField({ initial: 0, integer: true }),
cible: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
formulesimple: new fields.StringField({ initial: "" }),
formuleetendue: new fields.StringField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,15 @@
/**
* Data model pour les protections
*/
export default class ProtectionDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
protection: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true })
};
}
}

View File

@@ -1,176 +0,0 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandRollDialog } from "./wasteland-roll-dialog.js";
/* -------------------------------------------- */
export class WastelandActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-wasteland", "sheet", "actor"],
template: "systems/fvtt-wasteland/templates/actor-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
});
}
/* -------------------------------------------- */
async getData() {
const objectData = duplicate(this.object)
let actorData = objectData
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: actorData.system,
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
skills: this.actor.getSkills(),
armes: duplicate(this.actor.getWeapons()),
protections: duplicate(this.actor.getArmors()),
pouvoirs:duplicate(this.actor.getPouvoirs()),
origine: duplicate(this.actor.getOrigine() || {}),
heritage: duplicate(this.actor.getHeritage() || {}),
metier: duplicate(this.actor.getMetier() || {}),
combat: this.actor.getCombatValues(),
equipements: duplicate(this.actor.getEquipments()),
monnaies: duplicate(this.actor.getMonnaies()),
description: await TextEditor.enrichHTML(this.object.system.biodata.description, {async: true}),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM
}
this.formData = formData;
console.log("PC : ", formData, this.object);
return formData;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
const item = this.actor.items.get( itemId )
item.sheet.render(true)
})
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
WastelandUtility.confirmDelete(this, li);
})
html.find('.edit-item-data').change(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
let itemType = li.data("item-type")
let itemField = $(ev.currentTarget).data("item-field")
let dataType = $(ev.currentTarget).data("dtype")
let value = ev.currentTarget.value
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
})
html.find('.quantity-minus').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.incDecQuantity( li.data("item-id"), -1 );
} );
html.find('.quantity-plus').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.incDecQuantity( li.data("item-id"), +1 );
} );
html.find('.roll-attribut').click((event) => {
const li = $(event.currentTarget).parents(".item")
let attrKey = li.data("attr-key")
this.actor.rollAttribut(attrKey)
})
html.find('.roll-competence').click((event) => {
const li = $(event.currentTarget).parents(".item")
let attrKey = $(event.currentTarget).data("attr-key")
let compId = li.data("item-id")
this.actor.rollCompetence(attrKey, compId)
})
html.find('.roll-pouvoir').click((event) => {
const li = $(event.currentTarget).parents(".item")
let pouvoirId = li.data("item-id")
this.actor.rollPouvoir(pouvoirId)
})
html.find('.roll-arme-offensif').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeOffensif(armeId)
})
html.find('.roll-arme-degats').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeDegats(armeId)
})
html.find('.quantity-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
const value = Number($(event.currentTarget).data("quantite-value"))
this.actor.incDecQuantity( li.data("item-id"), value );
})
html.find('.item-add').click((event) => {
const itemType = $(event.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
})
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equipItem( li.data("item-id") );
this.render(true);
});
}
/* -------------------------------------------- */
/** @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 _onDropItem(event, dragData) {
let item = await WastelandUtility.searchItem( dragData)
this.actor.preprocessItem( event, item, true )
super._onDropItem(event, dragData)
}*/
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Update the Actor
return this.object.update(formData);
}
}

View File

@@ -1,6 +1,6 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { WastelandUtility } from "./wasteland-utility.js"; import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandRollDialog } from "./wasteland-roll-dialog.js"; import { WastelandRollDialog } from "./applications/wasteland-roll-dialog.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10] const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10]
@@ -37,11 +37,12 @@ export class WastelandActor extends Actor {
return actor; return actor;
} }
const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills")
if (data.type == 'personnage') { if (data.type == 'personnage') {
const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills")
data.items = skills.map(i => i.toObject()) data.items = skills.map(i => i.toObject())
} }
if (data.type == 'pnj') { if (data.type == 'creature') {
data.items = skills.filter(i=>i.name.toLowerCase().includes("mêlée")).map(i => i.toObject())
} }
return super.create(data, options); return super.create(data, options);
@@ -49,10 +50,11 @@ export class WastelandActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
prepareArme(arme) { prepareArme(arme) {
arme = duplicate(arme) arme = foundry.utils.duplicate(arme)
let combat = this.getCombatValues() let combat = this.getCombatValues()
if (arme.system.typearme == "contact" || arme.system.typearme == "contactjet") { if (arme.system.typearme == "contact" || arme.system.typearme == "contactjet") {
arme.system.competence = duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")) arme.system.isMelee = true
arme.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée"))
arme.system.attrKey = "pui" arme.system.attrKey = "pui"
arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal
arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff
@@ -61,7 +63,8 @@ export class WastelandActor extends Actor {
} }
} }
if (arme.system.typearme == "jet" || arme.system.typearme == "tir") { if (arme.system.typearme == "jet" || arme.system.typearme == "tir") {
arme.system.competence = duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "armes à distance")) arme.system.isDistance = true
arme.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "armes à distance"))
arme.system.attrKey = "adr" arme.system.attrKey = "adr"
arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff
arme.system.totalDegats = arme.system.degats arme.system.totalDegats = arme.system.degats
@@ -73,9 +76,9 @@ export class WastelandActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
prepareBouclier(bouclier) { prepareBouclier(bouclier) {
bouclier = duplicate(bouclier) bouclier = foundry.utils.duplicate(bouclier)
let combat = this.getCombatValues() let combat = this.getCombatValues()
bouclier.system.competence = duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")) bouclier.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée"))
bouclier.system.attrKey = "pui" bouclier.system.attrKey = "pui"
bouclier.system.totalDegats = bouclier.system.degats + "+" + combat.bonusDegatsTotal bouclier.system.totalDegats = bouclier.system.degats + "+" + combat.bonusDegatsTotal
bouclier.system.totalOffensif = this.system.attributs.pui.value + bouclier.system.competence.system.niveau bouclier.system.totalOffensif = this.system.attributs.pui.value + bouclier.system.competence.system.niveau
@@ -106,18 +109,46 @@ export class WastelandActor extends Actor {
WastelandUtility.sortArrayObjectsByName(items) WastelandUtility.sortArrayObjectsByName(items)
return items return items
} }
getCharmes() {
let items = this.items.filter(item => item.type=="charme" && item.system?.charmetype == "charme") || []
WastelandUtility.sortArrayObjectsByName(items)
return items
}
getTours() {
let items = this.items.filter(item => item.type=="charme" && item.system?.charmetype == "tour") || []
WastelandUtility.sortArrayObjectsByName(items)
return items
}
getArtifex() {
return this.getItemSorted(["artifex"])
}
getCapacites() {
return this.getItemSorted(["capacite"])
}
getPouvoirs() { getPouvoirs() {
return this.getItemSorted(["pouvoir"]) return this.getItemSorted(["pouvoir"])
} }
getDons() {
return this.getItemSorted(["don"])
}
getHubris() {
return this.getItemSorted(["hubris"])
}
getEquipments() { getEquipments() {
return this.getItemSorted(["equipement"]) return this.getItemSorted(["equipement"])
} }
getMonnaies() { getMonnaies() {
return this.getItemSorted(["monnaie"]) return this.getItemSorted(["monnaie"])
} }
getMutations() {
return this.getItemSorted(["mutation"])
}
getArmors() { getArmors() {
return this.getItemSorted(["protection"]) return this.getItemSorted(["protection"])
} }
getPeuple() {
return this.items.find(item => item.type == "peuple")
}
getOrigine() { getOrigine() {
return this.items.find(item => item.type == "origine") return this.items.find(item => item.type == "origine")
} }
@@ -131,7 +162,7 @@ export class WastelandActor extends Actor {
getSkills() { getSkills() {
let comp = [] let comp = []
for (let item of this.items) { for (let item of this.items) {
item = duplicate(item) item = foundry.utils.duplicate(item)
if (item.type == "competence") { if (item.type == "competence") {
item.system.attribut1total = item.system.niveau + (this.system.attributs[item.system.attribut1]?.value || 0) item.system.attribut1total = item.system.niveau + (this.system.attributs[item.system.attribut1]?.value || 0)
item.system.attribut2total = item.system.niveau + (this.system.attributs[item.system.attribut2]?.value || 0) item.system.attribut2total = item.system.niveau + (this.system.attributs[item.system.attribut2]?.value || 0)
@@ -193,7 +224,7 @@ export class WastelandActor extends Actor {
if (this.system.sante.base != newSante) { if (this.system.sante.base != newSante) {
this.update({ 'system.sante.base': newSante }) this.update({ 'system.sante.base': newSante })
} }
let newPsyche = (this.system.attributs.cla.value + this.system.attributs.tre.value) * this.system.biodata.psychemultiplier + 5 let newPsyche = ((this.system.attributs.cla.value + this.system.attributs.tre.value) * 2) + 5
if (this.system.psyche.fullmax != newPsyche) { if (this.system.psyche.fullmax != newPsyche) {
this.update({ 'system.psyche.fullmax': newPsyche }) this.update({ 'system.psyche.fullmax': newPsyche })
} }
@@ -207,12 +238,17 @@ export class WastelandActor extends Actor {
super._preUpdate(changed, options, user); super._preUpdate(changed, options, user);
} }
/* -------------------------------------------- */
incDecSante(value) {
let sante = foundry.utils.duplicate(this.system.sante)
sante.letaux += value
this.update({ 'system.sante': sante })
}
/* -------------------------------------------- */ /* -------------------------------------------- */
getItemById(id) { getItemById(id) {
let item = this.items.find(item => item.id == id); let item = this.items.find(item => item.id == id);
if (item) { if (item) {
item = duplicate(item) item = foundry.utils.duplicate(item)
} }
return item; return item;
} }
@@ -262,12 +298,16 @@ export class WastelandActor extends Actor {
changeEclat(value) { changeEclat(value) {
let newE = this.system.eclat.value let newE = this.system.eclat.value
newE += value newE += value
// Empêcher que l'éclat devienne négatif
if (newE < 0) {
newE = 0
}
this.update({ 'system.eclat.value': newE }) this.update({ 'system.eclat.value': newE })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
subPointsPsyche(value) { subPointsPsyche(value) {
let psyche = duplicate(this.system.psyche) let psyche = foundry.utils.duplicate(this.system.psyche)
psyche.currentmax -= value psyche.currentmax -= value
this.update( {'system.psyche': psyche}) this.update( {'system.psyche': psyche})
} }
@@ -306,13 +346,13 @@ export class WastelandActor extends Actor {
getSubActors() { getSubActors() {
let subActors = []; let subActors = [];
for (let id of this.system.subactors) { for (let id of this.system.subactors) {
subActors.push(duplicate(game.actors.get(id))); subActors.push(foundry.utils.duplicate(game.actors.get(id)));
} }
return subActors; return subActors;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async addSubActor(subActorId) { async addSubActor(subActorId) {
let subActors = duplicate(this.system.subactors); let subActors = foundry.utils.duplicate(this.system.subactors);
subActors.push(subActorId); subActors.push(subActorId);
await this.update({ 'system.subactors': subActors }); await this.update({ 'system.subactors': subActors });
} }
@@ -343,11 +383,37 @@ export class WastelandActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async setPredilectionUsed(compId, predIdx) { async setPredilectionUsed(compId, predIdx) {
let comp = this.items.get(compId) let comp = this.items.get(compId)
let pred = duplicate(comp.system.predilections) let pred = foundry.utils.duplicate(comp.system.predilections)
pred[predIdx].used = true pred[predIdx].used = true
await this.updateEmbeddedDocuments('Item', [{ _id: compId, 'system.predilections': pred }]) await this.updateEmbeddedDocuments('Item', [{ _id: compId, 'system.predilections': pred }])
} }
/* -------------------------------------------- */
async resetAllPredilections() {
let updates = []
for (let item of this.items) {
if (item.type === "competence" && item.system.predilections && item.system.predilections.length > 0) {
let pred = foundry.utils.duplicate(item.system.predilections)
let hasUsed = false
for (let p of pred) {
if (p.used) {
p.used = false
hasUsed = true
}
}
if (hasUsed) {
updates.push({ _id: item.id, 'system.predilections': pred })
}
}
}
if (updates.length > 0) {
await this.updateEmbeddedDocuments('Item', updates)
ui.notifications.info(`${updates.length} prédilection(s) réinitialisée(s) pour ${this.name}`)
} else {
ui.notifications.info(`Aucune prédilection à réinitialiser pour ${this.name}`)
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
getInitiativeScore( ) { getInitiativeScore( ) {
return Number(this.system.attributs.adr.value) + Number(this.system.combat.initbonus) return Number(this.system.attributs.adr.value) + Number(this.system.combat.initbonus)
@@ -366,7 +432,7 @@ export class WastelandActor extends Actor {
} }
if ( arme.system.totalDefensif > maxDef) { if ( arme.system.totalDefensif > maxDef) {
maxDef = arme.system.totalDefensif maxDef = arme.system.totalDefensif
bestArme = duplicate(arme) bestArme = foundry.utils.duplicate(arme)
} }
} }
return bestArme return bestArme
@@ -380,60 +446,70 @@ export class WastelandActor extends Actor {
rollData.actorId = this.id rollData.actorId = this.id
rollData.tokenId = this.token?.id rollData.tokenId = this.token?.id
rollData.img = this.img rollData.img = this.img
rollData.canEclatDoubleD20 = true // Always true in Wastelan rollData.canEclatDoubleD20 = this.getEclat() >= 1 // Vérifier que l'acteur a au moins 1 point d'éclat
rollData.doubleD20 = false rollData.doubleD20 = false
rollData.attributs = WastelandUtility.getAttributs() rollData.attributs = WastelandUtility.getAttributs()
rollData.config = foundry.utils.duplicate(game.system.wasteland.config)
rollData.desavantages = {}
rollData.isMonte = this.system.combat.monte
if (attrKey) { if (attrKey) {
rollData.attrKey = attrKey rollData.attrKey = attrKey
if (attrKey != "tochoose") { if (attrKey != "tochoose") {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp" rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp"
rollData.attr = duplicate(this.system.attributs[attrKey]) rollData.attr = foundry.utils.duplicate(this.system.attributs[attrKey])
} }
} }
if (compId) { if (compId) {
rollData.competence = duplicate(this.items.get(compId) || {}) rollData.competence = foundry.utils.duplicate(this.items.get(compId) || {})
rollData.actionImg = rollData.competence?.img rollData.actionImg = rollData.competence?.img
} }
if (compName) { if (compName) {
rollData.competence = duplicate(this.items.find( item => item.name.toLowerCase() == compName.toLowerCase()) || {}) rollData.competence = foundry.utils.duplicate(this.items.find( item => item.name.toLowerCase() == compName.toLowerCase()) || {})
rollData.actionImg = rollData.competence?.img rollData.actionImg = rollData.competence?.img
} }
return rollData return rollData
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollAttribut(attrKey) { async launchRoll(rollData) {
console.log("RollData", rollData)
await WastelandRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
rollAttribut(attrKey) {
let rollData = this.getCommonRollData(attrKey) let rollData = this.getCommonRollData(attrKey)
let rollDialog = await WastelandRollDialog.create(this, rollData) this.launchRoll(rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollCompetence(attrKey, compId) { rollCompetence(attrKey, compId) {
let rollData = this.getCommonRollData(attrKey, compId) let rollData = this.getCommonRollData(attrKey, compId)
console.log("RollDatra", rollData) console.log("RollDatra", rollData)
let rollDialog = await WastelandRollDialog.create(this, rollData) this.launchRoll(rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollPouvoir(pouvoirId) { async rollCharme(charmeId) {
let comp = this.items.find(comp => comp.type == "competence" && comp.name.toLowerCase() == "savoir : runes") let rollData = this.getCommonRollData("cla")
if ( !comp) { rollData.charme = foundry.utils.duplicate(this.items.get(charmeId) || {})
ui.notifications.warn("La compétence Savoirs : Runes n'a pas été trouvée, abandon.") rollData.charmeDice = "1d4"
return this.launchRoll(rollData)
}
let rollData = this.getCommonRollData("cla", undefined, "Savoir : Runes")
rollData.rune = duplicate(this.items.get(runeId) || {})
rollData.difficulte = rollData.rune?.system?.seuil || 0
rollData.runemode = "prononcer"
rollData.runeame = 1
console.log("runeData", rollData)
let rollDialog = await WastelandRollDialog.create(this, rollData)
rollDialog.render(true)
} }
/* -------------------------------------------- */
async rollPouvoir(pouvoirId) {
let pouvoir = foundry.utils.duplicate(this.items.get(pouvoirId) || {})
if (pouvoir?.system) {
let rollData = this.getCommonRollData(pouvoir.system.attribut1, undefined, pouvoir.system.competence)
if (!rollData.competence) {
ui.notifications.error("Le pouvoir " + pouvoir.name + " n'a pas de compétence associée. Editez le pouvoir avec la compétence associée.")
return
}
rollData.pouvoir = pouvoir
rollData.difficulte = pouvoir.system.seuil
this.launchRoll(rollData)
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async rollArmeOffensif(armeId) { async rollArmeOffensif(armeId) {
let arme = this.items.get(armeId) let arme = this.items.get(armeId)
@@ -445,9 +521,13 @@ export class WastelandActor extends Actor {
} }
let rollData = this.getCommonRollData(arme.system.attrKey, arme.system.competence._id) let rollData = this.getCommonRollData(arme.system.attrKey, arme.system.competence._id)
rollData.arme = arme rollData.arme = arme
console.log("ARME!", rollData) rollData.typeAttaque = "assaut"
let rollDialog = await WastelandRollDialog.create(this, rollData) rollData.typeCouvert = "aucun"
rollDialog.render(true) rollData.hasDesavantageBonus = true
rollData.visee = false
rollData.ciblecourt = false
rollData.cibleconsciente = false
this.launchRoll(rollData)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -459,7 +539,7 @@ export class WastelandActor extends Actor {
if (arme.type == "bouclier") { if (arme.type == "bouclier") {
arme = this.prepareBouclier(arme) arme = this.prepareBouclier(arme)
} }
let roll = new Roll(arme.system.totalDegats).roll({ async: false }) let roll = await new Roll(arme.system.totalDegats).roll()
await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode")); await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode"));
let rollData = { let rollData = {
arme: arme, arme: arme,
@@ -470,8 +550,52 @@ export class WastelandActor extends Actor {
actionImg: arme.img, actionImg: arme.img,
} }
WastelandUtility.createChatWithRollMode(rollData.alias, { WastelandUtility.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-wasteland/templates/chat-degats-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-degats-result-v2.hbs`, rollData)
}) })
} }
/* -------------------------------------------- */
async rollAssommer() {
let rollData = this.getCommonRollData("adr", undefined, "Mouvements")
rollData.specialAction = "Assommer"
rollData.typeAttaque = "assommer"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
rollData.difficulte = rollData.defender.system.attributs.tre.value * 2
}
this.launchRoll(rollData)
}
/* -------------------------------------------- */
async rollFuir() {
let rollData = this.getCommonRollData("adr", undefined, "Mouvements")
rollData.specialAction = "Fuir"
rollData.typeAttaque = "fuir"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
let comp = rollData.defender.items.find(it => it.type == "competence" && it.name.toLowerCase() == "mouvements")
rollData.difficulte = rollData.defender.system.attributs.adr.value + ((comp) ? comp.system.niveau : rollData.defender.system.attributs.adr.value)
}
this.launchRoll(rollData)
}
/* -------------------------------------------- */
async rollImmobiliser() {
let rollData = this.getCommonRollData("pui", undefined, "Mêlée")
rollData.specialAction = "Immobiliser"
rollData.typeAttaque = "immobiliser"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
rollData.difficulte = rollData.armeDefense ? rollData.armeDefense.system.totalDefensif : 10
}
this.launchRoll(rollData)
}
} }

View File

@@ -10,7 +10,7 @@ export class WastelandCombat extends Combat {
const c = this.combatants.get(ids[cId]); const c = this.combatants.get(ids[cId]);
let id = c._id || c.id; let id = c._id || c.id;
let initBonus = c.actor ? c.actor.getInitiativeScore() : 0 let initBonus = c.actor ? c.actor.getInitiativeScore() : 0
let roll = new Roll("1d10 + "+initBonus).roll({ async: false}) let roll = await new Roll("1d10 + "+initBonus).roll()
await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode")) await WastelandUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode"))
//console.log("Init bonus", initBonus, roll.total) //console.log("Init bonus", initBonus, roll.total)
await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: roll.total } ]); await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: roll.total } ]);

View File

@@ -1,7 +1,7 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { WastelandUtility } from "./wasteland-utility.js"; import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandRollDialog } from "./wasteland-roll-dialog.js"; import { WastelandRollDialog } from "./applications/wasteland-roll-dialog.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
export class WastelandCommands { export class WastelandCommands {

105
modules/wasteland-config.js Normal file
View File

@@ -0,0 +1,105 @@
export const WASTELAND_CONFIG = {
attributs: {
"adr": "Adresse",
"pui": "Puissance",
"cla": "Clairvoyance",
"pre": "Présence",
"tre": "Trempe"
},
cheminpouvoir : {
"force": "Chemin des Forces",
"forge": "Chemin des Forges",
"echo": "Chemin des Échos",
"reflet": "Chemin des Reflets",
"ame": "Chemin des Âmes",
"mort": "Chemin des Morts",
"vie": "Chemin de Vie",
"guerre": "Chemin des guerres",
"horizon": "Chemin vers l'horizon",
"voleurinvisible": "Chemin du Voleur invisible",
"nuit": "Chemin des Nuits",
"oiseaux": "Chemin des Oiseaux"
},
hubrisType: {
"mental": "Mental",
"physique": "Physique",
},
charmetype: {
tour: "Tour",
charme: "Charme",
},
dices: {
"1d4": "1d4",
"1d6": "1d6",
"1d8": "1d8",
"1d10": "1d10",
"1d12": "1d12",
"1d20": "1d20",
},
artifexType: {
"vapeur": "Vapeur",
"mecanique": "Mécanique",
"chimie": "Chimie",
"electricite": "Électricité",
"chimerie": "Chimérie",
},
typeArmeOptions: {
contact: "Arme de contact",
contactjet: "Arme de contact et de Jet",
jet: "Arme de Jet",
tir: "Arme de Tir",
special: "Spécial (capacité/don)"
},
attaques: {
assaut: "Assaut",
precise: "Attaque Précise",
feinte: "Feinte",
coupbas: "Coup Bas",
charger: "Charger",
contenir: "Contenir",
desarmer: "Désarmer"
},
couverts: {
aucun: { name: "Aucun", value: 0 },
rondache: { name: "Couvert Léger", value: -2 },
pavois: { name: "Couvert Moyen", value: -5 },
complet: { name: "Couvert Complet", value: -10 },
},
listePortees: {
"10": "Moins que Courte",
"15": "Courte ou plus",
"20": "Moyenne ou plus",
"25": "Longue ou plus"
},
difficulteOptions: {
"0": "Aucune/Inconnue",
"5": "Facile (5)",
"6": "(6)",
"7": "(7)",
"8": "(8)",
"9": "(9)",
"10": "Moyenne (10)",
"11": "(11)",
"12": "(12)",
"13": "(13)",
"14": "(14)",
"15": "Ardue (15)",
"16": "(16)",
"17": "(17)",
"18": "(18)",
"19": "(19)",
"20": "Hasardeuse (20)",
"21": "(21)",
"22": "(22)",
"23": "(23)",
"24": "(24)",
"25": "Insensée (25)",
"26": "(26)",
"27": "(27)",
"28": "(28)",
"29": "(29)",
"30": "Pure Folie (30)"
}
}

View File

@@ -1,179 +0,0 @@
import { WastelandUtility } from "./wasteland-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class WastelandItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-wasteland", "sheet", "item"],
template: "systems/fvtt-wasteland/templates/item-sheet.html",
dragDrop: [{ dragSelector: null, dropSelector: null }],
width: 620,
height: 550
//tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
/* -------------------------------------------- */
_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
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
if (this.item.type.includes('weapon')) {
position.width = 640;
}
return position;
}
/* -------------------------------------------- */
async getData() {
const objectData = duplicate(this.object)
let itemData = objectData
let formData = {
title: this.title,
id: this.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
attributs: WastelandUtility.getAttributs(),
data: itemData.system,
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
description: await TextEditor.enrichHTML(this.object.system.description, {async: true}),
mr: (this.object.type == 'specialisation'),
isGM: game.user.isGM
}
if ( objectData.type == "don") {
formData.sacrifice = await TextEditor.enrichHTML(this.object.system.sacrifice, {async: true})
}
//this.options.editable = !(this.object.origin == "embeddedItem");
console.log("ITEM DATA", formData, this);
return formData;
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({
class: "post",
icon: "fas fa-comment",
onclick: ev => this.postItem()
});
return buttons
}
/* -------------------------------------------- */
postItem() {
let chatData = duplicate(WastelandUtility.data(this.item));
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
// Don't post any image for the item (which would leave a large gap) if the default image is used
if (chatData.img.includes("/blank.png")) {
chatData.img = null;
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify(
{
compendium: "postedItem",
payload: chatData,
});
renderTemplate('systems/fvtt-Wasteland-rpg/templates/post-item.html', chatData).then(html => {
let chatOptions = WastelandUtility.chatDataSetup(html);
ChatMessage.create(chatOptions)
});
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// 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);
});
html.find('.delete-subitem').click(ev => {
this.deleteSubitem(ev);
})
html.find('.edit-prediction').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = duplicate(this.object.system.predilections)
pred[index].name = ev.currentTarget.value
this.object.update( { 'data.predilections': pred })
})
html.find('.delete-prediction').click(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = duplicate(this.object.system.predilections)
pred.splice(index,1)
this.object.update( { 'data.predilections': pred })
})
html.find('.use-prediction').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = duplicate(this.object.system.predilections)
pred[index].used = ev.currentTarget.checked
this.object.update( { 'data.predilections': pred })
})
html.find('#add-predilection').click(ev => {
let pred = duplicate(this.object.system.predilections)
pred.push( { name: "Nouvelle prédilection", used: false })
this.object.update( { 'data.predilections': pred })
})
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
let itemId = li.data("item-id");
let itemType = li.data("item-type");
});
}
/* -------------------------------------------- */
get template() {
let type = this.item.type;
return `systems/fvtt-wasteland/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
}
}

View File

@@ -16,6 +16,9 @@ export const defaultItemImg = {
artifex: "systems/fvtt-wasteland/assets/icons/artifact.webp", artifex: "systems/fvtt-wasteland/assets/icons/artifact.webp",
heritage: "systems/fvtt-wasteland/assets/icons/legacy.webp", heritage: "systems/fvtt-wasteland/assets/icons/legacy.webp",
charme: "systems/fvtt-wasteland/assets/icons/charm.webp", charme: "systems/fvtt-wasteland/assets/icons/charm.webp",
peuple: "systems/fvtt-wasteland/assets/icons/people.webp",
don: "systems/fvtt-wasteland/assets/icons/don.webp",
hubris: "systems/fvtt-wasteland/assets/icons/hubris.webp",
} }
/** /**

View File

@@ -9,12 +9,16 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
// Import Modules // Import Modules
import { WastelandActor } from "./wasteland-actor.js"; import { WastelandActor } from "./wasteland-actor.js";
import { WastelandItemSheet } from "./wasteland-item-sheet.js";
import { WastelandActorSheet } from "./wasteland-actor-sheet.js";
//import { WastelandNPCSheet } from "./wasteland-npc-sheet.js";
import { WastelandUtility } from "./wasteland-utility.js"; import { WastelandUtility } from "./wasteland-utility.js";
import { WastelandCombat } from "./wasteland-combat.js"; import { WastelandCombat } from "./wasteland-combat.js";
import { WastelandItem } from "./wasteland-item.js"; import { WastelandItem } from "./wasteland-item.js";
import { WASTELAND_CONFIG } from "./wasteland-config.js";
// Import DataModels
import * as models from "./models/index.mjs";
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs";
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
@@ -29,7 +33,7 @@ Hooks.once("init", async function () {
WastelandUtility.preloadHandlebarsTemplates(); WastelandUtility.preloadHandlebarsTemplates();
/* -------------------------------------------- */ /* -------------------------------------------- */
// Set an initiative formula for the system // Set an initiative formula for the system
CONFIG.Combat.initiative = { CONFIG.Combat.initiative = {
formula: "1d6", formula: "1d6",
decimals: 1 decimals: 1
@@ -44,58 +48,123 @@ Hooks.once("init", async function () {
// Define custom Entity classes // Define custom Entity classes
CONFIG.Combat.documentClass = WastelandCombat CONFIG.Combat.documentClass = WastelandCombat
CONFIG.Actor.documentClass = WastelandActor CONFIG.Actor.documentClass = WastelandActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
creature: models.CreatureDataModel
}
CONFIG.Item.documentClass = WastelandItem CONFIG.Item.documentClass = WastelandItem
game.system.wasteland = { } CONFIG.Item.dataModels = {
arme: models.ArmeDataModel,
artifex: models.ArtifexDataModel,
bouclier: models.BouclierDataModel,
capacite: models.CapaciteDataModel,
charme: models.CharmeDataModel,
competence: models.CompetenceDataModel,
don: models.DonDataModel,
equipement: models.EquipementDataModel,
heritage: models.HeritageDataModel,
hubris: models.HubrisDataModel,
metier: models.MetierDataModel,
monnaie: models.MonnaieDataModel,
mutation: models.MutationDataModel,
origine: models.OrigineDataModel,
peuple: models.PeupleDataModel,
pouvoir: models.PouvoirDataModel,
protection: models.ProtectionDataModel
}
game.system.wasteland = {
config: WASTELAND_CONFIG,
models,
sheets
}
// Initialize dynamic config options that need to be available immediately
// Create option lists inline to ensure they're available
const listeNiveauSkill = {}
for (let i = 0; i <= 10; i++) {
listeNiveauSkill[`${i}`] = `${i}`
}
game.system.wasteland.config.listeNiveauSkill = listeNiveauSkill
const listeNiveauAttribut = {}
for (let i = 0; i <= 10; i++) {
listeNiveauAttribut[`${i}`] = `${i}`
}
game.system.wasteland.config.listeNiveauAttribut = listeNiveauAttribut
const listeNiveauCreature = {}
for (let i = 0; i <= 35; i++) {
listeNiveauCreature[`${i}`] = `${i}`
}
game.system.wasteland.config.listeNiveauCreature = listeNiveauCreature
const modificateurOptions = []
for (let i = -15; i <= 15; i++) {
modificateurOptions.push({ key: i, label: `${i >= 0 ? '+' : ''}${i}` })
}
game.system.wasteland.config.modificateurOptions = modificateurOptions
const pointsAmeOptions = {}
for (let i = 0; i <= 20; i++) {
pointsAmeOptions[`${i}`] = `${i}`
}
game.system.wasteland.config.pointsAmeOptions = pointsAmeOptions
/* -------------------------------------------- */ /* -------------------------------------------- */
// Register sheet application classes // Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("fvtt-wasteland", WastelandActorSheet, { types: ["personnage"], makeDefault: true }) // Register AppV2 Actor Sheets
//Actors.registerSheet("fvtt-wasteland", WastelandNPCSheet, { types: ["npc"], makeDefault: false }); foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-wasteland", sheets.WastelandPersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-wasteland", sheets.WastelandCreatureSheet, { types: ["creature"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet); // Register AppV2 Item Sheets
Items.registerSheet("fvtt-wasteland", WastelandItemSheet, { makeDefault: true }) foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandArmeSheet, { types: ["arme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandArtifexSheet, { types: ["artifex"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandBouclierSheet, { types: ["bouclier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandCapaciteSheet, { types: ["capacite"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandCharmeSheet, { types: ["charme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandCompetenceSheet, { types: ["competence"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandDonSheet, { types: ["don"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandHeritageSheet, { types: ["heritage"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandHubrisSheet, { types: ["hubris"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandMetierSheet, { types: ["metier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandMonnaieSheet, { types: ["monnaie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandMutationSheet, { types: ["mutation"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandOrigineSheet, { types: ["origine"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandPeupleSheet, { types: ["peuple"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandPouvoirSheet, { types: ["pouvoir"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-wasteland", sheets.WastelandProtectionSheet, { types: ["protection"], makeDefault: true });
WastelandUtility.init(); WastelandUtility.init();
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
function welcomeMessage() { async function welcomeMessage() {
const templateData = {};
const html = await foundry.applications.handlebars.renderTemplate("systems/fvtt-wasteland/templates/chat-welcome-message.hbs", templateData);
ChatMessage.create({ ChatMessage.create({
user: game.user.id, user: game.user.id,
whisper: [game.user.id], whisper: [game.user.id],
content: `<div id="welcome-message-Wasteland"><span class="rdd-roll-part"> content: html
<strong>Bienvenue dans les Jeunes Royaumes de Wasteland !</strong> });
<p>Les livres de Wasteland sont nécessaires pour jouer : https://www.titam-france.fr</p>
<p>Wasteland est jeu de rôle publié par Titam France/Sombres projets, tout les droits leur appartiennent.</p>
<p>Système développé par LeRatierBretonnien, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p>
` });
} }
/* -------------------------------------------- */
// Register world usage statistics
function registerUsageCount( registerKey ) {
if ( game.user.isGM ) {
game.settings.register(registerKey, "world-key", {
name: "Unique world key",
scope: "world",
config: false,
default: "",
type: String
});
let worldKey = game.settings.get(registerKey, "world-key") /* -------------------------------------------- */
if ( worldKey == undefined || worldKey == "" ) { async function importDefaultScene() {
worldKey = randomID(32) let exists = game.scenes.find(j => j.name == "Accueil");
game.settings.set(registerKey, "world-key", worldKey ) if (!exists) {
} const scenes = await WastelandUtility.loadCompendium("fvtt-wasteland.scenes")
// Simple API counter let newDocuments = scenes.filter(i => i.name == "Accueil");
let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"` await game.scenes.documentClass.create(newDocuments);
//$.ajaxSetup({ game.scenes.find(i => i.name == "Accueil").activate();
//headers: { 'Access-Control-Allow-Origin': '*' }
//})
$.ajax(regURL)
} }
} }
@@ -105,6 +174,7 @@ function registerUsageCount( registerKey ) {
Hooks.once("ready", function () { Hooks.once("ready", function () {
WastelandUtility.ready(); WastelandUtility.ready();
// User warning // User warning
if (!game.user.isGM && game.user.character == undefined) { if (!game.user.isGM && game.user.character == undefined) {
ui.notifications.info("Attention ! Aucun personnage n'est relié au joueur !"); ui.notifications.info("Attention ! Aucun personnage n'est relié au joueur !");
@@ -113,9 +183,24 @@ Hooks.once("ready", function () {
user: game.user._id user: game.user._id
}); });
} }
if (!game.user.isGM && game.user.character && !game.user.character.prototypeToken.actorLink) {
registerUsageCount('fvtt-wasteland') ui.notifications.info("Le token de du joueur n'est pas connecté à l'acteur !");
ChatMessage.create({
content: "<b>ATTENTION</b> Le token du joueur " + game.user.name + " n'est pas connecté à l'acteur !",
user: game.user._id
});
}
import("https://www.uberwald.me/fvtt_appcount/count-class-ready.js").then(moduleCounter=>{
console.log("ClassCounter loaded", moduleCounter)
moduleCounter.ClassCounter.registerUsageCount()
}).catch(err=>
console.log("No stats available, giving up.")
)
welcomeMessage(); welcomeMessage();
importDefaultScene();
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -131,4 +216,3 @@ Hooks.on("chatMessage", (html, content, msg) => {
} }
return true; return true;
}); });

View File

@@ -1,79 +0,0 @@
import { WastelandUtility } from "./wasteland-utility.js";
export class WastelandRollDialog extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let options = { classes: ["WastelandDialog"], width: 340, height: 'fit-content', 'z-index': 99999 };
let html = await renderTemplate('systems/fvtt-wasteland/templates/roll-dialog-generic.html', rollData);
return new WastelandRollDialog(actor, rollData, html, options );
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: "Test de Capacité",
content: html,
buttons: {
rolld10: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d10",
callback: () => { this.roll("1d10") }
},
rolld20: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d20",
callback: () => { this.roll("1d20") }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
} },
close: close
}
super(conf, options);
this.actor = actor
this.rollData = rollData
}
/* -------------------------------------------- */
roll ( dice) {
this.rollData.mainDice = dice
WastelandUtility.rollWasteland( this.rollData )
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('#modificateur').change(async (event) => {
this.rollData.modificateur = Number(event.currentTarget.value)
})
html.find('#difficulte').change(async (event) => {
this.rollData.difficulte = Number(event.currentTarget.value)
})
html.find('#attrKey').change(async (event) => {
this.rollData.attrKey = String(event.currentTarget.value)
})
html.find('#runemode').change(async (event) => {
this.rollData.runemode = String(event.currentTarget.value)
})
html.find('#runeame').change(async (event) => {
this.rollData.runeame = Number(event.currentTarget.value)
})
html.find('#doubleD20').change(async (event) => {
this.rollData.doubleD20 = event.currentTarget.checked
})
}
}

View File

@@ -2,6 +2,20 @@
import { WastelandCombat } from "./wasteland-combat.js"; import { WastelandCombat } from "./wasteland-combat.js";
import { WastelandCommands } from "./wasteland-commands.js"; import { WastelandCommands } from "./wasteland-commands.js";
/* -------------------------------------------- */
const __contrecouptCharme = {
1 : {name: "Effet chromatique", description: "le corps du kobold prend des teintes aussi étranges que voyantes. L'effet sestompe progressivement et 24 heures plus tard, le kobold retrouve ses couleurs dorigine." },
3 : {name: "Enivrement Kobold", description: "très excité par son premier tour, le kobold doit immédiatement faire un autre tour, pour lequel il emploiera un dé plus gros." },
5 : {name: "Mutisme superstitieux", description: "le kobold ne doit plus parler» pendant les prochaines 24 heures. S'il le fait malgré tout, les effets de son tour sarrêtent." },
7 : {name: "Agité!", description: "le kobold ne tient plus en place. Il ne peut se reposer pendant les prochaines 12 heures. Dès. que 12 heures se sont écoulées, il s'effondre comme une masse et dort 12 heures d'affilée dun sommeil enchanté dont rien ne pourra le réveiller." },
9 : {name: "Somnolence", description: "le kobold devient somnolent. Il narrive pas à se concentrer même sur une tâche simple, bäille sans arrêt, traîne les pieds et nagit plus que de mauvaise grâce. Cela dure jusquà ce qu'il ait dormi au moins 12 heures." },
11 : {name: "Manie incontrôlable", description: "le kobold est pris dune manie incontrôlable. Dès qu'il voit un chapeau rouge, il doit suivre son porteur. Il ne mangera que si son voisin de gauche mange aussi, etc. Cela dure pendant une jour- née puis leffet sinverse pendant une heure : il ne suivra jamais un chapeau rouge, ne mangera jamais si son voi- sin de gauche mange, etc. Le contrecoup prend alors fin." },
13 : {name: "Malédiction des Ternes", description: "le kobold perd cette qualité mystérieuse qui fait que les kobolds sont des kobolds et devient tout. Terne. Il perd 1d20 point(s) de Bonne Aventure (sil doit en perdre plus qu'il nen a, il tombe simplement à 0). Ces points perdus pourront cependant être regagnés normalement." },
15 : {name: "La petite Mort", description: "le kobold s'endort pour 1420 heures. Rien ni personne ne pourra le tirer de ce sommeil enchanté avant que ce contrecoup ne prenne fin." },
17 : {name: "Angoisse cauchemardesque", description: "le kobold a une brève vision de pure horreur. Il perd 1420 points de Psyché {s'il doit en perdre plus qu'il nen a, il tombe à 0)." },
19 : {name: "Anémie Kobold", description: "le kobold se met à saigner du nez, des oreilles et même dautres endroits. Il perd 1420 point(s) de Santé." }
}
/* -------------------------------------------- */ /* -------------------------------------------- */
export class WastelandUtility { export class WastelandUtility {
@@ -9,14 +23,12 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async init() { static async init() {
Hooks.on('renderChatLog', (log, html, data) => WastelandUtility.chatListeners(html)) Hooks.on('renderChatLog', (log, html, data) => WastelandUtility.chatListeners(html))
Hooks.on("getChatLogEntryContext", (html, options) => WastelandUtility.chatRollMenu(html, options)) Hooks.on('renderChatMessageHTML', (message, html, data) => WastelandUtility.chatListeners(html))
Hooks.on("getChatMessageContextOptions", (html, options) => WastelandUtility.chatRollMenu(html, options))
Hooks.on("getCombatTrackerEntryContext", (html, options) => { Hooks.on("getCombatTrackerEntryContext", (html, options) => {
WastelandUtility.pushInitiativeOptions(html, options); WastelandUtility.pushInitiativeOptions(html, options);
}) })
Hooks.on("dropCanvasData", (canvas, data) => {
WastelandUtility.dropItemOnToken(canvas, data)
});
this.rollDataStore = {} this.rollDataStore = {}
this.defenderStore = {} this.defenderStore = {}
@@ -100,6 +112,25 @@ export class WastelandUtility {
static async ready() { static async ready() {
const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills") const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills")
this.skills = skills.map(i => i.toObject()) this.skills = skills.map(i => i.toObject())
// Note: listeNiveauSkill, listeNiveauCreature, etc. are now created in init hook
// to be available immediately when sheets are opened
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
static createArrayOptionList(min, max) {
let options = [];
for (let i = min; i <= max; i++) {
options.push({key:`${i}`, label:`${i}`});
}
return options;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -121,15 +152,41 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async chatListeners(html) { static async chatListeners(html) {
html.on("click", '.predilection-reroll', async event => { $(html).on("click", '.predilection-reroll', async event => {
let predIdx = $(event.currentTarget).data("predilection-index") let predIdx = $(event.currentTarget).data("predilection-index")
let messageId = WastelandUtility.findChatMessageId(event.currentTarget) let messageId = WastelandUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId) let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData) let actor = WastelandUtility.getActorFromRollData(rollData)
// Sauvegarder le résultat précédent
let previousResult = {
diceResult: rollData.diceResult,
finalResult: rollData.finalResult,
diceFormula: rollData.diceFormula
}
// Récupérer la prédilection utilisée
let predilectionUsed = foundry.utils.duplicate(rollData.competence.system.predilections[predIdx])
// Marquer la prédilection comme utilisée
await actor.setPredilectionUsed(rollData.competence._id, predIdx) await actor.setPredilectionUsed(rollData.competence._id, predIdx)
rollData.competence = duplicate(actor.getCompetence(rollData.competence._id))
await WastelandUtility.rollWasteland(rollData) // Mettre à jour la compétence dans rollData
rollData.competence = foundry.utils.duplicate(actor.getCompetence(rollData.competence._id))
// Lancer le reroll avec prédilection
await WastelandUtility.rollWastelandPredilection(rollData, predilectionUsed, previousResult)
})
$(html).on("click", '.arme-roll-degats', async event => {
let messageId = WastelandUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData && rollData.arme) {
let actor = WastelandUtility.getActorFromRollData(rollData)
await actor.rollArmeDegats(rollData.arme._id)
}
}) })
} }
@@ -137,11 +194,12 @@ export class WastelandUtility {
static async preloadHandlebarsTemplates() { static async preloadHandlebarsTemplates() {
const templatePaths = [ const templatePaths = [
'systems/fvtt-wasteland/templates/editor-notes-gm.html', 'systems/fvtt-wasteland/templates/editor-notes-gm.hbs',
'systems/fvtt-wasteland/templates/partial-item-description.html', 'systems/fvtt-wasteland/templates/partial-item-description.hbs',
'systems/fvtt-wasteland/templates/partial-list-niveau.html' 'systems/fvtt-wasteland/templates/partial-item-header.hbs',
'systems/fvtt-wasteland/templates/partial-item-nav.hbs'
] ]
return loadTemplates(templatePaths); return foundry.applications.handlebars.loadTemplates(templatePaths);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -173,15 +231,6 @@ export class WastelandUtility {
return undefined; return undefined;
} }
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static buildListOptions(min, max) { static buildListOptions(min, max) {
let options = "" let options = ""
@@ -206,14 +255,14 @@ export class WastelandUtility {
let id = rollData.rollId; let id = rollData.rollId;
let oldRollData = this.rollDataStore[id] || {}; let oldRollData = this.rollDataStore[id] || {};
let newRollData = mergeObject(oldRollData, rollData); let newRollData = foundry.utils.mergeObject(oldRollData, rollData);
this.rollDataStore[id] = newRollData; this.rollDataStore[id] = newRollData;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static saveRollData(rollData) { static saveRollData(rollData) {
game.socket.emit("system.fvtt-wasteland", { game.socket.emit("system.fvtt-wasteland", {
name: "msg_update_roll", data: rollData name: "msg_update_roll", data: rollData
}); // Notify all other clients of the roll }); // Notify all other clients of the roll
this.updateRollData(rollData); this.updateRollData(rollData);
} }
@@ -278,26 +327,51 @@ export class WastelandUtility {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static computeResult(rollData) { static async computeResult(rollData, actor) {
if (rollData.mainDice == "1d20") { if (rollData.charme) {
let diceValue = rollData.roll.terms[0].results[0].result let resultIndex = false
diceValue *= (rollData.doubleD20) ? 2 : 1 let resTab = foundry.utils.duplicate(rollData.charme.system.resultats)
//console.log("PAIR/IMP", diceValue) for(let id in resTab) {
if (diceValue % 2 == 1) { let res = resTab[id]
//console.log("PAIR/IMP2", diceValue) if (!resultIndex && rollData.finalResult >= res.value) {
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value resultIndex = id;
if (diceValue == 1 || diceValue == 11) { }
rollData.isDramatique = true }
rollData.isSuccess = false if (resultIndex) {
rollData.charmeDuree = rollData.charme.system.resultats[resultIndex].description
}
let effectRoll = await new Roll(rollData.charmeDice).roll()
if (rollData.charme.system.charmetype == "tour") {
rollData.contrecoupResult = effectRoll.total
if (rollData.contrecoupResult % 2 == 1) {
rollData.contrecoup = __contrecouptCharme[rollData.contrecoupResult]
}
}
if (rollData.charme.system.charmetype == "charme") {
rollData.charmeSante = effectRoll.total
actor.incDecSante(rollData.charmeSante)
}
} else {
if (rollData.mainDice == "1d20") {
let diceValue = rollData.roll.terms[0].results[0].result
diceValue *= (rollData.doubleD20) ? 2 : 1
//console.log("PAIR/IMP", diceValue)
if (diceValue % 2 == 1) {
//console.log("PAIR/IMP2", diceValue)
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
if (diceValue == 1 || diceValue == 11) {
rollData.isDramatique = true
rollData.isSuccess = false
}
} }
} }
}
//console.log("Result : ", rollData) //console.log("Result : ", rollData)
if (rollData.difficulte > 0 && !rollData.isDramatique) { if (rollData.difficulte > 0 && !rollData.isDramatique) {
rollData.isSuccess = (rollData.finalResult >= rollData.difficulte) rollData.isSuccess = (rollData.finalResult >= rollData.difficulte)
rollData.isHeroique = ((rollData.finalResult - rollData.difficulte) >= 10) rollData.isHeroique = ((rollData.finalResult - rollData.difficulte) >= 10)
rollData.isDramatique = ((rollData.finalResult - rollData.difficulte) <= -10) rollData.isDramatique = ((rollData.finalResult - rollData.difficulte) <= -10)
}
} }
} }
@@ -308,60 +382,90 @@ export class WastelandUtility {
if (rollData.attrKey == "tochoose") { // No attr selected, force address if (rollData.attrKey == "tochoose") { // No attr selected, force address
rollData.attrKey = "adr" rollData.attrKey = "adr"
} }
if (!rollData.attr) { if (!rollData.attr && actor && actor.system.attributs && actor.system.attributs[rollData.attrKey]) {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp" rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
rollData.attr = duplicate(actor.system.attributs[rollData.attrKey]) if (rollData.attr.labelnorm) {
} rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + rollData.attr.labelnorm + ".webp"
rollData.diceFormula = rollData.mainDice
if (rollData.doubleD20) { // Multiply result !
rollData.diceFormula += "*2"
if (!rollData.isReroll) {
actor.changeEclat(-1)
} }
} }
//console.log("BEFORE COMP", rollData)
if (rollData.competence) { if (rollData.charme) {
rollData.predilections = duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || []) rollData.diceFormula = rollData.charmeDice
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
rollData.diceFormula += `+${rollData.attr.value}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else { } else {
rollData.diceFormula += `+${rollData.attr.value}*2+${rollData.modificateur}` rollData.diceFormula = rollData.mainDice
// Double D20 ne s'applique que si on lance un D20, pas un D10
if (rollData.doubleD20 && rollData.mainDice === "1d20") {
rollData.diceFormula += "*2"
if (!rollData.isReroll) {
actor.changeEclat(-1)
}
}
} }
//console.log("BEFORE COMP", rollData)
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else {
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}*2+${rollData.modificateur}`
}
if (rollData.arme && rollData.arme.type == "arme") { if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}` rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
} }
if (rollData.rune) { // Apply desavantages (tactical advantages)
rollData.runeduree = Math.ceil((rollData.runeame + 3) / 3) let desavantagesBonus = 0
if (rollData.runemode == "inscrire") { if (rollData.desavantages) {
rollData.runeduree *= 2 for (let desavantage in rollData.desavantages) {
if (rollData.desavantages[desavantage]) {
desavantagesBonus += 5
}
} }
if (rollData.runemode == "prononcer") { desavantagesBonus = Math.min(15, desavantagesBonus)
rollData.runeduree = 1 if (desavantagesBonus > 0) {
rollData.diceFormula += `+${desavantagesBonus}`
} }
} }
let myRoll = new Roll(rollData.diceFormula).roll({ async: false }) // Monté ?
if (rollData.isMonte) {
rollData.diceFormula += "+5"
}
// Specific modifiers for ranged combat
if (rollData.arme?.system?.isDistance) {
if (rollData.visee) {
rollData.diceFormula += "+5"
}
if (rollData.cibleconsciente && rollData.defender) {
rollData.diceFormula += `-${rollData.defender.system.attributs.adr.value}`
}
if (rollData.ciblecourt) {
if (rollData.difficulte <= 15) { // Portée courte ou moins
rollData.diceFormula += `-5`
} else {
rollData.diceFormula += `-10`
}
}
if (rollData.typeCouvert && rollData.typeCouvert != "aucun" && rollData.config.couverts[rollData.typeCouvert]) {
rollData.diceFormula += `+${rollData.config.couverts[rollData.typeCouvert].value}`
}
}
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = duplicate(myRoll) rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.terms[0].results[0].result rollData.diceResult = myRoll.terms[0].results[0].result
console.log(">>>> ", myRoll) console.log(">>>> ", myRoll)
rollData.finalResult = myRoll.total rollData.finalResult = myRoll.total
this.computeResult(rollData) await this.computeResult(rollData, actor)
if (rollData.rune) {
let subAme = rollData.runeame
if (rollData.isEchec && !rollData.isDramatique) {
subAme = Math.ceil((subAme + 1) / 2)
}
actor.subPointsAme(rollData.runemode, subAme)
}
this.createChatWithRollMode(rollData.alias, { this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData) }, rollData)
} }
@@ -370,20 +474,146 @@ export class WastelandUtility {
static async bonusRollWasteland(rollData) { static async bonusRollWasteland(rollData) {
rollData.bonusFormula = rollData.addedBonus rollData.bonusFormula = rollData.addedBonus
let bonusRoll = new Roll(rollData.bonusFormula).roll({ async: false }) let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode")); await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
rollData.bonusRoll = duplicate(bonusRoll) rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += rollData.bonusRoll.total rollData.finalResult += rollData.bonusRoll.total
this.computeResult(rollData) await this.computeResult(rollData)
this.createChatWithRollMode(rollData.alias, { this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result.html`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData) }, rollData)
} }
/* -------------------------------------------- */
static async rollWastelandPredilection(rollData, predilectionUsed, previousResult) {
// Sauvegarder le premier résultat
rollData.firstRoll = {
diceResult: previousResult.diceResult,
finalResult: previousResult.finalResult,
diceFormula: previousResult.diceFormula
}
rollData.predilectionUsed = predilectionUsed
rollData.isPredilectionReroll = true
let actor = WastelandUtility.getActorFromRollData(rollData)
if (rollData.attrKey == "tochoose") {
rollData.attrKey = "adr"
}
if (!rollData.attr && actor && actor.system.attributs && actor.system.attributs[rollData.attrKey]) {
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
if (rollData.attr.labelnorm) {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + rollData.attr.labelnorm + ".webp"
}
}
if (rollData.charme) {
rollData.diceFormula = rollData.charmeDice
} else {
rollData.diceFormula = rollData.mainDice
// Double D20 ne s'applique que si on lance un D20, pas un D10
if (rollData.doubleD20 && rollData.mainDice === "1d20") {
rollData.diceFormula += "*2"
}
}
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else {
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}*2+${rollData.modificateur}`
}
if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
}
// Apply desavantages (tactical advantages)
if (rollData.desavantages) {
let totalDesavantage = 0
for (let key in rollData.desavantages) {
if (rollData.desavantages[key]) {
totalDesavantage += 5
}
}
totalDesavantage = Math.min(totalDesavantage, 15)
if (totalDesavantage > 0) {
rollData.diceFormula += `+${totalDesavantage}`
}
}
// Apply mounted bonus
if (rollData.isMonte) {
rollData.diceFormula += `+5`
}
// Apply ranged combat modifiers
if (rollData.arme && rollData.arme.system && rollData.arme.system.isDistance) {
if (rollData.visee) {
rollData.diceFormula += `+5`
}
if (rollData.cibleconsciente && rollData.defender) {
let adrMalus = -(rollData.defender.system?.attributs?.adr?.value || 0)
rollData.diceFormula += `${adrMalus}`
}
if (rollData.ciblecourt) {
if (rollData.portee === "portee-courte" || rollData.portee === "portee-moyenne") {
rollData.diceFormula += `-5`
} else {
rollData.diceFormula += `-10`
}
}
if (rollData.couvert && rollData.typeCouvertValue) {
rollData.diceFormula += `${rollData.typeCouvertValue}`
}
}
// Nouveau jet de dé
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.total
rollData.bonusFormula = "0"
if (rollData.charme && rollData.charme.system.bonusmajeur > 0) {
rollData.bonusFormula = `${rollData.charme.system.bonusmajeur}d6`
rollData.textBonus = "Bonus de Charme"
}
rollData.finalResult = rollData.diceResult
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"))
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += rollData.bonusRoll.total
// Comparer avec le premier jet et garder le meilleur
rollData.secondRoll = {
diceResult: rollData.diceResult,
finalResult: rollData.finalResult
}
if (rollData.firstRoll.finalResult > rollData.finalResult) {
// Garder le premier résultat
rollData.diceResult = rollData.firstRoll.diceResult
rollData.finalResult = rollData.firstRoll.finalResult
rollData.keptRoll = "first"
} else {
// Garder le second résultat
rollData.keptRoll = "second"
}
await this.computeResult(rollData)
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData)
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static getUsers(filter) { static getUsers(filter) {
return game.users.filter(filter).map(user => user.data._id); return game.users.filter(filter).map(user => user.data._id);
@@ -406,7 +636,7 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static blindMessageToGM(chatOptions) { static blindMessageToGM(chatOptions) {
let chatGM = duplicate(chatOptions); let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM); chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content; chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM); console.log("blindMessageToGM", chatGM);
@@ -468,12 +698,13 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static getBasicRollData() { static getBasicRollData() {
let rollData = { let rollData = {
rollId: randomID(16), rollId: foundry.utils.randomID(16),
rollMode: game.settings.get("core", "rollMode"), rollMode: game.settings.get("core", "rollMode"),
modificateursOptions: this.getModificateurOptions(), modificateursOptions: this.getModificateurOptions(),
pointAmeOptions: this.getPointAmeOptions(), pointAmeOptions: this.getPointAmeOptions(),
difficulte: 0, difficulte: 0,
modificateur: 0, modificateur: 0,
config: game.system.wasteland.config,
} }
WastelandUtility.updateWithTarget(rollData) WastelandUtility.updateWithTarget(rollData)
return rollData return rollData
@@ -485,6 +716,7 @@ export class WastelandUtility {
if (target) { if (target) {
rollData.defenderTokenId = target.id rollData.defenderTokenId = target.id
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
rollData.defender = defender.toObject() // For template access
rollData.armeDefense = defender.getBestDefenseValue() rollData.armeDefense = defender.getBestDefenseValue()
if (rollData.armeDefense) { if (rollData.armeDefense) {
rollData.difficulte = rollData.armeDefense.system.totalDefensif rollData.difficulte = rollData.armeDefense.system.totalDefensif
@@ -501,7 +733,7 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static applyBonneAventureRoll(li, changed, addedBonus) { static applyBonneAventureRoll(li, changed, addedBonus) {
let msgId = li.data("message-id") let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId) let msg = game.messages.get(msgId)
if (msg) { if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll") let rollData = msg.getFlag("world", "wasteland-roll")
@@ -520,7 +752,7 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static applyEclatRoll(li, changed, addedBonus) { static applyEclatRoll(li, changed, addedBonus) {
let msgId = li.data("message-id") let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId) let msg = game.messages.get(msgId)
if (msg) { if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll") let rollData = msg.getFlag("world", "wasteland-roll")
@@ -533,11 +765,90 @@ export class WastelandUtility {
} }
} }
/* -------------------------------------------- */
static cancelBlessure(li) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a au moins 1 point d'éclat
if (actor.getEclat() < 1) {
ui.notifications.warn("Pas assez de points d'Éclat")
return
}
// Déduire 1 point d'éclat
actor.changeEclat(-1)
// Annuler la dernière blessure (incrémenter la santé)
let currentSante = actor.system.sante.value
let maxSante = actor.system.sante.max
if (currentSante < maxSante) {
actor.update({ 'system.sante.value': currentSante + 1 })
ui.notifications.info("Blessure annulée - 1 Point d'Éclat dépensé")
} else {
ui.notifications.warn("Vous êtes déjà en pleine santé")
// Rembourser le point d'éclat
actor.changeEclat(1)
}
}
}
/* -------------------------------------------- */
static reloadBA(li) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a au moins 1 point d'éclat
if (actor.getEclat() < 1) {
ui.notifications.warn("Pas assez de points d'Éclat")
return
}
// Déduire 1 point d'éclat
actor.changeEclat(-1)
// Recharger les points de Bonne Aventure au maximum
let maxBA = actor.system.bonneaventure.base
actor.update({ 'system.bonneaventure.actuelle': maxBA })
ui.notifications.info(`Points de Bonne Aventure rechargés à ${maxBA} - 1 Point d'Éclat dépensé`)
}
}
/* -------------------------------------------- */
static incDecSante(li, value, costBA) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a assez de points de Bonne Aventure
if (actor.getBonneAventure() < costBA) {
ui.notifications.warn("Pas assez de points de Bonne Aventure")
return
}
// Déduire les points de Bonne Aventure
actor.changeBonneAventure(-costBA)
// Incrémenter la santé
actor.incDecSante(value)
ui.notifications.info(`+${value} Point(s) de Santé - ${costBA} Point(s) de Bonne Aventure dépensé(s)`)
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static chatRollMenu(html, options) { static chatRollMenu(html, options) {
let canApply = li => canvas.tokens.controlled.length && li.find(".wasteland-roll").length let canApply = li => canvas.tokens.controlled.length && li.find(".wasteland-roll").length
let hasBA = function (li) { let hasBA = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -547,7 +858,7 @@ export class WastelandUtility {
return false return false
} }
let hasBA2 = function (li) { let hasBA2 = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -557,7 +868,7 @@ export class WastelandUtility {
return false return false
} }
let hasBA3 = function (li) { let hasBA3 = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -567,7 +878,7 @@ export class WastelandUtility {
return false return false
} }
let hasPE = function (li) { let hasPE = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) { if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId) let actor = game.actors.get(rollData.actorId)
@@ -577,7 +888,7 @@ export class WastelandUtility {
return false return false
} }
let hasPredilection = function (li) { let hasPredilection = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData.competence) { if (rollData.competence) {
let nbPred = rollData.competence.data.predilections.filter(pred => !pred.used).length let nbPred = rollData.competence.data.predilections.filter(pred => !pred.used).length
@@ -586,7 +897,7 @@ export class WastelandUtility {
return false return false
} }
let canCompetenceDouble = function (li) { let canCompetenceDouble = function (li) {
let message = game.messages.get(li.attr("data-message-id")) let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll") let rollData = message.getFlag("world", "wasteland-roll")
if (rollData.competence) { if (rollData.competence) {
return rollData.competence.data.doublebonus return rollData.competence.data.doublebonus
@@ -606,7 +917,7 @@ export class WastelandUtility {
name: "Gain de 1 Point de Santé / 24 heure (1 point de Bonne Aventure)", name: "Gain de 1 Point de Santé / 24 heure (1 point de Bonne Aventure)",
icon: "<i class='fas fa-user-plus'></i>", icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && hasBA, condition: canApply && hasBA,
callback: li => WastelandUtility.incDecSante(1) callback: li => WastelandUtility.incDecSante(li, 1, 1)
} }
) )
options.push( options.push(
@@ -614,7 +925,7 @@ export class WastelandUtility {
name: "Gain de 2 Points de Santé / 24 heure (2 points de Bonne Aventure)", name: "Gain de 2 Points de Santé / 24 heure (2 points de Bonne Aventure)",
icon: "<i class='fas fa-user-plus'></i>", icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && hasBA2, condition: canApply && hasBA2,
callback: li => WastelandUtility.incDecSante(2) callback: li => WastelandUtility.incDecSante(li, 2, 2)
} }
) )
options.push( options.push(
@@ -670,30 +981,24 @@ export class WastelandUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async confirmDelete(actorSheet, li) { static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id"); const itemId = li.dataset.itemId
let msgTxt = "<p>Are you sure to remove this Item ?"; if (!itemId) return
let buttons = {
delete: { const item = actorSheet.document.items.get(itemId)
icon: '<i class="fas fa-check"></i>', if (!item) return
label: "Yes, remove it",
callback: () => { const confirmed = await foundry.applications.api.DialogV2.confirm({
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]); window: { title: "Confirmer la suppression" },
li.slideUp(200, () => actorSheet.render(false)); content: `<p>Êtes-vous sûr de vouloir supprimer <strong>${item.name}</strong> ?</p>`,
} rejectClose: false,
}, modal: true,
cancel: { yes: { label: "Supprimer", icon: "fa-trash" },
icon: '<i class="fas fa-times"></i>', no: { label: "Annuler", icon: "fa-times" }
label: "Cancel" })
}
if (confirmed) {
await actorSheet.document.deleteEmbeddedDocuments("Item", [itemId])
} }
msgTxt += "</p>";
let d = new Dialog({
title: "Confirm removal",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
} }
} }

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-wasteland",
"version": "1.0.0",
"description": "Wasteland 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"
}
}

Binary file not shown.

BIN
packs/armes/000211.ldb Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000002 MANIFEST-000224

View File

@@ -1,5 +1,3 @@
2023/11/28-20:39:47.162893 7fef57fff6c0 Delete type=3 #1 2026/01/10-09:44:14.205626 7f1c56bff6c0 Recovering log #222
2023/11/28-20:44:56.323628 7fef56ffd6c0 Level-0 table #5: started 2026/01/10-09:44:14.218871 7f1c56bff6c0 Delete type=3 #220
2023/11/28-20:44:56.327500 7fef56ffd6c0 Level-0 table #5: 7368 bytes OK 2026/01/10-09:44:14.219004 7f1c56bff6c0 Delete type=0 #222
2023/11/28-20:44:56.334760 7fef56ffd6c0 Delete type=0 #3
2023/11/28-20:44:56.341959 7fef56ffd6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)

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

@@ -0,0 +1,8 @@
2026/01/10-09:33:50.449402 7f1c553fc6c0 Recovering log #218
2026/01/10-09:33:50.499238 7f1c553fc6c0 Delete type=3 #216
2026/01/10-09:33:50.499325 7f1c553fc6c0 Delete type=0 #218
2026/01/10-09:42:25.332397 7f1c54bfb6c0 Level-0 table #223: started
2026/01/10-09:42:25.332474 7f1c54bfb6c0 Level-0 table #223: 0 bytes OK
2026/01/10-09:42:25.395595 7f1c54bfb6c0 Delete type=0 #221
2026/01/10-09:42:25.519167 7f1c54bfb6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
2026/01/10-09:42:25.519295 7f1c54bfb6c0 Manual compaction at level-1 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/armes/MANIFEST-000224 Normal file

Binary file not shown.

BIN
packs/artifex/000072.ldb Normal file

Binary file not shown.

1
packs/artifex/CURRENT Normal file
View File

@@ -0,0 +1 @@
MANIFEST-000085

3
packs/artifex/LOG Normal file
View File

@@ -0,0 +1,3 @@
2026/01/10-09:44:14.267526 7f1c563fe6c0 Recovering log #83
2026/01/10-09:44:14.284469 7f1c563fe6c0 Delete type=3 #81
2026/01/10-09:44:14.284596 7f1c563fe6c0 Delete type=0 #83

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

@@ -0,0 +1,8 @@
2026/01/10-09:33:50.652107 7f1c563fe6c0 Recovering log #79
2026/01/10-09:33:50.742710 7f1c563fe6c0 Delete type=3 #77
2026/01/10-09:33:50.742808 7f1c563fe6c0 Delete type=0 #79
2026/01/10-09:42:25.395879 7f1c54bfb6c0 Level-0 table #84: started
2026/01/10-09:42:25.395959 7f1c54bfb6c0 Level-0 table #84: 0 bytes OK
2026/01/10-09:42:25.461875 7f1c54bfb6c0 Delete type=0 #82
2026/01/10-09:42:25.519225 7f1c54bfb6c0 Manual compaction at level-0 from '!items!PqP7BWEkK7aK65yH' @ 72057594037927935 : 1 .. '!items!irEA0eyE731viEYl' @ 0 : 0; will stop at (end)
2026/01/10-09:42:25.519316 7f1c54bfb6c0 Manual compaction at level-1 from '!items!PqP7BWEkK7aK65yH' @ 72057594037927935 : 1 .. '!items!irEA0eyE731viEYl' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/bestiaire/000084.ldb Normal file

Binary file not shown.

1
packs/bestiaire/CURRENT Normal file
View File

@@ -0,0 +1 @@
MANIFEST-000097

3
packs/bestiaire/LOG Normal file
View File

@@ -0,0 +1,3 @@
2026/01/10-09:44:14.021312 7f1c55bfd6c0 Recovering log #95
2026/01/10-09:44:14.040496 7f1c55bfd6c0 Delete type=3 #93
2026/01/10-09:44:14.040627 7f1c55bfd6c0 Delete type=0 #95

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

@@ -0,0 +1,8 @@
2026/01/10-09:33:49.998930 7f1c56bff6c0 Recovering log #91
2026/01/10-09:33:50.047028 7f1c56bff6c0 Delete type=3 #89
2026/01/10-09:33:50.047160 7f1c56bff6c0 Delete type=0 #91
2026/01/10-09:42:24.848889 7f1c54bfb6c0 Level-0 table #96: started
2026/01/10-09:42:24.848942 7f1c54bfb6c0 Level-0 table #96: 0 bytes OK
2026/01/10-09:42:24.909912 7f1c54bfb6c0 Delete type=0 #94
2026/01/10-09:42:25.030500 7f1c54bfb6c0 Manual compaction at level-0 from '!actors!S7FhBajQ5KKhIpj6' @ 72057594037927935 : 1 .. '!folders!BHMWTRHF2lNlAK8u' @ 0 : 0; will stop at (end)
2026/01/10-09:42:25.030611 7f1c54bfb6c0 Manual compaction at level-1 from '!actors!S7FhBajQ5KKhIpj6' @ 72057594037927935 : 1 .. '!folders!BHMWTRHF2lNlAK8u' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
MANIFEST-000085

3
packs/capacreature/LOG Normal file
View File

@@ -0,0 +1,3 @@
2026/01/10-09:44:14.288059 7f1c553fc6c0 Recovering log #83
2026/01/10-09:44:14.307028 7f1c553fc6c0 Delete type=3 #81
2026/01/10-09:44:14.307153 7f1c553fc6c0 Delete type=0 #83

View File

@@ -0,0 +1,8 @@
2026/01/10-09:33:50.746372 7f1c553fc6c0 Recovering log #79
2026/01/10-09:33:50.798633 7f1c553fc6c0 Delete type=3 #77
2026/01/10-09:33:50.798745 7f1c553fc6c0 Delete type=0 #79
2026/01/10-09:42:25.580579 7f1c54bfb6c0 Level-0 table #84: started
2026/01/10-09:42:25.580643 7f1c54bfb6c0 Level-0 table #84: 0 bytes OK
2026/01/10-09:42:25.633628 7f1c54bfb6c0 Delete type=0 #82
2026/01/10-09:42:25.776283 7f1c54bfb6c0 Manual compaction at level-0 from '!items!JzGNaagJD2jLi9tH' @ 72057594037927935 : 1 .. '!items!LaiHuZ30K4iJr6ce' @ 0 : 0; will stop at (end)
2026/01/10-09:42:25.776373 7f1c54bfb6c0 Manual compaction at level-1 from '!items!JzGNaagJD2jLi9tH' @ 72057594037927935 : 1 .. '!items!LaiHuZ30K4iJr6ce' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@@ -1,30 +0,0 @@
{"name":"Protection du Sorcier","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu est prot&eacute;g&eacute; contre les attaques des cr&eacute;atures n&eacute;es ou invoqu&eacute;es par la sorcellerie (un type d&rsquo;&Eacute;l&eacute;mentaire, les D&eacute;mons, les Automata&hellip;). La protection dont il b&eacute;n&eacute;ficie d&eacute;pend du nombre de points d&rsquo;&Acirc;me sacrifi&eacute;s.</p>","allegeance":"chaos","prerequis":"6 ou plus en Trempe.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour 1 point de protection. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour 2 points de protection. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un 3 points de protection. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.WUPne9oDFTcdLtW9"}},"_id":"5dGXNiL3WN4cAk7X"}
{"name":"Conscience extraplanaire","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu a conscience du tissu de la r&eacute;alit&eacute;. Il peut percevoir les passages et les portails menant vers d&rsquo;autres plans dans une zone de 10 m&egrave;tres par point d&rsquo;Aspect chaotique gr&acirc;ce &agrave; un simple test de Clairvoyance + Perception / 15. S&rsquo;il ignore l&rsquo;existence de ce passage et qu&rsquo;il passe devant, le MJ peut faire un test de Clairvoyance + Perception / 25 pour l&rsquo;&Eacute;lu derri&egrave;re son &eacute;cran.</p>","allegeance":"chaos","prerequis":"6 ou plus en Clairvoyance.","sacrifice":"<p>1 point d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.1nIGWsNtx1NKxjPn"}},"_id":"5v0Y35LzqfAd0KnX"}
{"name":"Santé renforcée (Loi)","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Les points de Sant&eacute; de l&rsquo;&Eacute;lu sont recalcul&eacute;s en ajoutant &agrave; [(Puissance + Trempe) x 2]+ 5 le nombre de points d&rsquo;&Acirc;me sacrifi&eacute;s. Ce Don ne peut &ecirc;tre pris qu&rsquo;une seule fois m&ecirc;me si l&rsquo;&Eacute;lu conclut plusieurs Pactes.</p>","allegeance":"loi","prerequis":"6 ou plus en Trempe.","sacrifice":"<p>1 point d&rsquo;&Acirc;me par point de Sant&eacute; suppl&eacute;mentaire, pour un maximum &eacute;gal &agrave; l&rsquo;Aspect du personnage.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.4ow7N6kdAWXbTApf"}},"_id":"6IS4zstVwJxG2lwy"}
{"name":"Repos de lÂme","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu r&eacute;cup&egrave;re deux fois plus vite ses points d&rsquo;&Acirc;me, soit 2 par heure et 4 en cas de repos.</p>","allegeance":"chaos","prerequis":"6 ou plus en Présence.","sacrifice":"<p>3 points d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.KXciI0xwd6gSOLcZ"}},"_id":"8WLUjxBJtd33mZFp"}
{"name":"Trait chaotique","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu gagne un trait du Chaos qu&rsquo;il tire au hasard dans le tableau de la page 185 avec un d20. Il peut prendre ce Don trois fois. S&rsquo;il tire un trait qu&rsquo;il poss&egrave;de d&eacute;j&agrave;, il doit relancer le d20. Ces traits sont permanents et d&eacute;finitifs, &agrave; moins que les Seigneurs du Chaos n&rsquo;en d&eacute;cident autrement, notamment si l&rsquo;&Eacute;lu les dissimule trop &agrave; leur go&ucirc;t alors qu&rsquo;il n&rsquo;y est pas absolument contraint par des imp&eacute;ratifs de survie imm&eacute;diats.</p>","allegeance":"chaos","prerequis":"aucun","sacrifice":"<p>2 points d&rsquo;&Acirc;me par Trait chaotique. L&rsquo;&Eacute;lu ne peut en avoir plus que sa Marge.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.BKzeOjGA13wQgO0S"}},"_id":"BuZM5BnSaPIGEiEq"}
{"name":"Guérison","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu r&eacute;g&eacute;n&egrave;re rapidement. Chaque jour de repos, il regagne un nombre de points de Sant&eacute; &eacute;gal &agrave; sa Trempe /2 + son Aspect.</p>","allegeance":"loi","prerequis":"6 ou plus en Trempe.","sacrifice":"<p>3 point d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.yvY0G3PffZWEl92R"}},"_id":"Eme2iVAF670LCeIH"}
{"name":"Aide Élémentaire","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Ce Don permet d&rsquo;appeler un &Eacute;l&eacute;mentaire &agrave; son secours une fois par sc&eacute;nario. La Puissance de l&rsquo;&Eacute;l&eacute;mentaire est fi x&eacute;e lors de l&rsquo;octroi du Don. La nature de l&rsquo;&Eacute;l&eacute;mentaire correspond &agrave; celle du Seigneur &Eacute;l&eacute;mentaire avec lequel le Pacte a &eacute;t&eacute; pass&eacute;. Ce Don ne peut &ecirc;tre pris qu&rsquo;une fois par Seigneur &Eacute;l&eacute;mentaire v&eacute;n&eacute;r&eacute;.</p>","allegeance":"elementaires","prerequis":"","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un &Eacute;l&eacute;mentaire mineur disposant de 5 points d&rsquo;&Acirc;me. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>3 points d&rsquo;&Acirc;me pour un &Eacute;l&eacute;mentaire m&eacute;dian disposant de 10 points d&rsquo;&Acirc;me. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>5 points d&rsquo;&Acirc;me pour un &Eacute;l&eacute;mentaire majeur disposant de 15 points d&rsquo;&Acirc;me. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Wo42z8L33wLerUM8"}},"_id":"JAkSopwrh4QmKrPY"}
{"name":"Augmentation du modificateur de dégâts","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Les d&eacute;g&acirc;ts caus&eacute;s par l&rsquo;&Eacute;lu b&eacute;n&eacute;ficient d&rsquo;un bonus pour <em>un type d&rsquo;armes donn&eacute;</em>. Le bonus d&eacute;pend du nombre de points d&rsquo;&Acirc;me sacrifi&eacute;s.</p>","allegeance":"loi","prerequis":"6 ou plus en Puissance.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un bonus de + 1. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>3 points d&rsquo;&Acirc;me pour un bonus de + 2. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>5 points d&rsquo;&Acirc;me pour un bonus de + 3. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.bqdkbOf7EsdsCRqu"}},"_id":"K4H2VC0jTCy7pYj7"}
{"name":"Lexemple du Juste","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu est un exemple rayonnant pour ses camarades. Une fois par sc&eacute;nario, si l&rsquo;&Eacute;lu adresse pendant un tour de jeu une pri&egrave;re &agrave; une divinit&eacute; de la Loi, lui et ses camarades (une dizaine tout au plus) seront b&eacute;nis et recevront jusqu&rsquo;&agrave; la fin de la sc&egrave;ne un bonus &agrave; toutes leurs actions.</p>","allegeance":"loi","prerequis":"6 ou plus en Trempe.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un bonus de + 1. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour un bonus de + 2. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un bonus de + 3. Acc&egrave;s r&eacute;serv&eacute; aux Champions.&nbsp;</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.C5ofSFNXoXZFo8ak"}},"_id":"KlfScJjqvlPtkcdZ"}
{"name":"Abstinence","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu peut survivre en se privant d&rsquo;un &eacute;l&eacute;ment vital (nourriture, eau, sommeil, mais pas air) pendant un certain temps. La dur&eacute;e de ce pouvoir d&eacute;pend du nombre de points d&rsquo;&Acirc;me sacrifi&eacute;s.</p>","allegeance":"tous","prerequis":"6 ou plus en Trempe","sacrifice":"<ul>\n<li>2 points d&rsquo;&Acirc;me pour un jour. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>6 points d&rsquo;&Acirc;me pour une semaine. Acc&egrave;s r&eacute;serv&eacute; aux Che\u0002valiers et aux H&eacute;rauts.</li>\n<li>10 points d&rsquo;&Acirc;me pour un mois. Acc&egrave;s r&eacute;serv&eacute; aux Cham\u0002pions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Q4Sy7vOGgU7Zgi9p"}},"_id":"OJKMNtFsuVgkV3gh"}
{"name":"Rupture Chaotique","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Lorsque l&rsquo;&Eacute;lu manie un certain type d&rsquo;armes, il peut ajouter un bonus &agrave; son initiative ou b&eacute;n&eacute;ficier d&rsquo;un bonus aux d&eacute;g&acirc;ts lors d&rsquo;un combat, une fois par s&eacute;ance de jeu.</p>","allegeance":"chaos","prerequis":"6 ou plus en Puissance.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un bonus de + 1 aux d&eacute;g&acirc;ts ou + 5 en initiative. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>3 points d&rsquo;&Acirc;me pour un bonus de + 2 aux d&eacute;g&acirc;ts ou + 7 en initiative. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>5 points d&rsquo;&Acirc;me pour un bonus de + 3 aux d&eacute;g&acirc;ts ou + 10 en initiative. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.gMI6F4qY5Fi6okZ7"}},"_id":"Q1xDyjQy59lr5wAG"}
{"name":"Sens du Pur","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu a imm&eacute;diatement conscience de la pr&eacute;sence pass&eacute;e ou actuelle du Chaos (sortil&egrave;ge &agrave; l&rsquo;&oelig;uvre, puissant serviteur&hellip;). Le personnage est tr&egrave;s sensible aux &eacute;manations chaotiques, elles lui soul&egrave;vent le c&oelig;ur. La distance de d&eacute;tection peut aug\u0002menter en sacrifiant davantage de points d&rsquo;&Acirc;me</p>","allegeance":"loi","prerequis":"6 ou plus en Adresse ou Clairvoyance.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour d&eacute;tecter les ennemis dans un rayon de 10 m&egrave;tres. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour d&eacute;tecter les ennemis dans un rayon de 100 m&egrave;tres. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour d&eacute;tecter les ennemis dans un rayon de 1000 m&egrave;tres. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.tp2435vHrj63GeAK"}},"_id":"RYgdBIikcBfN8xAp"}
{"name":"Gardien des Millions de Sphères","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu a conscience du tissu de la r&eacute;alit&eacute;. Il peut sceller, par de longues pri&egrave;res psalmodi&eacute;es, les passages et les portails donnant sur d&rsquo;autres plans du Multivers. Cette action lui co&ucirc;te le sacrifice d&rsquo;un point d&rsquo;&Eacute;clat. Tant que sa concentration reste tourn&eacute;e vers ce passage, le portail demeure scell&eacute;, comme si Donblas lui-m&ecirc;me le gardait. Mais d&egrave;s que le personnage s&rsquo;endort, la porte, bien que ferm&eacute;e, n&rsquo;est plus scell&eacute;e. Certains plans tr&egrave;s importants poss&egrave;dent plusieurs passages menant vers les Jeunes Royaumes.</p>","allegeance":"loi","prerequis":"6 ou plus en Clairvoyance.","sacrifice":"<p>5 points d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.tm1fjaETqhMAldqJ"}},"_id":"TKCaHLHccYw7JvFy"}
{"name":"Vivacité","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Lors d&rsquo;un test de Capacit&eacute; Offensive, l&rsquo;&Eacute;lu voit la marge n&eacute;cessaire &agrave; l&rsquo;obtention d&rsquo;une r&eacute;ussite h&eacute;ro&iuml;que diminuer de 1 (9 ou plus), de 2 (8 ou plus) ou de 3 (7 ou plus).&nbsp;</p>","allegeance":"tous","prerequis":"6 ou plus en Adresse ou Clairvoyance.","sacrifice":"<ul>\n<li>3 points d&rsquo;&Acirc;me pour diminuer la marge de 1 (r&eacute;ussite h&eacute;ro&iuml;que sur 9 ou plus). Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>5 points d&rsquo;&Acirc;me pour diminuer la marge de 2 (r&eacute;ussite h&eacute;ro&iuml;que sur 8 ou plus). Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>7 points d&rsquo;&Acirc;me pour diminuer la marge de 3 (r&eacute;ussite h&eacute;ro&iuml;que sur 7 ou plus). Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.GlhqSASDW0XdR243"}},"_id":"XaJAo8otOXmgtjzA"}
{"name":"Augmentation de compétence (Chaos)","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Ce Don permet d&rsquo;augmenter une comp&eacute;tence importante pour la voie spirituelle choisie. Le bonus octroy&eacute; d&eacute;pend du nombre de points d&rsquo;&Acirc;me sacrifi &eacute;s. Ce Don peut &ecirc;tre choisi plusieurs fois, mais il ne peut s&rsquo;appliquer qu&rsquo;une fois par comp&eacute;tence.&nbsp;</p>","allegeance":"chaos","prerequis":"6 ou plus en Clairvoyance.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un + 1 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour un + 2 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un + 3 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.75OYBq5iGgdXVQcA"}},"_id":"aP4vSk6WOL4wXyyb"}
{"name":"Dur à cuire","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu r&eacute;siste aux effets li&eacute;s &agrave; la fatigue et &agrave; la maladie. Le b&eacute;n&eacute;\u0002fi ciaire de ce Don a droit &agrave; un bonus de + 1 lors de ses tests de Trempe pour r&eacute;sister &agrave; la fatigue et &agrave; la maladie. Ce bonus peut augmenter en sacrifi ant davantage de points d&rsquo;&Acirc;me.</p>","allegeance":"tous","prerequis":"6 ou plus en Trempe.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un bonus de + 2. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour un bonus de + 3. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un bonus de + 4. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.jNeLj9DyylUj7LAl"}},"_id":"gK3glTgKTKRw9ref"}
{"name":"Animal apprivoisé","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Le croyant se voit confier un animal normal (c&rsquo;est-&agrave;-dire non surnaturel). L&rsquo;animal apprivois&eacute; est un membre standard de son esp&egrave;ce et ob&eacute;it &agrave; des ordres simples, mais sans jamais aller contre sa nature. Le co&ucirc;t de ce Don d&eacute;pend de la Puissance de la cr&eacute;ature qui est fix&eacute;e lors de son octroi. Si 1 point d&rsquo;&Acirc;me suppl&eacute;mentaire est investi, la cr&eacute;ature peut communiquer t&eacute;l&eacute;\u0002pathiquement avec son ma&icirc;tre (et uniquement avec lui).</p>","allegeance":"betes","prerequis":"6 ou plus en Présence.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour une cr&eacute;ature d&rsquo;une Puissance de 0 &agrave; 4. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>3 points d&rsquo;&Acirc;me pour une cr&eacute;ature d&rsquo;une Puissance de 5 &agrave; 7. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>5 points d&rsquo;&Acirc;me pour une cr&eacute;ature d&rsquo;une Puissance de 8 &agrave; 10. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.qDUei4wRjpRYi9Ps"}},"_id":"gN7DtvMJPASpPpQl"}
{"name":"Protection du Mage","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu est prot&eacute;g&eacute; contre les attaques des cr&eacute;atures engendr&eacute;es ou invoqu&eacute;es par la Sorcellerie (les &Eacute;l&eacute;mentaires, les D&eacute;mons, les Automata&hellip;). La protection dont il b&eacute;n&eacute;ficie d&eacute;pend du nombre de points d&rsquo;&Acirc;me sacrifi&eacute;s.</p>","allegeance":"loi","prerequis":"6 ou plus en Trempe.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour 1 point de protection. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour 2 points de protection. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un 3 points de protection. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.NVIG52HOiWGm6JcW"}},"_id":"j7iTQOFdiJUviTC9"}
{"name":"Sang-Froid","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu peut supporter n&rsquo;importe quel type de souffrance psy\u0002chique. Il ne subit pas les p&eacute;nalit&eacute;s dues &agrave; l&rsquo;&eacute;tat D&eacute;stabilis&eacute; (&ndash; 2) et Choqu&eacute; (&ndash; 5). Il est immunis&eacute; &agrave; la torture mentale.</p>","allegeance":"chaos","prerequis":"6 ou plus en Trempe.","sacrifice":"<p>1 point d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.KvdQLDhSpfAd2Iig"}},"_id":"lWXxInM05EiBu6uX"}
{"name":"Dur à cuire (Chaos)","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu r&eacute;siste aux effets li&eacute;s &agrave; la fatigue et &agrave; la maladie. Le b&eacute;n&eacute;fi ciaire de ce Don a droit &agrave; un bonus de + 1 lors de ses tests de Trempe pour r&eacute;sister &agrave; la fatigue et &agrave; la maladie. Ce bonus peut augmenter en sacrifi ant davantage de points d&rsquo;&Acirc;me.</p>","allegeance":"chaos","prerequis":"6 ou plus en Trempe.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un bonus de + 1. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour un bonus de + 2. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un bonus de + 3.Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.E0kyJQ5gqCEcm0Aj"}},"_id":"oAKqbsLVd8cX4tjN"}
{"name":"Augmentation de compétence","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Ce Don permet d&rsquo;augmenter une comp&eacute;tence relative &agrave; la voie spirituelle choisie. Le bonus octroy&eacute; d&eacute;pend du nombre de points d&rsquo;&Acirc;me sacrifi &eacute;s. Ce Don peut &ecirc;tre choisi plusieurs fois, mais il ne peut s&rsquo;appliquer qu&rsquo;une fois par comp&eacute;tence.&nbsp;</p>","allegeance":"tous","prerequis":"6 ou plus en Clairvoyance.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un + 1 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour un + 2 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un + 3 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.djDFklKJaW1oVTds"}},"_id":"pPNKneH7scfaEmGq"}
{"name":"Augmentation de compétence (Loi)","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Ce Don permet d&rsquo;augmenter une comp&eacute;tence importante pour la voie spirituelle choisie. Le bonus octroy&eacute; d&eacute;pend du nombre de points d&rsquo;&Acirc;me sacrifi&eacute;s. Ce Don peut &ecirc;tre choisi plusieurs fois mais il ne peut s&rsquo;appliquer qu&rsquo;une fois par comp&eacute;tence.</p>","allegeance":"loi","prerequis":"6 ou plus en Clairvoyance.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour un + 1 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour un + 2 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour un + 3 &agrave; une comp&eacute;tence. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.gNL9lD7hofK32H7D"}},"_id":"qhOE8OTe5hNNgDtt"}
{"name":"Santé renforcée","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Les points de Sant&eacute; de l&rsquo;&Eacute;lu sont recalcul&eacute;s en ajoutant &agrave; [(Puissance + Trempe) x 2]+ 5 le nombre de points d&rsquo;&Acirc;me sacrifi&eacute;s. Ce Don ne peut &ecirc;tre pris qu&rsquo;une seule fois m&ecirc;me si l&rsquo;&Eacute;lu conclut plusieurs Pactes.</p>","allegeance":"tous","prerequis":"6 ou plus en Trempe.","sacrifice":"<p>1 point d&rsquo;&Acirc;me par point de Sant&eacute; suppl&eacute;mentaire.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.dJnYsDcETkidd2s5"}},"_id":"sLgGn6zhkDdbpQlH"}
{"name":"Rectitude","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu peut supporter n&rsquo;importe quel type de souffrance. Il ne subit pas les p&eacute;nalit&eacute;s dues &agrave; l&rsquo;&eacute;tat Bless&eacute; (-2) et Gravement Bless&eacute; (-5). Il est immunis&eacute; contre la torture.</p>","allegeance":"loi","prerequis":"6 ou plus en Trempe.","sacrifice":"<p>1 point d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.HpoTP9idEKi1NQGL"}},"_id":"t7Ez0rzqYOf48GfY"}
{"name":"La voie du milieu","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Lors d&rsquo;un combat, l&rsquo;&Eacute;lu ne subit plus les effets d&rsquo;une r&eacute;ussite h&eacute;ro&iuml;que contre lui lors des tests de Capacit&eacute; offensive. Il n&rsquo;en profite plus non plus. &Agrave; un haut niveau, il ne peut plus &ecirc;tre affect&eacute; par les &eacute;checs dramatiques.</p>","allegeance":"loi","prerequis":"6 ou plus en Adresse ou Clairvoyance.","sacrifice":"<ul>\n<li>3 points d&rsquo;&Acirc;me pour ne plus &ecirc;tre affect&eacute; par les r&eacute;ussite h&eacute;ro&iuml;ques.</li>\n<li>7 points d&rsquo;&Acirc;me pour ne plus &ecirc;tre affect&eacute; ni par les r&eacute;us\u0002sites h&eacute;ro&iuml;ques ni par les &eacute;checs dramatiques. Acc&egrave;s r&eacute;ser\u0002v&eacute; aux Champions</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.rOXDZ2020snvhtR2"}},"_id":"taXCPXiPEuOIKaUO"}
{"name":"Perfection","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;un des attributs de l&rsquo;&Eacute;lu est augment&eacute;, gr&acirc;ce &agrave; la bienveil\u0002lance de la divinit&eacute;. Il ne peut d&eacute;passer le maximum impos&eacute; par l&rsquo;esp&egrave;ce. Ce Don peut &ecirc;tre choisi plusieurs fois, mais il ne peut s&rsquo;appliquer qu&rsquo;une fois par attribut.</p>","allegeance":"tous","prerequis":"6 ou plus dans lattribut modifié.","sacrifice":"<ul>\n<li>3 points d&rsquo;&Acirc;me pour un bonus de + 1. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>6 points d&rsquo;&Acirc;me pour un bonus de + 2. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>9 points d&rsquo;&Acirc;me pour un bonus de + 3. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.nSlFw6q7TCqxZ6tH"}},"_id":"vZBNRTeGGU5hsXtT"}
{"name":"Sixième sens","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu sait o&ugrave; se trouvent des ennemis dont il a conscience et qui cherchent &agrave; lui nuire directement dans un rayon de 10 m&egrave;tres. Attention, cette d&eacute;tection ne fonctionnera pas pour deviner que quelqu&rsquo;un est simplement hostile &agrave; l&rsquo;&Eacute;lu ou le hait visc&eacute;ralement. Il s&rsquo;agit d&rsquo;un pouvoir de localisation et de vigilance. La distance de d&eacute;tection peut augmenter en sacrifiant davantage de points d&rsquo;&Acirc;me.</p>","allegeance":"chaos","prerequis":"6 ou plus en Adresse ou Clairvoyance.","sacrifice":"<ul>\n<li>1 point d&rsquo;&Acirc;me pour d&eacute;tecter les ennemis dans un rayon de 10 m&egrave;tres. Acc&egrave;s r&eacute;serv&eacute; aux Novices et aux Adeptes.</li>\n<li>2 points d&rsquo;&Acirc;me pour d&eacute;tecter les ennemis dans un rayon de 100 m&egrave;tres. Acc&egrave;s r&eacute;serv&eacute; aux Chevaliers et aux H&eacute;rauts.</li>\n<li>3 points d&rsquo;&Acirc;me pour d&eacute;tecter les ennemis dans un rayon de 1000 m&egrave;tres. Acc&egrave;s r&eacute;serv&eacute; aux Champions.</li>\n</ul>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.6CIPAVjSVsOqZBYc"}},"_id":"wsbGuOoFjHO6aQEm"}
{"name":"Endurance","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu peut supporter n&rsquo;importe quel type de souffrance. Il ne subit pas les p&eacute;nalit&eacute;s dues &agrave; l&rsquo;&eacute;tat Bless&eacute; (-2) et Gravement Bless&eacute; (-5). Il est immunis&eacute; &agrave; la torture si elle est infl ig&eacute;e &agrave; partir d&rsquo;un des &eacute;l&eacute;ments tut&eacute;laires du personnage.</p>","allegeance":"elementaires","prerequis":"6 ou plus en Trempe.","sacrifice":"<p>3 points d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.on94N7pylXJ8gtdF"}},"_id":"yJ6K9dYrdNxr5uWj"}
{"name":"Le pilier de la Loi","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>Une fois par sc&eacute;nario, l&rsquo;&Eacute;lu peut r&eacute;sister &agrave; une menace ou agres\u0002sion, physique ou mentale, sans jeter les d&eacute;s. Il est toutefois n&eacute;ces\u0002saire que l&rsquo;&Eacute;lu ait conscience du danger. L&rsquo;&Eacute;lu pourra par exemple r&eacute;sister &agrave; la terreur provoqu&eacute;e par une cr&eacute;ature du Chaos parti\u0002culi&egrave;rement abominable ou l&eacute;gendaire, mais ne pourra pas &eacute;viter d&rsquo;&ecirc;tre assomm&eacute; par un habile voleur de Bakshaan qu&rsquo;il n&rsquo;avait pas vu. Cette r&eacute;sistance peut m&ecirc;me &ecirc;tre &laquo; extraordinaire &raquo; si l&rsquo;Aspect de l&rsquo;&Eacute;lu est tr&egrave;s haut.</p>\n<p>S&rsquo;il est un Adepte ou un Novice, l&rsquo;&Eacute;lu peut r&eacute;sister &agrave; l&rsquo;ivresse lors d&rsquo;une nuit de beuverie chez un Prince-Marchand de Bakshaan.</p>\n<p>S&rsquo;il est un Chevalier ou un H&eacute;raut, l&rsquo;&Eacute;lu peut r&eacute;sister &agrave; un coup surpuissant (celui-ci est annul&eacute;) ou &agrave; l&rsquo;apparition gla\u0002&ccedil;ante d&rsquo;un D&eacute;mon. S&rsquo;il est un Champion, il peut par exemple soutenir l&rsquo;arche d&rsquo;un temple qui s&rsquo;&eacute;croule pendant quelques pr&eacute;cieuses secondes afi n de laisser le temps &agrave; ses camarades de se mettre &agrave; l&rsquo;abri (songez &agrave; Maciste, &agrave; Samson, ou tout simplement &agrave; Conan). Bref, n&rsquo;h&eacute;sitez pas &agrave; faire dans l&rsquo;extraordinaire, mais ne bas\u0002culez pas dans le grosbillisme sans limite (ou alors ne nous le dites pas). Il revient au MJ de doser avec justesse ce Don.</p>","allegeance":"loi","prerequis":"7 ou plus en Trempe","sacrifice":"<p>3 points d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.yZ84qzrGeIDfZXr1"}},"_id":"zef3VQat06Ronc5W"}
{"name":"Œil exercé","type":"don","img":"systems/fvtt-mournblade/assets/icons/don.webp","data":{"description":"<p>L&rsquo;&Eacute;lu re&ccedil;oit de la divinit&eacute; qu&rsquo;il v&eacute;n&egrave;re l&rsquo;&eacute;quivalent d&rsquo;une Pr&eacute;dilection dans la Capacit&eacute; sp&eacute;ciale &OElig;il du sorcier (voir p. 199). Une fois par s&eacute;ance, il peut donc relancer le test de Clairvoyance + Perception.</p>","allegeance":"chaos","prerequis":"6 ou plus en Clairvoyance.","sacrifice":"<p>2 points d&rsquo;&Acirc;me.</p>"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.9Xrm1cfrCmkRtOBc"}},"_id":"zzz9JrtWjELdoAfK"}

View File

@@ -1,55 +0,0 @@
{"name":"Chapeau fatigué","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":0,"prix":2},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.D26fUtZxrzcTR07B"}},"_id":"1cZd2hlTV9tykDED"}
{"name":"Bœuf","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.whAhz9pO1NUt8Pjm"}},"_id":"3pvWKiyXhc9mmg70"}
{"name":"Manteau","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":4},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.FZztAyGbF2cL9wyc"}},"_id":"40P9lOUYI16gEGZS"}
{"name":"Poney","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":30},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.VKZrNcrCm9Ju7ayv"}},"_id":"7MdI99vLmaytPNKE"}
{"name":"Tenue complète dhomme fortuné","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>(pantalons de coton, chemise de drap fi n, pourpoint de velours, chapeau, bottes montantes ou chaussures &agrave; boucles)</p>","rarete":7,"prix":6},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.dw6wLsNmwmEW79ei"}},"_id":"82fzHPjxUkGMCVUX"}
{"name":"Laboratoire dAlchimiste","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Permet de mettre au point les objets les plus complexes (difficult&eacute; de 25 et au-del&agrave;)</p>","rarete":10,"prix":2000},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.eq0FJtwQd2jHbdvv"}},"_id":"8GK1Hn6le3JlsT6T"}
{"name":"Vieille carne","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Modificateur de Monte -2</p>","rarete":2,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.iikcWs26aEjtXDKo"}},"_id":"8NPElVs0cYrGbmXO"}
{"name":"Nuit dauberge à létable","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":3,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.uo464pRDCNIPsUvW"}},"_id":"9dEaZklkBs1GgdD7"}
{"name":"Coursier","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Modificateur de Monte +2</p>","rarete":7,"prix":120},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.t4PPYVE2B6B9tUy1"}},"_id":"AZDTSEUo52dnf37U"}
{"name":"Papier, plume et encre","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":6,"prix":20},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.545S2qajtz6DSzzq"}},"_id":"Bg8jNqGusEdHxV00"}
{"name":"Chariot couvert ou bâché","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":3,"prix":100},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.GRhCO7lW7NaHThqU"}},"_id":"FlK49ZIVnceUTy1P"}
{"name":"Tenue complète bourgeoise","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>&nbsp;(chausses, chemise, pourpoint, chapeau, bottes)</p>","rarete":6,"prix":30},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.XZSiLPhbhlj1Wil1"}},"_id":"HdlqqXJ393Pf3GxA"}
{"name":"Nuit dauberge dans une chambre particulière","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":5,"prix":5},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.xtlOL4VOsTDvyUvR"}},"_id":"LnKDtuhsacvcet7B"}
{"name":"Louer une petite maison de ville pour un mois","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Se paie g&eacute;n&eacute;ralement d&rsquo;avance&hellip;</p>","rarete":0,"prix":40},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Tfu7ywqXoXzXgP6H"}},"_id":"NZ28hyteLgOxlXC7"}
{"name":"Repas de fête","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":5,"prix":4},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.gTCljExvFFpG7K1s"}},"_id":"OyRix5REF10GPbSp"}
{"name":"Cheval dattelage","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":4,"prix":80},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.KDfgGmQ4tUj765MN"}},"_id":"QHPoow1iDYEdExgs"}
{"name":"Repas chaud","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":0,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.ErKXhChAfgugKU1a"}},"_id":"QojR7XS4mGjbMq5w"}
{"name":"Charrette","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":70},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.4HRLU67Ipd1PFqUV"}},"_id":"RXMBJNKXfy3S3yck"}
{"name":"Onguent de soin","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>10 tours de jeu sont n&eacute;cessaires pour l&rsquo;appliquer. Permet de r&eacute;cup&eacute;rer [[/roll 1D4]] points de Sant&eacute; apr&egrave;s (10 &ndash; Trempe) heures</p>","rarete":5,"prix":20},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.RdqMNJzemUcCYO1H"}},"_id":"RyNJqjQvDbMixok0"}
{"name":"Barge","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":5,"prix":300},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.l7tiShWweGQ8fzDp"}},"_id":"TyeTRm4FxLeRNDTU"}
{"name":"Pommade à frotter pour faire circuler le sang","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Prot&egrave;ge des effets du froid (+ 5 aux tests de Trempe)</p>","rarete":4,"prix":2},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.BbzFc96y1QHi0Old"}},"_id":"VPkZxH3bkTR8WqXQ"}
{"name":"Cape","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":2},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.HZkgEeG4apx4I4Tn"}},"_id":"WL0809BYVR9JxaMM"}
{"name":"Poudre blanche","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>(&agrave; inhaler pour se sentir en forme)</p>\n<p>S&rsquo;inhale le temps d&rsquo;une action (&agrave; inhaler pour se sentir en forme) simple. Permet de r&eacute;cup&eacute;rer imm&eacute;diatement [[/roll 1D4]] cases de Sant&eacute; (d&eacute;g&acirc;ts non l&eacute;taux). &Agrave; chaque inhalation, lancez un [[/roll 1d20]]. Sur un 1 ou un 11, celui qui vient de priser la poudre s&rsquo;eff ondre et reste inconscient durant (10 &ndash; Trempe) heures.</p>\n<p>&nbsp;</p>","rarete":7,"prix":5},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.axOu6tHgHC7kYnG3"}},"_id":"WntSrr0j00DLntZV"}
{"name":"Lanterne","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.OKydo5obAtmOVNup"}},"_id":"Wo0tplt93X9wemlV"}
{"name":"Chien dressé","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.5jFTzXfwTgkVvjv7"}},"_id":"Wus5fIYO731eLG0i"}
{"name":"Pain de route (rations pour une semaine)","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":0,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.18Hb7BDncVyHgyeM"}},"_id":"X4nIqFrjWy0hCVbZ"}
{"name":"Matériel de survie","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>(sac, gourde, couverture, tente)</p>","rarete":2,"prix":5},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.wv6tWbuJjnZ7jiXH"}},"_id":"e8ZbG2WQCzJTRzBK"}
{"name":"Herbes à infuser contre les fièvres légères","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.iL0fjlODZ4JnNpwN"}},"_id":"eH4V6AlAZNEuwlrw"}
{"name":"Barque à rames, canoë","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":60},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.HKxNf2i5EEu5wAN5"}},"_id":"eXP5rck0IJK4Az8c"}
{"name":"Petit atelier et son outillage","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Permet de fabriquer les objets les plus simples (jusqu&rsquo;&agrave; une difficult&eacute; de 20)</p>","rarete":8,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.TD0bN2tEIG8VqnbD"}},"_id":"fc2fReiW3mV6jekF"}
{"name":"Antipoison","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Conf&egrave;re un bonus de + 5 au test de Soins pour arr&ecirc;ter les pertes de points de Sant&eacute;. Note : tous les poisons n&rsquo;ont pas d&rsquo;antipoison connu.</p>","rarete":7,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.7NDLIEeS4WVyXDqo"}},"_id":"fkleifA7qbOzRZCy"}
{"name":"Fonderie","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":10,"prix":10000},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Y14CMXm2avlCnqPC"}},"_id":"frS3jj7bs0l9zMGi"}
{"name":"Roulotte","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":5,"prix":150},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.X5GxbvOMqQ8aHh49"}},"_id":"g5rDtYzyQ4IxUWXu"}
{"name":"Forge","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":4,"prix":100},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.cma6u26NRsZL1awt"}},"_id":"gJStREuVAaaBuwXR"}
{"name":"Miroir et peigne","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.IzlENy0Oxg82cke0"}},"_id":"hRlPFzsoUjbu3Noj"}
{"name":"Tenue complète citadine","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>(chausses et chemise de coton, chapeau, chaussures)</p>","rarete":3,"prix":7},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.1ldZnq4lDPHOeVba"}},"_id":"hTLeVLn5yUd2etoS"}
{"name":"Nuit dauberge dans la salle commune","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":3,"prix":3},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.D7HcsRBK8uH6NBi4"}},"_id":"hc4w2PBpYLMQKsXS"}
{"name":"Matériel de cuisine","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":0,"prix":2},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.lJafSILU0LtNMONn"}},"_id":"i8BBr1WjfphuLTLI"}
{"name":"Chapeau à la mode","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":6,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.lmlWH96vjfTMN2OO"}},"_id":"kkNZoJdG86Dqci2b"}
{"name":"Herbes à mâcher les lendemains de beuverie","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.STMfa8tVupqJPllD"}},"_id":"muCWd5AKL21AbTg8"}
{"name":"Tenue complète paysanne","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>(chausses et chemise de jute, grand foulard, sandales)</p>","rarete":0,"prix":4},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.4WIrBhrpn4AlSnh3"}},"_id":"nJKLD6BqEHbBHmOY"}
{"name":"Selle et fontes","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":20},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.J1HAwhIBjgkBZ3FH"}},"_id":"oIZv9KxLVQUOfi5d"}
{"name":"Destrier","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":8,"prix":800},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.MF74kXfkiwLOwWNS"}},"_id":"oIob9qCttaQw7Aea"}
{"name":"Vêtements rapiécés, nippes","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":0,"prix":2},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.rPYf2u8b3qizxxFk"}},"_id":"sLZgByw2rA2PvQms"}
{"name":"Petit voilier","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":6,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.tuAI18GrGFkqVHpQ"}},"_id":"tAFXmBLUXcSUKZRL"}
{"name":"Matériel hivernal","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>(piolets, raquettes, manteau et graisses)</p>","rarete":4,"prix":20},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.LpdplcMj4j7dWnZU"}},"_id":"tRSk0np2smLpdixK"}
{"name":"Herbes parfumées contre les mauvaises odeurs","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.y6G9LprDaOAoVn9v"}},"_id":"tXYzLGS6SFolD8k4"}
{"name":"Barque de pêcheur avec voile","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":3,"prix":100},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.ZXEq8059S3YlFhN0"}},"_id":"vUFnyal2islmmpZF"}
{"name":"Torche","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>&nbsp;(lot de 3)</p>","rarete":1,"prix":5},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.aVBCMdM69vwfrSMW"}},"_id":"vahvt9QGYjnRlY4p"}
{"name":"Verre de vin ou de bière","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":0,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.5yA9UC3kExUx6CqN"}},"_id":"vmSrygoMxy6G87EO"}
{"name":"Matériel descalade","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>(marteau, cordes, pitons)</p>","rarete":3,"prix":8},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.TXj9b73MGGI6L9Av"}},"_id":"vtpjGFTYO7fiKXwE"}
{"name":"Longue-vue","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":8,"prix":250},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.n1UQvGJ8Zq6dGuEY"}},"_id":"wHU6MvZuBMmN9xfG"}
{"name":"Dose de poison violent","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Voir le chapitre &laquo; Le Combat et la sant&eacute; &raquo; pour conna&icirc;tre les eff ets des principaux poisons</p>","rarete":6,"prix":3},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.EuGLW5D5SIP58uaa"}},"_id":"wQ8cgwazhlIF6WG7"}
{"name":"Louer une grange pour un mois","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"<p>Se paie g&eacute;n&eacute;ralement d&rsquo;avance...</p>","rarete":0,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.8utWpkdD1FvHSYfv"}},"_id":"xy356PaIZisHJdUR"}
{"name":"Grande écharpe","type":"equipement","img":"systems/fvtt-mournblade/assets/icons/equipement.webp","data":{"description":"","rarete":2,"prix":2},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.j5Hei6F2XN5nrh7t"}},"_id":"y47dBO3Mf5Pn7tOd"}

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