Compare commits

..

52 Commits

Author SHA1 Message Date
uberwald 93f07adcee Ajout démons
Release Creation / build (release) Successful in 1m38s
2026-05-03 16:14:42 +02:00
uberwald eac8d93670 Ajout démons 2026-05-03 16:14:28 +02:00
uberwald 1cc6f92f15 Ajout démons 2026-05-03 16:03:45 +02:00
uberwald 0df4a5a9fb Esprit de la Loi + Automaton 2026-05-02 23:16:10 +02:00
uberwald d6b5891519 Potions et élémentaires 2026-05-02 08:26:28 +02:00
uberwald a234ba5d14 COrrection sur predilections
Release Creation / build (release) Successful in 54s
2026-04-30 23:34:16 +02:00
uberwald 463e9ebb19 Correction sur les runes actives et update compendiums
Release Creation / build (release) Successful in 51s
2026-04-09 23:48:01 +02:00
uberwald d8cfdfb96d Correction pour l'activation des runes, qui ne marchai plus 2026-04-08 23:53:32 +02:00
uberwald 1e63db8c39 Foundryv14 migration 2026-04-01 22:40:03 +02:00
uberwald b97f8688e4 Foundryv14 migration
Release Creation / build (release) Successful in 46s
2026-04-01 22:39:25 +02:00
uberwald 9836bd50e6 Resultat impari sur D20 dans messages et corrections diverses
Release Creation / build (release) Successful in 46s
2026-03-10 21:37:33 +01:00
uberwald 6b909b192b Resultat impari sur D20 dans messages et corrections diverses 2026-03-10 21:37:20 +01:00
uberwald e7a6c15bf7 Remove node_modules from repository
Release Creation / build (release) Successful in 56s
2026-01-09 17:25:00 +01:00
uberwald 1909ff443d Remove node_modules from repository 2026-01-09 17:24:36 +01:00
uberwald c72c9d8b02 Upgrade to auto-release 2026-01-09 17:23:02 +01:00
uberwald 536812e871 Upgrade to auto-release 2026-01-09 17:22:52 +01:00
uberwald 66fe1418f0 Migration datamodels ! 2026-01-09 17:11:12 +01:00
uberwald 901df5b395 Enhance CSS+fonts 2025-10-26 21:41:25 +01:00
uberwald f5d84832f3 CSS/Font enhancements 2025-10-26 15:08:45 +01:00
uberwald 216360e0d8 Correction sur usage BA sur un jet impair au D20 2025-06-24 22:11:57 +02:00
uberwald a0502dc467 Corrections cosmetiques v13 2025-06-03 13:49:09 +02:00
uberwald c8b94b74a1 Corrections cosmetiques v13 2025-06-03 13:48:23 +02:00
uberwald c9e4f125b4 Foundry v13 migration 2025-05-01 20:40:20 +02:00
uberwald e840b70ac9 Add UK/US translation 2024-09-10 13:17:45 +02:00
uberwald 03b8779862 Add UK/US translation 2024-09-10 07:20:55 +02:00
uberwald 580a2ccaba Corrections suite a migration Foundry v12 2024-05-01 09:46:37 +02:00
uberwald 699dba5511 Compatibilité v11/v12 2024-04-26 11:45:58 +02:00
uberwald aac552102a Add new automations 2024-04-05 22:33:36 +02:00
uberwald 4762dc33ac PNJ & Creatures 2024-03-03 08:20:30 +01:00
uberwald 0a030460e4 PNJ & Creatures 2024-03-02 23:58:27 +01:00
uberwald ed2dc58680 Enhance stats 2024-02-08 12:51:30 +01:00
uberwald 9c20f277ea Gestion plus fine ame+sante 2023-12-29 18:36:37 +01:00
uberwald 2a8617d781 Automatisations de combats, WIP 2023-12-24 12:38:02 +01:00
uberwald 91ad26730a Automatisations de combats, WIP 2023-12-22 09:30:26 +01:00
uberwald e1816b3dd7 CSS+Predilection+Niveau de competence 2023-12-21 19:21:06 +01:00
uberwald c48401a199 CSS+Predilection+Niveau de competence 2023-12-21 15:41:17 +01:00
uberwald f487908ecd Affichage des specialisations 2023-12-19 22:16:15 +01:00
uberwald e9dc31ada1 Affichage des specialisations 2023-12-19 21:46:13 +01:00
uberwald e504427dbb v10/v11 compatibility 2023-05-25 07:43:46 +02:00
uberwald 6aa63f9a98 Better chat result + hide some compendiums 2023-05-09 21:18:44 +02:00
uberwald 861aa19637 Update releas 2023-03-14 20:41:31 +01:00
uberwald ddb3f9dee1 Various fixes 2023-03-14 18:40:44 +01:00
uberwald da074d6ea1 Various fixes 2023-03-14 09:15:08 +01:00
uberwald 1c0c722bd0 Various fixes 2023-03-14 09:14:59 +01:00
uberwald fef42b7093 Various fixes 2023-03-14 09:14:28 +01:00
uberwald a63ec19362 Better use of tokens/actors 2022-11-30 12:22:40 +01:00
uberwald a26af23c9c Try to update movement 2022-10-05 21:05:35 +02:00
uberwald 983bc6a2ed Revision armes/boucliers 2022-10-01 08:47:01 +02:00
uberwald da6d6584c4 Minor fixes 2022-09-28 20:36:54 +02:00
uberwald f08ec8eaff Enable links in editor 2022-09-27 21:27:38 +02:00
uberwald c34bfbf229 Enhance welcome message 2022-09-26 16:39:36 +02:00
uberwald e7de42cf16 Mourblade : add initiative 2022-09-26 14:23:37 +02:00
569 changed files with 39595 additions and 3152 deletions
+112
View File
@@ -0,0 +1,112 @@
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-mournblade/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-mournblade.zip
# Set up Node.js and build compendium packs from source
- name: Setup Node.js
uses: https://github.com/actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Build journal-aide compendium pack
run: |
rm -rf packs/journal-aide/journal-aide
./node_modules/.bin/fvtt package pack -n journal-aide --inputDirectory ./packs/src/journal-aide --outputDirectory ./packs/journal-aide
- name: Build pouvoirs-elementaires compendium pack
run: |
rm -rf packs/pouvoirs-elementaires/pouvoirs-elementaires
./node_modules/.bin/fvtt package pack -n pouvoirs-elementaires --inputDirectory ./packs/src/pouvoirs-elementaires --outputDirectory ./packs/pouvoirs-elementaires
- name: Build creatures-elementaires compendium pack
run: |
rm -rf packs/creatures-elementaires/creatures-elementaires
./node_modules/.bin/fvtt package pack -n creatures-elementaires --inputDirectory ./packs/src/creatures-elementaires --outputDirectory ./packs/creatures-elementaires
- name: Build automatons compendium pack
run: |
rm -rf packs/automatons/automatons
./node_modules/.bin/fvtt package pack -n automatons --inputDirectory ./packs/src/automatons --outputDirectory ./packs/automatons
- name: Build capacites-automaton compendium pack
run: |
rm -rf packs/capacites-automaton/capacites-automaton
./node_modules/.bin/fvtt package pack -n capacites-automaton --inputDirectory ./packs/src/capacites-automaton --outputDirectory ./packs/capacites-automaton
- name: Build traits-demoniaques compendium pack
run: |
rm -rf packs/traits-demoniaques/traits-demoniaques
./node_modules/.bin/fvtt package pack -n traits-demoniaques --inputDirectory ./packs/src/traits-demoniaques --outputDirectory ./packs/traits-demoniaques
- name: Build faiblesses-demoniaques compendium pack
run: |
rm -rf packs/faiblesses-demoniaques/faiblesses-demoniaques
./node_modules/.bin/fvtt package pack -n faiblesses-demoniaques --inputDirectory ./packs/src/faiblesses-demoniaques --outputDirectory ./packs/faiblesses-demoniaques
- name: Build demons-types compendium pack
run: |
rm -rf packs/demons-types/demons-types
./node_modules/.bin/fvtt package pack -n demons-types --inputDirectory ./packs/src/demons --outputDirectory ./packs/demons-types
# 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-mournblade.zip system.json README.md LICENSE 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-mournblade.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-mournblade'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-mournblade/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-mournblade.zip'
compatibility-minimum: '13'
compatibility-verified: '14'
+4
View File
@@ -0,0 +1,4 @@
.history/
node_modules
.github/
regles/
+21 -2
View File
@@ -1,4 +1,23 @@
# fvtt-mournblade
# Système Foundry pour Mournblade (French RPG, Titam France/Sombres Projets)
FoundryVTT system for Mournblade from Sombres Projets
## EN
Unofficial system for Mournblade (French version from Titam France).
Books are mandatory to play and are available at : http://www.titam-france.fr
## FR
Système non-officiel pour le JDR Mournblade (Titam France).
Ce système a été autorisé par Le Département des Sombres Projets, merci à eux !
Les livres du jeu sont nécessaires pour jouer, et sont disponibles ici : http://www.titam-france.fr
# Credits
Mournblade, le jeu de rôle de Sword & Sorcery, is a property of Titam France/Sombres Projets.
# Developmement
LeRatierBretonnien
+1
View File
@@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 0h512v512H0z" fill="#661f1f" fill-opacity="1"></path><g class="" style="" transform="translate(0,0)"><path d="M94.055 21.9 18.998 96.96l42.727 23.6-26.98 26.952L142.35 212.39c-40.443 70.148-30.72 161.07 29.2 220.958 71.605 71.606 187.737 71.587 259.356 0 71.62-71.587 71.642-187.654.037-259.22-59.915-59.878-150.896-69.57-221.084-29.177L144.95 37.415l-8.44 8.432-18.588 18.57L94.055 21.9zm47.224 45.598 62.337 103.275 8.098-5.248c44.21-28.663 99.014-34.044 147.166-16.078-1.16-.026-2.328-.04-3.503-.04-38.988 0-70.594 14.807-70.594 33.073 0 18.27 31.606 33.075 70.594 33.075 31.53 0 58.225-9.684 67.287-23.05 15.942 17.34 27.492 37.224 34.65 58.253-7.76-3.387-18.28-6.706-30.902-9.563-31.383-7.1-75.547-11.615-124.305-11.615-48.757 0-92.92 4.514-124.304 11.615-13.71 3.102-24.997 6.75-32.893 10.438a163.85 163.85 0 0 1 18.018-37.383l5.263-8.104-103.33-62.3 13.894-13.88 46.937 25.923 27.914-27.915-26.18-46.635 13.855-13.842zm-1.087 201.287c.482.28.982.56 1.506.84 7.89 4.22 20.41 8.487 36.103 12.037 31.383 7.1 75.547 11.615 124.304 11.615 48.758 0 92.922-4.514 124.305-11.615 15.687-3.55 28.203-7.813 36.094-12.033a164.248 164.248 0 0 1 2.746 17.643c-9.432 4.277-21.204 7.893-35.074 11.032-33.205 7.513-78.27 12.037-128.07 12.037-49.802 0-94.866-4.524-128.07-12.037-14.67-3.32-27-7.17-36.69-11.776a164.503 164.503 0 0 1 2.845-17.745z" fill="#fff" fill-opacity="1"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

+35
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/mournblade.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;
+323
View File
@@ -0,0 +1,323 @@
{
"Adresse": "Agility",
"Clairvoyance": "Clarity",
"MNBL.abilities": "Gifts/Pacts",
"MNBL.activatedrunes": "Activated Runes",
"MNBL.addpredilection": "Add a specialization",
"MNBL.aimingbonus": "Aiming Bonus",
"MNBL.alignement": "Alignment",
"MNBL.all": "All",
"MNBL.allegiance": "Allegiance",
"MNBL.applydamage": "Apply damage/bonus/penalty",
"MNBL.aspect": "Aspect",
"MNBL.Assaut": "Smite",
"MNBL.attack": "Attack",
"MNBL.attackcapacity": "Offensive Ability",
"MNBL.attackmountbonus": "Mounted attacker vs ground defender (+5)",
"MNBL.attacks": "Attacks",
"MNBL.attribut": "Attribute",
"MNBL.attributes": "Attributes",
"MNBL.automalus": "Auto Penalty",
"MNBL.base": "Base",
"MNBL.beastslords": "Beast Lords",
"MNBL.bio": "Bio & Notes",
"MNBL.bonus": "Bonus",
"MNBL.candoublebonusskill": "Bonuses can be doubled (cf. Profession)",
"MNBL.chaos": "Chaos",
"MNBL.chaotictraits": "Chaotic Traits",
"MNBL.charge": "Charge",
"MNBL.consumed": "Consumed",
"MNBL.contain": "Contain the opponent",
"MNBL.creatureresourcecost": "Resource Cost (creatures)",
"MNBL.current": "Current",
"MNBL.currentmax": "Current Max",
"MNBL.damage": "Damage",
"MNBL.damagebonus": "Damage Bonus",
"MNBL.defense": "Defense",
"MNBL.defensebonus": "Defense Bonus",
"MNBL.defensecapacity": "Defensive Ability",
"MNBL.dice": "Dice",
"MNBL.difficulty": "Difficulty",
"MNBL.dirtyattack": "Cheap Shot",
"MNBL.disadvantagepositions": "Disadvantageous positions (Max bonus +15)",
"MNBL.disarm": "Disarm",
"MNBL.doubleD20": "Double d20 (1 Shard Point)",
"MNBL.dramaticfailure": "Dramatic Failure",
"MNBL.oddresult": "Odd result — the die counts as 0",
"MNBL.duration": "Duration",
"MNBL.easy": "Easy (5)",
"MNBL.eclat": "Shard",
"MNBL.elementslords": "Elemental Lords",
"MNBL.equipmentactions": "Equipment/Actions",
"MNBL.equipments": "Equipments",
"MNBL.equipped": "Equipped",
"MNBL.exp": "Experience",
"MNBL.eyes": "Eyes",
"MNBL.failure": "Failure",
"MNBL.feint": "Feint",
"MNBL.flee": "Flee",
"MNBL.formula": "Formula",
"MNBL.genre": "Gender",
"MNBL.gifts": "Gifts",
"MNBL.goodadventure": "Good Adventure",
"MNBL.hair": "Hair",
"MNBL.hard": "Hard (15)",
"MNBL.hascover": "Cover",
"MNBL.hazardous": "Tricky (20)",
"MNBL.health": "Health",
"MNBL.healthmalus": "Health Penalty",
"MNBL.heavycover": "Almost complete (-10)",
"MNBL.heroicsuccess": "Heroic Success",
"MNBL.highlanguage": "High Melnibonéan",
"MNBL.ignorearmor": "Ignore Armor",
"MNBL.ignorehealthmalus": "Ignore Health Penalty",
"MNBL.ignoresoulmalus": "Ignore Soul Penalty",
"MNBL.immobilize": "Immobilize",
"MNBL.insane": "Ridiculous (25)",
"MNBL.isdefense": "Defensive",
"MNBL.knockout": "Knock Out",
"MNBL.law": "Law",
"MNBL.legacy": "Legacy",
"MNBL.lessthanshort": "Less than short (10)",
"MNBL.lethal": "Lethal",
"MNBL.level": "Level",
"MNBL.lightcover": "Buckler or light (-2)",
"MNBL.longmore": "Long and more (25)",
"MNBL.longrange": "Long Range",
"MNBL.malus": "Penalty",
"MNBL.margin": "Margin",
"MNBL.medium": "Average (10)",
"MNBL.mediumcover": "Pavise or half (-5)",
"MNBL.mediummore": "Medium and more (20)",
"MNBL.mediumrange": "Medium Range",
"MNBL.meleethrowweapon": "Melee and Throwing Weapon",
"MNBL.meleeweapon": "Melee Weapon",
"MNBL.mode": "Mode",
"MNBL.modifier": "Modifiers",
"MNBL.modifiertype": "Modifier Type",
"MNBL.mounted": "Mounted",
"MNBL.nextactionmalus": "Penalty for next action",
"MNBL.nextattackbonus": "Bonus for next attack",
"MNBL.none": "None",
"MNBL.noneunknwon": "None/Unknown",
"MNBL.nonlethal": "Non-Lethal",
"MNBL.nonlethaldamage": "Non-Lethal Damage",
"MNBL.notarget": "No designated target",
"MNBL.origin": "Origin",
"MNBL.pacts": "Pacts",
"MNBL.points": "Points",
"MNBL.preciseattack": "Precision Attack",
"MNBL.predilections": "Specializations",
"MNBL.preferredhand": "Preferred Hand",
"MNBL.prerequisites": "Prerequisites",
"MNBL.price": "Price",
"MNBL.profession": "Profession",
"MNBL.pronounced": "Spoken",
"MNBL.pronouncedrune": "Spoken Rune",
"MNBL.pronouncerune": "Speak the rune",
"MNBL.protections": "Protections",
"MNBL.puremadness": "Insane (30)",
"MNBL.quantity": "Quantity",
"MNBL.range": "Range",
"MNBL.rarity": "Rarity",
"MNBL.registeredmodifiers": "Registered Modifiers",
"MNBL.reloadduration": "Reload Time",
"MNBL.ressources": "Resources",
"MNBL.roll": "Roll",
"MNBL.runes": "Runes",
"MNBL.runningtarget": "Running target (-5/-10 depending on range)",
"MNBL.shootmodifier": "Shooting Modifiers",
"MNBL.shootweapon": "Shoot Weapon",
"MNBL.shortmore": "Short and more (10)",
"MNBL.shortrange": "Short Range",
"MNBL.size": "Size",
"MNBL.skill": "Skill",
"MNBL.skills": "Skills",
"MNBL.smallroomtarget": "Target in confined space (+5)",
"MNBL.soul": "Soul",
"MNBL.soulmalus": "Soul Penalty",
"MNBL.soulmultiplier": "Soul Multiplier",
"MNBL.soulpoints": "Soul Points",
"MNBL.specialactions": "Special Actions",
"MNBL.specialweapon": "Special (ability/gift)",
"MNBL.speciestrait": "Species Trait",
"MNBL.speed": "Speed",
"MNBL.success": "Success",
"MNBL.target": "Target",
"MNBL.targetbelow": "Target below (+5)",
"MNBL.targetcantmove": "Target immobilized (+5)",
"MNBL.targetdefense": "Opponent's Defense",
"MNBL.targetground": "Target on the ground (+5)",
"MNBL.targetseeshoot": "Target is aware of the shot",
"MNBL.tendancies": "Tendencies",
"MNBL.throwweapon": "Throwing Weapon",
"MNBL.totalprotection": "Total Protection",
"MNBL.traced": "Written",
"MNBL.tracedrune": "Written Rune",
"MNBL.tracerune": "Write the rune",
"MNBL.treasuremoney": "Treasures and Money",
"MNBL.twohands": "Two-handed",
"MNBL.type": "Type",
"MNBL.unarmedtarget": "Unarmed target (+5)",
"MNBL.unit": "Unit",
"MNBL.use": "Use",
"MNBL.usedpredilection": "Used Predilection",
"MNBL.value": "Value",
"MNBL.weapon": "Weapon",
"MNBL.weaponbonusattack": "Handling Bonus (offensive)",
"MNBL.weaponbonusdefense": "Handling Bonus (defensive)",
"MNBL.weapons": "Weapons",
"MNBL.weaponscapacities": "Weapons/Abilities",
"MNBL.weapontype": "Weapon Type",
"MNBL.weight": "Weight",
"MNBL.total": "Total",
"Présence": "Presence",
"Puissance": "Might",
"Trempe": "Mettle",
"TYPES": {
"Actor": {
"creature": "Creature",
"personnage": "Character"
},
"Item": {
"arme": "Weapon",
"bouclier": "Shield",
"capacite": "Ability",
"competence": "Skill",
"don": "Gifts",
"equipement": "Equipment",
"heritage": "Background",
"metier": "Profession",
"modifier": "Modifier",
"monnaie": "Currency",
"origine": "Origin",
"pacte": "Pacts",
"protection": "Protections",
"rune": "Rune",
"runeeffect": "Rune Effect",
"tendance": "Signs of Chaos",
"traitchaotique": "Background",
"traitespece": "Species Trait"
}
},
"MNBL.potion": "Potion",
"MNBL.potionRune": "Rune",
"MNBL.potionSeuil": "Rune Threshold",
"MNBL.potionPointsAme": "Soul Points invested",
"MNBL.potionForme": "Form",
"MNBL.potionStatut": "Status",
"MNBL.potionVirulence": "Virulence",
"MNBL.potionDuree": "Duration",
"MNBL.potionConservation": "Conservation",
"MNBL.potionTemps": "Preparation Time",
"MNBL.potionEffetCuratif": "Curative Effect",
"MNBL.potionEffetLetal": "Lethal Effect",
"MNBL.potionEffetSecondaire": "Side Effects",
"MNBL.potionDescription": "Description",
"MNBL.potionLiquide": "Liquid",
"MNBL.potionOnguent": "Ointment",
"MNBL.potionCachets": "Tablets",
"MNBL.potionPilules": "Pills",
"MNBL.potionInconnue": "Unknown",
"MNBL.potionEfficace": "Effective",
"MNBL.potionHeroique": "Heroic",
"MNBL.potionInefficace": "Ineffective",
"MNBL.potionPoison": "Poison",
"MNBL.potions": "Potions",
"MNBL.potionStatut": "Status",
"MNBL.potionDuree": "Duration",
"MNBL.preparePotion": "Prepare a Potion",
"MNBL.capacites": "Capacities / Powers",
"MNBL.capacite": "Capacity",
"MNBL.typeCapacite": "Capacity Type",
"MNBL.typeCapaciteElue": "Chosen Power",
"MNBL.typeCapaciteElementaire": "Elemental Power",
"MNBL.typeCapaciteDemoniaque": "Demonic Power",
"MNBL.typeCapaciteAutomaton": "Automaton Ability",
"MNBL.typeCapaciteCreature": "Creature Power",
"MNBL.typeCapaciteFaiblesseDemoniaque": "Demonic Weakness",
"MNBL.creatureTypeCreature": "Creature",
"MNBL.creatureTypeDemon": "Demon",
"MNBL.creatureTypeElementaire": "Elemental",
"MNBL.elementTypeAir": "Air",
"MNBL.elementTypeTerre": "Earth",
"MNBL.elementTypeFeu": "Fire",
"MNBL.elementTypeEau": "Water",
"MNBL.creatureType": "Creature Type",
"MNBL.elementType": "Element",
"MNBL.demonType": "Demon Type",
"MNBL.demonPuissance": "Power Level",
"MNBL.demonTypeNone": "— Undefined —",
"MNBL.demonTypeCombat": "Combat Demon",
"MNBL.demonTypeDesir": "Desire Demon",
"MNBL.demonTypeSavoir": "Knowledge Demon",
"MNBL.demonTypeProtection": "Protection Demon",
"MNBL.demonTypeVoyage": "Travel Demon",
"MNBL.demonPuissanceNone": "— Undefined —",
"MNBL.demonPuissanceMineur": "Minor",
"MNBL.demonPuissanceMedian": "Median",
"MNBL.demonPuissanceMajeur": "Major",
"MNBL.creatureTypeAutomaton": "Automaton",
"MNBL.automatonType": "Automaton Type",
"MNBL.automatonPuissance": "Power Level",
"MNBL.automatonTypeNone": "— Undefined —",
"MNBL.automatonTypeCombat": "Combat",
"MNBL.automatonTypeVoyage": "Travel",
"MNBL.automatonTypePerception": "Perception",
"MNBL.automatonTypeRestauration": "Restoration",
"MNBL.automatonTypeReparateur": "Repairer",
"MNBL.automatonPuissanceNone": "— Undefined —",
"MNBL.automatonPuissanceMineur": "Minor",
"MNBL.automatonPuissanceMedian": "Median",
"MNBL.automatonPuissanceMajeur": "Major",
"MNBL.automatonVoyageType": "Travel Type",
"MNBL.automatonVoyageTypeNone": "— Undefined —",
"MNBL.automatonVoyageTypeTerrestre": "Terrestrial",
"MNBL.automatonVoyageTypeAquatique": "Aquatic",
"MNBL.automatonVoyageTypeAerien": "Aerial",
"MNBL.automatonVoyageTypeExtradimensionnel": "Extra-dimensional",
"MNBL.invoquerElementaire": "Summon an Elemental",
"MNBL.bannirElementaire": "Banish",
"MNBL.invocationsActives": "Active Invocations",
"MNBL.invocationElement": "Element",
"MNBL.invocationTier": "Power",
"MNBL.invocationSeuil": "Threshold",
"MNBL.invocationAmeExtra": "Extra Soul Points",
"MNBL.invocationAmeTotal": "Total Soul Cost",
"MNBL.invocationTemps": "Concentration Time",
"MNBL.invocationBonusPacte": "Elemental Pact Bonus",
"MNBL.invocationHautParler": "Savoir : Haut-Parler",
"MNBL.invocationSeigneursElem": "Savoir : Seigneurs Élémentaires",
"MNBL.invocationResultSucces": "The Elemental is summoned!",
"MNBL.invocationResultHeroique": "Heroic success! You may freely increase one of the Elemental's attributes by +2.",
"MNBL.invocationResultEchec": "The invocation failed. You lose half the Soul Points spent.",
"MNBL.invocationResultDramatique": "Dramatic failure! The invocation failed and you lose all invested Soul Points. A natural catastrophe is unleashed!",
"MNBL.invocationTierMineur": "Minor (Threshold 15)",
"MNBL.invocationTierMedian": "Median (Threshold 20)",
"MNBL.invocationTierMajeur": "Major (Threshold 25)",
"MNBL.invocationConcentrationMineur": "1 round",
"MNBL.invocationConcentrationMedian": "1 minute",
"MNBL.invocationConcentrationMajeur": "1 hour",
"MNBL.invocationAmeBloque": "Blocked Soul",
"MNBL.invoquerDemon": "Invoke a Demon",
"MNBL.invoquerEsprit": "Invoke a Law Spirit",
"MNBL.libererDemon": "Release",
"MNBL.invocationsDemons": "Active Demonic Invocations",
"MNBL.invocationDemonSeuil": "Difficulty Threshold",
"MNBL.invocationDemonAme": "Soul Spent",
"MNBL.invocationDemonCoercition": "Coercion",
"MNBL.invocationDemonLoiChaos": "Lore: Law & Chaos",
"MNBL.invocationDemonResultSucces": "The Demon is invoked!",
"MNBL.invocationDemonResultHeroique": "Heroic success! More favorable agreement.",
"MNBL.invocationDemonResultEchec": "Invocation failed. You lose half the Soul points spent.",
"MNBL.invocationDemonResultDramatique": "Dramatic failure! All Soul points are lost.",
"MNBL.enchantementLoi": "Law Enchantment",
"MNBL.enchanter": "Enchant",
"MNBL.enchantementActif": "Enchanted",
"MNBL.enchantementBonus": "Bonus",
"MNBL.enchantementAntiChaos": "Anti-Chaos",
"MNBL.enchantementStandard": "Standard (skill bonus)",
"MNBL.enchantementTypeAntiChaos": "Anti-Chaos (luminous aura)"
}
+333
View File
@@ -0,0 +1,333 @@
{
"TYPES": {
"Actor": {
"personnage": "Personnage",
"creature": "Créature"
},
"Item": {
"arme": "Arme",
"competence": "Compétence",
"protection": "Protection",
"pacte": "Pacte",
"traitchaotique": "Trait Chaotique",
"monnaie": "Monnaie",
"don": "Don",
"tendance": "Tendance",
"rune": "Rune",
"equipement": "Equipement",
"capacite": "Capacité",
"origine": "Origine",
"heritage": "Héritage",
"metier": "Métier",
"runeeffect": "Effet de Rune",
"bouclier": "Bouclier",
"modifier": "Modificateur",
"traitespece": "Trait d'Espèce",
"potion": "Potion"
}
},
"Adresse": "Adresse",
"Puissance": "Puissance",
"Clairvoyance": "Clairvoyance",
"Présence": "Présence",
"Trempe": "Trempe",
"MNBL.assaut": "Assaut",
"MNBL.preciseattack": "Attaque Précise",
"MNBL.feint": "Feinte",
"MNBL.dirtyattack": "Coup Bas",
"MNBL.charge": "Charger",
"MNBL.contain": "Contenir l'adversaire",
"MNBL.disarm": "Désarmer",
"MNBL.none": "Aucun",
"MNBL.lightcover": "Rondache ou léger (-2)",
"MNBL.mediumcover": "Pavois ou à moitié (-5)",
"MNBL.heavycover": "Quasi complet (-10)",
"MNBL.roll": "Jet",
"MNBL.defensecapacity": "Capacité défensive",
"MNBL.attackcapacity": "Capacité offensive",
"MNBL.lessthanshort": "Moins que courte (10)",
"MNBL.shortmore": "Courte et + (10)",
"MNBL.mediummore": "Moyenne et + (20)",
"MNBL.longmore": "Longue et + (25)",
"MNBL.noneunknwon": "Aucune/Inconnue",
"MNBL.easy": "Facile (5)",
"MNBL.medium": "Moyenne (10)",
"MNBL.hard": "Ardue (15)",
"MNBL.hazardous": "Hasardeuse (20)",
"MNBL.insane": "Insensée (25)",
"MNBL.puremadness": "Pure Folie (30)",
"MNBL.pronouncerune": "Prononcer la rune",
"MNBL.tracerune": "Tracer la rune",
"MNBL.pronounced": "Prononcée",
"MNBL.traced": "Tracée",
"MNBL.meleeweapon": "Arme de contact",
"MNBL.meleethrowweapon": "Arme de contact et de Jet",
"MNBL.throwweapon": "Arme de Lancer",
"MNBL.shootweapon": "Arme de Tir",
"MNBL.specialweapon": "Spécial (capacité/don)",
"MNBL.all": "Tous",
"MNBL.beastslords": "Seigneurs des Bêtes",
"MNBL.elementslords": "Seigneurs Elementaires",
"MNBL.law": "Loi",
"MNBL.chaos": "Chaos",
"MNBL.level": "Niveau",
"MNBL.points": "Points",
"MNBL.aspect": "Aspect",
"MNBL.margin": "Marge",
"MNBL.goodadventure": "Bonne Aventure",
"MNBL.base": "Base",
"MNBL.current": "Actuelle",
"MNBL.alignement": "Alignement",
"MNBL.eclat": "Eclat",
"MNBL.exp": "Expérience",
"MNBL.attributes": "Attributs",
"MNBL.skills": "Compétences",
"MNBL.abilities": "Dons/Pactes",
"MNBL.equipmentactions": "Equipements/Actions",
"MNBL.bio": "Bio&Notes",
"MNBL.health": "Santé",
"MNBL.bonus": "Bonus",
"MNBL.malus": "Malus",
"MNBL.nonlethal": "Non Létaux",
"MNBL.lethal": "Létaux",
"MNBL.automalus": "Malus Auto",
"MNBL.soul": "Ame",
"MNBL.currentmax": "Max Actuel",
"MNBL.consumed": "Consommé",
"MNBL.damagebonus": "B. Dégats",
"MNBL.speed": "Vitesse",
"MNBL.defense": "Défense",
"MNBL.totalprotection": "Protection Totale",
"MNBL.modifier": "Modificateurs",
"MNBL.type": "Type",
"MNBL.value": "Valeur",
"MNBL.gifts": "Dons",
"MNBL.allegiance": "Allégeance",
"MNBL.pacts": "Pactes",
"MNBL.runes": "Runes",
"MNBL.highlanguage": "Haut Parler",
"MNBL.difficulty": "Difficulté",
"MNBL.tendancies": "Tendances",
"MNBL.chaotictraits": "Traits Chaotique",
"MNBL.activatedrunes": "Runes actives",
"MNBL.mode": "Mode",
"MNBL.duration": "Durée",
"MNBL.treasuremoney": "Richesses et Argent",
"MNBL.quantity": "Quantité",
"MNBL.unit": "Unité",
"MNBL.specialactions": "Actions spéciales",
"MNBL.knockout": "Assomer",
"MNBL.flee": "Fuir",
"MNBL.immobilize": "Immobiliser",
"MNBL.mounted": "Monté",
"MNBL.weapons": "Armes",
"MNBL.attack": "Attaque",
"MNBL.damage": "Dégats",
"MNBL.protections": "Protections",
"MNBL.equipments": "Equipements",
"MNBL.equipment": "Equipement",
"MNBL.origin": "Origine",
"MNBL.legacy": "Héritage",
"MNBL.profession": "Métier",
"MNBL.genre": "Genre",
"MNBL.size": "Taille",
"MNBL.hair": "Cheveux",
"MNBL.eyes": "Yeux",
"MNBL.preferredhand": "Main Préférée",
"MNBL.weight": "Poids",
"MNBL.soulmultiplier": "Multiplicateur d'âme",
"MNBL.ignorehealthmalus": "Ignore le malus de santé",
"MNBL.ignoresoulmalus": "Ignore le malus d'âme",
"MNBL.weapon": "Arme",
"MNBL.nextattackbonus": "Bonus pour prochaine attaque",
"MNBL.nextactionmalus": "Malus au défenseur pour prochaine action",
"MNBL.applydamage": "Appliquer les dégats/bonus/malus",
"MNBL.attribut": "Attribut",
"MNBL.skill": "Compétence",
"MNBL.target": "Cible",
"MNBL.usedpredilection": "Prédilection utilisée",
"MNBL.soulpoints": "Points d'âme",
"MNBL.formula": "Formule",
"MNBL.dice": "Dé",
"MNBL.success": "Succés",
"MNBL.failure": "Echec",
"MNBL.heroicsuccess": "Succés Héroïque",
"MNBL.dramaticfailure": "Echec Dramatique",
"MNBL.oddresult": "Résultat impair — le dé compte pour 0",
"MNBL.attackmountbonus": "Attaquant monté vs def. au sol (+5)",
"MNBL.targetdefense": "Défense adversaire",
"MNBL.shootmodifier": "Modificateurs de Tir",
"MNBL.aimingbonus": "Bonus de visée",
"MNBL.targetseeshoot": "La cible est consciente du tir",
"MNBL.notarget": "Pas de cible désignée",
"MNBL.runningtarget": "La cible court (-5/-10 selon portée)",
"MNBL.hascover": "Couvert",
"MNBL.range": "Portée",
"MNBL.disadvantagepositions": "Positions désavantageuses (Bonus max +15)",
"MNBL.targetground": "Cible au sol (+5)",
"MNBL.unarmedtarget": "Cible désarmée (+5)",
"MNBL.smallroomtarget": "Cible en espace restreint (+5)",
"MNBL.targetcantmove": "Cible immobilisée (+5)",
"MNBL.targetbelow": "Cible surplombée (+5)",
"MNBL.healthmalus": "Malus de santé",
"MNBL.soulmalus": "Malus d'âme",
"MNBL.registeredmodifiers": "Modificateurs enregistrés",
"MNBL.doubleD20": "Doubler le d20 (1 Point d'Eclat)",
"MNBL.pronouncedrune": "Rune prononcée",
"MNBL.tracedrune": "Rune tracée",
"MNBL.equipped": "Equipé",
"MNBL.rarity": "Rareté",
"MNBL.price": "Prix",
"MNBL.modifiertype": "Type de modificateur",
"MNBL.prerequisites": "Prérequis",
"MNBL.predilections": "Prédilections",
"MNBL.candoublebonusskill": "Les bonus de Bonne Aventure et d'Eclat peuvent être doublés (cf. Métier)",
"MNBL.addpredilection": "Ajouter une prédilection",
"MNBL.defensebonus": "Bonus de défense",
"MNBL.nonlethaldamage": "Dégâts non létaux",
"MNBL.weapontype": "Type d'arme",
"MNBL.weaponbonusattack": "Bonus de maniement (offensif)",
"MNBL.weaponbonusdefense": "Bonus de maniement (défensif)",
"MNBL.isdefense": "Défensive",
"MNBL.twohands": "A deux mains",
"MNBL.ignorearmor": "Ignore l'armure",
"MNBL.creatureresourcecost": "Cout en Ressources (créatures)",
"MNBL.shortrange": "Portée courte",
"MNBL.mediumrange": "Portée moyenne",
"MNBL.longrange": "Portée longue",
"MNBL.reloadduration": "Temps de rechargement",
"MNBL.attacks": "Attaques",
"MNBL.ressources": "Ressources",
"MNBL.weaponscapacities": "Armes/Capacités",
"MNBL.use": "Utiliser",
"MNBL.speciestrait": "Trait d'espèce",
"MNBL.attribute": "Attribut",
"MNBL.Protections": "Protections",
"MNBL.rune": "Rune",
"MNBL.total": "Total",
"MNBL.description": "Description",
"MNBL.details": "Détails",
"MNBL.sacrifice": "Sacrifice",
"MNBL.identity": "Identité",
"MNBL.gmtools": "Outils MJ",
"MNBL.potion": "Potion",
"MNBL.potionRune": "Rune",
"MNBL.potionSeuil": "Seuil de la Rune",
"MNBL.potionPointsAme": "Points d'Âme investis",
"MNBL.potionForme": "Forme",
"MNBL.potionStatut": "Statut",
"MNBL.potionVirulence": "Virulence",
"MNBL.potionDuree": "Durée d'effet",
"MNBL.potionConservation": "Conservation",
"MNBL.potionTemps": "Temps de préparation",
"MNBL.potionEffetCuratif": "Effet curatif",
"MNBL.potionEffetLetal": "Effet létal",
"MNBL.potionEffetSecondaire": "Effets secondaires",
"MNBL.potionDescription": "Description",
"MNBL.potionLiquide": "Liquide",
"MNBL.potionOnguent": "Onguent",
"MNBL.potionCachets": "Cachets",
"MNBL.potionPilules": "Pilules",
"MNBL.potionInconnue": "Inconnue",
"MNBL.potionEfficace": "Efficace",
"MNBL.potionHeroique": "Héroïque",
"MNBL.potionInefficace": "Inefficace",
"MNBL.potionPoison": "Poison",
"MNBL.potions": "Potions",
"MNBL.potionStatut": "Statut",
"MNBL.potionDuree": "Durée",
"MNBL.preparePotion": "Préparer une Potion",
"MNBL.capacites": "Capacités / Pouvoirs",
"MNBL.capacite": "Capacité",
"MNBL.typeCapacite": "Type de capacité",
"MNBL.typeCapaciteElue": "Pouvoir Élue",
"MNBL.typeCapaciteElementaire": "Pouvoir Élémentaire",
"MNBL.typeCapaciteDemoniaque": "Pouvoir Démoniaque",
"MNBL.typeCapaciteAutomaton": "Capacité Automaton",
"MNBL.typeCapaciteCreature": "Pouvoir Créature",
"MNBL.typeCapaciteFaiblesseDemoniaque": "Faiblesse Démoniaque",
"MNBL.creatureTypeCreature": "Créature",
"MNBL.creatureTypeDemon": "Démon",
"MNBL.creatureTypeElementaire": "Élémentaire",
"MNBL.elementTypeAir": "Air",
"MNBL.elementTypeTerre": "Terre",
"MNBL.elementTypeFeu": "Feu",
"MNBL.elementTypeEau": "Eau",
"MNBL.creatureType": "Type de créature",
"MNBL.elementType": "Élément",
"MNBL.demonType": "Type de Démon",
"MNBL.demonPuissance": "Puissance",
"MNBL.demonTypeNone": "— Non défini —",
"MNBL.demonTypeCombat": "Démon de Combat",
"MNBL.demonTypeDesir": "Démon du Désir",
"MNBL.demonTypeSavoir": "Démon du Savoir",
"MNBL.demonTypeProtection": "Démon de Protection",
"MNBL.demonTypeVoyage": "Démon du Voyage",
"MNBL.demonPuissanceNone": "— Non défini —",
"MNBL.demonPuissanceMineur": "Mineur",
"MNBL.demonPuissanceMedian": "Médian",
"MNBL.demonPuissanceMajeur": "Majeur",
"MNBL.creatureTypeAutomaton": "Automaton",
"MNBL.automatonType": "Type d'Automaton",
"MNBL.automatonPuissance": "Puissance",
"MNBL.automatonTypeNone": "— Non défini —",
"MNBL.automatonTypeCombat": "Combat",
"MNBL.automatonTypeVoyage": "Voyage",
"MNBL.automatonTypePerception": "Perception",
"MNBL.automatonTypeRestauration": "Restauration",
"MNBL.automatonTypeReparateur": "Réparateur",
"MNBL.automatonPuissanceNone": "— Non défini —",
"MNBL.automatonPuissanceMineur": "Mineur",
"MNBL.automatonPuissanceMedian": "Médian",
"MNBL.automatonPuissanceMajeur": "Majeur",
"MNBL.automatonVoyageType": "Type de voyage",
"MNBL.automatonVoyageTypeNone": "— Non défini —",
"MNBL.automatonVoyageTypeTerrestre": "Terrestre",
"MNBL.automatonVoyageTypeAquatique": "Aquatique",
"MNBL.automatonVoyageTypeAerien": "Aérien",
"MNBL.automatonVoyageTypeExtradimensionnel": "Extra-dimensionnel",
"MNBL.invoquerElementaire": "Invoquer un Élémentaire",
"MNBL.bannirElementaire": "Bannir",
"MNBL.invocationsActives": "Invocations actives",
"MNBL.invocationElement": "Élément",
"MNBL.invocationTier": "Puissance",
"MNBL.invocationSeuil": "Seuil",
"MNBL.invocationAmeExtra": "Points d'Âme supplémentaires",
"MNBL.invocationAmeTotal": "Coût total en Âme",
"MNBL.invocationTemps": "Temps de concentration",
"MNBL.invocationBonusPacte": "Bonus Pacte élémentaire",
"MNBL.invocationHautParler": "Savoir : Haut-Parler",
"MNBL.invocationSeigneursElem": "Savoir : Seigneurs Élémentaires",
"MNBL.invocationResultSucces": "L'Élémentaire est invoqué !",
"MNBL.invocationResultHeroique": "Réussite héroïque ! Vous pouvez majorer gratuitement de +2 un attribut de l'Élémentaire.",
"MNBL.invocationResultEchec": "L'invocation a échoué. Vous perdez la moitié des points d'Âme dépensés.",
"MNBL.invocationResultDramatique": "Échec dramatique ! L'invocation a échoué et vous perdez tous les points d'Âme investis. Une catastrophe naturelle se déclenche !",
"MNBL.invocationTierMineur": "Mineur (Seuil 15)",
"MNBL.invocationTierMedian": "Médian (Seuil 20)",
"MNBL.invocationTierMajeur": "Majeur (Seuil 25)",
"MNBL.invocationConcentrationMineur": "1 tour",
"MNBL.invocationConcentrationMedian": "1 minute",
"MNBL.invocationConcentrationMajeur": "1 heure",
"MNBL.invocationAmeBloque": "Âme bloquée",
"MNBL.invoquerDemon": "Invoquer un Démon",
"MNBL.invoquerEsprit": "Invoquer un Esprit de la Loi",
"MNBL.libererDemon": "Libérer",
"MNBL.invocationsDemons": "Invocations démoniaques actives",
"MNBL.invocationDemonSeuil": "Seuil de difficulté",
"MNBL.invocationDemonAme": "Âme dépensée",
"MNBL.invocationDemonCoercition": "Coercition",
"MNBL.invocationDemonLoiChaos": "Savoir : Loi & Chaos",
"MNBL.invocationDemonResultSucces": "Le Démon est invoqué !",
"MNBL.invocationDemonResultHeroique": "Réussite héroïque ! Accord plus favorable.",
"MNBL.invocationDemonResultEchec": "L'invocation a échoué. Vous perdez la moitié des points d'Âme dépensés.",
"MNBL.invocationDemonResultDramatique": "Échec dramatique ! Tous les points d'Âme sont perdus.",
"MNBL.enchantementLoi": "Enchantement de la Loi",
"MNBL.enchanter": "Enchanter",
"MNBL.enchantementActif": "Enchanté",
"MNBL.enchantementBonus": "Bonus",
"MNBL.enchantementAntiChaos": "Anti-Chaos",
"MNBL.enchantementStandard": "Standard (bonus de compétence)",
"MNBL.enchantementTypeAntiChaos": "Anti-Chaos (aura lumineuse)"
}
+950
View File
@@ -0,0 +1,950 @@
/* ==================== Actor Sheet Styles ==================== */
// NOTE: Ce fichier surcharge certaines règles de simple-converted.less
// Les sélecteurs .fvtt-mournblade.actor sont plus spécifiques et prennent priorité
.fvtt-mournblade.actor {
// Background pour toute la fiche d'acteur
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
// AppV2 - Structure flex pour permettre le scroll
.window-content {
height: 100%;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
// Main form structure
form,
.sheet-form-layout {
height: 100%;
background: url("../assets/ui/pc_sheet_bg.webp") repeat-y;
color: black;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
padding: 0;
margin: 0;
// La section racine du template (if nested inside form, or simple direct children handling)
> section {
height: 100%;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
}
}
// SURCHARGE: simple-converted.less définit flex: 0 0 210px
.sheet-header {
background: url("../assets/ui/pc_sheet_bg.webp") repeat;
padding: 0.5rem;
margin: 0;
flex: 0 0 auto !important; // Override simple-converted
overflow: visible !important; // Override simple-converted
.background-sheet-header {
background: transparent;
}
.header-main-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.header-stats-grid {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 0.75rem;
padding: 0.5rem;
background: rgba(0, 0, 0, 0.15);
border: 2px solid rgba(139, 69, 19, 0.5);
border-radius: 4px;
}
.stat-group {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 0.35rem 0.6rem;
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(139, 69, 19, 0.4);
border-radius: 3px;
&.stat-group-health {
border-left: 3px solid rgba(200, 0, 0, 0.6);
background: rgba(40, 0, 0, 0.15);
}
}
.stat-title {
font-size: 0.75rem;
font-weight: bold;
color: #f5e6d3;
margin: 0;
padding: 0 0 0.3rem 0;
border-bottom: 1px solid rgba(139, 69, 19, 0.5);
text-transform: uppercase;
letter-spacing: 0.5px;
font-family: "CentaurMT", serif;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
.stat-values {
display: flex;
flex-direction: row;
gap: 0.5rem;
flex-wrap: wrap;
justify-content: center;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 0.2rem;
align-items: center;
flex: 1;
min-width: 0;
}
.stat-label {
font-size: 0.7rem;
color: #d4c5b0;
margin: 0;
font-weight: 600;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7);
}
.stat-value {
font-size: 0.85rem;
font-weight: bold;
color: #fff;
padding: 0.25rem 0.5rem;
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(139, 69, 19, 0.6);
border-radius: 3px;
min-width: 2.5rem;
text-align: center;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
width: 100%;
}
.stat-input {
font-size: 0.8rem;
font-weight: 600;
padding: 0.25rem 0.4rem;
background: rgba(40, 30, 20, 0.7);
border: 1px solid rgba(139, 69, 19, 0.7);
color: #fff;
border-radius: 3px;
text-align: center;
min-width: 2.5rem;
max-width: 100%;
width: 100%;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
&.stat-input-single {
min-width: 3.5rem;
}
&.stat-input-damage {
border-color: rgba(200, 0, 0, 0.8);
background: rgba(80, 0, 0, 0.5);
}
&:hover {
background: rgba(60, 45, 30, 0.8);
border-color: rgba(255, 102, 0, 0.6);
}
&:focus {
outline: none;
border-color: rgba(255, 102, 0, 1);
box-shadow: 0 0 6px rgba(255, 102, 0, 0.5), inset 0 1px 3px rgba(0, 0, 0, 0.3);
background: rgba(70, 50, 35, 0.9);
}
}
.header-fields {
h4.item-name-label.competence-name {
font-size: 0.75rem;
padding-top: 3px;
}
.item-name-label.competence-name {
font-size: 0.75rem;
}
label.item-name-label.competence-name {
font-size: 0.7rem;
}
.item-field-label-short {
font-size: 0.8rem;
}
.status-small-label {
font-size: 0.8rem;
}
}
}
// Sheet navigation tabs
// SURCHARGE: simple-converted.less définit flex: 0
.sheet-tabs {
margin: 0;
padding: 0 0.5rem;
flex: 0 0 auto !important; // Override simple-converted
}
// Sheet body - section scrollable
// SURCHARGE CRITIQUE: simple-converted.less définit height: auto sur .sheet-body et .tab
// Ces surcharges permettent le scroll vertical
.sheet-body {
margin: 0;
padding: 0.5rem;
flex: 1 !important; // Override simple-converted
min-height: 0 !important; // Critique pour le scroll
overflow-y: auto !important; // Override simple-converted
.tab {
padding: 0;
height: auto !important; // Override simple-converted qui met height: auto
&:not(.active) {
display: none;
}
// Assurer que les grilles peuvent scroller
.grid, .grid-2col {
height: auto;
overflow: visible;
}
.sheet-box {
height: auto;
}
}
// Listes compactes dans les sections
.compact-list {
list-style: none;
margin: 0;
padding: 0;
li.item {
padding: 0.2rem 0.4rem;
margin-bottom: 0.15rem;
background: rgba(0, 0, 0, 0.1);
border: 1px solid rgba(139, 69, 19, 0.3);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.15);
}
&.items-title-bg {
background: rgba(0, 0, 0, 0.3);
border-color: rgba(139, 69, 19, 0.5);
font-weight: 600;
margin-bottom: 0.3rem;
.item-name-label-header {
color: #f5e6d3;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7);
}
}
}
}
// Contrôles d'items (edit, delete, equip, etc.)
.item-controls {
display: flex;
align-items: center;
gap: 0.3rem;
justify-content: flex-end;
.item-control {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
color: #5a3a1a;
text-decoration: none;
cursor: pointer;
border-radius: 3px;
transition: all 0.2s;
&:hover {
color: #2a1a0a;
background: rgba(139, 69, 19, 0.2);
transform: scale(1.1);
}
i {
font-size: 0.85rem;
}
&.item-edit {
color: #4a7c59;
&:hover {
color: #2d5a3a;
background: rgba(74, 124, 89, 0.2);
}
}
&.item-delete {
color: #a04040;
&:hover {
color: #802020;
background: rgba(160, 64, 64, 0.2);
}
}
&.item-equip {
color: #6b5b3a;
&:hover {
color: #4a3a1a;
background: rgba(107, 91, 58, 0.2);
}
i.fa-circle {
color: #4a7c59;
}
i.fa-genderless {
color: #8a6a4a;
}
}
&.item-add {
color: #4a7c59;
&:hover {
color: #2d5a3a;
background: rgba(74, 124, 89, 0.2);
}
}
}
}
.item-controls-fixed {
min-width: 5rem;
max-width: 5rem;
}
// Couleurs pour les labels et textes dans les onglets
h4, h3, label, span.item-name-label, span.competence-name,
.label-name, .generic-label, .item-field-label-short,
.item-field-label-medium, .item-field-label-long,
.short-label, .items-title-text {
color: #3a2a1a !important;
text-shadow: 0px 0px 1px rgba(255, 255, 255, 0.3);
}
// Inputs dans le corps
input[type="text"], input[type="number"], select {
color: #2a1a0a;
background: rgba(255, 250, 240, 0.8);
border: 1px solid rgba(139, 69, 19, 0.5);
height: 24px;
padding: 0.15rem 0.3rem;
line-height: 1.2;
&:focus {
background: rgba(255, 255, 245, 0.95);
border-color: rgba(139, 69, 19, 0.8);
}
}
// Titres de sections
h3, h4 {
font-weight: bold;
color: #2a1a0a !important;
}
// Titres de sections Santé, Âme, Combat
h4.item-name-label.competence-name {
font-size: 0.85rem;
margin-top: 0.2rem;
margin-bottom: 0.3rem;
padding-top: 0;
}
// Section grids pour Santé, Âme, Combat
.section-grid {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding: 0.3rem 0.4rem;
margin-bottom: 0.3rem;
background: rgba(0, 0, 0, 0.15);
border: 2px solid rgba(139, 69, 19, 0.5);
border-radius: 4px;
.section-title {
font-size: 0.85rem;
font-weight: bold;
color: #f5e6d3;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 0 0 0.2rem 0;
padding: 0.2rem 0.4rem;
background: rgba(0, 0, 0, 0.3);
border-left: 3px solid rgba(139, 69, 19, 0.8);
border-radius: 2px;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
}
.grid-row {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
gap: 0.4rem;
padding: 0.25rem 0.4rem;
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(139, 69, 19, 0.4);
border-radius: 3px;
input {
width: 60px;
min-width: 60px;
}
&.attr-row {
grid-template-columns: 40px 1fr auto;
gap: 0.5rem;
.item-name-img {
width: 32px;
height: 32px;
border-radius: 3px;
border: 1px solid rgba(139, 69, 19, 0.5);
}
.label-name {
display: flex;
align-items: center;
a {
color: #f5e6d3;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7);
font-weight: 600;
cursor: pointer;
&:hover {
color: #fff;
text-shadow: 0 0 4px rgba(255, 200, 100, 0.8);
}
}
}
select {
width: 80px;
}
}
.label-name {
font-weight: 600;
font-size: 0.9rem;
color: #d4c5b0;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7);
}
.stat-label {
font-weight: 600;
font-size: 0.9rem;
color: #d4c5b0;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.7);
}
.value-display {
text-align: center;
font-weight: bold;
font-size: 1rem;
color: #fff;
background: rgba(0, 0, 0, 0.4);
padding: 0.2rem 0.4rem;
border-radius: 3px;
border: 1px solid rgba(139, 69, 19, 0.6);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
}
.malus-value {
text-align: center;
font-weight: bold;
font-size: 1rem;
color: #ff9999;
background: rgba(0, 0, 0, 0.4);
padding: 0.2rem 0.4rem;
border-radius: 3px;
border: 1px solid rgba(200, 50, 50, 0.6);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
}
&.damage-row {
grid-template-columns: 120px 1fr;
.damage-label {
font-size: 0.95rem;
font-weight: 600;
}
.damage-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
input {
width: 60px;
min-width: 60px;
flex-shrink: 0;
text-align: center;
font-size: 1.1rem;
font-weight: bold;
}
.plus-minus-button {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
font-weight: bold;
border-radius: 4px;
border: 2px solid rgba(139, 69, 19, 0.5);
background: linear-gradient(to bottom, rgba(210, 180, 140, 0.9), rgba(180, 150, 110, 0.9));
color: #2a1a0a;
cursor: pointer;
transition: all 0.15s;
&:hover {
background: linear-gradient(to bottom, rgba(230, 200, 160, 0.95), rgba(200, 170, 130, 0.95));
border-color: rgba(139, 69, 19, 0.8);
transform: scale(1.08);
}
&:active {
transform: scale(0.95);
}
}
}
}
&.soul-consumed-row {
grid-template-columns: 120px 1fr;
}
&.soul-malus-row {
grid-template-columns: 90px 1fr;
gap: 0.4rem;
.label-name {
font-size: 0.85rem;
}
.malus-value {
font-size: 0.9rem;
padding: 0.2rem 0.3rem;
}
}
&.malus-row {
grid-template-columns: 55px 60px 75px 50px;
gap: 0.3rem;
.label-name {
font-size: 0.85rem;
}
input {
width: 100%;
min-width: unset;
}
.malus-value {
font-size: 0.9rem;
padding: 0.2rem 0.3rem;
}
}
}
&.combat-grid {
.grid-row.combat-stat {
grid-template-columns: 85px 50px 60px 55px;
gap: 0.4rem;
.stat-label {
font-size: 0.85rem;
}
.stat-base {
text-align: center;
font-weight: bold;
font-size: 0.85rem;
color: #fff;
background: rgba(0, 0, 0, 0.4);
padding: 0.2rem 0.3rem;
border-radius: 3px;
border: 1px solid rgba(139, 69, 19, 0.6);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
}
input {
width: 50px;
min-width: 50px;
text-align: center;
}
.stat-total {
text-align: center;
font-weight: bold;
font-size: 0.85rem;
color: #fff;
background: rgba(0, 0, 0, 0.4);
padding: 0.2rem 0.3rem;
border-radius: 3px;
border: 1px solid rgba(139, 69, 19, 0.6);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
}
}
.grid-row.protection-row {
grid-template-columns: 1fr 1fr;
.protection-value {
text-align: center;
font-weight: bold;
font-size: 1.2rem;
color: #fff;
background: rgba(0, 0, 0, 0.4);
padding: 0.25rem 0.4rem;
border-radius: 3px;
border: 1px solid rgba(139, 69, 19, 0.6);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
}
}
}
}
// Boutons d'actions spéciales
.action-buttons {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 0.3rem 0;
button {
margin: 0;
}
}
.action-buttons-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
padding: 0.3rem 0;
button {
margin: 0;
flex: 1;
}
.mounted-checkbox {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.3rem 0.6rem;
white-space: nowrap;
label {
margin: 0;
font-size: 0.9rem;
}
input[type="checkbox"] {
margin: 0;
cursor: pointer;
}
}
}
}
}
/* ==================== Creature Sheet Specific Styles ==================== */
.fvtt-mournblade.actor.creature-sheet {
// Variant background pour creatures - teinte légèrement différente
.background-sheet-header-creature {
background: linear-gradient(135deg, rgba(0, 60, 0, 0.15) 0%, rgba(20, 80, 20, 0.1) 100%);
border: 2px solid rgba(34, 139, 34, 0.5);
border-radius: 4px;
padding: 0;
}
// Légère teinte verte pour les sections
.sheet-box {
&.color-bg-archetype {
background: linear-gradient(135deg, rgba(0, 40, 0, 0.08) 0%, rgba(20, 60, 20, 0.05) 100%);
}
}
// Header simplifié pour creatures
.sheet-header {
flex: 0 0 auto !important;
padding: 0;
}
.profile-img {
width: 80px;
height: 80px;
border-radius: 8px;
border: 2px solid rgba(34, 139, 34, 0.6);
object-fit: cover;
cursor: pointer;
flex-shrink: 0;
}
// Tabs avec teinte verte
nav.tabs {
.item.active {
border-bottom-color: rgba(34, 139, 34, 0.8);
color: #228b22;
}
}
// Section titles avec teinte verte
.section-title {
color: #1a5a1a;
border-bottom-color: rgba(34, 139, 34, 0.3);
}
// Items headers
.items-title-bg {
background: linear-gradient(135deg, rgba(0, 60, 0, 0.15) 0%, rgba(20, 80, 20, 0.1) 100%);
border-bottom: 1px solid rgba(34, 139, 34, 0.3);
}
}
// PNJ Sheet - Orange/Copper theme for visual distinction
.fvtt-mournblade.actor.pnj-sheet {
// Variant background pour PNJs - teinte orange/cuivre
.background-sheet-header-creature {
background: linear-gradient(135deg, rgba(80, 40, 0, 0.15) 0%, rgba(100, 50, 0, 0.1) 100%);
border: 2px solid rgba(205, 127, 50, 0.5);
border-radius: 4px;
padding: 0;
}
// Légère teinte orange/cuivre pour les sections
.sheet-box {
&.color-bg-archetype {
background: linear-gradient(135deg, rgba(60, 30, 0, 0.08) 0%, rgba(80, 40, 0, 0.05) 100%);
}
}
// Header simplifié pour PNJs
.sheet-header {
flex: 0 0 auto !important;
padding: 0;
}
.profile-img {
width: 80px;
height: 80px;
border-radius: 8px;
border: 2px solid rgba(205, 127, 50, 0.6);
object-fit: cover;
cursor: pointer;
flex-shrink: 0;
}
// Tabs avec teinte orange/cuivre
nav.tabs {
.item.active {
border-bottom-color: rgba(205, 127, 50, 0.8);
color: #cd7f32;
}
}
// Section titles avec teinte orange/cuivre
.section-title {
color: #b8734d;
border-bottom-color: rgba(205, 127, 50, 0.3);
}
// Items headers
.items-title-bg {
background: linear-gradient(135deg, rgba(80, 40, 0, 0.15) 0%, rgba(100, 50, 0, 0.1) 100%);
border-bottom: 1px solid rgba(205, 127, 50, 0.3);
}
}
// GM Tools section - labels plus larges
.gm-tools-section {
.grid {
.item-list .flexrow.item {
.label-name {
flex: 1;
min-width: 12rem;
}
}
}
}
// Alignment group in header
.stat-group-alignment {
.stat-values.alignment-values {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.alignment-row {
display: flex;
align-items: center;
gap: 0.3rem;
.alignment-label {
font-weight: bold;
min-width: 4rem;
font-size: 0.85rem;
}
.alignment-inputs {
display: flex;
align-items: center;
gap: 0.2rem;
flex: 1;
}
.stat-label-mini {
font-size: 0.7rem;
min-width: 2.5rem;
}
.stat-input-mini {
width: 2.5rem;
height: 1.5rem;
padding: 0.1rem 0.2rem;
font-size: 0.8rem;
}
select.stat-input-mini {
width: 3rem;
}
}
.alignment-info {
display: flex;
justify-content: space-around;
margin-top: 0.2rem;
padding-top: 0.2rem;
border-top: 1px solid rgba(139, 69, 19, 0.3);
.info-label {
font-size: 0.75rem;
color: #f5e6d3;
strong {
color: #ffd700;
}
}
}
}
/* ==================== Sortilège Launch Button ==================== */
.fvtt-mournblade.sheet {
.header-actions {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
padding: 4px 0;
.chat-card-button {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
background: linear-gradient(135deg, #6b1a1a 0%, #8b2222 50%, #6b1a1a 100%);
border: 1px solid #c0392b;
border-radius: 4px;
color: #f5e6d3;
font-family: CentaurMT, serif;
font-size: 0.9rem;
cursor: pointer;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
white-space: nowrap;
i { color: #d4af37; }
&:hover {
background: linear-gradient(135deg, #8b2222 0%, #a52a2a 50%, #8b2222 100%);
border-color: #d4af37;
color: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.15);
i { color: #ffd700; }
}
&:active {
background: linear-gradient(135deg, #5a1010 0%, #6b1a1a 50%, #5a1010 100%);
transform: translateY(1px);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
}
}
}
// Chat potion result — rune mini image and haut-parler styling
.mournblade-chat-result {
.detail-row {
.rune-mini-img {
width: 18px;
height: 18px;
border-radius: 2px;
object-fit: cover;
flex-shrink: 0;
border: 1px solid rgba(139, 69, 19, 0.4);
vertical-align: middle;
margin-right: 4px;
}
.potion-haut-parler {
font-style: italic;
color: #5a3a8a;
}
}
}
// Post-item card: danger divider variant (for létal effects)
.mournblade-item-card {
.item-card-divider--danger {
color: #8b0000;
&::before, &::after {
background: linear-gradient(90deg, transparent, #8b0000, transparent);
}
}
}
+480
View File
@@ -0,0 +1,480 @@
/* ==================== Item Sheet Styles ==================== */
/* Item header with image and name */
.fvtt-mournblade.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: "CentaurMT", 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 64px;
width: 64px;
height: 64px;
object-fit: cover;
border: 1px solid #999;
border-radius: 4px;
cursor: pointer;
&:hover {
box-shadow: 0 0 8px rgba(255, 102, 0, 0.8);
}
}
.header-fields {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.header-actions {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 0.5rem;
button {
background: rgba(0, 0, 0, 0.1);
border: 1px solid #999;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-family: "CentaurMT", serif;
&:hover {
background: rgba(255, 102, 0, 0.2);
box-shadow: 0 0 5px rgba(255, 102, 0, 0.5);
}
}
}
.chat-card-button {
background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%);
border: 2px ridge #846109;
color: #d4b5a8;
padding: 0.3rem 0.5rem;
transition: all 0.2s ease;
cursor: pointer;
i {
font-size: 0.9rem;
}
&:hover {
background: linear-gradient(to bottom, #800000 5%, #3e0101 100%);
color: #ffffff;
box-shadow: 0 0 8px rgba(128, 0, 0, 0.6);
}
&:active {
position: relative;
top: 1px;
}
}
.sheet-header h1.charname {
height: 50px;
padding: 0;
margin: 5px 0;
border-bottom: 0;
font-weight: bold;
font-size: 2rem;
font-family: "CentaurMT";
}
.sheet-header h1.charname input {
width: 100%;
height: 100%;
margin: 0;
font-weight: bold;
font-family: "CentaurMT";
font-size: 2rem;
text-align: left;
border: 0 none;
&:focus {
outline: 1px solid #ff6600;
}
}
/* Tabs - Modern style matching actor sheets */
nav.tabs {
display: flex;
border-bottom: 1px solid #7a7971;
margin: 0;
padding: 4px;
background-color: #1c1c1c;
flex: 0 0 auto;
}
nav.tabs a.item {
padding: 6px 12px;
color: #d4af37;
text-decoration: none;
border: 1px solid transparent;
border-radius: 4px 4px 0 0;
margin-right: 4px;
transition: all 0.2s;
i {
display: none; // Hide icons to match actor sheets
}
&:hover {
background-color: rgba(212, 175, 55, 0.1);
}
&.active {
background-color: #2a2a2a;
border-bottom-color: #d4af37;
color: #f5f5f5;
}
}
/* Tab content */
.tab {
display: none;
padding: 4px 8px;
overflow-y: auto;
flex: 1 1 auto;
&.active {
display: block;
}
}
/* Item list in details tab */
.item-list {
list-style: none;
margin: 0;
padding: 0;
li.item {
display: flex;
align-items: center;
margin-bottom: 2px;
padding: 2px 4px;
min-height: 24px;
border-bottom: none;
&.flexrow {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 4px;
input[type="checkbox"] {
margin-top: 3px;
flex-shrink: 0;
}
}
h3 {
color: #2a1a0a;
font-size: 1rem;
font-weight: bold;
margin: 0;
text-shadow: none;
}
}
}
/* Labels */
.generic-label {
display: inline-block;
white-space: nowrap;
font-weight: 700;
color: #464331c4;
font-size: 0.9rem;
font-family: "CentaurMT", serif;
margin: 0;
padding: 0 4px 0 0;
}
/* Field labels */
.item-field-label-short {
flex: 0 0 60px;
max-width: 60px;
}
.item-field-label-medium {
flex: 0 0 100px;
max-width: 100px;
}
.item-field-label-long {
flex: 1;
min-width: 150px;
}
.item-field-label-long1 {
flex: 1;
min-width: 200px;
}
.item-field-label-long2 {
flex: 1;
min-width: 250px;
max-width: 250px;
}
.item-field-label-long3 {
flex: 1;
min-width: 250px;
white-space: normal;
line-height: 1.3;
}
.numeric-input {
text-align: center;
width: 60px;
}
/* Editor fields */
.editor {
height: 300px;
border: 1px solid #999;
background: rgba(255, 255, 255, 0.9);
.editor-content {
height: 100%;
padding: 0.5rem;
color: #000;
}
}
}
/* ============================================= */
/* Potion Item Sheet */
/* ============================================= */
.potion-content {
.potion-header-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 0.5rem;
background: rgba(139, 0, 0, 0.08);
border-radius: 4px;
border: 1px solid rgba(139, 0, 0, 0.2);
margin-bottom: 0.5rem;
}
.potion-statut-badge {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.2rem 0.6rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: bold;
&.statut-efficace { background: rgba(0,128,0,0.15); color: #155215; border: 1px solid #4a904a; }
&.statut-heroique { background: rgba(180,120,0,0.15); color: #7a5000; border: 1px solid #c89000; }
&.statut-inefficace { background: rgba(100,100,100,0.15); color: #555; border: 1px solid #888; }
&.statut-poison { background: rgba(100,0,100,0.15); color: #4a004a; border: 1px solid #880088; }
&.statut-inconnue { background: rgba(50,50,50,0.1); color: #666; border: 1px solid #aaa; }
}
}
/* Potion in chat */
.potion-rune-info {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
padding: 0.4rem;
background: rgba(0,0,0,0.04);
border-radius: 4px;
margin-bottom: 0.35rem;
.potion-rune-name { font-weight: bold; }
.potion-rune-seuil { font-size: 0.85rem; color: #666; }
.potion-haut-parler { font-size: 0.85rem; color: #555; font-style: italic; }
}
.potion-result-gm {
border: 1px dashed rgba(139,0,0,0.4);
background: rgba(139,0,0,0.05);
border-radius: 4px;
padding: 0.4rem;
.potion-result-title {
font-size: 0.95rem;
margin: 0 0 0.3rem;
}
.potion-heroique { color: #8B6900; }
.potion-efficace { color: #155215; }
.potion-inefficace { color: #555; }
.potion-poison { color: #660066; }
}
.potion-result-player {
font-style: italic;
color: #666;
font-size: 0.9rem;
}
/* Potion dialog */
.potion-dialog {
.potion-dialog-section {
margin-bottom: 0.75rem;
}
.potion-dialog-title {
font-size: 0.9rem;
font-weight: bold;
border-bottom: 1px solid rgba(0,0,0,0.15);
padding-bottom: 0.2rem;
margin-bottom: 0.4rem;
}
.potion-runes-table {
width: 100%;
font-size: 0.85rem;
border-collapse: collapse;
margin-bottom: 0.5rem;
th, td {
padding: 0.2rem 0.4rem;
border-bottom: 1px solid rgba(0,0,0,0.08);
}
.potion-rune-row:hover { background: rgba(0,0,0,0.04); }
.potion-rune-radio { cursor: pointer; }
.rune-mini-img {
width: 20px;
height: 20px;
vertical-align: middle;
margin-right: 4px;
border-radius: 2px;
border: 1px solid rgba(139, 69, 19, 0.5);
object-fit: cover;
}
}
.potion-summary {
background: rgba(0,0,0,0.05);
border-radius: 4px;
padding: 0.4rem 0.6rem;
font-size: 0.85rem;
.summary-row { margin-bottom: 0.2rem; }
.summary-label { font-weight: bold; min-width: 150px; }
}
.potion-info-note {
color: #888;
font-style: italic;
gap: 0.4rem;
align-items: center;
}
}
/* Enchantement de la Loi */
.enchantement-section {
margin-top: 0.6rem;
padding: 0.5rem 0.6rem;
background: rgba(255, 215, 0, 0.05);
border: 1px solid rgba(255, 215, 0, 0.3);
border-radius: 4px;
.section-title-small {
font-size: 0.9rem;
margin: 0 0 0.4rem 0;
color: #a07800;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.3rem;
border-bottom: 1px solid rgba(255, 215, 0, 0.2);
padding-bottom: 0.2rem;
}
.enchant-badge {
display: inline-block;
background: gold;
color: #333;
font-size: 0.7rem;
padding: 0.1rem 0.4rem;
border-radius: 8px;
margin-left: 0.5rem;
}
.enchant-none {
color: #888;
font-size: 0.85rem;
margin: 0;
padding: 0.2rem 0;
}
}
+6
View File
@@ -0,0 +1,6 @@
// Main LESS file for Mournblade system
// Importing base styles and component-specific styles
@import "simple-converted";
@import "item-styles";
@import "actor-styles";
File diff suppressed because it is too large Load Diff
@@ -1,18 +1,18 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
* @extends {ActorSheetV2}
*/
import { MournbladeUtility } from "./mournblade-utility.js";
import { MournbladeRollDialog } from "./mournblade-roll-dialog.js";
/* -------------------------------------------- */
export class MournbladeActorSheet extends ActorSheet {
export class MournbladeActorSheet extends foundry.applications.sheets.ActorSheetV2 {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade", "sheet", "actor"],
template: "systems/fvtt-mournblade/templates/actor-sheet.html",
width: 640,
@@ -25,8 +25,8 @@ export class MournbladeActorSheet extends ActorSheet {
/* -------------------------------------------- */
async getData() {
const objectData = duplicate(this.object)
let actorData = objectData
const objectData = foundry.utils.duplicate(this.object)
let actorData = objectData
let formData = {
title: this.title,
@@ -40,19 +40,30 @@ export class MournbladeActorSheet extends ActorSheet {
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()),
dons: duplicate(this.actor.getDons()),
armes: foundry.utils.duplicate(this.actor.getWeapons()),
protections: foundry.utils.duplicate(this.actor.getArmors()),
dons: foundry.utils.duplicate(this.actor.getDons()),
pactes: foundry.utils.duplicate(this.actor.getPactes()),
alignement: this.actor.getAlignement(),
aspect: this.actor.getAspect(),
marge: this.actor.getMarge(),
tendances:duplicate(this.actor.getTendances()),
runes:duplicate(this.actor.getRunes()),
origine: duplicate(this.actor.getOrigine() || {}),
heritage: duplicate(this.actor.getHeritage() || {}),
metier: duplicate(this.actor.getMetier() || {}),
combat: this.actor.getCombatValues(),
equipements: duplicate(this.actor.getEquipments()),
tendances:foundry.utils.duplicate(this.actor.getTendances()),
runes:foundry.utils.duplicate(this.actor.getRunes()),
traitsChaotiques:foundry.utils.duplicate(this.actor.getTraitsChaotiques()),
traitsEspeces: foundry.utils.duplicate(this.actor.getTraitsEspeces()),
origine: foundry.utils.duplicate(this.actor.getOrigine() || {}),
heritage: foundry.utils.duplicate(this.actor.getHeritage() || {}),
metier: foundry.utils.duplicate(this.actor.getMetier() || {}),
combat: this.actor.getCombatValues(),
equipements: foundry.utils.duplicate(this.actor.getEquipments()),
modifiers: foundry.utils.duplicate(this.actor.getModifiers()),
monnaies: foundry.utils.duplicate(this.actor.getMonnaies()),
runeEffects: foundry.utils.duplicate(this.actor.getRuneEffects()),
config: game.system.mournblade.config,
protectionTotal: this.actor.getProtectionTotal(),
santeMalus: this.actor.getStatusMalus(),
ameMalus: this.actor.getAmeMalus(),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.description, {async: true}),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
@@ -64,7 +75,7 @@ export class MournbladeActorSheet extends ActorSheet {
return formData;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
@@ -72,14 +83,14 @@ export class MournbladeActorSheet extends ActorSheet {
// 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");
@@ -94,7 +105,7 @@ export class MournbladeActorSheet extends ActorSheet {
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 );
@@ -125,25 +136,58 @@ export class MournbladeActorSheet extends ActorSheet {
let armeId = li.data("item-id")
this.actor.rollArmeOffensif(armeId)
})
html.find('.roll-assomer').click((event) => {
this.actor.rollAssomer()
})
html.find('.roll-fuir').click((event) => {
this.actor.rollFuir()
})
html.find('.roll-immobiliser').click((event) => {
this.actor.rollImmobiliser()
})
html.find('.roll-arme-special').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeSpecial(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('.sante-modify').click((event) => {
const santeType = $(event.currentTarget).data("type")
const value = $(event.currentTarget).data("value")
this.actor.incDecSante(santeType, value, false)
})
html.find('.ame-modify').click((event) => {
const value = $(event.currentTarget).data("value")
this.actor.incDecAme(value)
})
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);
this.render(true);
});
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
@@ -0,0 +1,25 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { MournbladeActorSheet } from "./mournblade-actor-sheet.js";
import { MournbladeUtility } from "./mournblade-utility.js";
/* -------------------------------------------- */
export class MournbladeCreatureSheet extends MournbladeActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade", "sheet", "actor"],
template: "systems/fvtt-mournblade/templates/creature-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
})
}
}
@@ -0,0 +1,157 @@
import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeEnchantementDialog {
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe")
.replace(/æ/g, "ae")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ")
.trim()
}
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeEnchantementDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeEnchantementDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
static async create(actor, item) {
const normalize = MournbladeEnchantementDialog._normalize.bind(MournbladeEnchantementDialog)
const findComp = (...kw) => MournbladeEnchantementDialog._findCompetence(actor, ...kw)
const ameDisponible = Math.max(0, (actor.system.ame.currentmax - actor.system.ame.value))
const aspect = actor.system.balance.aspect ?? 0
// Skill lookups
const savoirRunesComp = findComp("rune")
const hautParlerComp = findComp("haut", "parler")
const artisanatComp = findComp("savoir", "artisanat")
const claAttr = actor.system.attributs?.clairvoyance
// Prerequisite: Rune de la Loi in inventory
const hasRuneLoi = actor.items.some(i => {
if (i.type !== "rune") return false
return normalize(i.name).includes("loi")
})
const savoirRunesNiveau = savoirRunesComp ? (savoirRunesComp.system.niveau ?? 0) : null
const hautParlerNiveau = hautParlerComp ? (hautParlerComp.system.niveau ?? 0) : null
const artisanatNiveau = artisanatComp ? (artisanatComp.system.niveau ?? 0) : null
const claValeur = claAttr ? (claAttr.value ?? 0) : 0
// Limit: CLA + Savoir:Runes is capped by min(Haut-Parler, Artisanat) if those skills exist
const limiteur = (hautParlerNiveau !== null && artisanatNiveau !== null)
? Math.min(hautParlerNiveau, artisanatNiveau)
: (hautParlerNiveau ?? artisanatNiveau ?? null)
const context = {
actorImg: actor.img,
actorName: actor.name,
itemImg: item.img,
itemName: item.name,
itemType: item.type,
itemId: item.id,
ameDisponible,
aspect,
hasRuneLoi,
savoirRunesNiveau,
hautParlerNiveau,
artisanatNiveau,
claValeur,
limiteur,
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
enchantementActif: item.system.enchantementLoi?.actif ?? false,
enchantementBonus: item.system.enchantementLoi?.bonus ?? 0,
enchantementAntiChaos: item.system.enchantementLoi?.antiChaos ?? false,
}
const prerequisOk = hasRuneLoi
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-enchantement.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeEnchantementDialog._attachListeners(form, ameDisponible, claValeur, savoirRunesNiveau, limiteur, prerequisOk)
})
return foundry.applications.api.DialogV2.wait({
window: { title: `Enchanter : ${item.name}`, icon: "fa-solid fa-star" },
classes: ["mournblade-roll-dialog"],
position: { width: 520 },
modal: false,
content,
buttons: [
{
action: "enchanter",
label: "Enchanter",
icon: "fa-solid fa-star",
default: true,
callback: async (event, button, dialog) => {
const elems = button.form.elements
const ptsAme = parseInt(elems["ptsAme"]?.value ?? 5) || 5
const antiChaos = elems["antiChaos"]?.value === "true"
const modificateur = parseInt(elems["modificateur"]?.value ?? 0) || 0
await MournbladeUtility.rollEnchantement({
actor,
item,
ptsAme,
antiChaos,
modificateur,
savoirRunesComp,
hautParlerComp,
artisanatComp,
claValeur,
limiteur,
})
}
},
],
rejectClose: false,
})
}
static _attachListeners(html, ameDisponible, claValeur, savoirRunesNiveau, limiteur, prerequisOk = true) {
const enchanterBtn = html.querySelector('button[data-action="enchanter"]')
if (enchanterBtn) enchanterBtn.disabled = !prerequisOk
const diffEl = html.querySelector('#enchant-difficulte')
const bonusEl = html.querySelector('#enchant-bonus-preview')
const warnAmeEl = html.querySelector('#enchant-ame-warn')
const warnLimitEl = html.querySelector('#enchant-limit-warn')
const totalEl = html.querySelector('#enchant-total-dice')
const recalculate = () => {
const ptsAme = parseInt(html.querySelector('[name="ptsAme"]')?.value ?? 5) || 0
const difficulte = ptsAme
const bonus = Math.floor(ptsAme / 5)
const savoir = savoirRunesNiveau ?? 0
const basePool = claValeur + savoir
const effectivePool = limiteur !== null ? Math.min(basePool, limiteur) : basePool
if (diffEl) diffEl.textContent = difficulte
if (bonusEl) bonusEl.textContent = `+${bonus}`
if (totalEl) totalEl.textContent = effectivePool
if (warnAmeEl) warnAmeEl.style.display = ptsAme > ameDisponible ? "" : "none"
if (warnLimitEl && limiteur !== null)
warnLimitEl.style.display = basePool > limiteur ? "" : "none"
}
const ptsAmeEl = html.querySelector('[name="ptsAme"]')
if (ptsAmeEl) {
ptsAmeEl.addEventListener('input', recalculate)
ptsAmeEl.addEventListener('change', recalculate)
}
recalculate()
}
}
@@ -0,0 +1,174 @@
import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeInvocationDemonDialog {
/**
* Normalize a string for fuzzy matching: lowercase, remove diacritics,
* replace œ/æ ligatures, collapse whitespace and punctuation.
*/
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe")
.replace(/æ/g, "ae")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "") // strip combining diacritics
.replace(/[^a-z0-9]+/g, " ") // replace any non-alnum with space
.trim()
}
/**
* Returns the first competence item whose name contains all of the given keywords.
* Keywords are normalized before comparison.
*/
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationDemonDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeInvocationDemonDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
/**
* Returns true if the actor has a capacite or don whose name contains all given keywords.
*/
static _hasCapacite(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationDemonDialog._normalize(k))
return actor.items.some(item => {
if (item.type !== "capacite" && item.type !== "don") return false
const norm = MournbladeInvocationDemonDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
})
}
static async create(actor, rollData) {
const ameDisponible = Math.max(0, (actor.system.ame.currentmax - actor.system.ame.value))
// Robust skill detection: partial keyword matching, diacritic-insensitive
const coercitionComp = MournbladeInvocationDemonDialog._findCompetence(actor, "coercition")
const hautParlerComp = MournbladeInvocationDemonDialog._findCompetence(actor, "haut", "parler")
const loiChaosComp = MournbladeInvocationDemonDialog._findCompetence(actor, "loi", "chaos")
// Check prerequisites — robust capacite/rune detection
const isChaotique = actor.system.balance.chaos > actor.system.balance.loi
const hasOeilSorcier = MournbladeInvocationDemonDialog._hasCapacite(actor, "oeil", "sorcier")
const hasRuneChaos = actor.items.some(i => {
if (i.type !== "rune") return false
return MournbladeInvocationDemonDialog._normalize(i.name).includes("chaos")
})
const prerequisOk = isChaotique && hasOeilSorcier && hasRuneChaos
// Auto-detect chaos link bonuses
const aspectGe8 = (actor.system.balance.aspect ?? 0) >= 8
// hasPacte: true if actor has a Pacte item whose associated deity matches a demon
// (simplified: just expose it as a selectable option — can't auto-detect)
const hasPacte = false
const context = {
...rollData,
img: actor.img,
name: actor.name,
ameDisponible,
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
coercitionNiveau: coercitionComp ? coercitionComp.system.niveau : null,
hautParlerNiveau: hautParlerComp ? hautParlerComp.system.niveau : null,
loiChaosNiveau: loiChaosComp ? loiChaosComp.system.niveau : null,
isChaotique,
hasOeilSorcier,
hasRuneChaos,
prerequisOk,
aspectGe8,
hasPacte,
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-invocation-demon.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeInvocationDemonDialog._attachListeners(form, ameDisponible, prerequisOk)
})
return foundry.applications.api.DialogV2.wait({
window: { title: "Invoquer un Démon", icon: "fa-solid fa-skull" },
classes: ["mournblade-roll-dialog"],
position: { width: 560 },
modal: false,
content,
buttons: [
{
action: "invoquer",
label: "Invoquer",
icon: "fa-solid fa-skull",
default: true,
callback: async (event, button, dialog) => {
MournbladeInvocationDemonDialog._updateRollData(rollData, button.form.elements, actor, {
coercitionComp, hautParlerComp, loiChaosComp
})
await MournbladeUtility.rollInvocationDemon(rollData)
}
},
],
rejectClose: false,
})
}
static _calculateSeuil(html) {
const get = (name) => parseInt(html.querySelector(`[name="${name}"]`)?.value ?? 0) || 0
const seuil = get("seuil_nature")
+ get("seuil_traits")
+ get("seuil_augmentation")
+ get("seuil_service")
+ get("seuil_duree")
+ get("seuil_marche")
+ get("seuil_chaos")
+ get("seuil_sacrifice")
return Math.max(1, seuil)
}
static _attachListeners(html, ameDisponible, prerequisOk = true) {
const invoquerBtn = html.querySelector('button[data-action="invoquer"]')
if (invoquerBtn) invoquerBtn.disabled = !prerequisOk
const coutEl = html.querySelector('#invoc-demon-cout')
const totalEl = html.querySelector('#invoc-demon-seuil-total')
const hiddenEl = html.querySelector('#invoc-demon-seuil-hidden')
const warnEl = html.querySelector('#invoc-demon-ame-warn')
const criteriaNames = [
"seuil_nature", "seuil_traits", "seuil_augmentation",
"seuil_service", "seuil_duree", "seuil_marche",
"seuil_chaos", "seuil_sacrifice"
]
const recalculate = () => {
const seuil = MournbladeInvocationDemonDialog._calculateSeuil(html)
if (totalEl) totalEl.textContent = seuil
if (hiddenEl) hiddenEl.value = seuil
if (coutEl) coutEl.textContent = seuil
if (warnEl) warnEl.style.display = seuil > ameDisponible ? "" : "none"
}
for (const name of criteriaNames) {
const el = html.querySelector(`[name="${name}"]`)
if (el) el.addEventListener('change', recalculate)
if (el && el.type === 'number') el.addEventListener('input', recalculate)
}
recalculate()
}
static _updateRollData(rollData, formElements, actor, { coercitionComp }) {
const seuil = parseInt(formElements['seuil']?.value ?? 5)
const modificateur = parseInt(formElements['modificateur']?.value ?? 0)
rollData.invocationSeuil = seuil
rollData.invocationSoulCost = seuil
rollData.difficulte = seuil
rollData.modificateur = modificateur
rollData.competence = coercitionComp ?? null
}
}
@@ -0,0 +1,154 @@
import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeInvocationDialog {
/**
* Normalize a string for fuzzy matching: lowercase, remove diacritics,
* replace œ/æ ligatures, collapse whitespace and punctuation.
*/
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe")
.replace(/æ/g, "ae")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ")
.trim()
}
/**
* Returns the first competence item whose name contains all of the given keywords.
*/
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeInvocationDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
static async create(actor, rollData) {
const ameDisponible = Math.max(0, (actor.system.ame.currentmax - actor.system.ame.value))
const maxExtra = Math.max(0, ameDisponible - 15)
// Detect elemental pacte bonus — the bonus is always available,
// but we display it dynamically based on chosen element.
// We pass the pactes to let JS listeners detect the match.
const pactes = actor.getPactes().map(p => ({
name: p.name,
allegeance: (p.system.allegeance || "").toLowerCase()
}))
// Robust skill detection: partial keyword matching, diacritic-insensitive
const hautParlerComp = MournbladeInvocationDialog._findCompetence(actor, "haut", "parler")
const seigneursElemComp = MournbladeInvocationDialog._findCompetence(actor, "seigneurs", "elementaires")
const context = {
...rollData,
img: actor.img,
name: actor.name,
ameDisponible,
ameExtraOptions: Array.from({ length: maxExtra + 1 }, (_, i) => i),
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
hautParlerNiveau: hautParlerComp ? hautParlerComp.system.niveau : null,
seigneursElemNiveau: seigneursElemComp ? seigneursElemComp.system.niveau : null,
bonusPacte: false,
pactes,
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-invocation-elementaire.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeInvocationDialog._attachListeners(form, pactes)
})
return foundry.applications.api.DialogV2.wait({
window: { title: "Invoquer un Élémentaire", icon: "fa-solid fa-wind" },
classes: ["mournblade-roll-dialog"],
position: { width: 480 },
modal: false,
content,
buttons: [
{
action: "invoquer",
label: "Invoquer",
icon: "fa-solid fa-wind",
default: true,
callback: async (event, button, dialog) => {
MournbladeInvocationDialog._updateRollData(rollData, button.form.elements, actor, pactes)
await MournbladeUtility.rollInvocationElementaire(rollData)
}
},
],
rejectClose: false,
})
}
static _elementKeywords = {
air: ["air", "vent", "sylphe", "seigneur de l'air"],
terre: ["terre", "gnome", "seigneur de la terre"],
feu: ["feu", "flamme", "salamandre", "seigneur du feu"],
eau: ["eau", "ondine", "seigneur de l'eau"],
}
static _hasPacteBonus(element, pactes) {
const keywords = MournbladeInvocationDialog._elementKeywords[element] || []
return pactes.some(p => keywords.some(kw => p.allegeance.includes(kw)))
}
static _attachListeners(html, pactes) {
const tierSeuils = { mineur: 15, median: 20, majeur: 25 }
const tierTemps = { mineur: "1 tour", median: "1 minute", majeur: "1 heure" }
const recalculate = () => {
const element = html.querySelector('[name="element"]')?.value ?? "air"
const tier = html.querySelector('[name="tier"]')?.value ?? "mineur"
const ameExtra = parseInt(html.querySelector('[name="ameExtra"]')?.value ?? 0)
const seuilBase = tierSeuils[tier] ?? 15
const seuil = seuilBase + ameExtra
const hasPacte = MournbladeInvocationDialog._hasPacteBonus(element, pactes)
const seuilEl = html.querySelector('#invoc-seuil')
const coutEl = html.querySelector('#invoc-cout')
const tempsEl = html.querySelector('#invoc-temps')
const pacteBanner = html.querySelector('.invoc-bonus-pacte')
if (seuilEl) seuilEl.textContent = seuil + (hasPacte ? " (-5 avec bonus Pacte)" : "")
if (coutEl) coutEl.textContent = seuil
if (tempsEl) tempsEl.textContent = tierTemps[tier] ?? "1 tour"
if (pacteBanner) pacteBanner.style.display = hasPacte ? "" : "none"
}
html.querySelectorAll('[name="element"], [name="tier"], [name="ameExtra"]').forEach(el => {
el.addEventListener('change', recalculate)
})
recalculate()
}
static _updateRollData(rollData, formElements, actor, pactes) {
const element = formElements['element']?.value ?? "air"
const tier = formElements['tier']?.value ?? "mineur"
const ameExtra = parseInt(formElements['ameExtra']?.value ?? 0)
const modificateur = parseInt(formElements['modificateur']?.value ?? 0)
const tierSeuils = { mineur: 15, median: 20, majeur: 25 }
const seuilBase = tierSeuils[tier] ?? 15
const seuil = seuilBase + ameExtra
rollData.invocationElement = element
rollData.invocationTier = tier
rollData.invocationSeuil = seuil
rollData.invocationAmeExtra = ameExtra
rollData.invocationSoulCost = seuil
rollData.difficulte = seuil
rollData.modificateur = modificateur
rollData.bonusPacte = MournbladeInvocationDialog._hasPacteBonus(element, pactes) ? 5 : 0
}
}
@@ -0,0 +1,138 @@
import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeInvocationEspritDialog {
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe").replace(/æ/g, "ae")
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ").trim()
}
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationEspritDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeInvocationEspritDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
/** Seuil and soul cost per puissance */
static SEUILS = { mineur: 15, median: 20, majeur: 25 }
static async create(actor, rollData) {
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
const persuasionComp = MournbladeInvocationEspritDialog._findCompetence(actor, "persuasion")
const hautParlerComp = MournbladeInvocationEspritDialog._findCompetence(actor, "haut", "parler")
const loiChaosComp = MournbladeInvocationEspritDialog._findCompetence(actor, "loi", "chaos")
const isLoyal = actor.system.balance.loi > actor.system.balance.chaos
const hasRuneLoi = actor.items.some(i =>
i.type === "rune" && MournbladeInvocationEspritDialog._normalize(i.name).includes("loi")
)
const prerequisOk = isLoyal && hasRuneLoi
const automatonTypes = {
combat: "Combat",
voyage: "Voyage",
perception: "Perception",
restauration:"Restauration",
reparateur: "Réparateur",
}
const context = {
...rollData,
img: actor.img,
name: actor.name,
ameDisponible,
treValeur: actor.system.attributs?.tre?.value ?? 0,
persuasionNiveau: persuasionComp?.system?.niveau ?? null,
hautParlerNiveau: hautParlerComp?.system?.niveau ?? null,
loiChaosNiveau: loiChaosComp?.system?.niveau ?? null,
isLoyal,
hasRuneLoi,
prerequisOk,
automatonTypes,
seuils: MournbladeInvocationEspritDialog.SEUILS,
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-invocation-esprit.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeInvocationEspritDialog._attachListeners(form, ameDisponible, prerequisOk)
})
return foundry.applications.api.DialogV2.wait({
window: { title: "Invoquer un Esprit de la Loi", icon: "fa-solid fa-star" },
classes: ["mournblade-roll-dialog"],
position: { width: 520 },
modal: false,
content,
buttons: [
{
action: "invoquer",
label: "Invoquer",
icon: "fa-solid fa-star",
default: true,
callback: async (event, button, dialog) => {
MournbladeInvocationEspritDialog._updateRollData(rollData, button.form.elements, actor, {
persuasionComp, hautParlerComp, loiChaosComp
})
await MournbladeUtility.rollInvocationEsprit(rollData)
}
},
],
rejectClose: false,
})
}
static _attachListeners(html, ameDisponible, prerequisOk = true) {
const invoquerBtn = html.querySelector('button[data-action="invoquer"]')
if (invoquerBtn) invoquerBtn.disabled = !prerequisOk
const puissanceEl = html.querySelector('[name="puissance"]')
const seuilEl = html.querySelector('#esprit-seuil-total')
const coutEl = html.querySelector('#esprit-cout-ame')
const hiddenEl = html.querySelector('#esprit-seuil-hidden')
const warnEl = html.querySelector('#esprit-ame-warn')
const dureeEl = html.querySelector('#esprit-duree')
const DUREE = { mineur: "1 heure", median: "1 jour", majeur: "1 semaine" }
const recalculate = () => {
const puissance = puissanceEl?.value ?? "mineur"
const seuil = MournbladeInvocationEspritDialog.SEUILS[puissance] ?? 15
if (seuilEl) seuilEl.textContent = seuil
if (coutEl) coutEl.textContent = seuil
if (hiddenEl) hiddenEl.value = seuil
if (dureeEl) dureeEl.textContent = DUREE[puissance] ?? "1 heure"
if (warnEl) warnEl.style.display = seuil > ameDisponible ? "" : "none"
}
if (puissanceEl) puissanceEl.addEventListener("change", recalculate)
recalculate()
}
static _updateRollData(rollData, formElements, actor, { persuasionComp }) {
const seuil = parseInt(formElements["seuil"]?.value ?? 15)
const modificateur = parseInt(formElements["modificateur"]?.value ?? 0)
const automatonType = formElements["automatonType"]?.value ?? "combat"
const puissance = formElements["puissance"]?.value ?? "mineur"
rollData.invocationSeuil = seuil
rollData.invocationSoulCost = seuil
rollData.difficulte = seuil
rollData.modificateur = modificateur
rollData.competence = persuasionComp ?? null
rollData.automatonType = automatonType
rollData.automatonPuissance = puissance
}
}
@@ -0,0 +1,402 @@
import { MournbladeUtility } from "../mournblade-utility.js"
/**
* Dialogue de jet de dé pour Mournblade - Version DialogV2
*/
export class MournbladeRollDialog {
/**
* Create and display the roll dialog
* @param {MournbladeActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise<MournbladeRollDialog>}
*/
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.mournblade.config,
}
// Si attrKey est "tochoose", préparer la liste des attributs sélectionnables
if (rollData.attrKey === "tochoose") {
context.selectableAttributes = actor.system.attributs
}
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/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: ["mournblade-roll-dialog"],
position: { width: 500 },
modal: false,
content,
buttons: [
{
action: "rolld10",
label: "Lancer 1d10",
icon: "fa-solid fa-dice-d10",
default: true,
callback: async (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
rollData.mainDice = "1d10"
await MournbladeUtility.rollMournblade(rollData)
}
},
{
action: "rolld20",
label: "Lancer 1d20",
icon: "fa-solid fa-dice-d20",
callback: async (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
rollData.mainDice = "1d20"
await MournbladeUtility.rollMournblade(rollData)
}
},
],
rejectClose: false,
})
}
/**
* Create and display the sortilège (multi-rune spell) roll dialog
* @param {MournbladeActor} actor
* @param {Object} rollData
*/
static async createSortilege(actor, rollData) {
const ameDisponible = Math.max(1, (actor.system.ame.currentmax - actor.system.ame.value))
const context = {
...rollData,
img: actor.img,
name: actor.name,
config: game.system.mournblade.config,
runes: actor.getRunes(),
runemode: "prononcer",
ameDisponible,
ameOptions: Array.from({ length: ameDisponible }, (_, i) => i + 1),
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-sortilege.hbs",
context
)
// Attach dynamic recalculation after render
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeRollDialog._attachSortilegeListeners(form)
})
return foundry.applications.api.DialogV2.wait({
window: { title: "Lancer un Sortilège", icon: "fa-solid fa-star-of-david" },
classes: ["mournblade-roll-dialog"],
position: { width: 520 },
modal: false,
content,
buttons: [
{
action: "rolld10",
label: "Lancer 1d10",
icon: "fa-solid fa-dice-d10",
default: true,
callback: async (event, button, dialog) => {
this._updateSortilegeRollData(rollData, button.form.elements, actor)
rollData.mainDice = "1d10"
await MournbladeUtility.rollSortilege(rollData)
}
},
{
action: "rolld20",
label: "Lancer 1d20",
icon: "fa-solid fa-dice-d20",
callback: async (event, button, dialog) => {
this._updateSortilegeRollData(rollData, button.form.elements, actor)
rollData.mainDice = "1d20"
await MournbladeUtility.rollSortilege(rollData)
}
},
],
rejectClose: false,
})
}
/**
* Open the potion preparation dialog
* @param {MournbladeActor} actor
* @param {object} rollData
*/
static async createPotion(actor, rollData) {
const ameDisponible = Math.max(1, (actor.system.ame.currentmax - actor.system.ame.value))
const context = {
...rollData,
img: actor.img,
name: actor.name,
config: game.system.mournblade.config,
runes: actor.getRunes(),
ameDisponible,
ameOptions: Array.from({ length: ameDisponible }, (_, i) => i + 1),
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-potion.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeRollDialog._attachPotionListeners(form)
})
return foundry.applications.api.DialogV2.wait({
window: { title: "Préparer une Potion", icon: "fa-solid fa-flask" },
classes: ["mournblade-roll-dialog"],
position: { width: 520 },
modal: false,
content,
buttons: [
{
action: "preparer",
label: "Préparer",
icon: "fa-solid fa-flask",
default: true,
callback: async (event, button, dialog) => {
MournbladeRollDialog._updatePotionRollData(rollData, button.form.elements, actor)
await MournbladeUtility.rollPotion(rollData)
}
},
],
rejectClose: false,
})
}
/**
* Attach dynamic recalculation listeners to the potion dialog
* @param {HTMLElement} html
*/
static _attachPotionListeners(html) {
const recalculate = () => {
const radioChecked = html.querySelector('.potion-rune-radio:checked')
const runeRow = radioChecked?.closest('[data-seuil]')
const seuil = parseInt(runeRow?.dataset.seuil ?? 0)
const pa = parseInt(html.querySelector('[name="pointsAme"]')?.value ?? 1)
const mod = parseInt(html.querySelector('[name="modificateur"]')?.value ?? 0)
const difficulteEl = html.querySelector('#potion-difficulte')
if (difficulteEl) difficulteEl.textContent = (seuil + pa) + (mod !== 0 ? ` (mod ${mod > 0 ? '+' : ''}${mod})` : '')
const tempsEl = html.querySelector('#potion-temps')
if (tempsEl) {
const heures = Math.max(1, Math.ceil(pa / 3))
tempsEl.textContent = heures === 1 ? '1 heure' : `${heures} heures`
}
}
html.querySelectorAll('.potion-rune-radio, [name="pointsAme"]').forEach(el => {
el.addEventListener('change', recalculate)
})
recalculate()
}
/**
* Update rollData from the potion dialog form elements
* @param {object} rollData
* @param {HTMLFormControlsCollection} formElements
* @param {MournbladeActor} actor
*/
static _updatePotionRollData(rollData, formElements, actor) {
const runeId = formElements['rune-selected']?.value
if (runeId) {
const rune = actor.getRunes().find(r => r._id === runeId)
if (rune) {
rollData.runeId = runeId
rollData.runeName = rune.name
rollData.runeImg = rune.img
rollData.runeSeuil = rune.system.seuil ?? 0
rollData.runeHautParler = rune.system.hautParler ?? ""
}
}
rollData.pointsAme = parseInt(formElements['pointsAme']?.value ?? 1)
rollData.forme = formElements['forme']?.value ?? "liquide"
rollData.modificateur = parseInt(formElements['modificateur']?.value ?? 0)
}
/**
* Attach dynamic recalculation listeners to the sortilège dialog
* @param {HTMLElement} html
*/
static _attachSortilegeListeners(html) {
const recalculate = () => {
const checkboxes = html.querySelectorAll('.sortilege-rune-checkbox:checked')
const modeSelect = html.querySelector('[name="runemode"]')
const mode = modeSelect?.value ?? "prononcer"
let maxSeuil = 0
let totalAme = 0
let totalActions = 0
let count = 0
// Enable/disable points inputs based on checkbox state
html.querySelectorAll('.sortilege-rune-checkbox').forEach(cb => {
const row = cb.closest('.sortilege-rune-row')
const pointsInput = row?.querySelector('.sortilege-rune-points')
if (pointsInput) pointsInput.disabled = !cb.checked
})
checkboxes.forEach(cb => {
const row = cb.closest('.sortilege-rune-row')
const seuil = Number(row?.dataset.seuil ?? 0)
const pts = Number(row?.querySelector('.sortilege-rune-points')?.value ?? 1)
if (seuil > maxSeuil) maxSeuil = seuil
totalAme += pts
totalActions += Math.ceil(pts / 3) * (mode === "inscrire" ? 2 : 1)
count++
})
const difficulte = count > 0 ? maxSeuil + (count - 1) : 0
html.querySelector('#sortilege-difficulte').textContent = count > 0 ? difficulte : '—'
html.querySelector('#sortilege-total-ame').textContent = totalAme
html.querySelector('#sortilege-actions').textContent = totalActions
}
html.querySelectorAll('.sortilege-rune-checkbox, .sortilege-rune-points, [name="runemode"]')
.forEach(el => el.addEventListener('change', recalculate))
}
/**
* Extract sortilège data from the form
* @param {Object} rollData
* @param {HTMLFormControlsCollection} formElements
* @param {MournbladeActor} actor
*/
static _updateSortilegeRollData(rollData, formElements, actor) {
rollData.runemode = formElements.runemode?.value ?? "prononcer"
rollData.modificateur = Number(formElements.modificateur?.value ?? 0)
rollData.runeautocible = formElements.runeautocible?.checked ?? false
// Collect selected runes with their soul points
const runes = actor.getRunes()
rollData.sortilegeRunes = []
for (const rune of runes) {
const checkbox = formElements[`rune-selected-${rune._id}`]
if (checkbox?.checked) {
const pts = Math.max(1, Number(formElements[`rune-points-${rune._id}`]?.value ?? 1))
rollData.sortilegeRunes.push({
id: rune._id,
name: rune.name,
img: rune.img,
seuil: rune.system.seuil,
formule: rune.system.formule,
pts,
})
}
}
if (rollData.sortilegeRunes.length === 0) return
const maxSeuil = Math.max(...rollData.sortilegeRunes.map(r => r.seuil))
rollData.difficulte = maxSeuil + (rollData.sortilegeRunes.length - 1)
rollData.runeame = rollData.sortilegeRunes.reduce((s, r) => s + r.pts, 0)
}
/**
* @param {Object} rollData - L'objet rollData à mettre à jour
* @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire
* @param {MournbladeActor} 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])
rollData.actionImg = "systems/fvtt-mournblade/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp"
}
}
// Modificateurs de base
if (formElements.difficulte) {
rollData.difficulte = Number(formElements.difficulte.value)
}
if (formElements.modificateur) {
rollData.modificateur = Number(formElements.modificateur.value)
}
// Runes
if (formElements.runemode) {
rollData.runemode = String(formElements.runemode.value)
}
if (formElements.runeame) {
rollData.runeame = Number(formElements.runeame.value)
}
if (formElements.runeautocible !== undefined) {
rollData.runeautocible = formElements.runeautocible.checked
}
// Combat mêlée
if (formElements.typeAttaque) {
rollData.typeAttaque = String(formElements.typeAttaque.value)
}
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)
}
// Désavantages
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
}
// Modifiers
if (rollData.modifiers) {
rollData.modifiers.forEach((modifier, idx) => {
const checkbox = formElements[`apply-modifier-${idx}`]
if (checkbox) {
modifier.system.apply = checkbox.checked
}
})
}
}
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Index des applications AppV2 pour Mournblade
* Ce fichier centralise tous les exports des applications
*/
// Applications de feuilles d'acteurs
export { default as MournbladePersonnageSheet } from './mournblade-personnage-sheet.mjs';
export { default as MournbladeCreatureSheet } from './mournblade-creature-sheet.mjs';
export { default as MournbladePnjSheet } from './mournblade-pnj-sheet.mjs';
// Applications de feuilles d'items
export { default as MournbladeArmeSheet } from './mournblade-arme-sheet.mjs';
export { default as MournbladeBouclierSheet } from './mournblade-bouclier-sheet.mjs';
export { default as MournbladeCapaciteSheet } from './mournblade-capacite-sheet.mjs';
export { default as MournbladeCompetenceSheet } from './mournblade-competence-sheet.mjs';
export { default as MournbladeDonSheet } from './mournblade-don-sheet.mjs';
export { default as MournbladeEquipementSheet } from './mournblade-equipement-sheet.mjs';
export { default as MournbladeHeritageSheet } from './mournblade-heritage-sheet.mjs';
export { default as MournbladeMetierSheet } from './mournblade-metier-sheet.mjs';
export { default as MournbladeModifierSheet } from './mournblade-modifier-sheet.mjs';
export { default as MournbladeMonnaieSheet } from './mournblade-monnaie-sheet.mjs';
export { default as MournbladeOrigineSheet } from './mournblade-origine-sheet.mjs';
export { default as MournbladePacteSheet } from './mournblade-pacte-sheet.mjs';
export { default as MournbladeProtectionSheet } from './mournblade-protection-sheet.mjs';
export { default as MournbladeRuneSheet } from './mournblade-rune-sheet.mjs';
export { default as MournbladeRuneEffectSheet } from './mournblade-runeeffect-sheet.mjs';
export { default as MournbladePotionSheet } from './mournblade-potion-sheet.mjs';
export { default as MournbladeTendanceSheet } from './mournblade-tendance-sheet.mjs';
export { default as MournbladeTraitChaotiqueSheet } from './mournblade-traitchaotique-sheet.mjs';
export { default as MournbladeTraitEspeceSheet } from './mournblade-traitespece-sheet.mjs';
@@ -0,0 +1,522 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { MournbladeUtility } from "../../mournblade-utility.js"
export default class MournbladeActorSheet 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 // Commencer en mode visualisation
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-mournblade", "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: MournbladeActorSheet.#onEditImage,
toggleSheet: MournbladeActorSheet.#onToggleSheet,
editItem: MournbladeActorSheet.#onEditItem,
deleteItem: MournbladeActorSheet.#onDeleteItem,
createItem: MournbladeActorSheet.#onCreateItem,
postItem: MournbladeActorSheet.#onPostItem,
equipItem: MournbladeActorSheet.#onEquipItem,
modifyQuantity: MournbladeActorSheet.#onModifyQuantity,
modifySante: MournbladeActorSheet.#onModifySante,
modifyAme: MournbladeActorSheet.#onModifyAme,
rollAttribut: MournbladeActorSheet.#onRollAttribut,
rollCompetence: MournbladeActorSheet.#onRollCompetence,
rollRune: MournbladeActorSheet.#onRollRune,
rollSortilege: MournbladeActorSheet.#onRollSortilege,
preparePotion: MournbladeActorSheet.#onPreparePotion,
invoquerElementaire: MournbladeActorSheet.#onInvoquerElementaire,
bannirElementaire: MournbladeActorSheet.#onBannirElementaire,
invoquerDemon: MournbladeActorSheet.#onInvoquerDemon,
libererDemon: MournbladeActorSheet.#onLibererDemon,
invoquerEsprit: MournbladeActorSheet.#onInvoquerEsprit,
enchanter: MournbladeActorSheet.#onEnchanter,
rollArmeOffensif: MournbladeActorSheet.#onRollArmeOffensif,
rollArmeSpecial: MournbladeActorSheet.#onRollArmeSpecial,
rollArmeDegats: MournbladeActorSheet.#onRollArmeDegats,
rollAssommer: MournbladeActorSheet.#onRollAssommer,
rollImmobiliser: MournbladeActorSheet.#onRollImmobiliser,
rollFuir: MournbladeActorSheet.#onRollFuir,
},
}
/**
* 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.mournblade.config,
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true }),
}
return context
}
/** @override */
_preSyncPartState(partId, newElement, priorElement, state) {
super._preSyncPartState(partId, newElement, priorElement, state)
// Save scrollable tab positions for deferred restoration in _onRender.
// Tabs are hidden (display:none) at _syncPartState time, so scrollTop
// assignments have no effect. We re-apply them after making tabs visible.
const part = this.constructor.PARTS?.[partId]
if (part?.scrollable) {
this._pendingScrollRestores = part.scrollable.map(selector => {
const el = selector ? priorElement.querySelector(selector) : priorElement
return el ? { selector, scrollTop: el.scrollTop } : null
}).filter(Boolean)
}
}
/** @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
const activeTab = this.tabGroups[group] || "stats"
const switchTab = (tab) => {
this.tabGroups[group] = tab
nav.querySelectorAll('[data-tab]').forEach(link => {
link.classList.toggle('active', link.dataset.tab === tab)
})
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === tab)
})
}
// Set initial state (makes active tab visible)
switchTab(activeTab)
// Restore scroll positions now that the active tab is visible
if (this._pendingScrollRestores?.length) {
for (const { selector, scrollTop } of this._pendingScrollRestores) {
const el = selector ? this.element.querySelector(selector) : this.element
if (el) el.scrollTop = scrollTop
}
this._pendingScrollRestores = null
}
// Tab clicks: DOM-only, no re-render (preserves scroll positions)
nav.querySelectorAll('[data-tab]').forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault()
switchTab(link.dataset.tab)
})
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
*/
#createDragDropHandlers() {
return []
}
// #region Actions
/** @override */
static ACTIONS = {
editImage: MournbladeActorSheet.#onEditImage,
toggleSheet: MournbladeActorSheet.#onToggleSheet,
editItem: MournbladeActorSheet.#onEditItem,
deleteItem: MournbladeActorSheet.#onDeleteItem,
createItem: MournbladeActorSheet.#onCreateItem,
equipItem: MournbladeActorSheet.#onEquipItem,
modifyQuantity: MournbladeActorSheet.#onModifyQuantity,
rollAttribut: MournbladeActorSheet.#onRollAttribut,
rollCompetence: MournbladeActorSheet.#onRollCompetence,
rollArmeOffensif: MournbladeActorSheet.#onRollArmeOffensif,
rollArmeDegats: MournbladeActorSheet.#onRollArmeDegats,
rollAssommer: MournbladeActorSheet.#onRollAssommer,
rollImmobiliser: MournbladeActorSheet.#onRollImmobiliser,
rollFuir: MournbladeActorSheet.#onRollFuir,
}
/**
* 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 MournbladeUtility.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 posting an item to chat
*/
static async #onPostItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId ?? target.dataset.itemId
if (!itemId) return
MournbladeUtility.postItemToChat(this.actor.id, itemId)
}
/**
* 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
const item = this.actor.items.get(itemId)
if (item) {
await item.update({ "system.equipped": !item.system.equipped })
}
}
/**
* Handle modifying the quantity of an item
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onModifyQuantity(event, target) {
const li = target.closest('[data-item-id]')
const itemId = li?.dataset.itemId
const value = Number.parseInt(target.dataset.quantiteValue)
const item = this.document.items.get(itemId)
if (item) {
const newQuantity = Math.max(0, (item.system.quantite || 0) + value)
await item.update({ "system.quantite": newQuantity })
}
}
/**
* Handle modifying santé (health) values
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onModifySante(event, target) {
const type = target.dataset.type
const value = Number.parseInt(target.dataset.value)
const actor = this.document
const currentValue = actor.system.sante[type] || 0
const newValue = Math.max(0, currentValue + value)
await actor.update({ [`system.sante.${type}`]: newValue })
}
/**
* Handle modifying âme (soul) value
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The target element
*/
static async #onModifyAme(event, target) {
const value = Number.parseInt(target.dataset.value)
const actor = this.document
const currentValue = actor.system.ame.value || 0
const newValue = Math.max(0, currentValue + value)
await actor.update({ "system.ame.value": newValue })
}
/**
* Handle rolling an attribut
* @param {Event} event - The triggering event
*/
static async #onRollAttribut(event, target) {
event.preventDefault()
const sheet = this
const attrKey = target.dataset.attrKey
const actor = sheet.document
await actor.rollAttribut(attrKey)
}
/**
* Handle rolling a competence
* @param {Event} event - The triggering event
*/
static async #onRollCompetence(event, target) {
event.preventDefault()
const sheet = this
const attrKey = target.dataset.attrKey
const li = target.closest('[data-item-id]')
const compId = li?.dataset.itemId
const actor = sheet.document
await actor.rollCompetence(attrKey, compId)
}
/**
* Handle rolling a rune
* @param {Event} event - The triggering event
*/
static async #onRollRune(event, target) {
event.preventDefault()
const sheet = this
const li = target.closest('[data-item-id]')
const runeId = li?.dataset.itemId
const actor = sheet.document
await actor.rollRune(runeId)
}
/**
* Handle launching a sortilège (multi-rune spell)
*/
static async #onRollSortilege(event, target) {
event.preventDefault()
await this.document.rollSortilege()
}
/**
* Handle preparing a potion
*/
static async #onPreparePotion(event, target) {
event.preventDefault()
await this.document.preparePotion()
}
/**
* Handle invoking an elemental
*/
static async #onInvoquerElementaire(event, target) {
event.preventDefault()
await this.document.invoquerElementaire()
}
/**
* Handle banishing an elemental invocation
*/
static async #onBannirElementaire(event, target) {
event.preventDefault()
const invocIndex = parseInt(target.dataset.invocIndex ?? "0")
await this.document.bannirElementaire(invocIndex)
}
/**
* Handle invoking a demon
*/
static async #onInvoquerDemon(event, target) {
event.preventDefault()
await this.document.invoquerDemon()
}
/**
* Handle releasing a demon invocation
*/
static async #onLibererDemon(event, target) {
event.preventDefault()
const invocIndex = parseInt(target.dataset.invocIndex ?? "0")
await this.document.libererDemon(invocIndex)
}
/**
* Handle invoking a Law Spirit into an Automaton
*/
static async #onInvoquerEsprit(event, target) {
event.preventDefault()
await this.document.invoquerEspritLoi()
}
/**
* Handle enchanting an item with Loi power
*/
static async #onEnchanter(event, target) {
event.preventDefault()
const itemId = target.dataset.itemId
await this.document.enchanter(itemId)
}
/**
* Handle rolling an arme offensif
* @param {Event} event - The triggering event
*/
static async #onRollArmeOffensif(event, target) {
event.preventDefault()
const sheet = this
const armeId = target.dataset.armeId
const actor = sheet.document
await actor.rollArmeOffensif(armeId)
}
/**
* Handle rolling an arme degats
* @param {Event} event - The triggering event
*/
static async #onRollArmeDegats(event, target) {
event.preventDefault()
const sheet = this
const armeId = target.dataset.armeId
const actor = sheet.document
await actor.rollArmeDegats(armeId)
}
/**
* Handle rolling an arme special
* @param {Event} event - The triggering event
*/
static async #onRollArmeSpecial(event, target) {
event.preventDefault()
const sheet = this
const armeId = target.dataset.armeId
const actor = sheet.document
await actor.rollArmeSpecial(armeId)
}
/**
* Handle rolling an assommer
* @param {Event} event - The triggering event
*/
static async #onRollAssommer(event, target) {
event.preventDefault()
const sheet = this
const actor = sheet.document
await actor.rollAssomer()
}
/**
* Handle rolling an immobiliser
* @param {Event} event - The triggering event
*/
static async #onRollImmobiliser(event, target) {
event.preventDefault()
const sheet = this
const actor = sheet.document
await actor.rollImmobiliser()
}
/**
* Handle rolling a fuir
* @param {Event} event - The triggering event
*/
static async #onRollFuir(event, target) {
event.preventDefault()
const sheet = this
const actor = sheet.document
await actor.rollFuir()
}
}
@@ -0,0 +1,200 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class MournbladeItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-mournblade", "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: MournbladeItemSheet.#onEditImage,
postItem: MournbladeItemSheet.#onPostItem,
enchanter: MournbladeItemSheet.#onEnchanter,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const item = this.document
const enchantableTypes = ["arme", "equipement", "protection", "bouclier"]
const actorIsLoyal = item.actor?.getAlignement?.() === "loyal"
const alreadyEnchanted = enchantableTypes.includes(item.type) && (item.system.enchantementLoi?.actif ?? false)
const canEnchant = enchantableTypes.includes(item.type) && !!item.actor && actorIsLoyal && !alreadyEnchanted
const context = {
fields: item.schema.fields,
systemFields: item.system.schema.fields,
item,
system: item.system,
source: item.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.system.description, { async: true }),
isEditMode: true,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.mournblade.config,
canEnchant,
enchantementActif: alreadyEnchanted,
}
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 enchanting this item with Loi power
*/
static async #onEnchanter(event) {
event.preventDefault()
const item = this.document
if (!item.actor) {
ui.notifications.warn("Cet objet doit être sur un personnage pour être enchanté.")
return
}
await item.actor.enchanter(item.id)
}
/**
* 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()
let chatData = foundry.utils.duplicate(this.document)
if (this.document.actor) {
chatData.actor = { id: this.document.actor.id }
}
if (chatData.img?.includes("/blank.png")) {
chatData.img = null
}
chatData.jsondata = JSON.stringify({
compendium: "postedItem",
payload: chatData,
})
// Localized type label
const typeLabels = {
arme: "Arme", bouclier: "Bouclier", competence: "Compétence",
rune: "Rune", runeeffect: "Rune Active", don: "Don", pacte: "Pacte",
protection: "Protection", equipement: "Équipement", heritage: "Héritage",
metier: "Métier", capacite: "Capacité", tendance: "Tendance",
traitchaotique: "Trait Chaotique", traitespece: "Trait d'Espèce",
origine: "Origine", modifier: "Modificateur", monnaie: "Monnaie",
potion: "Potion"
}
chatData.typeLabel = typeLabels[chatData.type] ?? chatData.type
// Type icon for the badge
const typeIcons = {
arme: "fa-sword", bouclier: "fa-shield-halved", competence: "fa-graduation-cap",
rune: "fa-star-of-david", runeeffect: "fa-star-of-david", don: "fa-hand-sparkles",
pacte: "fa-scroll", protection: "fa-shield", equipement: "fa-box",
heritage: "fa-dna", metier: "fa-hammer", capacite: "fa-bolt",
tendance: "fa-yin-yang", traitchaotique: "fa-skull", traitespece: "fa-paw",
origine: "fa-compass", modifier: "fa-sliders", monnaie: "fa-coins",
potion: "fa-flask"
}
chatData.typeIcon = typeIcons[chatData.type] ?? "fa-cube"
// Potion: add localized labels for statut and forme
if (chatData.type === "potion") {
const statutLabels = {
inconnue: game.i18n.localize("MNBL.potionInconnue"),
efficace: game.i18n.localize("MNBL.potionEfficace"),
heroique: game.i18n.localize("MNBL.potionHeroique"),
inefficace: game.i18n.localize("MNBL.potionInefficace"),
poison: game.i18n.localize("MNBL.potionPoison"),
}
const formeLabels = {
liquide: game.i18n.localize("MNBL.potionLiquide"),
onguent: game.i18n.localize("MNBL.potionOnguent"),
cachets: game.i18n.localize("MNBL.potionCachets"),
pilules: game.i18n.localize("MNBL.potionPilules"),
}
chatData.system.statutLabel = statutLabels[chatData.system.statut] ?? chatData.system.statut
chatData.system.formeLabel = formeLabels[chatData.system.forme] ?? chatData.system.forme
}
const html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-mournblade/templates/post-item.hbs', chatData)
ChatMessage.create({ user: game.user.id, content: html })
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeArmeSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["arme"],
position: {
width: 620,
},
window: {
contentClasses: ["arme-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-arme-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeBouclierSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["bouclier"],
position: {
width: 620,
},
window: {
contentClasses: ["bouclier-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-bouclier-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeCapaciteSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["capacite"],
position: {
width: 620,
},
window: {
contentClasses: ["capacite-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-capacite-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,78 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeCompetenceSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["competence"],
position: {
width: 620,
},
window: {
contentClasses: ["competence-content"],
},
actions: {
...MournbladeItemSheet.DEFAULT_OPTIONS.actions,
addPredilection: MournbladeCompetenceSheet.#onAddPredilection,
deletePredilection: MournbladeCompetenceSheet.#onDeletePredilection,
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-competence-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
/**
* Add a new empty predilection to the competence
* @param {PointerEvent} event
*/
static async #onAddPredilection(event) {
event.preventDefault()
const predilections = foundry.utils.duplicate(this.document.system.predilections ?? [])
predilections.push({ name: "", used: false })
await this.document.update({ "system.predilections": predilections })
}
/**
* Delete a predilection by index
* @param {PointerEvent} event
* @param {HTMLElement} target
*/
static async #onDeletePredilection(event, target) {
event.preventDefault()
const idx = Number(target.dataset.predictionIndex)
const predilections = foundry.utils.duplicate(this.document.system.predilections ?? [])
predilections.splice(idx, 1)
await this.document.update({ "system.predilections": predilections })
}
}
@@ -0,0 +1,45 @@
import MournbladeActorSheet from "./base-actor-sheet.mjs"
export default class MournbladeCreatureSheet extends MournbladeActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.creature",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-mournblade/templates/creature-sheet.hbs",
scrollable: [".tab.competences", ".tab.equipement", ".tab.biodata"],
},
}
/** @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.runes = foundry.utils.duplicate(actor.getRunes())
context.combat = actor.getCombatValues()
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.protectionTotal = actor.getProtectionTotal()
context.santeMalus = actor.getStatusMalus()
context.ameMalus = actor.getAmeMalus()
return context
}
}
@@ -0,0 +1,50 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeDonSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["don"],
position: {
width: 620,
},
window: {
contentClasses: ["don-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-don-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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()
context.enrichedSacrifice = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.sacrifice, { async: true })
return context
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeEquipementSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["equipement"],
position: {
width: 620,
},
window: {
contentClasses: ["equipement-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-equipement-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeHeritageSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["heritage"],
position: {
width: 620,
},
window: {
contentClasses: ["heritage-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-heritage-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeMetierSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["metier"],
position: {
width: 620,
},
window: {
contentClasses: ["metier-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-metier-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeModifierSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["modifier"],
position: {
width: 620,
},
window: {
contentClasses: ["modifier-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-modifier-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeMonnaieSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["monnaie"],
position: {
width: 620,
},
window: {
contentClasses: ["monnaie-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-monnaie-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeOrigineSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["origine"],
position: {
width: 620,
},
window: {
contentClasses: ["origine-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-origine-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladePacteSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["pacte"],
position: {
width: 620,
},
window: {
contentClasses: ["pacte-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-pacte-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,61 @@
import MournbladeActorSheet from "./base-actor-sheet.mjs"
export default class MournbladePersonnageSheet extends MournbladeActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.personnage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-mournblade/templates/actor-sheet.hbs",
scrollable: [".tab.principal", ".tab.competences", ".tab.dons", ".tab.equipement"],
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @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.dons = foundry.utils.duplicate(actor.getDons())
context.pactes = foundry.utils.duplicate(actor.getPactes())
context.alignement = actor.getAlignement()
context.isChaotique = context.alignement === "chaotique"
context.isLoyal = context.alignement === "loyal"
context.aspect = actor.getAspect()
context.marge = actor.getMarge()
context.tendances = foundry.utils.duplicate(actor.getTendances())
context.runes = foundry.utils.duplicate(actor.getRunes())
context.traitsChaotiques = foundry.utils.duplicate(actor.getTraitsChaotiques())
context.traitsEspeces = foundry.utils.duplicate(actor.getTraitsEspeces())
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.equipements = foundry.utils.duplicate(actor.getEquipments())
context.potions = foundry.utils.duplicate(actor.getPotions())
context.modifiers = foundry.utils.duplicate(actor.getModifiers())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.runeEffects = foundry.utils.duplicate(actor.getRuneEffects())
context.protectionTotal = actor.getProtectionTotal()
context.santeMalus = actor.getStatusMalus()
context.ameMalus = actor.getAmeMalus()
return context
}
}
@@ -0,0 +1,47 @@
/**
* Application de feuille de PNJ pour Mournblade
*/
import MournbladeActorSheet from "./base-actor-sheet.mjs"
export default class MournbladePnjSheet extends MournbladeActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
window: {
...super.DEFAULT_OPTIONS.window,
title: "SHEETS.Actor.pnj",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-mournblade/templates/actor-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Add pnj-specific data
context.skills = actor.getSkills()
context.armes = foundry.utils.duplicate(actor.getWeapons())
context.protections = foundry.utils.duplicate(actor.getArmors())
context.dons = foundry.utils.duplicate(actor.getDons())
context.pactes = foundry.utils.duplicate(actor.getPactes())
context.combat = actor.getCombatValues()
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.protectionTotal = actor.getProtectionTotal()
context.santeMalus = actor.getStatusMalus()
context.ameMalus = actor.getAmeMalus()
return context
}
}
@@ -0,0 +1,47 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladePotionSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["potion"],
position: {
width: 640,
},
window: {
contentClasses: ["potion-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-potion-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
#getTabs() {
const tabs = {
details: { id: "details", group: "primary", label: "Détails" },
effets: { id: "effets", group: "primary", label: "Effets" },
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()
context.config = game.system.mournblade.config
return context
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeProtectionSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["protection"],
position: {
width: 620,
},
window: {
contentClasses: ["protection-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-protection-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeRuneSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["rune"],
position: {
width: 620,
},
window: {
contentClasses: ["rune-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-rune-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeRuneEffectSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["runeeffect"],
position: {
width: 620,
},
window: {
contentClasses: ["runeeffect-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-runeeffect-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeTendanceSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["tendance"],
position: {
width: 620,
},
window: {
contentClasses: ["tendance-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-tendance-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeTraitChaotiqueSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["traitchaotique"],
position: {
width: 620,
},
window: {
contentClasses: ["traitchaotique-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-traitchaotique-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
@@ -0,0 +1,49 @@
import MournbladeItemSheet from "./base-item-sheet.mjs"
export default class MournbladeTraitEspeceSheet extends MournbladeItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["traitespece"],
position: {
width: 620,
},
window: {
contentClasses: ["traitespece-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-mournblade/templates/item-traitespece-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "description",
}
/**
* 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
}
}
+32
View File
@@ -0,0 +1,32 @@
/**
* 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 }),
ignorearmure: new fields.BooleanField({ initial: false }),
nbressources: new fields.NumberField({ initial: 0, integer: true }),
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 }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Data model de base pour les items Mournblade
*/
export default class BaseItemDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* 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 }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les capacités
*/
export default class CapaciteDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
typeCapacite: new fields.StringField({ initial: "creature", blank: false }),
description: new fields.HTMLField({ initial: "" })
};
}
}
+32
View File
@@ -0,0 +1,32 @@
/**
* 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: "" }),
used: new fields.BooleanField({ initial: false })
}),
{ initial: [] }
)
};
}
static migrateData(source) {
if (Array.isArray(source.predilections)) {
source.predilections = source.predilections.map(pred =>
typeof pred === "string" ? { name: pred, used: false } : pred
);
}
return super.migrateData(source);
}
}
+110
View File
@@ -0,0 +1,110 @@
/**
* 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: "" }),
creatureType: new fields.StringField({ initial: "creature" }),
elementType: new fields.StringField({ initial: "" }),
demonType: new fields.StringField({ initial: "" }),
demonPuissance: new fields.StringField({ initial: "" }),
automatonType: new fields.StringField({ initial: "" }),
automatonPuissance: new fields.StringField({ initial: "" }),
automatonVoyageType: 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: "" }),
amemultiplier: new fields.NumberField({ initial: 2, integer: true }),
ignoreamemalus: new fields.BooleanField({ initial: false }),
ignoresantemalus: new fields.BooleanField({ initial: false }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
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 }),
malusmanuel: new fields.NumberField({ initial: 0, integer: true }),
sequelles: new fields.StringField({ initial: "" })
}),
ame: 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 })
}),
balance: new fields.SchemaField({
loi: new fields.NumberField({ initial: 0, integer: true }),
chaos: new fields.NumberField({ initial: 0, integer: true }),
aspect: new fields.NumberField({ initial: 0, integer: true }),
marge: new fields.NumberField({ initial: 0, integer: true }),
pointschaos: new fields.NumberField({ initial: 0, integer: true }),
pointsloi: new fields.NumberField({ initial: 0, integer: true })
}),
ressources: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
};
}
}
+14
View File
@@ -0,0 +1,14 @@
/**
* 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: "" }),
allegeance: new fields.StringField({ initial: "" }),
prerequis: new fields.StringField({ initial: "" }),
sacrifice: new fields.HTMLField({ initial: "" })
};
}
}
+18
View File
@@ -0,0 +1,18 @@
/**
* 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: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+11
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: "" })
};
}
}
+29
View File
@@ -0,0 +1,29 @@
/**
* Index des DataModels pour Mournblade
* 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 ModifierDataModel } from './modifier.mjs';
export { default as MonnaieDataModel } from './monnaie.mjs';
export { default as OrigineDataModel } from './origine.mjs';
export { default as PacteDataModel } from './pacte.mjs';
export { default as ProtectionDataModel } from './protection.mjs';
export { default as RuneDataModel } from './rune.mjs';
export { default as RuneEffectDataModel } from './runeeffect.mjs';
export { default as PotionDataModel } from './potion.mjs';
export { default as TendanceDataModel } from './tendance.mjs';
export { default as TraitChaotiqueDataModel } from './traitchaotique.mjs';
export { default as TraitEspeceDataModel } from './traitespece.mjs';
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as CreatureDataModel } from './creature.mjs';
+11
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: "" })
};
}
}
+18
View File
@@ -0,0 +1,18 @@
/**
* Data model pour les modificateurs
*/
export default class ModifierDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
modifiertype: new fields.StringField({ initial: "roll" }),
value: new fields.NumberField({ initial: 0, integer: true }),
attribut: new fields.StringField({ initial: "aucun" }),
competence: new fields.StringField({ initial: "aucun" }),
permanent: new fields.BooleanField({ initial: true }),
once: new fields.BooleanField({ initial: false }),
duree: new fields.StringField({ initial: "" })
};
}
}
+13
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: "" })
};
}
}
+11
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: "" })
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les pactes
*/
export default class PacteDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
allegeance: new fields.StringField({ initial: "" })
};
}
}
+102
View File
@@ -0,0 +1,102 @@
/**
* 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: "" }),
amemultiplier: new fields.NumberField({ initial: 2, integer: true }),
ignoreamemalus: new fields.BooleanField({ initial: false }),
ignoresantemalus: new fields.BooleanField({ initial: false }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
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 }),
malusmanuel: new fields.NumberField({ initial: 0, integer: true }),
sequelles: new fields.StringField({ initial: "" })
}),
ame: 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: "" })
}),
invocationsElementaires: new fields.ArrayField(new fields.ObjectField(), { initial: [] }),
invocationsDemons: new fields.ArrayField(new fields.ObjectField(), { 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 })
}),
balance: new fields.SchemaField({
loi: new fields.NumberField({ initial: 0, integer: true }),
chaos: new fields.NumberField({ initial: 0, integer: true }),
aspect: new fields.NumberField({ initial: 0, integer: true }),
marge: new fields.NumberField({ initial: 0, integer: true }),
pointschaos: new fields.NumberField({ initial: 0, integer: true }),
pointsloi: new fields.NumberField({ initial: 0, integer: true })
})
};
}
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Data model pour les potions à base de runes
*/
export default class PotionDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
effetCuratif: new fields.HTMLField({ initial: "" }),
effetLetal: new fields.HTMLField({ initial: "" }),
effetSecondaire: new fields.HTMLField({ initial: "" }),
rune: new fields.StringField({ initial: "" }),
runeImg: new fields.StringField({ initial: "" }),
runeSeuil: new fields.NumberField({ initial: 0, integer: true }),
pointsAme: new fields.NumberField({ initial: 1, integer: true, min: 1 }),
forme: new fields.StringField({ initial: "liquide" }),
statut: new fields.StringField({ initial: "inconnue" }),
virulence: new fields.NumberField({ initial: 0, integer: true }),
duree: new fields.StringField({ initial: "" }),
conservation: new fields.StringField({ initial: "" }),
tempsPreparation: new fields.StringField({ initial: "" }),
};
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* 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: "" }),
typeprotection: new fields.StringField({ initial: "" }),
protection: new fields.NumberField({ initial: 0, integer: true }),
degats: new fields.StringField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Data model pour les runes
*/
export default class RuneDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
formule: new fields.StringField({ initial: "" }),
seuil: new fields.NumberField({ initial: 0, integer: true }),
prononcee: new fields.StringField({ initial: "" }),
tracee: new fields.StringField({ initial: "" })
};
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* Data model pour les effets de runes
*/
export default class RuneEffectDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
rune: new fields.StringField({ initial: "" }),
mode: new fields.StringField({ initial: "" }),
duree: new fields.StringField({ initial: "" }),
pointame: new fields.NumberField({ initial: 0, integer: true })
};
}
}
+12
View File
@@ -0,0 +1,12 @@
/**
* Data model pour les tendances
*/
export default class TendanceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" }),
allegeance: new fields.StringField({ initial: "" })
};
}
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Data model pour les traits chaotiques
*/
export default class TraitChaotiqueDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Data model pour les traits d'espèce
*/
export default class TraitEspeceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}
+599 -107
View File
@@ -1,6 +1,10 @@
/* -------------------------------------------- */
import { MournbladeUtility } from "./mournblade-utility.js";
import { MournbladeRollDialog } from "./mournblade-roll-dialog.js";
import { MournbladeRollDialog } from "./applications/mournblade-roll-dialog.mjs";
import MournbladeInvocationDialog from "./applications/mournblade-invocation-dialog.mjs";
import MournbladeInvocationDemonDialog from "./applications/mournblade-invocation-demon-dialog.mjs";
import MournbladeInvocationEspritDialog from "./applications/mournblade-invocation-esprit-dialog.mjs";
import MournbladeEnchantementDialog from "./applications/mournblade-enchantement-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]
@@ -12,13 +16,41 @@ const __vitesseBonus = [-2, -2, -1, -1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6
* @extends {Actor}
*/
export class MournbladeActor extends Actor {
prepareData() {
super.prepareData();
// Calculate derived attributes
const data = this.system;
if (this.type == 'personnage') {
// Compute base health and max soul from attributes (derived, no DB write needed)
data.sante.base = data.sante.bonus + (data.attributs.pui.value + data.attributs.tre.value) * 2 + 5;
data.ame.fullmax = (data.attributs.cla.value + data.attributs.tre.value) * data.biodata.amemultiplier + 5;
}
// Calculate total health
data.sante.total = data.sante.base + data.sante.bonus;
// Calculate current health
data.sante.current = data.sante.total - data.sante.nonletaux - data.sante.letaux;
// Calculate total ame
data.ame.total = data.ame.fullmax;
// Calculate balance
data.balance.total = data.balance.loi + data.balance.chaos;
}
prepareDerivedData() {
super.prepareDerivedData();
}
/* -------------------------------------------- */
/**
* Override the create() function to provide additional SoS functionality.
*
* This overrided create() function adds initial items
* Namely: Basic skills, money,
* This overrided create() function adds initial items
* Namely: Basic skills, money,
*
* @param {Object} data Barebones actor data which this function adds onto.
* @param {Object} options (Unused) Additional options which customize the creation workflow.
@@ -31,7 +63,7 @@ export class MournbladeActor extends Actor {
if (data instanceof Array) {
return super.create(data, options);
}
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
// If the created actor has items (only applicable to foundry.utils.duplicated actors) bypass the new actor creation logic
if (data.items) {
let actor = super.create(data, options);
return actor;
@@ -41,36 +73,90 @@ export class MournbladeActor extends Actor {
const skills = await MournbladeUtility.loadCompendium("fvtt-mournblade.skills")
data.items = skills.map(i => i.toObject())
}
if (data.type == 'pnj') {
if (data.type == 'creature') {
const skills = await MournbladeUtility.loadCompendium("fvtt-mournblade.skills-creatures")
data.items = skills.map(i => i.toObject())
data.items.push({ name: "Arme naturelle 1", type: 'arme', img: "systems/fvtt-mournblade/assets/icons/arme.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
data.items.push({ name: "Arme naturelle 2", type: 'arme', img: "systems/fvtt-mournblade/assets/icons/arme.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
}
return super.create(data, options);
}
/* -------------------------------------------- */
setModifier(name, type, value) {
this.createEmbeddedDocuments("Item", [{ type: "modifier", name: name, system: { modifiertype: type, value: value } }])
ui.notifications.info("Le modificateur " + name + " a été ajouté à " + this.name + ".")
}
/* -------------------------------------------- */
prepareArme(arme) {
arme = duplicate(arme)
arme = foundry.utils.duplicate(arme)
let combat = this.getCombatValues()
const enchBonus = (arme.system.enchantementLoi?.actif && arme.system.enchantementLoi?.bonus > 0)
? arme.system.enchantementLoi.bonus : 0
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.attrKey = "pui"
arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal
arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff
if (arme.system.isdefense) {
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.bonusmaniementdef
arme.system.isMelee = true
let competence = this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")
if (competence) {
arme.system.competence = foundry.utils.duplicate(competence)
arme.system.attrKey = "pui"
arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal
arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff + combat.attaqueModifier + enchBonus
if (arme.system.isdefense) {
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.bonusmaniementdef
}
} else {
arme.system.competence = null
arme.system.totalOffensif = 0
arme.system.totalDegats = arme.system.degats
arme.system.totalDefensif = 0
}
}
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.attrKey = "adr"
arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff
arme.system.totalDegats = arme.system.degats
if (arme.system.isdefense) {
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.bonusmaniementdef
arme.system.isDistance = true
let competence = this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "armes à distance")
if (competence) {
arme.system.competence = foundry.utils.duplicate(competence)
arme.system.attrKey = "adr"
arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff + combat.attaqueModifier + enchBonus
arme.system.totalDegats = arme.system.degats
if (arme.system.isdefense) {
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.bonusmaniementdef
}
} else {
arme.system.competence = null
arme.system.totalOffensif = 0
arme.system.totalDegats = arme.system.degats
arme.system.totalDefensif = 0
}
}
return arme
}
/* -------------------------------------------- */
prepareBouclier(bouclier) {
bouclier = foundry.utils.duplicate(bouclier)
let combat = this.getCombatValues()
const enchBonus = (bouclier.system.enchantementLoi?.actif && bouclier.system.enchantementLoi?.bonus > 0)
? bouclier.system.enchantementLoi.bonus : 0
let competence = this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")
if (competence) {
bouclier.system.competence = foundry.utils.duplicate(competence)
bouclier.system.attrKey = "pui"
bouclier.system.totalDegats = bouclier.system.degats + "+" + combat.bonusDegatsTotal
bouclier.system.totalOffensif = this.system.attributs.pui.value + bouclier.system.competence.system.niveau + enchBonus
bouclier.system.isdefense = true
bouclier.system.bonusmaniementoff = 0
bouclier.system.totalDefensif = combat.defenseTotal + bouclier.system.competence.system.niveau + bouclier.system.bonusdefense
} else {
bouclier.system.competence = null
bouclier.system.totalOffensif = 0
bouclier.system.totalDegats = bouclier.system.degats
bouclier.system.totalDefensif = 0
}
return bouclier
}
/* -------------------------------------------- */
getWeapons() {
@@ -79,28 +165,72 @@ export class MournbladeActor extends Actor {
if (arme.type == "arme") {
armes.push(this.prepareArme(arme))
}
if (arme.type == "bouclier") {
armes.push(this.prepareBouclier(arme))
}
}
MournbladeUtility.sortArrayObjectsByName(armes)
return armes
}
/* -------------------------------------------- */
getDons() {
return this.items.filter(item => item.type == "don")
getModifiersForRoll() {
let modifiers = []
for (let mod of this.items) {
if (mod.type == "modifier" && mod.system.modifiertype == "roll") {
let modObj = mod.toObject()
modObj.system.apply = true
modifiers.push(modObj)
}
}
MournbladeUtility.sortArrayObjectsByName(modifiers)
return modifiers
}
/* -------------------------------------------- */
getItemSorted(types) {
let items = this.items.filter(item => types.includes(item.type)) || []
MournbladeUtility.sortArrayObjectsByName(items)
return items
}
getDons() {
return this.getItemSorted(["don"])
}
getPactes() {
return this.getItemSorted(["pacte"])
}
getTendances() {
return this.items.filter(item => item.type == "tendance")
return this.getItemSorted(["tendance"])
}
getRunes() {
return this.items.filter(item => item.type == "rune")
return this.getItemSorted(["rune"])
}
/* -------------------------------------------- */
getEquipments() {
return this.items.filter(item => item.type == "equipement")
return this.getItemSorted(["equipement"])
}
getModifiers() {
return this.getItemSorted(["modifier"])
}
getTraitsChaotiques() {
return this.getItemSorted(["traitchaotique"])
}
getTraitsEspeces() {
return this.getItemSorted(["traitespece"])
}
getMonnaies() {
return this.getItemSorted(["monnaie"])
}
getPotions() {
return this.getItemSorted(["potion"])
}
/* -------------------------------------------- */
getArmors() {
return this.items.filter(item => item.type == "protection")
return this.getItemSorted(["protection"])
}
getCapacites() {
return this.getItemSorted(["capacite"])
}
getRuneEffects() {
return this.getItemSorted(["runeeffect"])
}
getOrigine() {
return this.items.find(item => item.type == "origine")
@@ -115,7 +245,7 @@ export class MournbladeActor extends Actor {
getSkills() {
let comp = []
for (let item of this.items) {
item = duplicate(item)
item = foundry.utils.duplicate(item)
if (item.type == "competence") {
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)
@@ -131,25 +261,27 @@ export class MournbladeActor extends Actor {
comp.push(item)
}
}
return comp.sort(function (a, b) {
let fa = a.name.toLowerCase(),
fb = b.name.toLowerCase();
if (fa < fb) {
return -1;
MournbladeUtility.sortArrayObjectsByName(comp)
return comp
}
/* -------------------------------------------- */
getProtectionTotal() {
let protection = 0
for (let item of this.items) {
if (item.type == "protection" && item.system.equipped) {
protection += item.system.protection
}
if (fa > fb) {
return 1;
}
return 0;
})
}
return protection
}
/* -------------------------------------------- */
getAspect() {
return (this.system.balance.loi > this.system.balance.chaos) ? this.system.balance.loi : this.system.balance.chaos
return (this.system.balance.loi > this.system.balance.chaos) ? this.system.balance.loi : this.system.balance.chaos
}
getMarge() {
return Math.abs( this.system.balance.loi - this.system.balance.chaos)
return Math.abs(this.system.balance.loi - this.system.balance.chaos)
}
getAlignement() {
return (this.system.balance.loi > this.system.balance.chaos) ? "loyal" : "chaotique"
@@ -162,12 +294,19 @@ export class MournbladeActor extends Actor {
/* -------------------------------------------- */
getVitesseBase() {
return 5 + __vitesseBonus[this.system.attributs.adr.value]
return 5 + __vitesseBonus[Math.min(21, Math.max(0, this.system.attributs.adr.value))]
}
/* -------------------------------------------- */
getCombatValues() {
let defenserModifier = 0
this.items.filter(item => item.type == "modifier" && item.system.modifiertype == "defense").map(e => defenserModifier += e.system.value)
let attaqueModifier = 0
this.items.filter(item => item.type == "modifier" && item.system.modifiertype == "attaque").map(e => attaqueModifier += e.system.value)
let combat = {
defenserModifier,
attaqueModifier,
initBase: this.system.attributs.adr.value,
initTotal: this.system.attributs.adr.value + this.system.combat.initbonus,
bonusDegats: this.getBonusDegats(),
@@ -175,35 +314,10 @@ export class MournbladeActor extends Actor {
vitesseBase: this.getVitesseBase(),
vitesseTotal: this.getVitesseBase() + this.system.combat.vitessebonus,
defenseBase: this.getDefenseBase(),
defenseTotal: this.getDefenseBase() + this.system.combat.defensebonus
defenseTotal: this.getDefenseBase() + this.system.combat.defensebonus + defenserModifier
}
return combat
}
/* -------------------------------------------- */
prepareBaseData() {
}
/* -------------------------------------------- */
async prepareData() {
super.prepareData();
}
/* -------------------------------------------- */
prepareDerivedData() {
if (this.type == 'personnage') {
let newSante = this.system.sante.bonus + (this.system.attributs.pui.value + this.system.attributs.tre.value) * 2 + 5
if (this.system.sante.base != newSante) {
this.update({ 'system.sante.base': newSante })
}
let newAme = (this.system.attributs.cla.value + this.system.attributs.tre.value) * this.system.biodata.amemultiplier + 5
if (this.system.ame.fullmax != newAme) {
this.update({ 'system.ame.fullmax': newAme })
}
}
super.prepareDerivedData()
}
/* -------------------------------------------- */
_preUpdate(changed, options, user) {
@@ -215,20 +329,57 @@ export class MournbladeActor extends Actor {
getItemById(id) {
let item = this.items.find(item => item.id == id);
if (item) {
item = duplicate(item)
item = foundry.utils.duplicate(item)
}
return item;
}
/* -------------------------------------------- */
async equipItem(itemId) {
let item = this.items.find(item => item.id == itemId);
if (item && item.system.data) {
let update = { _id: item.id, "system.equipped": !item.system.equipped };
let item = this.items.find(item => item.id == itemId)
if (item?.system) {
let update = { _id: item.id, "system.equipped": !item.system.equipped }
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
getStatusMalus() {
if (this.system.biodata.ignoresantemalus) {
return 0
}
let malusL = 0
let malusNL = 0
if (this.system.sante.base - this.system.sante.letaux < 10) {
malusL = -2
}
if (this.system.sante.base - this.system.sante.letaux < 5) {
malusL = -5
}
if (this.system.sante.base - this.system.sante.nonletaux < 10) {
malusNL = -2
}
if (this.system.sante.base - this.system.sante.nonletaux < 5) {
malusNL = -5
}
return Math.min(malusL, malusNL)
}
/* -------------------------------------------- */
getAmeMalus() {
if (this.system.biodata.ignoreamemalus) {
return 0
}
let malusA = 0
if (this.system.ame.currentmax - this.system.ame.value < 10) {
malusA = -2
}
if (this.system.ame.currentmax - this.system.ame.value < 5) {
malusA = -5
}
return malusA
}
/* -------------------------------------------- */
editItemField(itemId, itemType, itemField, dataType, value) {
let item = this.items.find(item => item.id == itemId)
@@ -244,16 +395,54 @@ export class MournbladeActor extends Actor {
}
}
/* -------------------------------------------- */
async incDecSante(type, value, applyArmure = true) {
value = Number(value)
if (value && applyArmure) {
let protection = this.getProtectionTotal()
value -= protection
value = Math.max(0, Number(value))
}
if (value) {
let newSante = foundry.utils.duplicate(this.system.sante)
newSante[type] += Number(value)
newSante[type] = Math.max(0, newSante[type])
if (newSante[type] > this.system.sante.base) {
value -= this.system.sante.base - newSante[type]
newSante[type] = this.system.sante.base
} else {
value = 0
}
newSante[type] = Math.min(newSante[type], newSante.base)
if (value && type == "nonletaux") {
newSante["letaux"] += value
}
await this.update({ 'system.sante': newSante })
}
}
/* -------------------------------------------- */
async incDecAme(value) {
value = Number(value)
if (value) {
let newAme = foundry.utils.duplicate(this.system.ame)
newAme.value += Number(value)
newAme.value = Math.max(0, newAme.value)
newAme.value = Math.min(newAme.value, newAme.currentmax)
await this.update({ 'system.ame': newAme })
}
}
/* -------------------------------------------- */
getBonneAventure() {
return this.system.bonneaventure.actuelle
}
/* -------------------------------------------- */
changeBonneAventure(value) {
async changeBonneAventure(value) {
let newBA = this.system.bonneaventure.actuelle
newBA += value
this.update({ 'system.bonneaventure.actuelle': newBA })
await this.update({ 'system.bonneaventure.actuelle': newBA })
}
/* -------------------------------------------- */
@@ -262,10 +451,10 @@ export class MournbladeActor extends Actor {
}
/* -------------------------------------------- */
changeEclat(value) {
async changeEclat(value) {
let newE = this.system.eclat.value
newE += value
this.update({ 'system.eclat.value': newE })
await this.update({ 'system.eclat.value': newE })
}
/* -------------------------------------------- */
@@ -274,13 +463,13 @@ export class MournbladeActor extends Actor {
}
/* -------------------------------------------- */
subPointsAme(runeMode, value) {
let ame = duplicate(this.system.ame)
if(runeMode == "prononcer") {
ame.value -= value
let ame = foundry.utils.duplicate(this.system.ame)
if (runeMode == "prononcer") {
ame.value += value
} else {
ame.currentmax -= value
}
this.update( {'system.ame': ame})
return this.update({ 'system.ame': ame })
}
/* -------------------------------------------- */
@@ -296,12 +485,15 @@ export class MournbladeActor extends Actor {
/* -------------------------------------------- */
getAttribute(attrKey) {
return this.system.attributes[attrKey]
return this.system.attributs[attrKey]
}
/* -------------------------------------------- */
getBonusDegats() {
return __degatsBonus[this.system.attributs.pui.value]
if (this.type == "creature") {
return 0
}
return __degatsBonus[Math.min(21, Math.max(0, this.system.attributs.pui.value))]
}
/* -------------------------------------------- */
@@ -317,13 +509,13 @@ export class MournbladeActor extends Actor {
getSubActors() {
let 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;
}
/* -------------------------------------------- */
async addSubActor(subActorId) {
let subActors = duplicate(this.system.subactors);
let subActors = foundry.utils.duplicate(this.system.subactors);
subActors.push(subActorId);
await this.update({ 'system.subactors': subActors });
}
@@ -342,11 +534,10 @@ export class MournbladeActor extends Actor {
async incDecQuantity(objetId, incDec = 0) {
let objetQ = this.items.get(objetId)
if (objetQ) {
let newQ = objetQ.system.quantity + incDec;
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantity': newQ }]); // pdates one EmbeddedEntity
let newQ = objetQ.system.quantite + incDec;
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantite': newQ }]); // pdates one EmbeddedEntity
}
}
/* -------------------------------------------- */
getCompetence(compId) {
return this.items.get(compId)
@@ -355,15 +546,55 @@ export class MournbladeActor extends Actor {
/* -------------------------------------------- */
async setPredilectionUsed(compId, predIdx) {
let comp = this.items.get(compId)
let pred = duplicate(comp.system.predilections)
let pred = foundry.utils.duplicate(comp.system.predilections)
pred[predIdx].used = true
await this.updateEmbeddedDocuments('Item', [{ _id: compId, 'system.predilections': pred }])
}
/* -------------------------------------------- */
getInitiativeScore( ) {
getInitiativeScore() {
return Number(this.system.attributs.adr.value) + Number(this.system.combat.initbonus)
}
/* -------------------------------------------- */
getBestDefenseValue() {
let defenseList = this.items.filter(item => (item.type == "arme" || item.type == "bouclier") && item.system.equipped)
let maxDef = 0
let bestArme
for (let arme of defenseList) {
if (arme.type == "arme" && arme.system.isdefense) {
arme = this.prepareArme(arme)
}
if (arme.type == "bouclier") {
arme = this.prepareBouclier(arme)
}
if (arme.system.totalDefensif > maxDef) {
maxDef = arme.system.totalDefensif
bestArme = foundry.utils.duplicate(arme)
}
}
return bestArme
}
/* -------------------------------------------- */
depenseRessources(arme) {
if (arme.system.nbressources && Number(arme.system.nbressources) > 0) {
if (this.type == "creature") {
let ressources = foundry.utils.duplicate(this.system.ressources)
if (Number(ressources.value) >= Number(arme.system.nbressources)) {
ressources.value -= arme.system.nbressources
this.update({ 'system.ressources': ressources })
ChatMessage.create({
content: "L'utilisation de la capacité/arme a dépensé " + arme.system.nbressources + " ressources.",
whisper: game.user.id,
user: game.user.id
});
} else {
ui.notifications.warn("Points de ressources insuffisants.")
}
} else {
ui.notifications.warn("Les ressources ne sont pas disponibles pour les personnages.")
}
}
}
/* -------------------------------------------- */
getCommonRollData(attrKey = undefined, compId = undefined, compName = undefined) {
@@ -371,24 +602,34 @@ export class MournbladeActor extends Actor {
rollData.alias = this.name
rollData.actorImg = this.img
rollData.actorId = this.id
rollData.tokenId = this.token?.id
rollData.img = this.img
rollData.canEclatDoubleD20 = this.canEclatDoubleD20()
rollData.doubleD20 = false
rollData.attributs = MournbladeUtility.getAttributs()
rollData.selectDifficulte = true
rollData.malusSante = this.getStatusMalus() + this.system.sante.malusmanuel
rollData.malusAme = this.getAmeMalus()
rollData.modifiers = this.getModifiersForRoll()
rollData.desavantages = {}
rollData.isMonte = this.system.combat.monte
if (rollData.isMonte) {
rollData.config.attaques["chargecavalerie"] = "Charge de cavalerie"
}
if (attrKey) {
rollData.attrKey = attrKey
if (attrKey != "tochoose") {
rollData.actionImg = "systems/fvtt-mournblade/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) {
rollData.competence = duplicate(this.items.get(compId) || {})
rollData.competence = foundry.utils.duplicate(this.items.get(compId) || {})
rollData.actionImg = rollData.competence?.img
}
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
}
return rollData
@@ -397,49 +638,300 @@ export class MournbladeActor extends Actor {
/* -------------------------------------------- */
async rollAttribut(attrKey) {
let rollData = this.getCommonRollData(attrKey)
console.log("RollDatra", rollData)
let rollDialog = await MournbladeRollDialog.create(this, rollData)
rollDialog.render(true)
await MournbladeRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
async rollCompetence(attrKey, compId) {
let rollData = this.getCommonRollData(attrKey, compId)
console.log("RollDatra", rollData)
let rollDialog = await MournbladeRollDialog.create(this, rollData)
rollDialog.render(true)
await MournbladeRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
async rollRune(runeId) {
async rollRune(runeId) {
let comp = this.items.find(comp => comp.type == "competence" && comp.name.toLowerCase() == "savoir : runes")
if (!comp) {
ui.notifications.warn("La compétence Savoirs : Runes n'a pas été trouvée, abandon.")
return
}
let rollData = this.getCommonRollData("cla", undefined, "Savoir : Runes")
rollData.rune = duplicate(this.items.get(runeId) || {})
rollData.rune = foundry.utils.duplicate(this.items.get(runeId) || {})
rollData.difficulte = rollData.rune?.system?.seuil || 0
rollData.runemode = "prononcer"
rollData.runeame = 1
rollData.runeame = 1
console.log("runeData", rollData)
let rollDialog = await MournbladeRollDialog.create(this, rollData)
rollDialog.render(true)
await MournbladeRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
async rollSortilege() {
const runes = this.getRunes()
if (runes.length < 2) {
ui.notifications.warn("Il faut au moins deux Runes pour lancer un Sortilège.")
return
}
const comp = this.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : runes")
if (!comp) {
ui.notifications.warn("La compétence Savoir : Runes n'a pas été trouvée, abandon.")
return
}
let rollData = this.getCommonRollData("cla", undefined, "Savoir : Runes")
rollData.isSortilege = true
rollData.difficulte = 0
rollData.runemode = "prononcer"
rollData.runeame = 0
await MournbladeRollDialog.createSortilege(this, rollData)
}
/* -------------------------------------------- */
async preparePotion() {
const runes = this.getRunes()
if (runes.length === 0) {
ui.notifications.warn("Aucune Rune disponible pour préparer une potion.")
return
}
const comp = this.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : runes")
if (!comp) {
ui.notifications.warn("La compétence Savoir : Runes n'a pas été trouvée, abandon.")
return
}
let rollData = this.getCommonRollData("cla", comp._id)
rollData.isPotion = true
rollData.mainDice = "1d10"
// Optionally cap by Savoir:Haut-Parler and Savoir:Alchimie
const hautParlerComp = this.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : haut-parler")
const alchimieComp = this.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : alchimie")
if (hautParlerComp) rollData.limitHautParlerValue = hautParlerComp.system.niveau
if (alchimieComp) rollData.limitAlchimieValue = alchimieComp.system.niveau
await MournbladeRollDialog.createPotion(this, rollData)
}
/* -------------------------------------------- */
async invoquerElementaire() {
const persuasionComp = this.items.find(c => c.type == "competence" && c.name.toLowerCase() == "persuasion")
if (!persuasionComp) {
ui.notifications.warn("La compétence Persuasion est requise pour invoquer un Élémentaire.")
return
}
let rollData = this.getCommonRollData("pre", persuasionComp._id)
rollData.isInvocationElementaire = true
rollData.mainDice = "1d10"
const hautParlerComp = this.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : haut-parler")
const seigneursElemComp = this.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : seigneurs élémentaires")
if (hautParlerComp) rollData.hautParlerNiveau = hautParlerComp.system.niveau
if (seigneursElemComp) rollData.seigneursElemNiveau = seigneursElemComp.system.niveau
await MournbladeInvocationDialog.create(this, rollData)
}
/* -------------------------------------------- */
async bannirElementaire(invocIndex) {
const invocations = foundry.utils.duplicate(this.system.invocationsElementaires || [])
const invoc = invocations[invocIndex]
if (!invoc) return
// Free the blocked soul
await this.subPointsAme("prononcer", -invoc.soulCost)
// Delete the created actor if it still exists
if (invoc.actorId) {
const createdActor = game.actors.get(invoc.actorId)
if (createdActor) await createdActor.delete()
}
// Remove from the list
invocations.splice(invocIndex, 1)
await this.update({ "system.invocationsElementaires": invocations })
ui.notifications.info(`L'Élémentaire ${invoc.actorName} a été banni. ${invoc.soulCost} points d'Âme libérés.`)
}
/* -------------------------------------------- */
async invoquerDemon() {
const normalize = str => str.toLowerCase()
.replace(/œ/g, "oe").replace(/æ/g, "ae")
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ").trim()
const hasKeywords = (item, ...words) => {
const n = normalize(item.name)
return words.every(w => n.includes(normalize(w)))
}
const coercitionComp = this.items.find(c => c.type === "competence" && hasKeywords(c, "coercition"))
if (!coercitionComp) {
ui.notifications.warn("La compétence Coercition est requise pour invoquer un Démon.")
return
}
const isChaotique = this.system.balance.chaos > this.system.balance.loi
const hasOeilSorcier = this.items.some(c => (c.type === "capacite" || c.type === "don") && hasKeywords(c, "oeil", "sorcier"))
const hasRuneChaos = this.items.some(i => i.type === "rune" && hasKeywords(i, "chaos"))
if (!isChaotique || !hasOeilSorcier || !hasRuneChaos) {
const missing = []
if (!isChaotique) missing.push("alignement chaotique (Chaos > Loi)")
if (!hasOeilSorcier) missing.push("Capacité/Don Œil du Sorcier")
if (!hasRuneChaos) missing.push("Rune du Chaos")
ui.notifications.warn(`Prérequis manquants pour l'invocation démoniaque : ${missing.join(", ")}.`)
}
let rollData = this.getCommonRollData("tre", coercitionComp._id)
rollData.isInvocationDemon = true
rollData.mainDice = "1d10"
await MournbladeInvocationDemonDialog.create(this, rollData)
}
/* -------------------------------------------- */
async libererDemon(invocIndex) {
const invocations = foundry.utils.duplicate(this.system.invocationsDemons || [])
const invoc = invocations[invocIndex]
if (!invoc) return
invocations.splice(invocIndex, 1)
await this.update({ "system.invocationsDemons": invocations })
ui.notifications.info(`Le Démon ${invoc.demonName ?? "invoqué"} a été libéré.`)
}
/* -------------------------------------------- */
async invoquerEspritLoi() {
const normalize = str => str.toLowerCase()
.replace(/œ/g, "oe").replace(/æ/g, "ae")
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ").trim()
const hasKeywords = (item, ...words) => {
const n = normalize(item.name)
return words.every(w => n.includes(normalize(w)))
}
const persuasionComp = this.items.find(c => c.type === "competence" && hasKeywords(c, "persuasion"))
const isLoyal = this.system.balance.loi > this.system.balance.chaos
const hasRuneLoi = this.items.some(i => i.type === "rune" && hasKeywords(i, "loi"))
if (!isLoyal || !hasRuneLoi) {
const missing = []
if (!isLoyal) missing.push("alignement loyal (Loi > Chaos)")
if (!hasRuneLoi) missing.push("Rune de la Loi")
ui.notifications.warn(`Prérequis manquants : ${missing.join(", ")}.`)
}
const rollData = this.getCommonRollData("tre", persuasionComp?._id ?? null)
rollData.isInvocationEsprit = true
rollData.mainDice = "1d10"
await MournbladeInvocationEspritDialog.create(this, rollData)
}
/* -------------------------------------------- */
async enchanter(itemId) {
const item = this.items.get(itemId)
if (!item) return
if (!["arme", "equipement", "protection", "bouclier"].includes(item.type)) {
ui.notifications.warn("Seules les armes, équipements, protections et boucliers peuvent être enchantés.")
return
}
await MournbladeEnchantementDialog.create(this, item)
}
/* -------------------------------------------- */
async rollArmeOffensif(armeId) {
let arme = this.items.get(armeId)
arme = this.prepareArme(arme)
if (arme.type == "arme") {
arme = this.prepareArme(arme)
}
if (arme.type == "bouclier") {
arme = this.prepareBouclier(arme)
}
let rollData = this.getCommonRollData(arme.system.attrKey, arme.system.competence._id)
rollData.arme = arme
rollData.typeAttaque = "assaut"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
rollData.visee = false
rollData.ciblecourt = false
rollData.cibleconsciente = false
// Do not display difficulte if defense weapon or distance
if (rollData.armeDefense || rollData.arme.system.isDistance) {
rollData.selectDifficulte = false
rollData.difficulte = (rollData.arme.system.isDistance) ? 0 : rollData.difficulte
}
console.log("ARME!", rollData)
let rollDialog = await MournbladeRollDialog.create(this, rollData)
rollDialog.render(true)
this.depenseRessources(arme)
await MournbladeRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
async rollAssomer() {
let rollData = this.getCommonRollData("adr", undefined, "Filouterie")
rollData.typeAttaque = "assomer"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
rollData.difficulte = rollData.defender.system.attributs.tre.value * 2
}
console.log("Assomer!", rollData)
await MournbladeRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
async rollFuir() {
let rollData = this.getCommonRollData("adr", undefined, "Mouvements")
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)
}
console.log("Fuir!", rollData)
await MournbladeRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
async rollImmobiliser() {
let rollData = this.getCommonRollData("pui", undefined, "Mêlée")
rollData.typeAttaque = "immobiliser"
rollData.typeCouvert = "aucun"
rollData.hasDesavantageBonus = true
if (rollData.defender) {
rollData.selectDifficulte = false
rollData.difficulte = rollData.defenderCombatValues.defenseTotal
}
console.log("Immobiliser!", rollData)
await MournbladeRollDialog.create(this, rollData)
}
/* -------------------------------------------- */
async rollArmeSpecial(armeId) {
let arme = this.items.get(armeId)
if (arme) {
MournbladeUtility.createChatWithRollMode("GM", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade/templates/chat-display-description.hbs`, arme)
}, arme)
this.depenseRessources(arme)
}
}
/* -------------------------------------------- */
async rollArmeDegats(armeId) {
let arme = this.items.get(armeId)
arme = this.prepareArme(arme)
let roll = new Roll(arme.system.totalDegats).roll({ async: false })
if (arme.type == "arme") {
arme = this.prepareArme(arme)
}
if (arme.type == "bouclier") {
arme = this.prepareBouclier(arme)
}
//Unused rollData.degatsFormula = arme.system.totalDegats
let roll
try {
roll = await new Roll(arme.system.totalDegats).evaluate()
} catch (err) {
ui.notifications.error(`Formule de dégâts invalide : ${arme.system.totalDegats}`)
console.error("rollArmeDegats error:", err)
return
}
await MournbladeUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode"));
let rollData = {
degatsFormula: arme.system.totalDegats,
arme: arme,
finalResult: roll.total,
alias: this.name,
@@ -448,7 +940,7 @@ export class MournbladeActor extends Actor {
actionImg: arme.img,
}
MournbladeUtility.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-mournblade/templates/chat-degats-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade/templates/chat-degats-result-v2.hbs`, rollData)
})
}
+1 -1
View File
@@ -10,7 +10,7 @@ export class MournbladeCombat extends Combat {
const c = this.combatants.get(ids[cId]);
let id = c._id || c.id;
let initBonus = c.actor ? c.actor.getInitiativeScore() : 0
let roll = new Roll("1d10 + "+initBonus).roll({ async: false})
let roll = await new Roll("1d10 + "+initBonus).evaluate()
await MournbladeUtility.showDiceSoNice(roll, game.settings.get("core", "rollMode"))
//console.log("Init bonus", initBonus, roll.total)
await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: roll.total } ]);
+4 -5
View File
@@ -89,7 +89,7 @@ export class MournbladeCommands {
if (command && command.func) {
const result = command.func(content, msg, params);
if (result == false) {
RdDCommands._chatAnswer(msg, command.descr);
MournbladeCommands._chatAnswer(msg, command.descr);
}
return true;
}
@@ -98,8 +98,8 @@ export class MournbladeCommands {
/* -------------------------------------------- */
async createChar(msg) {
game.system.Mournblade.creator = new MournbladeActorCreate();
game.system.Mournblade.creator.start();
game.system.mournblade.creator = new MournbladeActorCreate();
game.system.mournblade.creator.start();
}
/* -------------------------------------------- */
@@ -116,8 +116,7 @@ export class MournbladeCommands {
rollData.mode = "generic"
rollData.title = `Dice Pool Roll`;
let rollDialog = await MournbladeRollDialog.create( this, rollData);
rollDialog.render( true );
await MournbladeRollDialog.create( this, rollData);
}
}
+149
View File
@@ -0,0 +1,149 @@
export class MournbladeConfig {
static getConfig() {
let MOURNBLADE_CONFIG = {
attaques: {
assaut: game.i18n.localize("MNBL.assaut"),
precise: game.i18n.localize("MNBL.preciseattack"),
feinte: game.i18n.localize("MNBL.feint"),
coupbas: game.i18n.localize("MNBL.dirtyattack"),
charger: game.i18n.localize("MNBL.charge"),
contenir: game.i18n.localize("MNBL.contain"),
desarmer: game.i18n.localize("MNBL.disarm")
},
couverts: {
aucun: { name: game.i18n.localize("MNBL.none"), value: 0 },
rondache: { name: game.i18n.localize("MNBL.lightcover"), value: -2 },
pavois: { name: game.i18n.localize("MNBL.mediumcover"), value: -5 },
complet: { name: game.i18n.localize("MNBL.heavycover"), value: -10 },
},
modifierTypes: {
aucun: { name: game.i18n.localize("MNBL.none"), value: 0 },
roll: { name: game.i18n.localize("MNBL.roll"), value: 0 },
degats: { name: game.i18n.localize("MNBL.damage"), value: 0 },
defense: { name: game.i18n.localize("MNBL.defensecapacity"), value: 0 },
attaque: { name: game.i18n.localize("MNBL.attackcapacity"), value: 0 },
},
listeNiveau: {
},
listeNiveauCreature: {
},
listePortees: {
"10": game.i18n.localize("MNBL.lessthanshort"),
"15": game.i18n.localize("MNBL.shortmore"),
"20": game.i18n.localize("MNBL.mediummore"),
"25": game.i18n.localize("MNBL.longmore")
},
modificateurOptions: {},
pointsAmeOptions: {},
difficulteOptions: {
"0": game.i18n.localize("MNBL.noneunknwon"),
"5": game.i18n.localize("MNBL.easy"),
"10": game.i18n.localize("MNBL.medium"),
"15": game.i18n.localize("MNBL.hard"),
"20": game.i18n.localize("MNBL.hazardous"),
"25": game.i18n.localize("MNBL.insane"),
"30": game.i18n.localize("MNBL.puremadness")
},
attributs: {
adr: game.i18n.localize("Adresse"), pui: game.i18n.localize("Puissance"),
cla: game.i18n.localize("Clairvoyance"), pre: game.i18n.localize("Présence"), tre: game.i18n.localize("Trempe")
},
lancementRuneOptions: {
prononcer: game.i18n.localize("MNBL.pronouncerune"),
inscrire: game.i18n.localize("MNBL.tracerune")
},
effetRuneOptions: {
prononcee: game.i18n.localize("MNBL.pronounced"),
inscrite: game.i18n.localize("MNBL.traced")
},
typeArmeOptions: {
contact: game.i18n.localize("MNBL.meleeweapon"),
contactjet: game.i18n.localize("MNBL.meleethrowweapon"),
jet: game.i18n.localize("MNBL.throwweapon"),
tir: game.i18n.localize("MNBL.shootweapon"),
special: game.i18n.localize("MNBL.specialweapon")
},
allegeanceOptions: {
tous: game.i18n.localize("MNBL.all"),
chaos: game.i18n.localize("MNBL.chaos"),
loi: game.i18n.localize("MNBL.law"),
betes: game.i18n.localize("MNBL.beastslords"),
elementaires: game.i18n.localize("MNBL.elementslords")
},
potionFormeOptions: {
liquide: game.i18n.localize("MNBL.potionLiquide"),
onguent: game.i18n.localize("MNBL.potionOnguent"),
cachets: game.i18n.localize("MNBL.potionCachets"),
pilules: game.i18n.localize("MNBL.potionPilules"),
},
potionStatutOptions: {
inconnue: game.i18n.localize("MNBL.potionInconnue"),
efficace: game.i18n.localize("MNBL.potionEfficace"),
heroique: game.i18n.localize("MNBL.potionHeroique"),
inefficace: game.i18n.localize("MNBL.potionInnefficace"),
poison: game.i18n.localize("MNBL.potionPoison"),
},
typeCapaciteOptions: {
elue: game.i18n.localize("MNBL.typeCapaciteElue"),
elementaire: game.i18n.localize("MNBL.typeCapaciteElementaire"),
demoniaque: game.i18n.localize("MNBL.typeCapaciteDemoniaque"),
automaton: game.i18n.localize("MNBL.typeCapaciteAutomaton"),
creature: game.i18n.localize("MNBL.typeCapaciteCreature"),
faiblessedemoniaque: game.i18n.localize("MNBL.typeCapaciteFaiblesseDemoniaque"),
},
creatureTypeOptions: {
creature: game.i18n.localize("MNBL.creatureTypeCreature"),
demon: game.i18n.localize("MNBL.creatureTypeDemon"),
elementaire: game.i18n.localize("MNBL.creatureTypeElementaire"),
automaton: game.i18n.localize("MNBL.creatureTypeAutomaton"),
},
demonTypeOptions: {
"": game.i18n.localize("MNBL.demonTypeNone"),
combat: game.i18n.localize("MNBL.demonTypeCombat"),
desir: game.i18n.localize("MNBL.demonTypeDesir"),
savoir: game.i18n.localize("MNBL.demonTypeSavoir"),
protection: game.i18n.localize("MNBL.demonTypeProtection"),
voyage: game.i18n.localize("MNBL.demonTypeVoyage"),
},
demonPuissanceOptions: {
"": game.i18n.localize("MNBL.demonPuissanceNone"),
mineur: game.i18n.localize("MNBL.demonPuissanceMineur"),
median: game.i18n.localize("MNBL.demonPuissanceMedian"),
majeur: game.i18n.localize("MNBL.demonPuissanceMajeur"),
},
elementTypeOptions: {
air: game.i18n.localize("MNBL.elementTypeAir"),
terre: game.i18n.localize("MNBL.elementTypeTerre"),
feu: game.i18n.localize("MNBL.elementTypeFeu"),
eau: game.i18n.localize("MNBL.elementTypeEau"),
},
automatonTypeOptions: {
"": game.i18n.localize("MNBL.automatonTypeNone"),
combat: game.i18n.localize("MNBL.automatonTypeCombat"),
voyage: game.i18n.localize("MNBL.automatonTypeVoyage"),
perception: game.i18n.localize("MNBL.automatonTypePerception"),
restauration: game.i18n.localize("MNBL.automatonTypeRestauration"),
reparateur: game.i18n.localize("MNBL.automatonTypeReparateur"),
},
automatonPuissanceOptions: {
"": game.i18n.localize("MNBL.automatonPuissanceNone"),
mineur: game.i18n.localize("MNBL.automatonPuissanceMineur"),
median: game.i18n.localize("MNBL.automatonPuissanceMedian"),
majeur: game.i18n.localize("MNBL.automatonPuissanceMajeur"),
},
automatonVoyageTypeOptions: {
"": game.i18n.localize("MNBL.automatonVoyageTypeNone"),
terrestre: game.i18n.localize("MNBL.automatonVoyageTypeTerrestre"),
aquatique: game.i18n.localize("MNBL.automatonVoyageTypeAquatique"),
aerien: game.i18n.localize("MNBL.automatonVoyageTypeAerien"),
extradimensionnel: game.i18n.localize("MNBL.automatonVoyageTypeExtradimensionnel"),
}
}
return MOURNBLADE_CONFIG;
}
};
+23 -18
View File
@@ -4,14 +4,14 @@ import { MournbladeUtility } from "./mournblade-utility.js";
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class MournbladeItemSheet extends ItemSheet {
export class MournbladeItemSheet extends foundry.appv1.sheets.ItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade", "sheet", "item"],
template: "systems/fvtt-mournblade/templates/item-sheet.html",
template: "systems/fvtt-mournblade/templates/item-sheet.hbs",
dragDrop: [{ dragSelector: null, dropSelector: null }],
width: 620,
height: 550
@@ -48,8 +48,8 @@ export class MournbladeItemSheet extends ItemSheet {
/* -------------------------------------------- */
async getData() {
const objectData = duplicate(this.object)
let itemData = objectData
const objectData = foundry.utils.duplicate(this.object)
let itemData = objectData
let formData = {
title: this.title,
id: this.id,
@@ -63,10 +63,15 @@ export class MournbladeItemSheet extends ItemSheet {
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, {async: true}),
config: game.system.mournblade.config,
mr: (this.object.type == 'specialisation'),
isGM: game.user.isGM
}
if ( objectData.type == "don") {
formData.sacrifice = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.sacrifice, {async: true})
}
//this.options.editable = !(this.object.origin == "embeddedItem");
console.log("ITEM DATA", formData, this);
return formData;
@@ -86,7 +91,7 @@ export class MournbladeItemSheet extends ItemSheet {
/* -------------------------------------------- */
postItem() {
let chatData = duplicate(MournbladeUtility.data(this.item));
let chatData = foundry.utils.duplicate(MournbladeUtility.data(this.item));
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
@@ -101,7 +106,7 @@ export class MournbladeItemSheet extends ItemSheet {
payload: chatData,
});
renderTemplate('systems/fvtt-Mournblade-rpg/templates/post-item.html', chatData).then(html => {
foundry.applications.handlebars.renderTemplate('systems/fvtt-mournblade/templates/post-item.hbs', chatData).then(html => {
let chatOptions = MournbladeUtility.chatDataSetup(html);
ChatMessage.create(chatOptions)
});
@@ -129,28 +134,28 @@ export class MournbladeItemSheet extends ItemSheet {
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)
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].name = ev.currentTarget.value
this.object.update( { 'data.predilections': pred })
this.object.update( { 'system.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)
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred.splice(index,1)
this.object.update( { 'data.predilections': pred })
this.object.update( { 'system.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)
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].used = ev.currentTarget.checked
this.object.update( { 'data.predilections': pred })
})
this.object.update( { 'system.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 })
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred.push( { name: "Nouvelle prédilection", id: randomID(), used: false })
this.object.update( { 'system.predilections': pred })
})
// Update Inventory Item
html.find('.item-delete').click(ev => {
@@ -164,7 +169,7 @@ export class MournbladeItemSheet extends ItemSheet {
/* -------------------------------------------- */
get template() {
let type = this.item.type;
return `systems/fvtt-mournblade/templates/item-${type}-sheet.html`;
return `systems/fvtt-mournblade/templates/item-${type}-sheet.hbs`;
}
/* -------------------------------------------- */
+15 -12
View File
@@ -1,18 +1,21 @@
import { MournbladeUtility } from "./mournblade-utility.js";
export const defaultItemImg = {
competence: "systems/fvtt-mournblade/assets/icons/competence.webp",
arme: "systems/fvtt-mournblade/assets/icons/arme.webp",
capacite: "systems/fvtt-mournblade/assets/icons/capacite.webp",
don: "systems/fvtt-mournblade/assets/icons/don.webp",
equipement: "systems/fvtt-mournblade/assets/icons/equipement.webp",
monnaie: "systems/fvtt-mournblade/assets/icons/monnaie.webp",
pacte: "systems/fvtt-mournblade/assets/icons/pacte.webp",
predilection: "systems/fvtt-mournblade/assets/icons/predilection.webp",
protection: "systems/fvtt-mournblade/assets/icons/protection.webp",
rune: "systems/fvtt-mournblade/assets/icons/rune.webp",
tendance: "systems/fvtt-mournblade/assets/icons/tendance.webp",
traitchaotique: "systems/fvtt-mournblade/assets/icons/traitchaotique.webp",
competence: "systems/fvtt-mournblade/assets/icons/competence.webp",
arme: "systems/fvtt-mournblade/assets/icons/arme.webp",
capacite: "systems/fvtt-mournblade/assets/icons/capacite.webp",
don: "systems/fvtt-mournblade/assets/icons/don.webp",
equipement: "systems/fvtt-mournblade/assets/icons/equipement.webp",
monnaie: "systems/fvtt-mournblade/assets/icons/monnaie.webp",
pacte: "systems/fvtt-mournblade/assets/icons/pacte.webp",
predilection: "systems/fvtt-mournblade/assets/icons/predilection.webp",
protection: "systems/fvtt-mournblade/assets/icons/protection.webp",
rune: "systems/fvtt-mournblade/assets/icons/rune.webp",
runeeffect: "systems/fvtt-mournblade/assets/icons/rune.webp",
potion: "systems/fvtt-mournblade/assets/icons/potion.webp",
tendance: "systems/fvtt-mournblade/assets/icons/tendance.webp",
traitchaotique: "systems/fvtt-mournblade/assets/icons/traitchaotique.webp",
traitespece: "systems/fvtt-mournblade/assets/icons/capacite.webp",
}
/**
+108 -25
View File
@@ -10,11 +10,19 @@
// Import Modules
import { MournbladeActor } from "./mournblade-actor.js";
import { MournbladeItemSheet } from "./mournblade-item-sheet.js";
import { MournbladeActorSheet } from "./mournblade-actor-sheet.js";
//import { MournbladeNPCSheet } from "./mournblade-npc-sheet.js";
// import { MournbladeActorSheet } from "./mournblade-actor-sheet.js";
// import { MournbladeCreatureSheet } from "./mournblade-creature-sheet.js";
import { MournbladeUtility } from "./mournblade-utility.js";
import { MournbladeCombat } from "./mournblade-combat.js";
import { MournbladeItem } from "./mournblade-item.js";
import { MournbladeConfig } from "./mournblade-config.js";
// Import DataModels
import * as models from "./models/index.mjs";
// Import AppV2 Item Sheets
import * as sheets from "./applications/sheets/_module.mjs";
import { MournbladeRollDialog } from "./mournblade-roll-dialog.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
@@ -29,14 +37,14 @@ Hooks.once("init", async function () {
MournbladeUtility.preloadHandlebarsTemplates();
/* -------------------------------------------- */
// Set an initiative formula for the system
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1d6",
decimals: 1
};
/* -------------------------------------------- */
game.socket.on("system.fvtt-mournblade-rpg", data => {
game.socket.on("system.fvtt-mournblade", data => {
MournbladeUtility.onSocketMesssage(data);
});
@@ -44,53 +52,129 @@ Hooks.once("init", async function () {
// Define custom Entity classes
CONFIG.Combat.documentClass = MournbladeCombat
CONFIG.Actor.documentClass = MournbladeActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
creature: models.CreatureDataModel,
}
CONFIG.Item.documentClass = MournbladeItem
game.system.mournblade = { }
CONFIG.Item.dataModels = {
arme: models.ArmeDataModel,
bouclier: models.BouclierDataModel,
capacite: models.CapaciteDataModel,
competence: models.CompetenceDataModel,
don: models.DonDataModel,
equipement: models.EquipementDataModel,
heritage: models.HeritageDataModel,
metier: models.MetierDataModel,
modifier: models.ModifierDataModel,
monnaie: models.MonnaieDataModel,
origine: models.OrigineDataModel,
pacte: models.PacteDataModel,
protection: models.ProtectionDataModel,
rune: models.RuneDataModel,
runeeffect: models.RuneEffectDataModel,
potion: models.PotionDataModel,
tendance: models.TendanceDataModel,
traitchaotique: models.TraitChaotiqueDataModel,
traitespece: models.TraitEspeceDataModel
}
game.system.mournblade = {
config : MournbladeConfig.getConfig(),
models,
sheets,
MournbladeRollDialog
}
/* -------------------------------------------- */
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("fvtt-mournblade", MournbladeActorSheet, { types: ["personnage"], makeDefault: true })
//Actors.registerSheet("fvtt-mournblade", MournbladeNPCSheet, { types: ["npc"], makeDefault: false });
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade", sheets.MournbladePersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade", sheets.MournbladeCreatureSheet, { types: ["creature"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("fvtt-mournblade", MournbladeItemSheet, { makeDefault: true })
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeArmeSheet, { types: ["arme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeBouclierSheet, { types: ["bouclier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeCapaciteSheet, { types: ["capacite"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeCompetenceSheet, { types: ["competence"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeDonSheet, { types: ["don"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeHeritageSheet, { types: ["heritage"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeMetierSheet, { types: ["metier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeModifierSheet, { types: ["modifier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeMonnaieSheet, { types: ["monnaie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeOrigineSheet, { types: ["origine"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladePacteSheet, { types: ["pacte"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeProtectionSheet, { types: ["protection"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeRuneSheet, { types: ["rune"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeRuneEffectSheet, { types: ["runeeffect"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladePotionSheet, { types: ["potion"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeTendanceSheet, { types: ["tendance"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeTraitChaotiqueSheet, { types: ["traitchaotique"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-mournblade", sheets.MournbladeTraitEspeceSheet, { types: ["traitespece"], makeDefault: true });
MournbladeUtility.init();
});
/* -------------------------------------------- */
function welcomeMessage() {
ChatMessage.create({
async function welcomeMessage() {
const templateData = {};
const html = await foundry.applications.handlebars.renderTemplate("systems/fvtt-mournblade/templates/chat-welcome-message.hbs", templateData);
await ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-Mournblade"><span class="rdd-roll-part">
<strong>Bienvenue dans les Jeunes Royaumes de Mournblade !</strong>
` });
content: html
});
}
/* -------------------------------------------- */
async function importDefaultScene() {
let exists = game.scenes.find(j => j.name == "Accueil");
if (!exists) {
const scenes = await MournbladeUtility.loadCompendium("fvtt-mournblade.scenes")
let newDocuments = scenes.filter(i => i.name == "Accueil");
await game.scenes.documentClass.create(newDocuments);
game.scenes.find(i => i.name == "Accueil").activate();
}
}
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.once("ready", function () {
Hooks.once("ready", async function () {
game.system.mournblade = {
config : MournbladeConfig.getConfig(),
}
MournbladeUtility.ready();
// User warning
if (!game.user.isGM && game.user.character == undefined) {
ui.notifications.info("Attention ! Aucun personnage n'est relié au joueur !");
ChatMessage.create({
await ChatMessage.create({
content: "<b>ATTENTION</b> Le joueur " + game.user.name + " n'est relié à aucun personnage !",
user: game.user._id
user: game.user.id
});
}
// CSS patch for v9
if (game.version) {
let sidebar = document.getElementById("sidebar");
sidebar.style.width = "min-content";
if (!game.user.isGM && game.user.character && !game.user.character.prototypeToken.actorLink) {
ui.notifications.info("Le token de du joueur n'est pas connecté à l'acteur !");
await 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.")
)
importDefaultScene();
welcomeMessage();
});
@@ -107,4 +191,3 @@ Hooks.on("chatMessage", (html, content, msg) => {
}
return true;
});
+82 -41
View File
@@ -1,79 +1,120 @@
import { MournbladeUtility } from "./mournblade-utility.js";
export class MournbladeRollDialog extends Dialog {
export class MournbladeRollDialog extends Application {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let options = { classes: ["MournbladeDialog"], width: 340, height: 420, 'z-index': 99999 };
let html = await renderTemplate('systems/fvtt-mournblade/templates/roll-dialog-generic.html', rollData);
return new MournbladeRollDialog(actor, rollData, html, options );
return new MournbladeRollDialog(actor, rollData);
}
/* -------------------------------------------- */
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
constructor(actor, rollData, options = {}) {
super(options);
this.actor = actor;
this.rollData = rollData;
}
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade", "sheet", "item"],
template: "systems/fvtt-mournblade/templates/roll-dialog-generic.hbs",
width: 400,
height: "auto",
title: "Test de Capacité"
});
}
getData() {
const data = foundry.utils.duplicate(this.rollData);
if (!data.config) {
data.config = game.system.mournblade.config;
}
return data;
}
super(conf, options);
this.actor = actor
this.rollData = rollData
_onCancel() {
this.close();
}
/* -------------------------------------------- */
roll ( dice) {
this.rollData.mainDice = dice
MournbladeUtility.rollMournblade( this.rollData )
_onRoll(dice) {
this.rollData.mainDice = dice;
MournbladeUtility.rollMournblade(this.rollData);
this.close();
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
// Roll buttons
html.find(".rolld10").click(this._onRoll.bind(this, "1d10"));
html.find(".rolld20").click(this._onRoll.bind(this, "1d20"));
html.find(".cancel").click(this._onCancel.bind(this));
var dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('.apply-modifier').change(async (event) => {
let modifierIdx = $(event.currentTarget).data("modifier-idx")
let modifier = this.rollData.modifiers[modifierIdx]
modifier.system.apply = event.currentTarget.checked
})
html.find('#modificateur').change(async (event) => {
this.rollData.modificateur = Number(event.currentTarget.value)
})
html.find('#typeAttaque').change(async (event) => {
this.rollData.typeAttaque = String(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('#isMonte').change(async (event) => {
this.rollData.desavantages.isMonte = event.currentTarget.checked
})
html.find('#cibleausol').change(async (event) => {
this.rollData.desavantages.cibleausol = event.currentTarget.checked
})
html.find('#cibledesarmee').change(async (event) => {
this.rollData.desavantages.cibledesarmee = event.currentTarget.checked
})
html.find('#ciblerestreint').change(async (event) => {
this.rollData.desavantages.ciblerestreint = event.currentTarget.checked
})
html.find('#cibleimmobilisée').change(async (event) => {
this.rollData.desavantages.cibleimmobilisée = event.currentTarget.checked
})
html.find('#ciblesurplomb').change(async (event) => {
this.rollData.desavantages.ciblesurplomb = event.currentTarget.checked
})
html.find('#doubleD20').change(async (event) => {
this.rollData.doubleD20 = event.currentTarget.checked
})
})
html.find('#visee').change(async (event) => {
this.rollData.visee = event.currentTarget.checked
})
html.find('#cibleconsciente').change(async (event) => {
this.rollData.cibleconsciente = event.currentTarget.checked
})
html.find('#ciblecourt').change(async (event) => {
this.rollData.ciblecourt = event.currentTarget.checked
})
html.find('#typeCouvert').change(async (event) => {
this.rollData.typeCouvert = String(event.currentTarget.value)
})
}
}
File diff suppressed because it is too large Load Diff
+5707
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
{
"name": "fvtt-mournblade",
"version": "1.0.0",
"description": "Mournblade RPG for FoundryVTT - French",
"scripts": {
"build": "gulp build",
"watch": "gulp watch"
},
"author": "Uberwald/LeRatierBretonnien",
"license": "SEE LICENSE IN LICENCE.txt",
"devDependencies": {
"@foundryvtt/foundryvtt-cli": "^3.0.3",
"gulp": "^4.0.2",
"gulp-less": "^5.0.0",
"gulp-sourcemaps": "^3.0.0"
}
}
+46 -42
View File
@@ -1,42 +1,46 @@
{"name":"Fouet","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contactjet","bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d4 + 1","deuxmains":false,"courte":3,"moyenne":0,"longue":0,"tr":0,"rarete":4,"prix":40},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Qb1h25U88mTM4h72"}},"_id":"0swiE8k5zfUIqmXu"}
{"_id":"2BSVJXr0FGZZJ9fh","name":"Hache des mers","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"2d6","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":150},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.mGYFgteaWBDo10Xb"}}}
{"_id":"2qezkR1BdC0DcRIl","name":"Fléau darmes","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D10+1","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":70},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.PHQabMgfk1GNOjKo"}}}
{"name":"Arbalète","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"tir","bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"2d6","deuxmains":false,"courte":25,"moyenne":50,"longue":75,"tr":3,"rarete":10,"prix":500},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.WYp8ojQN8HYJwUJc"}},"_id":"2zIdrBK88iuMbOF0"}
{"_id":"3Iv1oLpZcPEJVaI4","name":"Cimeterre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":1,"degats":"1d10","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":100},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.RYxEg0gJfAlIZ4mw"}}}
{"name":"Fronde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"tir","bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D4","deuxmains":false,"courte":10,"moyenne":25,"longue":50,"tr":1,"rarete":2,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.TgVuNnytO9k2K0Xe"}},"_id":"4To8rLxv4efsoZK0"}
{"name":"Pierre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contactjet","bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D4","deuxmains":false,"courte":3,"moyenne":6,"longue":15,"tr":1,"rarete":0,"prix":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.3YS9IfQddECnoagK"}},"_id":"4yKlK8MeSj5Zk8lM"}
{"_id":"6e1JHoD1Jrz020R7","name":"Grand marteau","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"2D6+2","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":120},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.vjic4cPkJ0iMCIt5"}}}
{"name":"Arc de cavalerie","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"tir","bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d6","deuxmains":false,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":7,"prix":100},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.5EWrgNQjJMx1t2v1"}},"_id":"88UuW1bdpFuCmkqb"}
{"_id":"9Eo9mRbPPZHN98Cr","name":"Épée de maître","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1d8+ 2","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":120},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.kX3MbDFu9uYZVNf2"}}}
{"_id":"9xPd4ITtyk3nmMoN","name":"Lance ilmioréenne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"<p>La lance ilmior&eacute;enne est un solide b&acirc;ton de combat surmont&eacute; d&rsquo;une pointe sur lequel est fi x&eacute; quasi perpendiculairement un fer de hache plat et &eacute;troit. Un contrepoids fi x&eacute; &agrave; l&rsquo;autre extr&eacute;mit&eacute; sert &agrave; &eacute;quilibrer l&rsquo;arme qui peut &ecirc;tre mani&eacute;e &agrave; la fois comme une lance et comme une hache &agrave; deux mains, ce qui permet de maintenir ses ennemis &agrave; distance ou de leur porter des coups d&eacute;vastateurs. Ces lances furent invent&eacute;es &agrave; l&rsquo;&eacute;poque de la tribu d&rsquo;Ilm, quand les lances et &eacute;p&eacute;es &eacute;taient d&eacute;clar&eacute;es ill&eacute;gales par les gouverneurs melnibon&eacute;ens de la r&eacute;gion. Certaines &eacute;coles militaires ilmior&eacute;ennes enseignent toujours le Style Imar, qui m&ecirc;le en une seule et m&ecirc;me technique le combat &agrave; deux mains &agrave; la lance et le combat au b&acirc;ton.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":2,"degats":"1d8 + 2","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":100},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.8ApbiVgkx79OHVp2"}}}
{"_id":"CeC5lAKs7NdPF05l","name":"Masse lourde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d10 + 1","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":80},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.DPfXgFj3gpjJ3nbR"}}}
{"_id":"CqP80SQFQOefpGgd","name":"Gourdin","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1D6","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":5},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.CBIfq9eRd2RmoI4H"}}}
{"_id":"CupT9m8L909l4Fo5","name":"Rapière","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":3,"bonusmaniementdef":1,"degats":"1d8+ 1","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":150},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.XIStcClTnmLoSwyR"}}}
{"name":"Hachette / Hache de lancer","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contactjet","bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d4 + 1","deuxmains":false,"courte":5,"moyenne":10,"longue":15,"tr":1,"rarete":5,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.5NHbRanxXA5vUE3B"}},"_id":"DzkeP7jbruqW4nQH"}
{"_id":"FDeRBkxXwoZIuL9B","name":"Lance légère","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d8","deuxmains":false,"courte":10,"moyenne":25,"longue":50,"tr":1,"rarete":5,"prix":5},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.LIVeq22E9EanRS9L"}}}
{"name":"Arc de chasse","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"tir","bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 - 1","deuxmains":false,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":8,"prix":250},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.TrYZ9BxYukS1mbBX"}},"_id":"Hrm08cpgAMMV6Jpq"}
{"_id":"Iw3j4oC6H8HJ9MFQ","name":"Coup de pied / poing / tête","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":3,"bonusmaniementdef":0,"degats":"1d4","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.nBPKHsYIM0HgMcBy"}}}
{"_id":"KY22L6Lx5WxgvyGD","name":"Lance lourde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":2,"degats":"1d10","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Kcl07bs7TOcne37W"}}}
{"name":"Arc du Désert des Larmes","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"<p>Cet arc composite fait de bois, d&rsquo;andouillers de cerf et/ou d&rsquo;os est un arc court et tr&egrave;s recourb&eacute; utilis&eacute; par les barbares du D&eacute;sert des Larmes pour la chasse. On peut &eacute;galement trouver ce type d&rsquo;arcs &agrave; Pikarayd et au Dorel.</p>","typearme":"tir","bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 + 1","deuxmains":false,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":8,"prix":250},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.QmYjdRCAk3MnxKJg"}},"_id":"MDpFpqmXpIX5VV80"}
{"_id":"MP49mYF7FVuW9ALB","name":"Hache de bataille dite lormyrienne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"<p>Cette double hache parfaitement &eacute;quilibr&eacute;e et proportionn&eacute;e est l&rsquo;arme favorite des chevaliers lormyriens. Il s&rsquo;agit de l&rsquo;arme id&eacute;ale pour trancher, d&eacute;couper les lances ennemies et fendre les cr&acirc;nes.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"2d6 + 2","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":250},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Lpa6kesbzgKVtogn"}}}
{"_id":"N5BbO2lGwzQnpvH0","name":"Couteau / Dague","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contactjet","isdefense":true,"bonusmaniementoff":3,"bonusmaniementdef":0,"degats":"1d4","deuxmains":false,"courte":3,"moyenne":6,"longue":15,"tr":1,"rarete":1,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.5a9GKIvNgbbXBq3s"}}}
{"_id":"QwrZbFeJUQv2OBqI","name":"Pique filkharienne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"<p>Cette longue pique &agrave; grande lame de pr&egrave;s de trois m&egrave;tres de long est l&rsquo;arme de pr&eacute;dilection des piquiers de l&rsquo;arm&eacute;e de Filkhar. Tr&egrave;s effi cace pour lutter contre des cavaliers ou des ennemis en train de charger, elle l&rsquo;est beaucoup moins en combat rapproch&eacute;.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d8 + 2","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":150},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.dF9LxfA1crZBmT92"}}}
{"_id":"SzgkzHvzma8NiMd5","name":"Masse légère","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 + 2","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":5},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.xrWRNZBrEzkUQzRG"}}}
{"_id":"U8CoqFhGuT3ZHeq1","name":"Sabre dabordage","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d8","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":50},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.aNUqO9OZT0GBszvv"}}}
{"name":"Javelot","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"jet","bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d6","deuxmains":false,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":5,"prix":30},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Zwiz9E3TbeUP8qHH"}},"_id":"aELvOR7A9DIJKgBk"}
{"_id":"aJBVfqQ3JQNyVL7c","name":"Fourche / Faux de paysan","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D6","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":1,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.K5Ayim6mPKZoCSCc"}}}
{"_id":"c6l49ZmEBjUxfI3W","name":"Arme improvisée","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d6","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":0},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.E7gcmnNQK2t5OQjv"}}}
{"name":"Arc de guerre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"tir","bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d10 + 1","deuxmains":false,"courte":30,"moyenne":50,"longue":100,"tr":1,"rarete":6,"prix":70},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Tq6lUFPTkBN5cBwn"}},"_id":"cKf5z3fajUnxJh0r"}
{"_id":"fLHX6ut131CQI630","name":"Main gauche","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":3,"degats":"1d4 + 1","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":10},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.BBii0qi1unvSRNO4"}}}
{"_id":"freCCeiYGfWmUAQU","name":"Marteau de guerre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d8 + 2","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":6,"prix":70},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.6n2uSdYfRAsSqQnA"}}}
{"_id":"j5659PJlrHz56V1k","name":"Sabre de Pan Tang","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"<p>Ce long sabre effi l&eacute; sert aux Cavaliers d&eacute;moniaques de Pan Tang pour couper les t&ecirc;tes depuis le dos de leurs monstrueuses montures reptiliennes &agrave; six pattes.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d10 + 2","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":120},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.l3ffzso7GyHMKguD"}}}
{"_id":"lYtm5MwP96numskx","name":"Hallebarde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":2,"degats":"2d6","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":150},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.ZTdyIoMzE9kqm4e6"}}}
{"_id":"mQGT0vmDjoTZCW5u","name":"Lance melnibonéenne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"<p>La lance melnibon&eacute;enne, faite pour le combat &agrave; cheval ou &agrave; dos de dragon, ressemble &agrave; une lance ordinaire. Son fer est cependant plus fi n et plus &eacute;troit. Avant la chute d&rsquo;Imrryr, il est quasi impossible de se procurer de telles armes sur les march&eacute;s des Jeunes Royaumes. Elles peuvent &ecirc;tre mani&eacute;es &agrave; une main comme lors des joutes &agrave; cheval ou &agrave; deux mains au c&oelig;ur des m&ecirc;l&eacute;es.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":2,"degats":"2d6+ 4","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":10,"prix":70},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.KAl5qXHeYDoYRU6G"}}}
{"name":"Arc en os","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"<p>Arc composite recourb&eacute; fait de bois, d&rsquo;os et d&rsquo;acier, l&rsquo;arc en os melnibon&eacute;en est une arme extraordinairement puissante entre les mains d&rsquo;un guerrier melnibon&eacute;en ou d&rsquo;un archer esclave du Glorieux Empire. On peut se le procurer dans les Jeunes Royaumes apr&egrave;s la chute d&rsquo;Imrryr.</p>","typearme":"tir","bonusmaniementoff":3,"bonusmaniementdef":0,"degats":"1d8 + 1","deuxmains":false,"courte":30,"moyenne":60,"longue":125,"tr":2,"rarete":10,"prix":500},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Dy9dzVhXbNAVm3zn"}},"_id":"nzve5qucVN6FC5wn"}
{"_id":"sHj90bPcMaDlOw51","name":"Bâton ferré ou lesté","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":2,"degats":"1D8","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":1},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.zoBHSbvJPnjbtPh6"}}}
{"_id":"sju2r73hUHyGbHYg","name":"Épée courte / Glaive","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1D6+1","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":4,"prix":40},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.Mxv5gMHRvRrsuhPA"}}}
{"_id":"t2uQcWe7kirUOp3D","name":"Faucheur","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1d4 + 1","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":100},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.OlmzE29fh9DFd8TF"}}}
{"_id":"uatjBTFCE5ZCqhAd","name":"Fléau lourd","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d10+ 3","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":150},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.vDDw0qqM1Kg7Pu6T"}}}
{"_id":"v7ymzlEALvsk0poA","name":"Hache","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 + 2","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":2,"prix":30},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.2MwgptY2kE4tgYBe"}}}
{"_id":"wWk0Wfer06Ttmouv","name":"Épée large","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1d6+ 2","deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":70},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.iuAizxCvnUxmsjrn"}}}
{"_id":"wv5EiePmPTpqFutt","name":"Épée longue","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","data":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"2d6 + 1","deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":250},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"RiMAsQHaUMojde7N":3},"flags":{"core":{"sourceId":"Item.yiYtZ0sCUpMihvzz"}}}
{"name":"Fouet","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.Qb1h25U88mTM4h72"}},"_id":"0swiE8k5zfUIqmXu","system":{"description":"","typearme":"contactjet","isdefense":false,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d4 + 1","nonletaux":true,"deuxmains":false,"courte":3,"moyenne":0,"longue":0,"tr":0,"rarete":4,"prix":40,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964292,"modifiedTime":1664573659387,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"2BSVJXr0FGZZJ9fh","name":"Hache des mers","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.mGYFgteaWBDo10Xb"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"2d6","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":150,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964293,"modifiedTime":1664573659388,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Ecu d'acier","type":"bouclier","img":"systems/fvtt-mournblade/assets/icons/protection.webp","system":{"description":"","bonusdefense":3,"degats":"1d8","nonletaux":"false","rarete":9,"prix":10,"equipped":false,"isdefense":null},"effects":[],"ownership":{"default":0,"1Hr6ON03ooIXzvIm":3},"flags":{"core":{"sourceId":"Item.Gn52Vfp6Vg4cnpd0"}},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572521380,"modifiedTime":1664573706183,"lastModifiedBy":"1Hr6ON03ooIXzvIm"},"folder":null,"sort":0,"_id":"2PmIgKP4wZLHGp0s"}
{"_id":"2qezkR1BdC0DcRIl","name":"Fléau darmes","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.PHQabMgfk1GNOjKo"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D10+1","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":70,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964293,"modifiedTime":1664573659387,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Arbalète","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.WYp8ojQN8HYJwUJc"}},"_id":"2zIdrBK88iuMbOF0","system":{"description":"","typearme":"tir","isdefense":null,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"2d6","nonletaux":null,"deuxmains":"","courte":25,"moyenne":50,"longue":75,"tr":3,"rarete":10,"prix":500,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964293,"modifiedTime":1664573659385,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"3Iv1oLpZcPEJVaI4","name":"Cimeterre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.RYxEg0gJfAlIZ4mw"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":1,"degats":"1d10","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":100,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Fronde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.TgVuNnytO9k2K0Xe"}},"_id":"4To8rLxv4efsoZK0","system":{"description":"","typearme":"tir","isdefense":false,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D4","nonletaux":false,"deuxmains":false,"courte":10,"moyenne":25,"longue":50,"tr":1,"rarete":2,"prix":1,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659388,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Pierre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.3YS9IfQddECnoagK"}},"_id":"4yKlK8MeSj5Zk8lM","system":{"description":"","typearme":"contactjet","isdefense":false,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D4","nonletaux":true,"deuxmains":false,"courte":3,"moyenne":6,"longue":15,"tr":1,"rarete":0,"prix":0,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659390,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"6e1JHoD1Jrz020R7","name":"Grand marteau","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.vjic4cPkJ0iMCIt5"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"2D6+2","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":120,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659388,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Arc de cavalerie","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.5EWrgNQjJMx1t2v1"}},"_id":"88UuW1bdpFuCmkqb","system":{"description":"","typearme":"tir","isdefense":null,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d6","nonletaux":null,"deuxmains":null,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":7,"prix":100,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659385,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"9Eo9mRbPPZHN98Cr","name":"Épée de maître","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.kX3MbDFu9uYZVNf2"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1d8+ 2","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":120,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"9xPd4ITtyk3nmMoN","name":"Lance ilmioréenne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.8ApbiVgkx79OHVp2"}},"system":{"description":"<p>La lance ilmior&eacute;enne est un solide b&acirc;ton de combat surmont&eacute; d&rsquo;une pointe sur lequel est fi x&eacute; quasi perpendiculairement un fer de hache plat et &eacute;troit. Un contrepoids fi x&eacute; &agrave; l&rsquo;autre extr&eacute;mit&eacute; sert &agrave; &eacute;quilibrer l&rsquo;arme qui peut &ecirc;tre mani&eacute;e &agrave; la fois comme une lance et comme une hache &agrave; deux mains, ce qui permet de maintenir ses ennemis &agrave; distance ou de leur porter des coups d&eacute;vastateurs. Ces lances furent invent&eacute;es &agrave; l&rsquo;&eacute;poque de la tribu d&rsquo;Ilm, quand les lances et &eacute;p&eacute;es &eacute;taient d&eacute;clar&eacute;es ill&eacute;gales par les gouverneurs melnibon&eacute;ens de la r&eacute;gion. Certaines &eacute;coles militaires ilmior&eacute;ennes enseignent toujours le Style Imar, qui m&ecirc;le en une seule et m&ecirc;me technique le combat &agrave; deux mains &agrave; la lance et le combat au b&acirc;ton.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":2,"degats":"1d8 + 2","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":100,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"CeC5lAKs7NdPF05l","name":"Masse lourde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.DPfXgFj3gpjJ3nbR"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d10 + 1","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":80,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"CqP80SQFQOefpGgd","name":"Gourdin","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.CBIfq9eRd2RmoI4H"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1D6","nonletaux":true,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":5,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964294,"modifiedTime":1664573659388,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"CupT9m8L909l4Fo5","name":"Rapière","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.XIStcClTnmLoSwyR"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":3,"bonusmaniementdef":1,"degats":"1d8+ 1","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":150,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964295,"modifiedTime":1664573659390,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Hachette / Hache de lancer","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.5NHbRanxXA5vUE3B"}},"_id":"DzkeP7jbruqW4nQH","system":{"description":"","typearme":"contactjet","isdefense":false,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d4 + 1","nonletaux":false,"deuxmains":false,"courte":5,"moyenne":10,"longue":15,"tr":1,"rarete":5,"prix":50,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964295,"modifiedTime":1664573659388,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"FDeRBkxXwoZIuL9B","name":"Lance légère","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.LIVeq22E9EanRS9L"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d8","nonletaux":false,"deuxmains":false,"courte":10,"moyenne":25,"longue":50,"tr":1,"rarete":5,"prix":5,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964295,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Arc de chasse","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.TrYZ9BxYukS1mbBX"}},"_id":"Hrm08cpgAMMV6Jpq","system":{"description":"","typearme":"tir","isdefense":false,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 - 1","nonletaux":false,"deuxmains":false,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":8,"prix":250,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964295,"modifiedTime":1664573659385,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"Iw3j4oC6H8HJ9MFQ","name":"Coup de pied / poing / tête","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.nBPKHsYIM0HgMcBy"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":3,"bonusmaniementdef":0,"degats":"1d4","nonletaux":true,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":0,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964295,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"KY22L6Lx5WxgvyGD","name":"Lance lourde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.Kcl07bs7TOcne37W"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":2,"degats":"1d10","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":50,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964295,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Arc du Désert des Larmes","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.QmYjdRCAk3MnxKJg"}},"_id":"MDpFpqmXpIX5VV80","system":{"description":"<p>Cet arc composite fait de bois, d&rsquo;andouillers de cerf et/ou d&rsquo;os est un arc court et tr&egrave;s recourb&eacute; utilis&eacute; par les barbares du D&eacute;sert des Larmes pour la chasse. On peut &eacute;galement trouver ce type d&rsquo;arcs &agrave; Pikarayd et au Dorel.</p>","typearme":"tir","isdefense":null,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 + 1","nonletaux":null,"deuxmains":null,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":8,"prix":250,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964296,"modifiedTime":1664573659385,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"MP49mYF7FVuW9ALB","name":"Hache de bataille dite lormyrienne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.Lpa6kesbzgKVtogn"}},"system":{"description":"<p>Cette double hache parfaitement &eacute;quilibr&eacute;e et proportionn&eacute;e est l&rsquo;arme favorite des chevaliers lormyriens. Il s&rsquo;agit de l&rsquo;arme id&eacute;ale pour trancher, d&eacute;couper les lances ennemies et fendre les cr&acirc;nes.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"2d6 + 2","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":250,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964296,"modifiedTime":1664573659388,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"N5BbO2lGwzQnpvH0","name":"Couteau / Dague","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.5a9GKIvNgbbXBq3s"}},"system":{"description":"","typearme":"contactjet","isdefense":true,"bonusmaniementoff":3,"bonusmaniementdef":0,"degats":"1d4","nonletaux":false,"deuxmains":false,"courte":3,"moyenne":6,"longue":15,"tr":1,"rarete":1,"prix":10,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964297,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"QwrZbFeJUQv2OBqI","name":"Pique filkharienne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.dF9LxfA1crZBmT92"}},"system":{"description":"<p>Cette longue pique &agrave; grande lame de pr&egrave;s de trois m&egrave;tres de long est l&rsquo;arme de pr&eacute;dilection des piquiers de l&rsquo;arm&eacute;e de Filkhar. Tr&egrave;s effi cace pour lutter contre des cavaliers ou des ennemis en train de charger, elle l&rsquo;est beaucoup moins en combat rapproch&eacute;.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d8 + 2","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":150,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964297,"modifiedTime":1664573659390,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"SzgkzHvzma8NiMd5","name":"Masse légère","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.xrWRNZBrEzkUQzRG"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 + 2","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":5,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964297,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"U8CoqFhGuT3ZHeq1","name":"Sabre dabordage","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.aNUqO9OZT0GBszvv"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d8","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":50,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964297,"modifiedTime":1664573659390,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Javelot","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.Zwiz9E3TbeUP8qHH"}},"_id":"aELvOR7A9DIJKgBk","system":{"description":"","typearme":"jet","isdefense":false,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d6","nonletaux":false,"deuxmains":false,"courte":25,"moyenne":50,"longue":75,"tr":1,"rarete":5,"prix":30,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964297,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"aJBVfqQ3JQNyVL7c","name":"Fourche / Faux de paysan","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.K5Ayim6mPKZoCSCc"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1D6","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":1,"prix":10,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964298,"modifiedTime":1664573659387,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Pavois","type":"bouclier","img":"systems/fvtt-mournblade/assets/icons/protection.webp","system":{"description":"<p>Le Pavois se plante dans le sol au d&eacute;but du combat et ne peut plus &ecirc;tre d&eacute;plac&eacute; jusqu'&agrave; son issue.</p>","bonusdefense":3,"degats":"0","nonletaux":"false","rarete":7,"prix":5,"equipped":false,"isdefense":"false"},"effects":[],"ownership":{"default":0,"1Hr6ON03ooIXzvIm":3},"flags":{"core":{"sourceId":"Item.E3A8EnGzQctvYndd"}},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572521380,"modifiedTime":1664573708981,"lastModifiedBy":"1Hr6ON03ooIXzvIm"},"folder":null,"sort":0,"_id":"b2VNhFBgbvlzaMw8"}
{"name":"Bouclier d'Infanterie","type":"bouclier","img":"systems/fvtt-mournblade/assets/icons/protection.webp","system":{"description":"","bonusdefense":2,"degats":"1d6","nonletaux":"false","rarete":5,"prix":2,"equipped":false},"effects":[],"ownership":{"default":0,"1Hr6ON03ooIXzvIm":3},"flags":{"core":{"sourceId":"Item.jeMLhLTJhTU6TJ58"}},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572521380,"modifiedTime":1664573703190,"lastModifiedBy":"1Hr6ON03ooIXzvIm"},"folder":null,"sort":0,"_id":"bqDEyPj9OlGnEJsr"}
{"_id":"c6l49ZmEBjUxfI3W","name":"Arme improvisée","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.E7gcmnNQK2t5OQjv"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d6","nonletaux":true,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":0,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964298,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Arc de guerre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.Tq6lUFPTkBN5cBwn"}},"_id":"cKf5z3fajUnxJh0r","system":{"description":"","typearme":"tir","isdefense":false,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d10 + 1","nonletaux":false,"deuxmains":false,"courte":30,"moyenne":50,"longue":100,"tr":1,"rarete":6,"prix":70,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964298,"modifiedTime":1664573659385,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Targe","type":"bouclier","img":"systems/fvtt-mournblade/assets/icons/protection.webp","system":{"description":"","bonusdefense":1,"degats":"1d4","nonletaux":"false","rarete":4,"prix":2,"equipped":false},"effects":[],"ownership":{"default":0,"1Hr6ON03ooIXzvIm":3},"flags":{"core":{"sourceId":"Item.9rJkBsRltxH08QT8"}},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572521380,"modifiedTime":1664573712553,"lastModifiedBy":"1Hr6ON03ooIXzvIm"},"folder":null,"sort":0,"_id":"cZorS8WAKNS0dN0T"}
{"_id":"fLHX6ut131CQI630","name":"Main gauche","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.BBii0qi1unvSRNO4"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":3,"degats":"1d4 + 1","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":10,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964298,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"freCCeiYGfWmUAQU","name":"Marteau de guerre","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.6n2uSdYfRAsSqQnA"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":0,"degats":"1d8 + 2","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":6,"prix":70,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964298,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"j5659PJlrHz56V1k","name":"Sabre de Pan Tang","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.l3ffzso7GyHMKguD"}},"system":{"description":"<p>Ce long sabre effi l&eacute; sert aux Cavaliers d&eacute;moniaques de Pan Tang pour couper les t&ecirc;tes depuis le dos de leurs monstrueuses montures reptiliennes &agrave; six pattes.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d10 + 2","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":120,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964299,"modifiedTime":1664573659390,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"lYtm5MwP96numskx","name":"Hallebarde","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.ZTdyIoMzE9kqm4e6"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":0,"bonusmaniementdef":2,"degats":"2d6","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":150,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964299,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"mQGT0vmDjoTZCW5u","name":"Lance melnibonéenne","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.KAl5qXHeYDoYRU6G"}},"system":{"description":"<p>La lance melnibon&eacute;enne, faite pour le combat &agrave; cheval ou &agrave; dos de dragon, ressemble &agrave; une lance ordinaire. Son fer est cependant plus fi n et plus &eacute;troit. Avant la chute d&rsquo;Imrryr, il est quasi impossible de se procurer de telles armes sur les march&eacute;s des Jeunes Royaumes. Elles peuvent &ecirc;tre mani&eacute;es &agrave; une main comme lors des joutes &agrave; cheval ou &agrave; deux mains au c&oelig;ur des m&ecirc;l&eacute;es.</p>","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":2,"degats":"2d6+ 4","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":10,"prix":70,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964299,"modifiedTime":1664573659389,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"name":"Arc en os","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.Dy9dzVhXbNAVm3zn"}},"_id":"nzve5qucVN6FC5wn","system":{"description":"<p>Arc composite recourb&eacute; fait de bois, d&rsquo;os et d&rsquo;acier, l&rsquo;arc en os melnibon&eacute;en est une arme extraordinairement puissante entre les mains d&rsquo;un guerrier melnibon&eacute;en ou d&rsquo;un archer esclave du Glorieux Empire. On peut se le procurer dans les Jeunes Royaumes apr&egrave;s la chute d&rsquo;Imrryr.</p>","typearme":"tir","isdefense":false,"bonusmaniementoff":3,"bonusmaniementdef":0,"degats":"1d8 + 1","nonletaux":false,"deuxmains":false,"courte":30,"moyenne":60,"longue":125,"tr":2,"rarete":10,"prix":500,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964299,"modifiedTime":1664573659385,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"sHj90bPcMaDlOw51","name":"Bâton ferré ou lesté","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.zoBHSbvJPnjbtPh6"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":2,"degats":"1D8","nonletaux":true,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":0,"prix":1,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964299,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"sju2r73hUHyGbHYg","name":"Épée courte / Glaive","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.Mxv5gMHRvRrsuhPA"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1D6+1","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":4,"prix":40,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964300,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"t2uQcWe7kirUOp3D","name":"Faucheur","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.OlmzE29fh9DFd8TF"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1d4 + 1","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":100,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964300,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"uatjBTFCE5ZCqhAd","name":"Fléau lourd","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.vDDw0qqM1Kg7Pu6T"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":1,"bonusmaniementdef":0,"degats":"1d10+ 3","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":7,"prix":150,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964300,"modifiedTime":1664573659387,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"v7ymzlEALvsk0poA","name":"Hache","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.2MwgptY2kE4tgYBe"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"1d6 + 2","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":2,"prix":30,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964300,"modifiedTime":1664573659388,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"wWk0Wfer06Ttmouv","name":"Épée large","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.iuAizxCvnUxmsjrn"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":1,"degats":"1d6+ 2","nonletaux":false,"deuxmains":false,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":5,"prix":70,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964300,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
{"_id":"wv5EiePmPTpqFutt","name":"Épée longue","type":"arme","img":"systems/fvtt-mournblade/assets/icons/arme.webp","effects":[],"folder":null,"sort":0,"flags":{"core":{"sourceId":"Item.yiYtZ0sCUpMihvzz"}},"system":{"description":"","typearme":"contact","isdefense":true,"bonusmaniementoff":2,"bonusmaniementdef":0,"degats":"2d6 + 1","nonletaux":false,"deuxmains":true,"courte":0,"moyenne":0,"longue":0,"tr":0,"rarete":8,"prix":250,"equipped":false},"ownership":{"default":0,"RiMAsQHaUMojde7N":3},"_stats":{"systemId":"fvtt-mournblade","systemVersion":"10.0.9","coreVersion":"10.286","createdTime":1664572964301,"modifiedTime":1664573659386,"lastModifiedBy":"1Hr6ON03ooIXzvIm"}}
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000404
View File
+7
View File
@@ -0,0 +1,7 @@
2026/05/02-23:45:02.647102 7fd747fff6c0 Recovering log #402
2026/05/02-23:45:02.656665 7fd747fff6c0 Delete type=3 #400
2026/05/02-23:45:02.656732 7fd747fff6c0 Delete type=0 #402
2026/05/02-23:46:06.817245 7fd7477fe6c0 Level-0 table #407: started
2026/05/02-23:46:06.817291 7fd7477fe6c0 Level-0 table #407: 0 bytes OK
2026/05/02-23:46:06.852878 7fd7477fe6c0 Delete type=0 #405
2026/05/02-23:46:06.933278 7fd7477fe6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
+7
View File
@@ -0,0 +1,7 @@
2026/05/02-23:32:07.584562 7fd7557ee6c0 Recovering log #398
2026/05/02-23:32:07.681342 7fd7557ee6c0 Delete type=3 #396
2026/05/02-23:32:07.681454 7fd7557ee6c0 Delete type=0 #398
2026/05/02-23:32:54.252126 7fd7477fe6c0 Level-0 table #403: started
2026/05/02-23:32:54.252168 7fd7477fe6c0 Level-0 table #403: 0 bytes OK
2026/05/02-23:32:54.259560 7fd7477fe6c0 Delete type=0 #401
2026/05/02-23:32:54.270552 7fd7477fe6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
Binary file not shown.
View File
View File
Binary file not shown.

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