Compare commits

..

31 Commits

Author SHA1 Message Date
55d7e401c1 Finalize aappv2 data models migration
All checks were successful
Release Creation / build (release) Successful in 1m3s
2026-02-27 14:38:33 +01:00
5e2916202e Finalize aappv2 data models migration 2026-02-27 14:37:22 +01:00
c45837ea31 Finalize aappv2 data models migration 2026-02-27 14:36:54 +01:00
8735b7e4a4 Fix basic system stuff 2026-02-26 21:25:05 +01:00
34db8695d7 Fix tirage de cartes 2025-09-29 19:51:39 +02:00
a109fd6acb Fix tirage de cartes 2025-09-29 19:49:02 +02:00
d647fcc35e Foundry v13 migration 2025-05-01 08:48:02 +02:00
342f9c2342 Fix waring about grid 2025-02-14 13:37:51 +01:00
cd14db85cc Fix v11/v12 2024-05-02 09:29:42 +02:00
ec06f0fdcb Update pour v11/v12 et correction d'un bug sur les defenses 2024-04-26 18:35:39 +02:00
234bd44742 Enhance stats 2024-02-08 12:51:06 +01:00
e0df1f1ff5 Fix CSS pour confrontation et CSS pour DiceTray 2023-10-26 09:25:07 +02:00
2c92dd6ef9 Fix notes + CSS 2023-10-25 00:00:30 +02:00
8af5851246 v10/v11 compatibility 2023-05-25 08:04:31 +02:00
14b536cc52 Fix sur ajout tarots 2023-05-08 23:24:05 +02:00
9944ebe64d Fix sur armes et affichage 2023-03-08 20:24:19 +01:00
165c836f39 Fix sur tarots et message 2023-02-28 13:06:46 +01:00
cb8e70c6c1 Ajout zone libre, macros et tirage de carte 2023-02-26 15:38:44 +01:00
b2a9d8cb75 Gestion armes 2023-02-25 09:38:35 +01:00
6d75c8532c Update README 2023-02-24 15:28:35 +01:00
dce8ad025b Update README 2023-02-24 15:27:49 +01:00
6e4cd71b99 Minot fixes 2023-02-22 23:08:03 +01:00
e62480efb0 Fix tarots 2023-02-20 08:17:21 +01:00
c5509143b1 Sort, confrontation, update tarots, etc 2023-02-08 17:51:16 +01:00
e146c6ba5b Tirage des tarots 2023-02-07 20:21:50 +01:00
04039513bc Tirage des tarots 2023-02-07 19:55:33 +01:00
1923a63ebf Update tarot management 2023-02-07 15:36:06 +01:00
06537cbcd9 Ajout elements bio et fix mineurs 2023-02-06 16:08:11 +01:00
925f15627c Ajout elements bio et fix mineurs 2023-02-06 16:05:59 +01:00
a4b0c44255 Ajout archetypes 2023-02-05 16:57:00 +01:00
fbe77dcdc0 Sync all 2023-02-04 09:31:46 +01:00
166 changed files with 11677 additions and 1112 deletions

View File

@@ -0,0 +1,62 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
- 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-malefices/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-malefices.zip
# Create a zip file with all files required by the system to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./fvtt-malefices.zip system.json README.md fonts/ images/ lang/ modules/ packs/ styles/ templates/
- 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-malefices.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-malefices'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-malefices/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-malefices.zip'
compatibility-minimum: '11'
compatibility-verified: '13'

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -1,5 +1,7 @@
# Système Foundry pour Maléfices (French RPG, Arkhane Asylum Publishing) # Système Foundry pour Maléfices (French RPG, Arkhane Asylum Publishing)
[Vue du système](https://www.lahiette.com/leratierbretonnien/wp-content/uploads/2023/02/malefices_snapshot.webp)
## EN ## EN
Unofficial system for Maléfices v4 (French version from Arkhane Asylum Publishing). Unofficial system for Maléfices v4 (French version from Arkhane Asylum Publishing).

27
gulpfile.js Normal file
View File

@@ -0,0 +1,27 @@
const gulp = require('gulp');
const less = require('gulp-less');
const sourcemaps = require('gulp-sourcemaps');
const rename = require('gulp-rename');
// Compile LESS to CSS
function styles() {
return gulp.src('less/malefices.less')
.pipe(sourcemaps.init())
.pipe(less())
.pipe(rename('simple.css'))
.pipe(sourcemaps.write('.', { mapFile: () => 'simple.css.map' }))
.pipe(gulp.dest('styles/'));
}
// Watch files
function watchFiles() {
gulp.watch('less/**/*.less', styles);
}
const build = gulp.series(styles);
const watch = gulp.series(build, watchFiles);
exports.styles = styles;
exports.build = build;
exports.watch = watch;
exports.default = build;

6
images/icons/.directory Normal file
View File

@@ -0,0 +1,6 @@
[Dolphin]
SortRole=modificationtime
Timestamp=2023,2,26,15,32,34.892
Version=4
ViewMode=1
VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails

BIN
images/icons/Artiste.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
images/icons/Comedien.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
images/icons/Ecrivain.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
images/icons/Ingenieur.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
images/icons/Juriste.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
images/icons/Medecin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
images/icons/Medium.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
images/icons/Militaire.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
images/icons/Rentier.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
images/icons/archetype.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
images/icons/resume.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/icons/sortilege.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
images/icons/tarot.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
images/icons/tirage.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
images/icons/tirer.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
images/icons/wisdom.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 KiB

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

After

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 KiB

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 KiB

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 598 KiB

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 KiB

After

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 KiB

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 KiB

After

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 KiB

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 KiB

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 KiB

After

Width:  |  Height:  |  Size: 537 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 KiB

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 KiB

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 KiB

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 KiB

After

Width:  |  Height:  |  Size: 571 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 KiB

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 KiB

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 KiB

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

View File

@@ -1,22 +1,15 @@
{ {
"ACTOR": { "TYPES": {
"TypeCharacter": "Character", "Actor": {
"TypeNpc": "NPC" "personnage" : "Personnage"
}, },
"ITEM": { "Item": {
"TypeWeapon": "Weapon", "arme" : "Arme",
"TypeShield": "Shield", "equipement" : "Equipement",
"TypeArmor": "Armor", "tarot" : "Tarot",
"TypeSpell": "Spell", "elementbio" : "Elément Biographique",
"TypeModule": "Module", "archetype" : "Archetype",
"TypeMoney": "Money", "sortilege" : "Sortilège"
"TypeEquipment": "Equipment", }
"TypeAction": "Action",
"TypeFreeaction": "Free Action",
"TypeReaction": "Reaction",
"TypeStance": "Stance",
"TypeTrait": "Trait",
"TypeCondition": "Condition",
"TypeCraftingskill": "Crafting Skill"
} }
} }

235
less/actor-sheet.less Normal file
View File

@@ -0,0 +1,235 @@
/* ===================================================================
AppV2 Actor Sheets
=================================================================== */
/* NOTE: DocumentSheetV2 uses tag:"form" — the APPLICATION ROOT is the <form>.
The DOM is: form.application > section.window-content > section.editable > ...
There is NO <form> child inside window-content. */
.fvtt-malefices.actor {
/* window-content: background and remove Foundry's default padding.
Foundry AppV2 already sets: display:flex flex-flow:column overflow:hidden */
.window-content {
padding: 0;
background: @bg-sheet;
color: @color-text;
font-size: 0.8rem;
}
/* All sections inside actor fill their flex container and form a flex column.
This covers: section.editable (template root) and section.sheet-body. */
section {
height: 100%;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
}
/* sheet-body: scroll container instead of clipping */
section.sheet-body {
overflow-y: auto;
overflow-x: hidden;
padding: 0.25rem 0.5rem;
}
/* Override legacy fixed 210px height — shrink to fit content */
.sheet-header {
flex: 0 0 auto;
overflow: visible;
}
/* Override legacy "height: 100%" on .tab divs so their content
can overflow sheet-body and trigger the scrollbar;
hide all tabs, show only the active one */
.tab[data-tab] {
height: auto;
display: none;
&.active {
display: block;
}
}
/* Tab navigation */
nav.tabs {
display: flex;
flex: 0 0 auto;
font-family: @font-rivanna;
font-size: @tab-font-size;
font-weight: bold;
height: @tab-height;
margin: 0;
padding: 0 0 0 0.25rem;
text-align: center;
text-transform: uppercase;
line-height: @tab-height;
border-top: 0 none;
border-bottom: 0 none;
background-color: @color-tab-bg;
color: @color-tab-text;
gap: 0.25rem;
a.item {
position: relative;
padding: 0 0.5rem;
color: @color-tab-text;
font-family: @font-rivanna;
font-size: @tab-font-size;
text-decoration: none;
line-height: @tab-height;
&:hover {
text-shadow: 1px 0px 0px @color-accent;
}
&.active {
text-shadow: 1px 0px 0px @color-accent;
text-decoration: underline;
}
}
}
/* Force dark text on section titles and labels inside actor sheet body
(overrides Foundry core heading/label colors from the default dark theme) */
section.sheet-body {
h1, h2, h3, h4, label, span, a {
color: @color-text;
}
}
/* ── Belle Époque aesthetic improvements ─────────────────────────── */
@be-bordeaux: #5a0a14;
@be-gold: #8b6914;
@be-gold-border: rgba(139, 105, 20, 0.45);
@be-sepia: #3d2b1f;
// Sheet header — portrait with gold border, charname input styled
.sheet-header {
.profile-img {
border: 2px solid @be-gold-border;
border-radius: 2px;
box-shadow: 1px 1px 4px rgba(0,0,0,0.25);
}
h1.charname input {
font-family: @font-rivanna, serif;
font-size: 1.3rem;
color: @be-bordeaux;
border-bottom: 1px solid @be-gold-border;
border-top: none;
border-left: none;
border-right: none;
background: transparent;
text-shadow: 0 1px 0 rgba(255,255,255,0.4);
}
input[type="text"] {
border-bottom: 1px solid @be-gold-border;
border-top: none;
border-left: none;
border-right: none;
background: transparent;
&:focus {
outline: none;
border-bottom-color: @be-bordeaux;
box-shadow: none;
}
}
}
// Section title rows (Attributs, Armes, Équipements, etc.)
.items-title-bg {
background: linear-gradient(to right, rgba(90,10,20,0.15), rgba(139,105,20,0.10)) !important;
border-bottom: 1px solid @be-gold-border;
border-top: 1px solid @be-gold-border;
margin-top: 6px;
.items-title-text {
font-family: @font-rivanna, serif;
font-size: 0.9rem;
color: @be-bordeaux !important;
letter-spacing: 0.03em;
}
}
// Roll links — attributs and weapons
a.roll-attribut, a.roll-arme {
color: @be-sepia !important;
font-weight: 600;
transition: color 0.12s;
i { color: fade(@be-gold, 80%); margin-left: 0.2rem; font-size: 0.75em; }
&:hover {
color: @be-bordeaux !important;
text-decoration: none;
i { color: @be-bordeaux; }
}
}
// Attribute value inputs
input.item-field-label-short, input.item-field-label-medium {
text-align: center;
font-weight: 700;
font-size: 0.9rem;
color: @be-bordeaux;
border: 1px solid @be-gold-border;
border-radius: 2px;
background: rgba(255,252,240,0.6);
&:focus {
outline: none;
border-color: @be-bordeaux;
box-shadow: 0 0 3px rgba(90,10,20,0.2);
}
}
// Item rows — zebra effect with Belle Époque tones
.alternate-list {
.list-item:nth-child(even) {
background: rgba(139,105,20,0.07);
}
.list-item:nth-child(odd) {
background: rgba(61,43,31,0.04);
}
.list-item:hover {
background: rgba(139,105,20,0.15) !important;
}
}
// Item icon — small gold border
.sheet-competence-img {
border: 1px solid @be-gold-border;
border-radius: 1px;
}
// Item control icons (edit/delete)
.item-controls a.item-control {
color: fade(@be-sepia, 60%);
&:hover { color: @be-bordeaux; }
}
// Biography tab inputs
.tab.biodata {
input[type="text"] {
border-bottom: 1px solid @be-gold-border;
border-top: none;
border-left: none;
border-right: none;
background: transparent;
color: @be-sepia;
&:focus {
outline: none;
border-bottom-color: @be-bordeaux;
}
}
label.item-name-label-medium {
font-weight: 600;
color: @be-sepia !important;
}
}
}

202
less/base.less Normal file
View File

@@ -0,0 +1,202 @@
/* ==================== (A) Fonts ==================== */
@font-face {
font-family: "Rivanna";
src: url('../fonts/rivanna.ttf') format("truetype");
}
:root {
/* =================== 1. ACTOR SHEET FONT STYLES =========== */
--window-header-title-font-size: 1.3rem;
--window-header-title-font-weight: normal;
--window-header-title-color: #f5f5f5;
--major-button-font-size: 1.05rem;
--major-button-font-weight: normal;
--major-button-color: #dadada;
--tab-header-font-size: 1.0rem;
--tab-header-font-weight: 700;
--tab-header-color: #403f3e;
--tab-header-color-active: #4a0404;
--actor-input-font-size: 0.8rem;
--actor-input-font-weight: 500;
--actor-input-color: black;
--actor-label-font-size: 0.8rem;
--actor-label-font-weight: 700;
--actor-label-color: #464331c4;
/* =================== 2. DEBUGGING HIGHLIGHTERS ============ */
--debug-background-color-red: #ff000054;
--debug-background-color-blue: #1d00ff54;
--debug-background-color-green: #54ff0054;
--debug-box-shadow-red: inset 0 0 2px red;
--debug-box-shadow-blue: inset 0 0 2px blue;
--debug-box-shadow-green: inset 0 0 2px green;
}
/*@import url("https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap");*/
/* Global styles & Font */
.fvtt-malefices .window-app {
text-align: justify;
font-size: 16px;
letter-spacing: 1px;
}
/* Fonts */
.sheet header.sheet-header h1 input, .window-app .window-header, #actors .directory-list, #navigation #scene-list .scene.nav-item {
font-size: 1.0rem;
} /* For title, sidebar character and scene */
.fvtt-malefices .sheet nav.sheet-tabs {
font-size: 0.8rem;
} /* For nav and title */
.window-app input, .fvtt-malefices .item-form, .sheet header.sheet-header .flex-group-center.flex-compteurs, .sheet header.sheet-header .flex-group-center.flex-fatigue, select, button, .item-checkbox, #sidebar, #players, #navigation #nav-toggle {
font-size: 0.8rem;
}
.window-header{
background: rgba(0,0,0,0.75);
}
.window-app.sheet .window-content {
margin: 0;
padding: 0;
}
.strong-text{
font-weight: bold;
}
.tabs .item.active, .blessures-list li ul li:first-child:hover, a:hover {
text-shadow: 1px 0px 0px @color-accent;
}
.rollable:hover, .rollable:focus {
color: #000;
text-shadow: 0 0 10px red;
cursor: pointer;
}
input:hover, select:hover {
border-width: 4px;
border-color: rgb(85, 65, 130);
}
input:disabled {
color:#1c2058;
}
select:disabled {
color:#1c2058;
}
table {border: 1px solid #7a7971;}
.grid, .grid-2col {
display: grid;
grid-column: span 2 / span 2;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin: 10px 0;
padding: 0;
}
.grid-3col {
grid-column: span 3 / span 3;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid-4col {
grid-column: span 4 / span 4;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-5col {
grid-column: span 5 / span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-6col {
grid-column: span 5 / span 5;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.grid-7col {
grid-column: span 7 / span 7;
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.grid-8col {
grid-column: span 8 / span 8;
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.grid-9col {
grid-column: span 9 / span 9;
grid-template-columns: repeat(9, minmax(0, 1fr));
}
.grid-10col {
grid-column: span 10 / span 10;
grid-template-columns: repeat(10, minmax(0, 1fr));
}
.grid-11col {
grid-column: span 11 / span 11;
grid-template-columns: repeat(11, minmax(0, 1fr));
}
.grid-12col {
grid-column: span 12 / span 12;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.flex-group-center,
.flex-group-left,
.flex-group-right {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
text-align: center;
padding: 5px;
}
.flex-group-left {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
text-align: left;
}
.flex-group-right {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
text-align: right;
}
.flex-center {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.table-create-actor {
font-size: 0.8rem;
}
.flex-between {
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.flex-shrink {
flex: 'flex-shrink' ;
}

325
less/chat.less Normal file
View File

@@ -0,0 +1,325 @@
// ============================================================
// Chat messages Belle Époque theme
// ============================================================
@be-bordeaux: #5a0a14;
@be-gold: #8b6914;
@be-gold-light: #c8a84b;
@be-gold-border: rgba(139, 105, 20, 0.45);
@be-sepia: #3d2b1f;
@be-sepia-light: rgba(61, 43, 31, 0.06);
.malefices-chat-card {
font-size: 0.85rem;
color: @be-sepia;
border: 1px solid @be-gold-border;
border-radius: 3px;
overflow: hidden;
background: rgb(218, 218, 208); // opaque pour masquer le fond parchemin du sidebar
// ── Header ──────────────────────────────────────────────
.chat-card-header {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.2rem 0.4rem;
background: linear-gradient(135deg, rgba(90,10,20,0.12), rgba(139,105,20,0.12));
border-bottom: 1px solid @be-gold-border;
.chat-actor-img {
width: 32px;
height: 32px;
border: 1px solid @be-gold-border;
border-radius: 2px;
object-fit: cover;
flex-shrink: 0;
}
.chat-actor-name {
font-family: @font-rivanna, serif;
font-size: 1.1rem;
font-weight: normal;
color: @be-bordeaux;
line-height: 1.1;
text-shadow: 0 1px 0 rgba(255,255,255,0.5);
flex: 1;
}
.chat-header-roll {
display: flex;
align-items: center;
gap: 0.3rem;
padding-left: 0.4rem;
border-left: 1px solid @be-gold-border;
.chat-roll-icon {
width: 24px;
height: 24px;
border: 1px solid @be-gold-border;
border-radius: 2px;
object-fit: cover;
flex-shrink: 0;
}
.chat-roll-name {
font-size: 0.75rem;
color: fade(@be-sepia, 80%);
font-style: italic;
max-width: 80px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
// ── Decorative separator ─────────────────────────────────
.chat-card-separator {
height: 14px;
margin: 0.15rem 0.4rem 0;
background: url("../images/ui/separator_01.webp") center/auto 100% no-repeat;
opacity: 0.55;
}
// ── Roll detail rows ─────────────────────────────────────
.chat-roll-details {
margin: 0;
padding: 0 0.4rem 0.15rem;
.chat-detail-row {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 0.05rem 0;
border-bottom: 1px dotted rgba(139, 105, 20, 0.2);
&:last-child { border-bottom: none; }
dt {
color: fade(@be-sepia, 75%);
font-weight: normal;
font-size: 0.8rem;
}
dd {
font-weight: 600;
color: @be-sepia;
font-size: 0.85rem;
margin-left: 0.5rem;
}
}
// Target and roll value stand out slightly
.chat-detail-target dd,
.chat-detail-roll dd {
font-size: 1rem;
color: @be-bordeaux;
}
}
// ── Result banner ────────────────────────────────────────
.chat-card-result {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.1rem;
margin: 0.2rem 0.4rem 0.25rem;
padding: 0.25rem 0.5rem;
border-radius: 2px;
font-family: @font-rivanna, serif;
font-size: 1.15rem;
text-align: center;
border: 1px solid transparent;
&.result-success {
background: linear-gradient(135deg, rgba(20, 80, 20, 0.12), rgba(40, 100, 40, 0.08));
border-color: rgba(30, 100, 30, 0.35);
color: #1a5c1a;
}
&.result-failure {
background: linear-gradient(135deg, rgba(90, 10, 20, 0.12), rgba(120, 20, 20, 0.08));
border-color: rgba(90, 10, 20, 0.35);
color: @be-bordeaux;
}
i { margin-right: 0.3rem; }
.chat-result-damage {
font-family: sans-serif;
font-size: 0.85rem;
opacity: 0.85;
font-style: normal;
}
}
// ── Action buttons (Relancer, etc.) ──────────────────────
.chat-card-actions {
padding: 0.15rem 0.4rem 0.25rem;
display: flex;
justify-content: center;
.chat-card-button {
padding: 0.3rem 1rem;
font-size: 0.82rem;
border: 1px solid @be-gold-border;
border-radius: 2px;
background: linear-gradient(to bottom, rgba(255,252,240,0.9), rgba(240,230,200,0.9));
color: @be-sepia;
cursor: pointer;
transition: all 0.15s ease;
&:hover {
background: linear-gradient(to bottom, rgba(240,220,170,0.95), rgba(220,195,140,0.95));
border-color: @be-gold;
color: @be-bordeaux;
}
}
}
// ── Tarot card display ──────────────────────────────────
.tarot-card-display {
display: flex;
gap: 0.6rem;
align-items: flex-start;
padding: 0.3rem 0.5rem 0.4rem;
position: relative;
&:hover {
z-index: 100;
.tarot-card-img {
transform: scale(2.5);
box-shadow: 3px 3px 12px rgba(0,0,0,0.4);
}
}
.tarot-card-img {
width: 126px;
flex-shrink: 0;
border: 1px solid @be-gold-border;
border-radius: 2px;
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
transition: transform 0.2s ease, box-shadow 0.2s ease;
transform-origin: left center;
cursor: zoom-in;
}
.tarot-card-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
padding-top: 0.2rem;
.tarot-card-name {
font-family: @font-rivanna, serif;
font-size: 1rem;
color: @be-bordeaux;
text-shadow: 0 1px 0 rgba(255,255,255,0.4);
}
.tarot-card-side {
font-size: 0.8rem;
font-weight: 600;
&.tarot-positif { color: #1a5c1a; }
&.tarot-negatif { color: @be-bordeaux; }
}
.tarot-card-value {
font-size: 0.82rem;
color: @be-sepia;
strong { color: @be-bordeaux; font-size: 1rem; }
}
}
}
}
// ── Item post message ────────────────────────────────────
.malefices-chat-card.malefices-chat-item {
.item-type-label {
font-family: "Cinzel Decorative", "Cinzel", serif;
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: @be-bordeaux;
opacity: 0.8;
margin-left: auto;
align-self: center;
}
.item-damage {
font-weight: 700;
color: @be-bordeaux;
font-size: 1rem;
&.item-damage-crit {
color: #8b0000;
}
}
.chat-item-description {
margin: 0.4rem 0.5rem 0.3rem;
font-size: 0.82rem;
color: @be-sepia;
line-height: 1.5;
border-top: 1px solid fade(@be-gold-border, 50%);
padding-top: 0.4rem;
p { margin: 0 0 0.3rem; &:last-child { margin: 0; } }
em { color: @be-bordeaux; font-style: italic; }
strong { color: @be-sepia; }
}
}
// ── Welcome message ─────────────────────────────────────
.malefices-chat-card.malefices-welcome {
.welcome-body {
padding: 0.3rem 0.6rem 0.4rem;
font-size: 0.82rem;
color: @be-sepia;
line-height: 1.4;
p { margin: 0.2rem 0; }
.welcome-title {
font-family: @font-rivanna, serif;
font-size: 0.95rem;
color: @be-bordeaux;
font-weight: normal;
margin-bottom: 0.35rem;
}
a { color: @be-gold; &:hover { color: @be-bordeaux; } }
}
.welcome-commands {
margin-top: 0.4rem;
border-top: 1px solid @be-gold-border;
padding-top: 0.3rem;
.welcome-commands-title {
font-weight: 700;
font-size: 0.8rem;
color: @be-sepia;
margin-bottom: 0.2rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.welcome-command-row {
font-size: 0.8rem;
color: @be-sepia;
margin: 0.1rem 0;
code {
background: rgba(139,105,20,0.12);
border: 1px solid @be-gold-border;
border-radius: 2px;
padding: 0 3px;
font-size: 0.78rem;
color: @be-bordeaux;
}
}
}
}

616
less/components.less Normal file
View File

@@ -0,0 +1,616 @@
/* ======================================== */
/* Global UI elements */
/* ======================================== */
h1, h2, h3, h4 {
font-weight: bold;
}
ul, ol {
margin: 0;
padding: 0;
}
ul, li {
list-style-type: none;
}
.sheet li {
margin: 0.010rem;
padding: 0.25rem;
}
.header-fields li {
margin: 0;
padding: 0;
}
.alterne-list > .list-item:hover {
background: rgba(100, 100, 50, 0.25);
}
.alterne-list > .list-item:nth-child(even) {
background: rgba(80, 60, 0, 0.10);
}
.alterne-list > .list-item:nth-child(odd) {
background: rgb(160, 130, 100, 0.05);
}
.specialisation-label {
font-size: 0.8rem;
}
.carac-label,
.attr-label {
font-weight: bold;
}
.list-item {
margin: 0.125rem;
/*box-shadow: inset 0px 0px 1px #00000096;
border-radius: 0.25rem;*/
padding: 0.125rem;
flex: 1 1 5rem;
display: flex !important;
color: @color-text;
}
.list-item-shadow {
background:rgba(87, 60, 32, 0.35);
flex-grow: 0;
flex-wrap: nowrap;
justify-content: flex-start;
}
.list-item-shadow2 {
background:rgba(87, 60, 32, 0.25);
flex-grow: 0;
flex-wrap: nowrap;
justify-content: flex-start;
}
.item-display-show {
display: block;
}
.item-display-hide {
display: none;
}
.item-quantite {
margin-left: 0.5rem;
}
.list-item-margin1 {
margin-left: 1rem;
}
.list-item-margin2 {
margin-left: 2rem;
}
.list-item-margin3 {
margin-left: 3rem;
}
.list-item-margin4 {
margin-left: 4rem;
}
.sheet-competence-img {
width: 24px;
max-width: 24px;
height: 24px;
max-height: 24px;
flex-grow: 0;
margin-right: 0.25rem;
}
.competence-column {
flex-direction: column;
align-content: flex-start;
justify-content: flex-start;
flex-grow: 0;
flex-basis: 1;
}
.competence-header {
align-content: flex-start;
justify-content: flex-start;
font-weight: bold;
flex-grow: 0;
}
.description-label {
flex-grow: 2;
margin-left: 4px;
}
.status-header-label {
margin-left: 2px;
}
.roll-dialog-label {
margin: 4px 0;
min-width: 96px;
}
.short-label {
flex-grow: 1;
}
.keyword-label {
font-size: 0.85rem;
}
.item-sheet-label {
flex-grow: 1;
}
.item-text-long-line {
flex-grow: 3;
}
.score-label {
flex-grow: 2;
align-content: center;
}
.attribut-value,
.carac-value {
flex-grow: 0;
flex-basis: 64px;
margin-right: 4px;
margin-left: 4px;
}
.sante-value,
.competence-value {
flex-grow: 0;
flex-basis: 2rem;
margin-right: 0.25rem;
margin-left: 0.25rem;
}
.description-value {
flex-grow: 0;
flex-basis: 4rem;
margin-right: 0.25rem;
margin-left: 0.25rem;
}
.small-label {
margin-top: 5px;
}
.padd-right {
margin-right: 8px;
}
.padd-left {
margin-left: 8px;
}
.stack-left {
align-items:center;
flex-shrink: 1;
flex-grow: 0;
}
.packed-left {
white-space: nowrap;
flex-grow: 0;
}
.input-numeric-short {
width: 40px;
max-width: 40px;
flex-grow: 0;
flex-shrink: 0;
flex-basis: 40px;
margin-right: 0.25rem;
margin-left: 0.25rem;
}
.abilities-table {
align-content: flex-start;
}
/* ======================================== */
.tokenhudext {
display: flex;
flex: 0 !important;
font-weight: 600;
}
.tokenhudext.left {
justify-content: flex-start;
flex-direction: column;
position: absolute;
top: 2.75rem;
right: 4rem;
}
.tokenhudext.right {
justify-content: flex-start;
flex-direction: column;
position: absolute;
top: 2.75rem;
left: 4rem;
}
.control-icon.tokenhudicon {
width: fit-content;
height: fit-content;
min-width: 6rem;
flex-basis: auto;
padding: 0;
line-height: 1rem;
margin: 0.25rem;
}
.control-icon.tokenhudicon.right {
margin-left: 8px;
}
#token-hud .status-effects.active{
z-index: 2;
}
.token-sheet .window-content .flexcol .sheet-tabs {
font-size: 0.8rem;
}
/* ======================================== */
.item-checkbox {
height: 25px;
border: 1px solid #736953a6;
border-left: none;
font-weight: 500;
font-size: 1rem;
color: black;
padding-top: 5px;
margin-right: 0px;
width: 45px;
position: relative;
left: 0px;
text-align: center;
}
.skill-label {
font-size: 0.7rem;
}
.skill-good-checkbox {
max-height: 10px;
max-width: 10px;
}
.flex-actions-bar {
flex-grow: 2;
}
/* ======================================== */
/* Sidebar CSS */
#sidebar {
font-size: 1rem;
/*background-position: 100%;*/
background-color: @color-sidebar-bg;
background-position: 0px 35px;
background-repeat: no-repeat;
background-image: @bg-sheet;
color: @color-text;
}
#sidebar .scene {
color: rgba(237, 240, 199, 0.95);
}
/* background: rgb(105,85,65) url("../images/ui/texture_feuille_perso_onglets.webp") no-repeat right bottom;*/
#sidebar.collapsed {
height: 470px !important;
}
#sidebar-tabs > .collapsed, #chat-controls .chat-control-icon {
color: @color-text;
text-shadow: 1px 1px 0 rgba(0,0,0,0.75);
}
.sidebar-tab .directory-list .entity {
border-top: 1px dashed rgba(0,0,0,0.25);
border-bottom: 0 none;
padding: 0.25rem 0;
}
.sidebar-tab .directory-list .entity:hover {
background: rgba(0,0,0,0.05);
cursor: pointer;
}
.chat-message-header {
background: rgba(220,220,210,0.5);
font-size: 1.1rem;
height: 48px;
text-align: center;
vertical-align: middle;
display: flex;
align-items: center;
}
.chat-message .message-header .flavor-text, .chat-message .message-header .whisper-to {
font-size: 0.9rem;
}
.chat-result-text,
.chat-actor-name {
font-weight: bold;
font-family: Rivanna;
font-size: 1.2rem;
padding: 4px;
}
.chat-result-success {
color:darkgreen;
}
.chat-result-failure {
color:darkred;
}
.chat-img {
width: 64px;
height: 64px;
}
.roll-dialog-header {
height: 52px;
}
.actor-icon {
float: left;
width: 48px;
height: 48px;
padding: 2px 6px 2px 2px;
}
.padding-dice {
padding-top: .2rem;
padding-bottom: .2rem;
}
.dice-image {
box-sizing: border-box;
border: none;
border-radius: 0;
max-width: 100%;
}
.dice-image-reroll {
background-color:rgba(115, 224, 115, 0.25);
border-color: #011d33;
box-sizing: border-box;
border: 1px;
border-radius: 0%;
max-width: 100%;
}
.chat-dice {
width: 15%;
height: 15%;
font-size: 15px;
padding: 10px;
padding-bottom: 20px;
padding-top: .2rem;
padding-bottom: .2rem;
}
.div-center {
align-self: center;
}
.chat-message {
background: rgba(220,220,210,0.5);
font-size: 0.9rem;
}
.chat-message.whisper {
background: rgba(220,220,210,0.75);
border: 2px solid #545469;
}
.chat-message .chat-icon {
border: 0;
padding: 2px 6px 2px 2px;
float: left;
width: 64px;
height: 64px;
}
.ability-icon {
border: 0;
padding: 2px 2px 2px 2px;
max-width:32px;
max-height:32px;
width: auto;
height: auto;
}
.small-ability-icon {
border: 0;
padding: 2px 2px 2px 2px;
max-width:16px;
max-height:16px;
width: auto;
height: auto;
}
.combat-icon {
border: 0;
padding: 2px 2px 2px 2px;
max-width:24px;
max-height:24px;
width: auto;
height: auto;
}
#sidebar-tabs {
flex: 0 0 32px;
box-sizing: border-box;
margin: 0 0 5px;
border-bottom: 1px solid rgba(0,0,0,0);
box-shadow: inset 0 0 2rem rgba(0,0,0,0.5);
}
#sidebar-tabs > .item.active {
border: 1px solid rgba(114,98,72,1);
background: rgba(30, 25, 20, 0.75);
box-shadow: 0 0 6px inset rgba(114,98,72,1);
}
#sidebar #sidebar-tabs i{
display: inline-block;
background-position:center;
background-size:cover;
text-shadow: 1px 1px 0 rgba(0,0,0,0.75);
}
/*--------------------------------------------------------------------------*/
/* Control, Tool, hotbar & navigation */
#controls .scene-control, #controls .control-tool {
box-shadow: 0 0 3px #000;
margin: 0 0 8px;
border-radius: 0;
background: rgba(30, 25, 20, 1);
background-origin: padding-box;
border-image: url(img/ui/footer-button.png) 10 repeat;
border-image-width: 4px;
border-image-outset: 0px;
}
#controls .scene-control.active, #controls .control-tool.active, #controls .scene-control:hover, #controls .control-tool:hover {
background: rgba(72, 46, 28, 1);
background-origin: padding-box;
border-image: url(img/ui/footer-button.png) 10 repeat;
border-image-width: 4px;
border-image-outset: 0px;
box-shadow: 0 0 3px #ff6400;
}
#hotbar #action-bar #macro-list {
border: 1px solid rgba(72, 46, 28, 1);
box-shadow: 2px 2px 5px #000000;
}
#hotbar #action-bar .macro {
border-image: url(img/ui/bg_control.jpg) 21 repeat;
border-image-slice: 6 6 6 6 fill;
border-image-width: 6px 6px 6px 6px;
border-image-outset: 0px 0px 0px 0px;
border-radius: 0px;
}
#hotbar .bar-controls {
background: rgba(30, 25, 20, 1);
border: 1px solid rgba(72, 46, 28, 1);
}
#players {
border-image: url(img/ui/footer-button.png) 10 repeat;
border-image-width: 4px;
border-image-outset: 0px;
background: rgba(30, 25, 20, 1);
}
#navigation #scene-list .scene.nav-item.active {
background: rgba(72, 46, 28, 1);
}
#navigation #scene-list .scene.nav-item {
background: rgba(30, 25, 20, 1);
background-origin: padding-box;
border-image: url(img/ui/footer-button.png) 10 repeat;
border-image-width: 4px;
border-image-outset: 0px;
}
#navigation #scene-list .scene.view, #navigation #scene-list .scene.context {
background: rgba(72, 46, 28, 1);
background-origin: padding-box;
border-image: url(img/ui/footer-button.png) 10 repeat;
border-image-width: 4px;
border-image-outset: 0px;
box-shadow: 0 0 3px #ff6400;
}
#navigation #nav-toggle {
background: rgba(30, 25, 20, 1);
background-origin: padding-box;
border-image: url(img/ui/footer-button.png) 10 repeat;
border-image-width: 4px;
border-image-outset: 0px;
}
/* Tooltip container */
.tooltip {
position: relative;
display: inline-block;
/*border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
}
/* Tooltip text */
.tooltip .tooltiptext {
text-align: left;
background: rgba(231, 229, 226, 0.9);
width: 150px;
padding: 3px 0;
font-size: 0.9rem;
/* Position the tooltip text */
top: 1px;
position: absolute;
z-index: 1;
/* Fade in tooltip */
visibility: hidden;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip-nobottom {
border-bottom: unset; /* If you want dots under the hoverable text */
}
/* Show the tooltip text when you mouse over the tooltip container */
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.chat-card-button {
box-shadow: inset 0px 1px 0px 0px #a6827e;
background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%);
background-color: #7d5d3b00;
border-radius: 3px;
border: 2px ridge #846109;
display: inline-block;
cursor: pointer;
color: #ffffff;
font-size: 0.8rem;
padding: 4px 12px 0px 12px;
text-decoration: none;
text-shadow: 0px 1px 0px #4d3534;
position: relative;
margin:2px;
}
.chat-card-button:hover {
background: linear-gradient(to bottom, #800000 5%, #3e0101 100%);
background-color: red;
}
.chat-card-button:active {
position:relative;
top:1px;
}
.plus-minus-button {
box-shadow: inset 0px 1px 0px 0px #a6827e;
background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%);
background-color: #7d5d3b00;
border-radius: 2px;
border: 1px ridge #846109;
display: inline-block;
cursor: pointer;
color: #ffffff;
margin: 2px 2px 2px 2px;
padding: 2px 2px 2px 2px;
text-decoration: none;
text-shadow: 0px 1px 0px #4d3534;
position: relative;
margin:0px;
}
.plus-minus-button:hover,
.chat-card-button:hover {
background: linear-gradient(to bottom, #800000 5%, #3e0101 100%);
background-color: red;
}
.plus-minus-button:active,
.chat-card-button:active {
position:relative;
top:1px;
}
.plus-minus {
font-size: 0.9rem;
font-weight: bold;
}
.ul-level1 {
padding-left: 2rem;
}

469
less/dialogs.less Normal file
View File

@@ -0,0 +1,469 @@
/* ===================================================================
AppV2 Dialogs — Style Belle Époque (France, ~1900)
Palette : bordeaux, or antique, sépia, fond parchemin clair
=================================================================== */
// Couleurs Belle Époque
@be-bordeaux: #5a0a14;
@be-gold: #8b6914;
@be-gold-light: rgba(139, 105, 20, 0.25);
@be-gold-border: rgba(139, 105, 20, 0.55);
@be-sepia: #3d2b1f;
@be-sepia-light: rgba(61, 43, 31, 0.08);
.malefices-roll-dialog {
.window-content {
padding: 0;
background: @bg-sheet;
color: @be-sepia;
font-size: 0.85rem;
}
.skill-roll-dialog {
display: flex;
flex-direction: column;
// ── En-tête ──────────────────────────────────────────────
header.roll-dialog-header {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
padding: 0.6rem 0.75rem 0.5rem;
background: linear-gradient(to bottom, rgba(90, 10, 20, 0.12), rgba(90, 10, 20, 0.04));
border-bottom: 2px solid @be-gold-border;
}
.actor-icon {
width: 52px;
height: 52px;
object-fit: cover;
border: 2px solid @be-gold-border;
border-radius: 2px;
box-shadow: 0 1px 4px rgba(0,0,0,0.35);
flex-shrink: 0;
}
// Custom title div — no h1 to avoid Foundry theme overrides
.dialog-roll-title {
color: @be-bordeaux;
font-family: @font-rivanna;
font-size: 2rem;
font-weight: bold;
flex: 1;
line-height: 1.1;
text-shadow: 1px 1px 2px rgba(255,255,255,0.6);
}
// ── Séparateur décoratif ──────────────────────────────────
.dialog-separator {
width: 100%;
height: 18px;
background: url("../images/ui/separator_01.webp") center/auto 100% no-repeat;
opacity: 0.7;
margin: 0.1rem 0;
}
// ── Corps du dialog ───────────────────────────────────────
.dialog-body {
display: flex;
flex-direction: column;
gap: 0.4rem;
padding: 0.5rem 0.75rem 0.75rem;
}
// Ligne attribut principal
.dialog-attribute-row {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 0.5rem;
padding: 0.3rem 0.5rem;
background: @be-gold-light;
border: 1px solid @be-gold-border;
border-radius: 2px;
.dialog-attr-label {
font-family: @font-rivanna;
font-size: 1.1rem;
color: @be-bordeaux;
font-weight: bold;
flex: 1;
}
.dialog-attr-value {
font-family: @font-rivanna;
font-size: 1.3rem;
font-weight: bold;
color: @be-sepia;
min-width: 2rem;
text-align: right;
}
}
// Titre de section (éléments biographiques)
.dialog-section-title {
font-family: @font-rivanna;
font-size: 0.95rem;
color: @be-gold;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid @be-gold-border;
padding-bottom: 0.15rem;
margin-top: 0.25rem;
}
// Liste des éléments biographiques
.dialog-bio-list {
list-style: none;
margin: 0 0 0.25rem 0;
padding: 0 0 0 0.5rem;
li {
color: @be-sepia;
font-size: 0.8rem;
padding: 0.1rem 0;
border-bottom: 1px dashed rgba(139, 105, 20, 0.2);
&:last-child { border-bottom: none; }
&::before {
content: "✦ ";
color: @be-gold;
font-size: 0.65rem;
}
}
}
// Zone des modificateurs
.dialog-modifiers {
display: flex;
flex-direction: column;
gap: 0.3rem;
margin-top: 0.2rem;
}
.dialog-modifier-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.5rem;
border-bottom: 1px solid rgba(139, 105, 20, 0.18);
&:hover {
background: @be-sepia-light;
}
.dialog-modifier-label {
flex: 1;
color: @be-sepia;
font-size: 0.82rem;
font-weight: 600;
cursor: default;
}
select {
flex: 0 0 140px;
width: 140px;
border: 1px solid @be-gold-border;
background: rgba(255, 252, 245, 0.85);
color: @be-sepia;
font-size: 0.82rem;
padding: 1px 4px;
border-radius: 2px;
&:hover {
border-color: @be-gold;
border-width: 2px;
}
&:focus {
outline: none;
border-color: @be-bordeaux;
box-shadow: 0 0 3px rgba(90, 10, 20, 0.3);
}
}
}
}
footer.form-footer {
display: flex;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: linear-gradient(to bottom, rgba(139, 105, 20, 0.08), rgba(139, 105, 20, 0.16));
border-top: 1px solid @be-gold-border;
button {
flex: 1;
padding: 0.4rem 0.75rem;
font-size: 0.9rem;
font-weight: 600;
border: 1px solid @be-gold-border;
border-radius: 2px;
background: linear-gradient(to bottom, rgba(255, 252, 240, 0.9), rgba(240, 230, 200, 0.9));
color: @be-sepia;
cursor: pointer;
transition: all 0.15s ease;
&:hover {
background: linear-gradient(to bottom, rgba(240, 220, 170, 0.95), rgba(220, 195, 140, 0.95));
border-color: @be-gold;
color: @be-bordeaux;
}
&[data-action="roll"],
&[type="submit"] {
background: linear-gradient(to bottom, @be-bordeaux, darken(@be-bordeaux, 8%));
color: rgba(255, 245, 220, 0.95);
border-color: darken(@be-bordeaux, 10%);
font-size: 0.95rem;
&:hover {
background: linear-gradient(to bottom, lighten(@be-bordeaux, 5%), @be-bordeaux);
border-color: @be-bordeaux;
color: #fff;
}
}
}
}
}
/* ===================================================================
Dialogs AppV2 partagés (class: MaleficesDialog)
— utilisé par Tirage Tarot et Résumé des Personnages
=================================================================== */
.MaleficesDialog {
.window-content {
background: @bg-sheet;
padding: 0;
overflow-y: auto;
overflow-x: hidden;
}
// ── Résumé des Personnages ─────────────────────────────
.character-summary-container {
padding: 0.5rem 0.75rem;
color: @be-sepia;
.items-list {
list-style: none;
margin: 0;
padding: 0;
.item {
display: flex;
align-items: center;
padding: 0.2rem 0.4rem;
border-bottom: 1px solid fade(@be-gold-border, 40%);
&:last-child { border-bottom: none; }
&.item-header {
background: linear-gradient(135deg, rgba(90,10,20,0.10), rgba(139,105,20,0.10));
border: 1px solid @be-gold-border;
border-radius: 2px;
margin-bottom: 0.25rem;
font-family: "Cinzel Decorative", "Cinzel", serif;
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.04em;
color: @be-bordeaux;
text-transform: uppercase;
&:not(:first-child) { margin-top: 0.5rem; }
}
&:hover:not(.item-header) {
background: rgba(139, 105, 20, 0.08);
}
}
.item-field {
flex: 1;
text-align: center;
font-size: 0.85rem;
&.item-name { flex: 3; text-align: left; font-weight: 600; }
&.item-name-label-long { flex: 3; text-align: left; }
&.item-name-label-short { flex: 1; text-align: center; }
&.right { text-align: right; }
}
a.summary-roll, a.actor-open {
cursor: pointer;
color: @be-sepia;
&:hover { color: @be-bordeaux; text-decoration: underline; }
}
.actor-delete {
color: fade(@be-sepia, 50%);
font-size: 0.75rem;
cursor: pointer;
&:hover { color: @be-bordeaux; }
}
}
}
.tirage-tarot-dialog {
display: block;
padding: 0.6rem 0.75rem;
color: @be-sepia;
// ── Sélection joueur / attribution ────────────────────
.tirage-select-row {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.4rem 0.6rem;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, rgba(90,10,20,0.08), rgba(139,105,20,0.08));
border: 1px solid @be-gold-border;
border-radius: 2px;
.tirage-select-label {
flex: 1;
font-size: 0.85rem;
font-weight: 600;
color: @be-sepia;
}
.tirage-select {
flex: 0 0 180px;
width: 180px;
border: 1px solid @be-gold-border;
background: rgba(255,252,240,0.85);
color: @be-sepia;
font-size: 0.85rem;
padding: 2px 4px;
border-radius: 2px;
&:focus { outline: none; border-color: @be-bordeaux; }
}
}
.tirage-attribute-row {
background: linear-gradient(135deg, rgba(20,80,20,0.08), rgba(30,100,30,0.05));
border-color: rgba(30,100,30,0.35);
}
// ── Section (main joueur / main secrète) ──────────────
.tirage-section {
display: block;
margin-bottom: 0.5rem;
.tirage-section-title {
display: block;
font-family: @font-rivanna, serif;
font-size: 1rem;
color: @be-bordeaux;
border-bottom: 1px solid @be-gold-border;
padding-bottom: 0.2rem;
margin-bottom: 0.4rem;
text-shadow: 0 1px 0 rgba(255,255,255,0.4);
i { margin-right: 0.35rem; font-size: 0.85em; opacity: 0.7; }
.tirage-player-name {
font-size: 0.85rem;
font-style: italic;
color: @be-sepia;
opacity: 0.8;
}
}
.tirage-secret-title { color: fade(@be-sepia, 70%); }
}
// ── Grille de cartes ──────────────────────────────────
.tirage-cards-row {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-bottom: 0.5rem;
}
.tirage-card {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
width: 100px;
vertical-align: top;
&:hover {
position: relative;
z-index: 100;
.tirage-card-img {
transform: scale(2.2);
transform-origin: center top;
box-shadow: 3px 3px 12px rgba(0,0,0,0.4);
}
}
.tirage-card-img {
width: 100px;
border: 1px solid @be-gold-border;
border-radius: 2px;
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
transition: transform 0.2s ease, box-shadow 0.2s ease;
transform-origin: center center;
cursor: zoom-in;
}
.tirage-card-name {
font-family: @font-rivanna, serif;
font-size: 0.75rem;
color: @be-bordeaux;
text-align: center;
line-height: 1.2;
}
.tirage-card-side {
font-size: 0.7rem;
font-weight: 600;
&.tirage-positif { color: #1a5c1a; }
&.tirage-negatif { color: @be-bordeaux; }
}
&.tirage-card-secret {
opacity: 0.85;
.tirage-card-name { color: fade(@be-sepia, 70%); }
}
}
// ── Séparateur décoratif ──────────────────────────────
.tirage-separator {
display: block;
height: 18px;
background: url("../images/ui/separator_01.webp") center/auto 100% no-repeat;
opacity: 0.5;
margin: 0.25rem 0;
}
}
// ── Footer avec bouton Fermer ─────────────────────────
.tirage-footer {
display: flex;
justify-content: flex-end;
padding: 0.4rem 0.75rem;
border-top: 1px solid @be-gold-border;
background: linear-gradient(to bottom, rgba(139,105,20,0.08), rgba(139,105,20,0.16));
.tirage-close-btn {
padding: 0.3rem 1rem;
font-size: 0.85rem;
border: 1px solid @be-gold-border;
border-radius: 2px;
background: linear-gradient(to bottom, rgba(255,252,240,0.9), rgba(240,230,200,0.9));
color: @be-sepia;
cursor: pointer;
&:hover {
background: linear-gradient(to bottom, rgba(240,220,170,0.95), rgba(220,195,140,0.95));
border-color: @be-gold;
color: @be-bordeaux;
}
}
}
}

136
less/item-sheet.less Normal file
View File

@@ -0,0 +1,136 @@
/* ===================================================================
AppV2 Item Sheets
Selectors use .fvtt-malefices.item (both classes on the same root
element, no space) to target only AppV2 item sheet windows.
=================================================================== */
.fvtt-malefices.item {
/* window-content: background and remove padding.
Foundry AppV2 already sets: display:flex flex-flow:column overflow:hidden */
.window-content {
padding: 0;
background: @bg-sheet;
color: @color-text;
font-size: 0.8rem;
}
/* All sections inside item fill their flex container */
section {
height: 100%;
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
}
/* Fixed header bar */
.header {
flex: 0 0 auto;
border-bottom: 1px solid #999;
}
/* sheet-body: scroll container */
section.sheet-body {
overflow-y: auto;
overflow-x: hidden;
padding: 0.25rem 0.5rem;
}
/* Override legacy height: 100% on tab divs; hide all tabs, show only the active one */
.tab[data-tab] {
height: auto;
display: none;
&.active {
display: block;
}
}
/* Header layout override for item sheets (smaller than actor sheet header) */
.sheet-header {
flex: 0 0 auto;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
margin-bottom: 0;
}
.item-sheet-img {
flex: 0 0 64px;
width: 64px;
height: 64px;
object-fit: cover;
border: 1px solid #999;
cursor: pointer;
}
.item-sheet-title {
flex: 1;
h1 {
margin: 0;
padding: 0;
border-bottom: none;
input {
background: transparent;
border: none;
font-family: @font-rivanna;
font-size: 1.8rem;
width: 100%;
height: auto;
}
}
}
.header-actions {
flex: 0 0 auto;
display: flex;
gap: 0.25rem;
align-items: center;
}
/* Tab navigation: AppV2 uses nav.tabs (not nav.sheet-tabs) */
nav.tabs {
display: flex;
flex: 0 0 auto;
font-family: @font-rivanna;
font-size: @tab-font-size;
font-weight: bold;
height: @tab-height;
margin: 0;
padding: 0 0 0 0.25rem;
text-align: center;
text-transform: uppercase;
line-height: @tab-height;
border-top: 0 none;
border-bottom: 0 none;
background-color: @color-tab-bg;
color: @color-tab-text;
gap: 0.25rem;
a.item {
position: relative;
padding: 0 0.5rem;
color: @color-tab-text;
font-family: @font-rivanna;
font-size: @tab-font-size;
text-decoration: none;
line-height: @tab-height;
&:hover {
text-shadow: 1px 0px 0px @color-accent;
}
&.active {
text-shadow: 1px 0px 0px @color-accent;
text-decoration: underline;
}
}
}
}

340
less/legacy-sheets.less Normal file
View File

@@ -0,0 +1,340 @@
/* Styles limited to sheets */
.fvtt-malefices .sheet-header {
-webkit-box-flex: 0;
-ms-flex: 0 0 210px;
flex: 0 0 210px;
overflow: hidden;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
margin-bottom: 10px;
}
.fvtt-malefices .sheet-header .profile-img {
-webkit-box-flex: 0;
-ms-flex: 0 0 128px;
flex: 0 0 128px;
width: 128px;
height: auto;
max-height:128px;
margin-top: 0px;
margin-right: 10px;
object-fit: cover;
object-position: 50% 0;
border-width: 0px;
}
.profile-img-container {
margin-right: 0.2rem;
max-width: 140px;
width: 140px;
}
.button-img {
vertical-align: baseline;
width: 8%;
height: 8%;
max-height: 48px;
border-width: 0px;
border: 1px solid rgba(0, 0, 0, 0);
}
.button-img:hover {
color: rgba(255, 255, 128, 0.7);
border: 1px solid rgba(255, 128, 0, 0.8);
cursor: pointer;
}
.button-effect-img {
vertical-align: baseline;
width: 16px;
max-height: 16px;
height: 16;
border-width: 0;
}
.small-button-container {
height: 16px;
width: 16px;
border: 0;
vertical-align: bottom;
}
.fvtt-malefices .sheet-header .header-fields {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.fvtt-malefices .sheet-header h1.charname {
height: 50px;
padding: 0px;
margin: 5px 0;
border-bottom: 0;
}
.fvtt-malefices .sheet-header h1.charname input {
font-family: Rivanna;
font-size: 3rem;
width: 100%;
height: 100%;
margin: 0;
}
.fvtt-malefices .sheet-tabs {
-webkit-box-flex: 0;
-ms-flex: 0;
flex: 0;
font-family: Rivanna;
font-size: 2.2rem;
}
.fvtt-malefices .sheet-body,
.fvtt-malefices .sheet-body .tab,
.fvtt-malefices .sheet-body .tab .editor {
height: 100%;
font-size: 0.8rem;
}
.editor {
border: 2;
height: 100%;
padding: 0 3px;
}
.medium-editor {
border: 2;
height: 240px;
padding: 0 3px;
}
.small-editor {
border: 2;
height: 120px;
padding: 0 3px;
}
.fvtt-malefices .tox .tox-editor-container {
background: #fff;
}
.fvtt-malefices .tox .tox-edit-area {
padding: 0 8px;
}
.fvtt-malefices .resource-label {
font-weight: bold;
text-transform: uppercase;
}
.fvtt-malefices .tabs {
height: 40px;
border-top: 1px solid #AAA;
border-bottom: 1px solid #AAA;
color: #000000;
}
.fvtt-malefices .tabs .item {
line-height: 40px;
font-weight: bold;
}
.fvtt-malefices .tabs .item.active {
text-decoration: underline;
text-shadow: none;
}
.fvtt-malefices .items-list {
list-style: none;
margin: 1px 0;
padding: 0;
overflow-y: auto;
}
.fvtt-malefices .items-list .item-header {
font-weight: bold;
}
.fvtt-malefices .items-list .item {
height: 30px;
line-height: 24px;
padding: 1px 0;
border-bottom: 1px solid #BBB;
}
.fvtt-malefices .items-list .item .item-image {
-webkit-box-flex: 0;
-ms-flex: 0 0 24px;
flex: 0 0 24px;
margin-right: 5px;
}
.fvtt-malefices .items-list .item img {
display: block;
}
.fvtt-malefices .items-list .item-name {
margin: 0;
}
.fvtt-malefices .items-list .item-controls {
-webkit-box-flex: 0;
-ms-flex: 0 0 86px;
flex: 0 0 86px;
text-align: right;
}
li.folder > .folder-header h3 {
color: @color-text;
}
/* ======================================== */
/* Sheet */
.fvtt-malefices .window-app.sheet .window-content .sheet-header{
color: @color-text;
background: @bg-sheet;
/*background: #494e6b;*/
}
input[type="text"], select[type="text"] {
background:white;
color: @color-input-text;
}
select {
background:white;
color: @color-input-text;
}
/* background: #011d33 url("../images/ui/fond1.webp") repeat left top;*/
/*color: rgba(168, 139, 139, 0.5);*/
.window-app.sheet .window-content .sheet-header select[type="text"], .window-app.sheet .window-content .sheet-header input[type="text"], .window-app.sheet .window-content .sheet-header input[type="number"], .window-app.sheet .window-content .sheet-body input[type="text"], .window-app.sheet .window-content .sheet-body input[type="number"], .window-app.sheet .window-content .sheet-body select[type="text"] {
color: @color-text;
/*color: #494e6b;*/
}
.window-app.sheet .window-content .sheet-header input[type="password"], .window-app.sheet .window-content .sheet-header input[type="date"], .window-app.sheet .window-content .sheet-header input[type="time"] {
color: @color-text;
background: @bg-sheet;
border: 1 none;
margin-bottom: 0.25rem;
margin-left: 2px;
}
.window-app.sheet .window-content .sheet-body input[type="password"], .window-app.sheet .window-content .sheet-body input[type="date"], .window-app.sheet .window-content .sheet-body input[type="time"] {
color: @color-text;
background: @bg-sheet;
border: 1 none;
margin-bottom: 0.25rem;
margin-left: 2px;
}
.window-app.sheet .window-content .sheet-body select, .window-app.sheet .window-content .sheet-header select {
color: @color-text;
background: #fff;
border: 1 none;
margin-bottom: 0.25rem;
margin-left: 2px;
}
.fvtt-malefices .window-app .window-content, .fvtt-malefices .window-app.sheet .window-content .sheet-body{
font-size: 0.8rem;
/*background: url("../images/ui/pc_sheet_bg.webp") repeat left top;*/
background: @bg-sheet;
color: @color-text;
}
/* background: rgba(245,245,240,0.6) url("../images/ui/sheet_background.webp") left top;*/
section.sheet-body{padding: 0.25rem 0.5rem;}
.sheet header.sheet-header .profile-img {
object-fit: cover;
object-position: 50% 0;
margin: 0.5rem 0 0.5rem 0.5rem;
padding: 0;
}
nav.sheet-tabs {
font-size: @tab-font-size;
font-weight: bold;
height: @tab-height;
flex: 0 0 @tab-height;
margin: 0;
padding: 0 0 0 0.25rem;
text-align: center;
text-transform: uppercase;
line-height: 1.5rem;
border-top: 0 none;
border-bottom: 0 none;
background-color: @color-tab-bg;
color: @color-tab-text;
}
/* Dice tray specific overrides */
.dice-tray button svg * {
fill: #6d5923 !important;
}
.dice-tray input[type="text"] {
color: #6d5923 !important;
}
.dice-tray button {
color: #6d5923 !important;
}
nav.sheet-tabs .item {
position: relative;
padding: 0 0.25rem;
color: @color-tab-text;
margin-top: 4px;
margin-bottom: 4px;
}
nav.sheet-tabs .item:after {
content: "";
position: absolute;
top: 0;
right: 0;
height: 2rem;
width: 1px;
/*border-right: 1px dashed rgba(52, 52, 52, 0.25);*/
}
.sheet .tab[data-tab] {
padding: 0;
}
section.sheet-body:after {
content: "";
display: block;
clear: both;
}
.sheet header.sheet-header .flex-compteurs {text-align: right;}
.sheet header.sheet-header .resource-content {width: 2rem;}
.select-diff {
display: inline-block;
text-align: left;
width: 50px;
}
.window-app.sheet .window-content .tooltip:hover .tooltiptext {
top: 2rem;
left: 2rem;
margin: 0;
padding: 0.25rem;
}
.window-app.sheet .window-content .carac-value, .window-app.sheet .window-content .competence-xp {
margin: 0.05rem;
flex-basis: 3rem;
text-align: center;
}

12
less/malefices.less Normal file
View File

@@ -0,0 +1,12 @@
// Main LESS file for Maléfices system
// Importing base styles and component-specific styles
@import "variables";
@import "base";
@import "legacy-sheets";
@import "components";
@import "ui";
@import "dialogs";
@import "chat";
@import "item-sheet";
@import "actor-sheet";

180
less/ui.less Normal file
View File

@@ -0,0 +1,180 @@
/*************************************************************/
#pause
{
font-size: 2rem;
}
#pause > h3
{
color: #CCC
}
#pause > img {
content: url(../images/ui/logo_pause.webp);
height: 200px;
width: 200px;
top: -100px;
left: calc(50% - 132px);
}
#logo {
content : url(../images/ui/logo_pause.webp);
width: 100px;
height: 60px;
}
.dice-cell {
padding-left: 12px;
padding-right: 12px;
width: 60px;
text-align: center;
}
.dice-formula,
.dice-total {
height: 54px;
position:relative;
}
.status-small-label {
font-size: 0.65rem;
}
.no-grow {
flex-grow: 1;
max-width: 32px;
}
.status-col-name {
max-width: 72px;
}
.img-no-border {
max-width: 48px;
max-height: 48px;
border: 0px;
}
.items-title-bg {
margin-top: 6px;
color: @color-text;
}
.items-title-text {
margin-left: 4px;
}
.lock-icon {
width:16px;
height: 16px;
}
.item-sheet-img {
width: 64px;
height: auto;
border: 0;
}
.item-name-img {
flex-grow:1;
max-width: 2rem;
min-width: 2rem;
}
.item-name-label-header {
flex-grow:2;
max-width: 12rem;
min-width: 12rem;
}
.item-name-label-header-long {
flex-grow:2;
max-width: 14rem;
min-width: 14rem;
}
.item-name-label-header-long2 {
flex-grow:2;
max-width: 24rem;
min-width: 24rem;
}
.item-name-label {
flex-grow:2;
max-width: 10rem;
min-width: 10rem;
}
.item-name-label-long {
margin-top: 4px;
flex-grow:2;
max-width: 10rem;
min-width: 10rem;
}
.item-name-label-short {
flex-grow:1;
max-width: 4rem;
min-width: 4rem;
}
.item-name-label-medium {
margin-top: 4px;
flex-grow:2;
max-width: 6rem;
min-width: 6rem;
}
.item-name-label-long2 {
margin-top: 4px;
flex-grow:2;
max-width: 22rem;
min-width: 22rem;
}
.item-name-label-level2 {
flex-grow:2;
max-width: 9rem;
min-width: 9rem;
}
.item-field-label-short {
flex-grow:1;
max-width: 4rem;
min-width: 4rem;
}
.item-field-label-medium {
flex-grow:1;
max-width: 6rem;
min-width: 6rem;
}
.item-field-skill {
flex-grow:1;
max-width: 6.8rem;
min-width: 6.8rem;
}
.item-field-label-long {
flex-grow:1;
max-width: 10rem;
min-width: 10rem;
}
.item-control-end {
align-self: flex-end;
}
.alternate-list {
margin-top: 4px;
flex-wrap: nowrap;
}
.item-filler {
flex-grow: 6;
flex-shrink: 7;
}
.item-controls-fixed {
min-width:2rem;
max-width: 2rem;
}
.attribute-label {
font-weight: bold;
}
.flexrow-no-expand {
flex-grow: 0;
}
.item-input-small {
max-width: 16px;
max-height: 12px;
}
.flip-tarot {
transform: scaleY(-1);
}
.tarot-fixed-width {
width: 140px;
max-width: 140px;
}
.tarot-title {
text-align: center;
font-weight: bold;
}
.character-summary-rollable {
text-decoration: underline;
}

19
less/variables.less Normal file
View File

@@ -0,0 +1,19 @@
// ==================== Variables ====================
// Fonts
@font-rivanna: Rivanna, serif;
// Colors
@color-text: rgba(19, 18, 18, 0.95);
@color-accent: #ff6600;
@color-tab-bg: #252525;
@color-tab-text: beige;
@color-input-text: #494e6b;
@color-sidebar-bg: #f5f5f5;
// Backgrounds
@bg-sheet: url("../images/ui/background_01_clear.webp");
// Tabs
@tab-height: 3rem;
@tab-font-size: 1.2rem;

View File

@@ -0,0 +1,13 @@
/**
* Index des fiches AppV2 pour Maléfices
*/
// Actor sheets
export { default as MaleficesPersonnageSheet } from './malefices-personnage-sheet.mjs';
export { default as MaleficesNPCActorSheet } from './malefices-npc-actor-sheet.mjs';
// Item sheets
export { default as MaleficesArmeSheet } from './malefices-arme-sheet.mjs';
export { default as MaleficesEquipementSheet } from './malefices-equipement-sheet.mjs';
export { default as MaleficesArchetypeSheet } from './malefices-archetype-sheet.mjs';
export { default as MaleficesTarotSheet } from './malefices-tarot-sheet.mjs';
export { default as MaleficesSortilegeSheet } from './malefices-sortilege-sheet.mjs';
export { default as MaleficesElementbioSheet } from './malefices-elementbio-sheet.mjs';

View File

@@ -0,0 +1,132 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class MaleficesItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-malefices", "item"],
position: {
width: 620,
height: 600,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
editImage: MaleficesItemSheet.#onEditImage,
postItem: MaleficesItemSheet.#onPostItem,
},
}
/** @type {object} */
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.description ?? "", { async: true }
),
isEditable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
isGM: game.user.isGM,
config: game.system.malefices.config,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Manual tab navigation
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
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()
})
})
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
}
// #region Drag-and-Drop
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
_canDragStart(selector) { return this.isEditable }
_canDragDrop(selector) { return this.isEditable }
_onDragStart(event) {
const dragData = { type: "Item", uuid: this.document.uuid }
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
_onDragOver(event) {}
async _onDrop(event) {}
// #endregion
// #region Actions
static async #onEditImage(event, target) {
const fp = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => { this.document.update({ img: path }) },
})
return fp.browse()
}
static async #onPostItem(event, target) {
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 })
const html = await foundry.applications.handlebars.renderTemplate(
'systems/fvtt-malefices/templates/post-item.hbs', chatData
)
ChatMessage.create({ user: game.user.id, content: html })
}
// #endregion
}

View File

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

View File

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

View File

@@ -0,0 +1,225 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { MaleficesUtility } from "../../malefices-utility.js"
export default class MaleficesActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
this._editScore = true
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-malefices", "actor"],
position: {
width: 640,
height: 680,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
actions: {
editImage: MaleficesActorSheet.#onEditImage,
toggleSheet: MaleficesActorSheet.#onToggleSheet,
editItem: MaleficesActorSheet.#onEditItem,
deleteItem: MaleficesActorSheet.#onDeleteItem,
createItem: MaleficesActorSheet.#onCreateItem,
equipItem: MaleficesActorSheet.#onEquipItem,
modifyQuantity: MaleficesActorSheet.#onModifyQuantity,
modifyAmmo: MaleficesActorSheet.#onModifyAmmo,
rollAttribut: MaleficesActorSheet.#onRollAttribut,
rollArme: MaleficesActorSheet.#onRollArme,
editSubActor: MaleficesActorSheet.#onEditSubActor,
deleteSubActor: MaleficesActorSheet.#onDeleteSubActor,
},
}
/** @type {object} */
tabGroups = { primary: "main" }
/** @override */
async _prepareContext() {
const actor = this.document
return {
actor,
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
isGM: game.user.isGM,
config: game.system.malefices.config,
editScore: this._editScore,
}
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
// Ignore Enter key in text inputs (not textarea)
this.element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') e.preventDefault()
})
// Manual tab navigation
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
const activeTab = this.tabGroups[group] || "main"
nav.querySelectorAll('[data-tab]').forEach(link => {
link.classList.toggle('active', link.dataset.tab === activeTab)
link.addEventListener('click', (event) => {
event.preventDefault()
this.tabGroups[group] = link.dataset.tab
this.render()
})
})
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
// Handle .update-field change events (legacy support)
this.element.querySelectorAll('.update-field').forEach(el => {
el.addEventListener('change', (ev) => {
const fieldName = ev.currentTarget.dataset.fieldName
const value = Number(ev.currentTarget.value)
this.actor.update({ [fieldName]: value })
})
})
}
// #region Drag-and-Drop
#createDragDropHandlers() {
return this.options.dragDrop.map((d) => {
d.permissions = {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
_canDragStart(selector) { return this.isEditable }
_canDragDrop(selector) { return this.isEditable }
_onDragStart(event) {
const li = event.currentTarget.closest('.item')
if (!li) return
const itemId = li.dataset.itemId
const item = this.actor.items.get(itemId)
if (item) {
event.dataTransfer.setData("text/plain", JSON.stringify({ type: "Item", uuid: item.uuid }))
}
}
_onDragOver(event) {}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data?.type === "Actor") {
const actor = await fromUuid(data.uuid)
if (actor) this.actor.addSubActor(actor.id)
} else {
super._onDrop(event)
}
}
// #endregion
// #region Actions
static async #onEditImage(event, target) {
const fp = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => { this.document.update({ img: path }) },
})
return fp.browse()
}
static async #onToggleSheet(event, target) {
this._editScore = !this._editScore
this.render()
}
static async #onEditItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
this.actor.items.get(itemId)?.sheet.render(true)
}
static async #onDeleteItem(event, target) {
const li = target.closest(".item")
MaleficesUtility.confirmDelete(this, li)
}
static async #onCreateItem(event, target) {
const dataType = target.dataset.type
this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true })
}
static async #onEquipItem(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
await this.actor.equipItem(itemId)
this.render()
}
static async #onModifyQuantity(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const delta = parseInt(target.dataset.delta) || 0
this.actor.incDecQuantity(itemId, delta)
}
static async #onModifyAmmo(event, target) {
const li = target.closest(".item")
const itemId = li?.dataset.itemId
if (!itemId) return
const delta = parseInt(target.dataset.delta) || 0
this.actor.incDecAmmo(itemId, delta)
}
static async #onRollAttribut(event, target) {
const attrKey = target.dataset.attrKey
this.actor.rollAttribut(attrKey)
}
static async #onRollArme(event, target) {
const armeId = target.dataset.armeId
this.actor.rollArme(armeId)
}
static async #onEditSubActor(event, target) {
const li = target.closest(".item")
const actorId = li?.dataset.actorId
if (!actorId) return
game.actors.get(actorId)?.sheet.render(true)
}
static async #onDeleteSubActor(event, target) {
const li = target.closest(".item")
const actorId = li?.dataset.actorId
if (!actorId) return
this.actor.delSubActor(actorId)
}
// #endregion
}

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
import MaleficesActorSheet from "./malefices-base-actor-sheet.mjs"
export default class MaleficesNPCActorSheet extends MaleficesActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["pnj"],
position: { width: 560, height: 460 },
}
/** @override */
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/actors/npc-sheet.hbs" },
}
/** @override */
tabGroups = { primary: "main" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.description ?? "", { async: true }
)
return context
}
}

View File

@@ -0,0 +1,48 @@
import MaleficesActorSheet from "./malefices-base-actor-sheet.mjs"
export default class MaleficesPersonnageSheet extends MaleficesActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["personnage"],
position: { width: 640, height: 680 },
}
/** @override */
static PARTS = {
main: { template: "systems/fvtt-malefices/templates/actors/actor-sheet.hbs" },
}
/** @override */
tabGroups = { primary: "main" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
context.armes = foundry.utils.duplicate(actor.getArmes())
context.tarots = foundry.utils.duplicate(actor.getTarots())
context.tarotsCache = foundry.utils.duplicate(actor.getHiddenTarots())
context.archetype = foundry.utils.duplicate(actor.getArchetype())
context.equipements = foundry.utils.duplicate(actor.getEquipements())
context.elementsbio = actor.getElementsBio()
context.sorts = actor.getSorts()
context.phyMalus = actor.getPhysiqueMalus()
context.subActors = foundry.utils.duplicate(actor.getSubActors())
// Expose nested biodata schema fields for {{formInput}} helper
context.biodataFields = actor.system.schema.fields.biodata.fields
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.biodata?.description ?? "", { async: true }
)
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.biodata?.notes ?? "", { async: true }
)
context.enrichedEquipementlibre = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.equipementlibre ?? "", { async: true }
)
return context
}
}

View File

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

View File

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

View File

@@ -6,16 +6,16 @@
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
export class MaleficesActorSheet extends ActorSheet { export class MaleficesActorSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-malefices", "sheet", "actor"], classes: ["fvtt-malefices", "sheet", "actor", "malefices-actor-sheet"],
template: "systems/fvtt-malefices/templates/actors/actor-sheet.hbs", template: "systems/fvtt-malefices/templates/actors/actor-sheet.hbs",
width: 640, width: 640,
height: 640, height:680,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "skills" }], tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "skills" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }], dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: true editScore: true
@@ -33,14 +33,20 @@ export class MaleficesActorSheet extends ActorSheet {
name: this.actor.name, name: this.actor.name,
editable: this.isEditable, editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked", cssClass: this.isEditable ? "editable" : "locked",
system: duplicate(this.object.system), system: foundry.utils.duplicate(this.object.system),
limited: this.object.limited, limited: this.object.limited,
armes: duplicate(this.actor.getArmes()), armes: foundry.utils.duplicate(this.actor.getArmes()),
tarots: duplicate(this.actor.getTarots()), tarots: foundry.utils.duplicate(this.actor.getTarots()),
archetype: duplicate(this.actor.getArchetype()), tarotsCache: foundry.utils.duplicate(this.actor.getHiddenTarots()),
equipements: duplicate(this.actor.getEquipements()), archetype: foundry.utils.duplicate(this.actor.getArchetype()),
subActors: duplicate(this.actor.getSubActors()), equipements: foundry.utils.duplicate(this.actor.getEquipements()),
subActors: foundry.utils.duplicate(this.actor.getSubActors()),
phyMalus: this.actor.getPhysiqueMalus(), phyMalus: this.actor.getPhysiqueMalus(),
elementsbio: this.actor.getElementsBio(),
sorts: this.actor.getSorts(),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.description, { async: true }),
notes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.notes, { async: true }),
equipementlibre: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.equipementlibre, { async: true }),
options: this.options, options: this.options,
owner: this.document.isOwner, owner: this.document.isOwner,
editScore: this.options.editScore, editScore: this.options.editScore,
@@ -48,7 +54,7 @@ export class MaleficesActorSheet extends ActorSheet {
} }
this.formData = formData; this.formData = formData;
console.log("PC : ", formData, this.object); console.log("PC : ", formData, this.object );
return formData; return formData;
} }
@@ -60,10 +66,10 @@ export class MaleficesActorSheet extends ActorSheet {
// Everything below here is only needed if the sheet is editable // Everything below here is only needed if the sheet is editable
if (!this.options.editable) return; if (!this.options.editable) return;
html.bind("keydown", function(e) { // Ignore Enter in actores sheet html.bind("keydown", function(e) { // Ignore Enter in actores sheet
if (e.keyCode === 13) return false; if (e.keyCode === 13) return false;
}); });
// Update Inventory Item // Update Inventory Item
html.find('.item-edit').click(ev => { html.find('.item-edit').click(ev => {
@@ -81,14 +87,14 @@ export class MaleficesActorSheet extends ActorSheet {
let dataType = $(ev.currentTarget).data("type") let dataType = $(ev.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true }) this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true })
}) })
html.find('.subactor-edit').click(ev => { html.find('.subactor-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
let actorId = li.data("actor-id"); let actorId = li.data("actor-id");
let actor = game.actors.get( actorId ); let actor = game.actors.get( actorId );
actor.sheet.render(true); actor.sheet.render(true);
}); });
html.find('.subactor-delete').click(ev => { html.find('.subactor-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
let actorId = li.data("actor-id"); let actorId = li.data("actor-id");
@@ -111,38 +117,38 @@ export class MaleficesActorSheet extends ActorSheet {
const li = $(event.currentTarget).parents(".item") const li = $(event.currentTarget).parents(".item")
this.actor.incDecAmmo( li.data("item-id"), +1 ) this.actor.incDecAmmo( li.data("item-id"), +1 )
} ); } );
html.find('.roll-attribut').click((event) => { html.find('.roll-attribut').click((event) => {
let attrKey = $(event.currentTarget).data("attr-key") let attrKey = $(event.currentTarget).data("attr-key")
this.actor.rollAttribut(attrKey) this.actor.rollAttribut(attrKey)
}); });
html.find('.roll-arme').click((event) => { html.find('.roll-arme').click((event) => {
const armeId = $(event.currentTarget).data("arme-id") const armeId = $(event.currentTarget).data("arme-id")
this.actor.rollArme(armeId) this.actor.rollArme(armeId)
}); });
html.find('.lock-unlock-sheet').click((event) => { html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore; this.options.editScore = !this.options.editScore;
this.render(true); this.render(true);
}); });
html.find('.item-link a').click((event) => { html.find('.item-link a').click((event) => {
const itemId = $(event.currentTarget).data("item-id"); const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
item.sheet.render(true); item.sheet.render(true);
}); });
html.find('.item-equip').click(ev => { html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
this.actor.equipItem( li.data("item-id") ); this.actor.equipItem( li.data("item-id") );
this.render(true); this.render(true);
}); });
html.find('.update-field').change(ev => { html.find('.update-field').change(ev => {
const fieldName = $(ev.currentTarget).data("field-name"); const fieldName = $(ev.currentTarget).data("field-name");
let value = Number(ev.currentTarget.value); let value = Number(ev.currentTarget.value);
this.actor.update( { [`${fieldName}`]: value } ); this.actor.update( { [`${fieldName}`]: value } );
}); });
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
setPosition(options = {}) { setPosition(options = {}) {

View File

@@ -29,7 +29,7 @@ export class MaleficesActor extends Actor {
if (data instanceof Array) { if (data instanceof Array) {
return super.create(data, options); 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) { if (data.items) {
let actor = super.create(data, options); let actor = super.create(data, options);
return actor; return actor;
@@ -86,23 +86,40 @@ export class MaleficesActor extends Actor {
MaleficesUtility.sortArrayObjectsByName(comp) MaleficesUtility.sortArrayObjectsByName(comp)
return comp; return comp;
} }
getSorts() {
let comp = this.items.filter(item => item.type == 'sortilege');
MaleficesUtility.sortArrayObjectsByName(comp)
return comp;
}
getArchetype() { getArchetype() {
let comp = duplicate(this.items.find(item => item.type == 'archetype') || {name: "Pas d'archetype"}) let comp = foundry.utils.duplicate(this.items.find(item => item.type == 'archetype') || {name: "Pas d'archetype"})
if (comp) { if (comp && comp.system) {
comp.tarot = MaleficesUtility.getTarot(comp.system.lametutelaire) comp.tarot = MaleficesUtility.getTarot(comp.system.lametutelaire)
} }
return comp; return comp;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getElementsBio() {
let comp = foundry.utils.duplicate(this.items.filter(item => item.type == 'elementbio') || [])
MaleficesUtility.sortArrayObjectsByName(comp)
return comp;
}
/* -------------------------------------------- */
getTarots() { getTarots() {
let comp = duplicate(this.items.filter(item => item.type == 'tarot') || []) let comp = foundry.utils.duplicate(this.items.filter(item => item.type == 'tarot' && !item.system.isgm) || [])
MaleficesUtility.sortArrayObjectsByName(comp)
return comp;
}
/* -------------------------------------------- */
getHiddenTarots() {
let comp = foundry.utils.duplicate(this.items.filter(item => item.type == 'tarot' && item.system.isgm) || [])
MaleficesUtility.sortArrayObjectsByName(comp) MaleficesUtility.sortArrayObjectsByName(comp)
return comp; return comp;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getArmes() { getArmes() {
let comp = duplicate(this.items.filter(item => item.type == 'arme') || []) let comp = foundry.utils.duplicate(this.items.filter(item => item.type == 'arme') || [])
MaleficesUtility.sortArrayObjectsByName(comp) MaleficesUtility.sortArrayObjectsByName(comp)
return comp; return comp;
} }
@@ -110,7 +127,7 @@ export class MaleficesActor extends Actor {
getItemById(id) { getItemById(id) {
let item = this.items.find(item => item.id == id); let item = this.items.find(item => item.id == id);
if (item) { if (item) {
item = duplicate(item) item = foundry.utils.duplicate(item)
} }
return item; return item;
} }
@@ -156,7 +173,7 @@ export class MaleficesActor extends Actor {
/* ------------------------------------------- */ /* ------------------------------------------- */
async buildContainerTree() { async buildContainerTree() {
let equipments = duplicate(this.items.filter(item => item.type == "equipment") || []) let equipments = foundry.utils.duplicate(this.items.filter(item => item.type == "equipment") || [])
for (let equip1 of equipments) { for (let equip1 of equipments) {
if (equip1.system.iscontainer) { if (equip1.system.iscontainer) {
equip1.system.contents = [] equip1.system.contents = []
@@ -212,22 +229,22 @@ export class MaleficesActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getInitiativeScore(combatId, combatantId) { getInitiativeScore(combatId, combatantId) {
let init = Math.floor(this.system.attributs.physique.value+this.system.attributs.habilete.value) let init = Math.floor( (this.system.attributs.physique.value+this.system.attributs.habilite.value) / 2)
let subvalue = new Roll("1d20").roll({async: false}) let subvalue = new Roll("1d20").roll({async: false})
return init + (subvalue / 100) return init + (subvalue.total / 100)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getSubActors() { getSubActors() {
let subActors = []; let subActors = [];
for (let id of this.system.subactors) { for (let id of this.system.subactors) {
subActors.push(duplicate(game.actors.get(id))) subActors.push(foundry.utils.duplicate(game.actors.get(id)))
} }
return subActors; return subActors;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async addSubActor(subActorId) { async addSubActor(subActorId) {
let subActors = duplicate(this.system.subactors); let subActors = foundry.utils.duplicate(this.system.subactors);
subActors.push(subActorId); subActors.push(subActorId);
await this.update({ 'system.subactors': subActors }); await this.update({ 'system.subactors': subActors });
} }
@@ -255,7 +272,29 @@ export class MaleficesActor extends Actor {
await this.createEmbeddedDocuments('Item', [newItem]); await this.createEmbeddedDocuments('Item', [newItem]);
} }
} }
/* -------------------------------------------- */
incDecFluide(value) {
let fluide = this.system.fluide + value
this.update( {'system.fluide': fluide} )
}
incDecDestin(value) {
let destin = this.system.pointdestin + value
this.update( {'system.pointdestin': destin} )
}
incDecMPMB(value) {
let mpmb = this.system.mpmb + value
this.update( {'system.mpmb': mpmb} )
}
incDecMPMN(value) {
let mpmn = this.system.mpmn + value
this.update( {'system.mpmn': mpmn} )
}
/* -------------------------------------------- */
incDecAttr(attrKey, value) {
let attr = foundry.utils.duplicate(this.system.attributs[attrKey])
attr.value += value
this.update( { [`system.attributs.${attrKey}`]: attr})
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async incDecQuantity(objetId, incDec = 0) { async incDecQuantity(objetId, incDec = 0) {
let objetQ = this.items.get(objetId) let objetQ = this.items.get(objetId)
@@ -297,8 +336,12 @@ export class MaleficesActor extends Actor {
rollData.actorId = this.id rollData.actorId = this.id
rollData.img = this.img rollData.img = this.img
rollData.phyMalus = this.getPhysiqueMalus() rollData.phyMalus = this.getPhysiqueMalus()
rollData.elementsbio = this.getElementsBio()
rollData.destin = this.system.pointdestin rollData.destin = this.system.pointdestin
rollData.isReroll = false rollData.isReroll = false
rollData.confrontationDegre = 0
rollData.confrontationModif = 0
rollData.config = game.system.malefices.config
console.log("ROLLDATA", rollData) console.log("ROLLDATA", rollData)
@@ -316,7 +359,7 @@ export class MaleficesActor extends Actor {
rollAttribut(attrKey) { rollAttribut(attrKey) {
let attr = this.system.attributs[attrKey] let attr = this.system.attributs[attrKey]
let rollData = this.getCommonRollData() let rollData = this.getCommonRollData()
rollData.attr = duplicate(attr) rollData.attr = foundry.utils.duplicate(attr)
rollData.mode = "attribut" rollData.mode = "attribut"
rollData.title = attr.label rollData.title = attr.label
rollData.img = this.getAtttributImage(attrKey) rollData.img = this.getAtttributImage(attrKey)
@@ -327,12 +370,12 @@ export class MaleficesActor extends Actor {
rollArme(weaponId) { rollArme(weaponId) {
let arme = this.items.get(weaponId) let arme = this.items.get(weaponId)
if (arme) { if (arme) {
arme = duplicate(arme) arme = foundry.utils.duplicate(arme)
let rollData = this.getCommonRollData() let rollData = this.getCommonRollData()
if (arme.system.armetype == "mainsnues" || arme.system.armetype == "epee") { if (arme.system.armetype == "mainsnues" || arme.system.armetype == "epee") {
rollData.attr = { label: "(Physique+Habilité)/2", value: Math.floor( (this.getPhysiqueMalus()+this.system.attributs.physique+this.system.attributs.habilite) / 2) } rollData.attr = { label: "(Physique+Habilité)/2", value: Math.floor( (this.getPhysiqueMalus()+this.system.attributs.physique.value+this.system.attributs.habilite.value) / 2) }
} else { } else {
rollData.attr = duplicate(this.system.attributs.habilite) rollData.attr = foundry.utils.duplicate(this.system.attributs.habilite)
} }
rollData.mode = "arme" rollData.mode = "arme"
rollData.arme = arme rollData.arme = arme
@@ -346,8 +389,7 @@ export class MaleficesActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
async startRoll(rollData) { async startRoll(rollData) {
let rollDialog = await MaleficesRollDialog.create(this, rollData) await MaleficesRollDialog.create(this, rollData)
rollDialog.render(true)
} }
} }

View File

@@ -2,6 +2,8 @@
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
import { MaleficesRollDialog } from "./malefices-roll-dialog.js"; import { MaleficesRollDialog } from "./malefices-roll-dialog.js";
import { MaleficesTirageTarotDialog } from "./malefices-tirage-tarot-dialog.js"
import { MaleficesCharacterSummary } from "./malefices-summary-app.js"
/* -------------------------------------------- */ /* -------------------------------------------- */
export class MaleficesCommands { export class MaleficesCommands {
@@ -9,8 +11,9 @@ export class MaleficesCommands {
static init() { static init() {
if (!game.system.malefices.commands) { if (!game.system.malefices.commands) {
const commands = new MaleficesCommands(); const commands = new MaleficesCommands();
//crucibleCommands.registerCommand({ path: ["/char"], func: (content, msg, params) => crucibleCommands.createChar(msg), descr: "Create a new character" }); commands.registerCommand({ path: ["/tirage"], func: (content, msg, params) => MaleficesCommands.createTirage(msg), descr: "Tirage des tarots" });
//crucibleCommands.registerCommand({ path: ["/pool"], func: (content, msg, params) => crucibleCommands.poolRoll(msg), descr: "Generic Roll Window" }); commands.registerCommand({ path: ["/carte"], func: (content, msg, params) => MaleficesCommands.tirerCarte(msg), descr: "Tirer une carte" });
commands.registerCommand({ path: ["/resume"], func: (content, msg, params) => MaleficesCharacterSummary.displayPCSummary(), descr: "Affiche la liste des PJs!" });
game.system.malefices.commands = commands; game.system.malefices.commands = commands;
} }
} }
@@ -77,7 +80,7 @@ export class MaleficesCommands {
console.log("===> Processing command") console.log("===> Processing command")
let command = commandsTable[name]; let command = commandsTable[name];
path = path + name + " "; path = path + name + " ";
if (command && command.subTable) { if (command?.subTable) {
if (params[0]) { if (params[0]) {
return this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path) return this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path)
} }
@@ -86,7 +89,7 @@ export class MaleficesCommands {
return true; return true;
} }
} }
if (command && command.func) { if (command?.func) {
const result = command.func(content, msg, params); const result = command.func(content, msg, params);
if (result == false) { if (result == false) {
CrucibleCommands._chatAnswer(msg, command.descr); CrucibleCommands._chatAnswer(msg, command.descr);
@@ -100,18 +103,44 @@ export class MaleficesCommands {
static _chatAnswer(msg, content) { static _chatAnswer(msg, content) {
msg.whisper = [game.user.id]; msg.whisper = [game.user.id];
msg.content = content; msg.content = content;
ChatMessage.create(msg); ChatMessage.create(msg);
} }
/* -------------------------------------------- */ /* --------------------------------------------- */
async poolRoll( msg) { static async createTirage(msg) {
let rollData = MaleficesUtility.getBasicRollData() if (game.user.isGM) {
rollData.alias = "Dice Pool Roll", let tirageData = {
rollData.mode = "generic" state: 'select-player',
rollData.title = `Dice Pool Roll`; nbCard: 0,
maxPlayerCard: 4,
let rollDialog = await MaleficesRollDialog.create( this, rollData); maxSecretCard: 1,
rollDialog.render( true ); cards: [],
players: foundry.utils.duplicate(game.users),
secretCards: [],
deck: MaleficesUtility.getTarots()
}
for (let i = 0; i < 4; i++) {
tirageData.cards.push({ name: "???", img: "systems/fvtt-malefices/images/tarots/background.webp" })
}
tirageData.secretCards.push({ name: "???", img: "systems/fvtt-malefices/images/tarots/background.webp" })
let tirageDialog = await MaleficesTirageTarotDialog.create(this, tirageData)
}
}
/* --------------------------------------------- */
static async tirerCarte(msg) {
let deck = MaleficesUtility.getTarots()
let index = Math.round(Math.random() * (deck.length-1))
let selectedCard = deck[index]
selectedCard.system.ispositif = true
if ( selectedCard.system.isdualside) { // Cas des cartes pouvant avoir 2 sens
selectedCard.system.ispositif = (Math.random() > 0.5)
}
selectedCard.system.isgm = false
selectedCard.value = (selectedCard.system.ispositif)? selectedCard.system.numericvalueup : selectedCard.system.numericvaluedown
MaleficesUtility.createChatMessage(game.user.name, "", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
})
} }
} }

View File

@@ -1,5 +1,14 @@
export const MALEFICES_CONFIG = { export const MALEFICES_CONFIG = {
attributs: {
"constitution": "Cons",
"physique": "Phy",
"culturegenerale": "CGén",
"habilite": "Hab",
"perception": "Per",
"spiritualite": "Spi",
"rationnalite": "Rat"
},
tarotType: { tarotType: {
"majeur": "Arcane Majeur", "majeur": "Arcane Majeur",
@@ -18,4 +27,41 @@ export const MALEFICES_CONFIG = {
"epee": "Epée, sabre, javelot, etc", "epee": "Epée, sabre, javelot, etc",
"mainsnues": "Mains Nues" "mainsnues": "Mains Nues"
}, },
confrontationDegreOptions :{
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5"
},
confrontationModifOptions: {
"-1": "-1",
"0": "0",
"1": "+1"
},
bonusMalusPersoOptions: [
{value: "-3", label: "-3"},
{value: "-2", label: "-2"},
{value: "-1", label: "-1"},
{value: "0", label: "0"},
{value: "+1", label: "+1"},
{value: "+2", label: "+2"},
{value: "+3", label: "+3"}
],
bonusMalusDefOptions: [
{value: "-6", label: "-6 (réussite critique)"},
{value: "-3", label: "-3 (réussite)"},
{value: "0", label: "0 (echec ou pas d'esquive)"},
{value: "+3", label: "+3 (echec critique)"}
],
bonusMalusPorteeOptions: [
{value: "1", label: "+1 (Portée courte)"},
{value: "0", label: "0 (Portée moyenne)"},
{value: "-1", label: "-1 (Portée longue)"}
]
} }

View File

@@ -4,35 +4,21 @@ import { MaleficesUtility } from "./malefices-utility.js";
* Extend the basic ItemSheet with some very simple modifications * Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet} * @extends {ItemSheet}
*/ */
export class MaleficesItemSheet extends ItemSheet { export class MaleficesItemSheet extends foundry.appv1.sheets.ItemSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-malefices", "sheet", "item"], classes: ["fvtt-malefices", "sheet", "item"],
template: "systems/fvtt-malefices/templates/item-sheet.hbs", template: "systems/fvtt-malefices/templates/item-sheet.hbs",
dragDrop: [{ dragSelector: null, dropSelector: null }], dragDrop: [{ dragSelector: null, dropSelector: null }],
width: 620, width: 620,
height: 'fit-content', height: 580,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }] tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
}); });
} }
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => { }
})
return buttons
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** @override */ /** @override */
setPosition(options = {}) { setPosition(options = {}) {
@@ -57,20 +43,20 @@ export class MaleficesItemSheet extends ItemSheet {
name: this.object.name, name: this.object.name,
editable: this.isEditable, editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked", cssClass: this.isEditable ? "editable" : "locked",
system: duplicate(this.object.system), system: foundry.utils.duplicate(this.object.system),
config: duplicate(game.system.malefices.config), config: foundry.utils.duplicate(game.system.malefices.config),
limited: this.object.limited, limited: this.object.limited,
options: this.options, options: this.options,
owner: this.document.isOwner, owner: this.document.isOwner,
description: await TextEditor.enrichHTML(this.object.system.description, { async: true }), description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, { async: true }),
notes: await TextEditor.enrichHTML(this.object.system.notes, { async: true }), notes: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.notes, { async: true }),
isGM: game.user.isGM isGM: game.user.isGM
} }
if ( this.object.type == "archetype") { if ( this.object.type == "archetype") {
formData.tarots = MaleficesUtility.getTarots() formData.tarots = MaleficesUtility.getTarots()
} }
this.options.editable = !(this.object.origin == "embeddedItem"); this.options.editable = !(this.object.origin == "embeddedItem");
console.log("ITEM DATA", formData, this); console.log("ITEM DATA", formData, this);
return formData; return formData;
@@ -90,24 +76,18 @@ export class MaleficesItemSheet extends ItemSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
postItem() { postItem() {
let chatData = duplicate(this.item) let chatData = foundry.utils.duplicate(this.item)
if (this.actor) { if (this.actor) {
chatData.actor = { id: this.actor.id }; chatData.actor = { id: this.actor.id };
} }
// Don't post any image for the item (which would leave a large gap) if the default image is used if (chatData.img?.includes("/blank.png")) {
if (chatData.img.includes("/blank.png")) {
chatData.img = null; chatData.img = null;
} }
// JSON object for easy creation chatData.config = game.system.malefices.config
chatData.jsondata = JSON.stringify( chatData.jsondata = JSON.stringify({ compendium: "postedItem", payload: chatData })
{
compendium: "postedItem",
payload: chatData,
});
renderTemplate('systems/Malefices/templates/post-item.html', chatData).then(html => { foundry.applications.handlebars.renderTemplate('systems/fvtt-malefices/templates/post-item.hbs', chatData).then(html => {
let chatOptions = MaleficesUtility.chatDataSetup(html); ChatMessage.create(MaleficesUtility.chatDataSetup(html))
ChatMessage.create(chatOptions)
}); });
} }
@@ -116,7 +96,7 @@ export class MaleficesItemSheet extends ItemSheet {
let levelIndex = Number($(ev.currentTarget).parents(".item").data("level-index")) let levelIndex = Number($(ev.currentTarget).parents(".item").data("level-index"))
let choiceIndex = Number($(ev.currentTarget).parents(".item").data("choice-index")) let choiceIndex = Number($(ev.currentTarget).parents(".item").data("choice-index"))
let featureId = $(ev.currentTarget).parents(".item").data("feature-id") let featureId = $(ev.currentTarget).parents(".item").data("feature-id")
let itemData = this.object.system.levels[levelIndex].choices[choiceIndex].features[featureId] let itemData = this.object.system.levels[levelIndex].choices[choiceIndex].features[featureId]
if (itemData.name != 'None') { if (itemData.name != 'None') {

View File

@@ -1,8 +1,12 @@
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
export const defaultItemImg = { export const defaultItemImg = {
//skill: "systems/fvtt-malefices/images/icons/skill1.webp", arme: "systems/fvtt-malefices/images/icons/epee.webp",
arme: "systems/fvtt-malefices/images/icones/arme.webp" equipement: "systems/fvtt-malefices/images/icons/equipement.webp",
elementbio: "systems/fvtt-malefices/images/icons/wisdom.webp",
archetype: "systems/fvtt-malefices/images/icons/archetype.webp",
tarot: "systems/fvtt-malefices/images/icons/tarot.webp",
sortilege: "systems/fvtt-malefices/images/icons/sortilege.webp",
} }
/** /**

View File

@@ -9,14 +9,19 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
// Import Modules // Import Modules
import { MaleficesActor } from "./malefices-actor.js"; import { MaleficesActor } from "./malefices-actor.js";
import { MaleficesItemSheet } from "./malefices-item-sheet.js";
import { MaleficesActorSheet } from "./malefices-actor-sheet.js";
import { MaleficesNPCSheet } from "./malefices-npc-sheet.js";
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
import { MaleficesCombat } from "./malefices-combat.js"; import { MaleficesCombat } from "./malefices-combat.js";
import { MaleficesItem } from "./malefices-item.js"; import { MaleficesItem } from "./malefices-item.js";
import { MaleficesHotbar } from "./malefices-hotbar.js" import { MaleficesHotbar } from "./malefices-hotbar.js"
import { MaleficesCharacterSummary } from "./malefices-summary-app.js"
import { MALEFICES_CONFIG } from "./malefices-config.js" import { MALEFICES_CONFIG } from "./malefices-config.js"
import { ClassCounter} from "https://www.uberwald.me/fvtt_appcount/count-class-ready.js"
// Import DataModels
import * as models from "./models/index.mjs"
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs"
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
@@ -26,7 +31,7 @@ import { MALEFICES_CONFIG } from "./malefices-config.js"
Hooks.once("init", async function () { Hooks.once("init", async function () {
console.log(`Initializing Malefices RPG`); console.log(`Initializing Malefices RPG`);
game.system.malefices = { game.system.malefices = {
config: MALEFICES_CONFIG, config: MALEFICES_CONFIG,
MaleficesHotbar MaleficesHotbar
@@ -37,7 +42,7 @@ Hooks.once("init", async function () {
MaleficesUtility.preloadHandlebarsTemplates(); MaleficesUtility.preloadHandlebarsTemplates();
/* -------------------------------------------- */ /* -------------------------------------------- */
// Set an initiative formula for the system // Set an initiative formula for the system
CONFIG.Combat.initiative = { CONFIG.Combat.initiative = {
formula: "1d6", formula: "1d6",
decimals: 1 decimals: 1
@@ -52,31 +57,52 @@ Hooks.once("init", async function () {
// Define custom Entity classes // Define custom Entity classes
CONFIG.Combat.documentClass = MaleficesCombat CONFIG.Combat.documentClass = MaleficesCombat
CONFIG.Actor.documentClass = MaleficesActor CONFIG.Actor.documentClass = MaleficesActor
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
pnj: models.PnjDataModel
}
CONFIG.Item.documentClass = MaleficesItem CONFIG.Item.documentClass = MaleficesItem
CONFIG.Item.dataModels = {
arme: models.ArmeDataModel,
equipement: models.EquipementDataModel,
archetype: models.ArchetypeDataModel,
tarot: models.TarotDataModel,
sortilege: models.SortilegeDataModel,
elementbio: models.ElementbioDataModel
}
/* -------------------------------------------- */ /* -------------------------------------------- */
// Register sheet application classes // Register AppV2 Actor Sheets
Actors.unregisterSheet("core", ActorSheet); foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
Actors.registerSheet("fvtt-malefices", MaleficesActorSheet, { types: ["personnage"], makeDefault: true }); foundry.documents.collections.Actors.registerSheet("fvtt-malefices", sheets.MaleficesPersonnageSheet, { types: ["personnage"], makeDefault: true });
Actors.registerSheet("fvtt-malefices", MaleficesNPCSheet, { types: ["pnj"], makeDefault: false }); foundry.documents.collections.Actors.registerSheet("fvtt-malefices", sheets.MaleficesNPCActorSheet, { types: ["pnj"], makeDefault: true });
Items.unregisterSheet("core", ItemSheet); // Register AppV2 Item Sheets
Items.registerSheet("fvtt-malefices", MaleficesItemSheet, { makeDefault: true }); foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesArmeSheet, { types: ["arme"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesArchetypeSheet, { types: ["archetype"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesTarotSheet, { types: ["tarot"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesSortilegeSheet, { types: ["sortilege"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-malefices", sheets.MaleficesElementbioSheet, { types: ["elementbio"], makeDefault: true });
MaleficesUtility.init() MaleficesUtility.init()
}); });
/* -------------------------------------------- */ /* -------------------------------------------- */
function welcomeMessage() { async function welcomeMessage() {
ChatMessage.create({ if (game.user.isGM) {
user: game.user.id, const content = await foundry.applications.handlebars.renderTemplate(
whisper: [game.user.id], 'systems/fvtt-malefices/templates/chat/welcome-message.hbs', {}
content: `<div id="welcome-message-malefices"><span class="rdd-roll-part"> )
<strong>Bienvenu dans Malefices, le JDR qui sent le souffre !</strong> ChatMessage.create({
<p>Le Livre de Base de Maléfices v4 est nécessaire pour jouer : https://arkhane-asylum.fr/en/malefices/</p> user: game.user.id,
<p>Maléfices et un jeu de rôle publié par Arkhane Asylum Publishing, tout les droits leur appartiennent.</p> whisper: [game.user.id],
<p>Système développé par LeRatierBretonnien avec l'aide de la Dame du Lac et Malik, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p> content
` }); });
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -92,18 +118,15 @@ Hooks.once("ready", function () {
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";
}
ClassCounter.registerUsageCount();
welcomeMessage(); welcomeMessage();
MaleficesUtility.ready() MaleficesUtility.ready()
MaleficesUtility.init() MaleficesCharacterSummary.ready()
}) })
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Foundry VTT Initialization */ /* Foundry VTT Initialization */
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -111,10 +134,9 @@ Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') { if (content[0] == '/') {
let regExp = /(\S+)/g; let regExp = /(\S+)/g;
let commands = content.match(regExp); let commands = content.match(regExp);
if (game.system.Malefices.commands.processChatCommand(commands, content, msg)) { if (game.system.malefices.commands.processChatCommand(commands, content, msg)) {
return false; return false;
} }
} }
return true; return true;
}); });

View File

@@ -6,12 +6,12 @@
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
export class MaleficesNPCSheet extends ActorSheet { export class MaleficesNPCSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
return mergeObject(super.defaultOptions, { return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["Malefices", "sheet", "actor"], classes: ["Malefices", "sheet", "actor"],
template: "systems/fvtt-malefices/templates/npc-sheet.html", template: "systems/fvtt-malefices/templates/npc-sheet.html",
width: 640, width: 640,
@@ -25,7 +25,7 @@ export class MaleficesNPCSheet extends ActorSheet {
/* -------------------------------------------- */ /* -------------------------------------------- */
async getData() { async getData() {
const objectData = this.object.system const objectData = this.object.system
let actorData = duplicate(objectData) let actorData = foundry.utils.duplicate(objectData)
let formData = { let formData = {
title: this.title, title: this.title,
@@ -38,16 +38,16 @@ export class MaleficesNPCSheet extends ActorSheet {
data: actorData, data: actorData,
limited: this.object.limited, limited: this.object.limited,
skills: this.actor.getSkills( ), skills: this.actor.getSkills( ),
weapons: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getWeapons()) ), weapons: this.actor.checkAndPrepareEquipments( foundry.utils.duplicate(this.actor.getWeapons()) ),
armors: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getArmors())), armors: this.actor.checkAndPrepareEquipments( foundry.utils.duplicate(this.actor.getArmors())),
shields: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getShields())), shields: this.actor.checkAndPrepareEquipments( foundry.utils.duplicate(this.actor.getShields())),
spells: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getLore())), spells: this.actor.checkAndPrepareEquipments( foundry.utils.duplicate(this.actor.getLore())),
equipments: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquipmentsOnly()) ), equipments: this.actor.checkAndPrepareEquipments(foundry.utils.duplicate(this.actor.getEquipmentsOnly()) ),
equippedWeapons: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquippedWeapons()) ), equippedWeapons: this.actor.checkAndPrepareEquipments(foundry.utils.duplicate(this.actor.getEquippedWeapons()) ),
equippedArmor: this.actor.getEquippedArmor(), equippedArmor: this.actor.getEquippedArmor(),
equippedShield: this.actor.getEquippedShield(), equippedShield: this.actor.getEquippedShield(),
subActors: duplicate(this.actor.getSubActors()), subActors: foundry.utils.duplicate(this.actor.getSubActors()),
moneys: duplicate(this.actor.getMoneys()), moneys: foundry.utils.duplicate(this.actor.getMoneys()),
encCapacity: this.actor.getEncumbranceCapacity(), encCapacity: this.actor.getEncumbranceCapacity(),
saveRolls: this.actor.getSaveRoll(), saveRolls: this.actor.getSaveRoll(),
conditions: this.actor.getConditions(), conditions: this.actor.getConditions(),
@@ -71,10 +71,10 @@ export class MaleficesNPCSheet extends ActorSheet {
// Everything below here is only needed if the sheet is editable // Everything below here is only needed if the sheet is editable
if (!this.options.editable) return; if (!this.options.editable) return;
html.bind("keydown", function(e) { // Ignore Enter in actores sheet html.bind("keydown", function(e) { // Ignore Enter in actores sheet
if (e.keyCode === 13) return false; if (e.keyCode === 13) return false;
}); });
// Update Inventory Item // Update Inventory Item
html.find('.item-edit').click(ev => { html.find('.item-edit').click(ev => {
@@ -92,17 +92,17 @@ export class MaleficesNPCSheet extends ActorSheet {
let dataType = $(ev.currentTarget).data("type") let dataType = $(ev.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true }) this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true })
}) })
html.find('.equip-activate').click(ev => { html.find('.equip-activate').click(ev => {
const li = $(ev.currentTarget).parents(".item") const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id") let itemId = li.data("item-id")
this.actor.equipActivate( itemId) this.actor.equipActivate( itemId)
}); });
html.find('.equip-deactivate').click(ev => { html.find('.equip-deactivate').click(ev => {
const li = $(ev.currentTarget).parents(".item") const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id") let itemId = li.data("item-id")
this.actor.equipDeactivate( itemId) this.actor.equipDeactivate( itemId)
}); });
html.find('.subactor-edit').click(ev => { html.find('.subactor-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
@@ -110,7 +110,7 @@ export class MaleficesNPCSheet extends ActorSheet {
let actor = game.actors.get( actorId ); let actor = game.actors.get( actorId );
actor.sheet.render(true); actor.sheet.render(true);
}); });
html.find('.subactor-delete').click(ev => { html.find('.subactor-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
let actorId = li.data("actor-id"); let actorId = li.data("actor-id");
@@ -133,7 +133,7 @@ export class MaleficesNPCSheet extends ActorSheet {
const li = $(event.currentTarget).parents(".item") const li = $(event.currentTarget).parents(".item")
this.actor.incDecAmmo( li.data("item-id"), +1 ) this.actor.incDecAmmo( li.data("item-id"), +1 )
} ); } );
html.find('.roll-ability').click((event) => { html.find('.roll-ability').click((event) => {
const abilityKey = $(event.currentTarget).data("ability-key"); const abilityKey = $(event.currentTarget).data("ability-key");
this.actor.rollAbility(abilityKey); this.actor.rollAbility(abilityKey);
@@ -142,7 +142,7 @@ export class MaleficesNPCSheet extends ActorSheet {
const li = $(event.currentTarget).parents(".item") const li = $(event.currentTarget).parents(".item")
const skillId = li.data("item-id") const skillId = li.data("item-id")
this.actor.rollSkill(skillId) this.actor.rollSkill(skillId)
}); });
html.find('.roll-weapon').click((event) => { html.find('.roll-weapon').click((event) => {
const li = $(event.currentTarget).parents(".item"); const li = $(event.currentTarget).parents(".item");
@@ -163,28 +163,28 @@ export class MaleficesNPCSheet extends ActorSheet {
const saveKey = $(event.currentTarget).data("save-key") const saveKey = $(event.currentTarget).data("save-key")
this.actor.rollSave(saveKey) this.actor.rollSave(saveKey)
}); });
html.find('.lock-unlock-sheet').click((event) => { html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore; this.options.editScore = !this.options.editScore;
this.render(true); this.render(true);
}); });
html.find('.item-link a').click((event) => { html.find('.item-link a').click((event) => {
const itemId = $(event.currentTarget).data("item-id"); const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.getOwnedItem(itemId); const item = this.actor.getOwnedItem(itemId);
item.sheet.render(true); item.sheet.render(true);
}); });
html.find('.item-equip').click(ev => { html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
this.actor.equipItem( li.data("item-id") ); this.actor.equipItem( li.data("item-id") );
this.render(true); this.render(true);
}); });
html.find('.update-field').change(ev => { html.find('.update-field').change(ev => {
const fieldName = $(ev.currentTarget).data("field-name"); const fieldName = $(ev.currentTarget).data("field-name");
let value = Number(ev.currentTarget.value); let value = Number(ev.currentTarget.value);
this.actor.update( { [`${fieldName}`]: value } ); this.actor.update( { [`${fieldName}`]: value } );
}); });
} }

View File

@@ -1,75 +1,57 @@
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
export class MaleficesRollDialog extends Dialog { export class MaleficesRollDialog {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async create(actor, rollData) { static async create(actor, rollData) {
const isCard = rollData?.attr?.iscard
const template = isCard
? 'systems/fvtt-malefices/templates/dialogs/confrontation-dialog.hbs'
: 'systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs'
let options = { classes: ["MaleficesDialog"], width: 540, height: 'fit-content', 'z-index': 99999 }; const content = await foundry.applications.handlebars.renderTemplate(template, rollData)
let html = await renderTemplate('systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs', rollData);
return new MaleficesRollDialog(actor, rollData, html, options); return foundry.applications.api.DialogV2.wait({
} window: {
title: isCard ? "Tirage" : "Jet de dé",
/* -------------------------------------------- */ icon: isCard ? "fa-solid fa-layer-group" : "fa-solid fa-dice-d20",
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: (rollData.mode == "skill") ? "Skill" : "Attribute",
content: html,
buttons: {
roll: {
icon: '<i class="fas fa-check"></i>',
label: "Roll !",
callback: () => { this.roll() }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Cancel",
callback: () => { this.close() }
}
}, },
close: close classes: ["malefices-roll-dialog"],
} position: { width: 540 },
modal: false,
super(conf, options); rejectClose: false,
content,
this.actor = actor; buttons: [
this.rollData = rollData; {
action: "roll",
label: isCard ? "Tirer une carte" : "Lancer le dé",
icon: isCard ? "fa-solid fa-layer-group" : "fa-solid fa-check",
default: true,
callback: (event, button, dialog) => {
MaleficesRollDialog._updateRollDataFromForm(rollData, button.form.elements)
if (isCard) {
MaleficesUtility.tirageConfrontationMalefices(rollData)
} else {
MaleficesUtility.rollMalefices(rollData)
}
}
},
{
action: "cancel",
label: "Annuler",
icon: "fa-solid fa-times",
}
],
})
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
roll() { static _updateRollDataFromForm(rollData, elements) {
MaleficesUtility.rollMalefices(this.rollData) if (elements.bonusMalusPerso) rollData.bonusMalusPerso = Number(elements.bonusMalusPerso.value)
} if (elements.bonusMalusSituation) rollData.bonusMalusSituation = Number(elements.bonusMalusSituation.value)
if (elements.bonusMalusDef) rollData.bonusMalusDef = Number(elements.bonusMalusDef.value)
/* -------------------------------------------- */ if (elements.bonusMalusPortee) rollData.bonusMalusPortee = Number(elements.bonusMalusPortee.value)
async refreshDialog() { if (elements.confrontationDegre) rollData.confrontationDegre = Number(elements.confrontationDegre.value)
const content = await renderTemplate("systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs", this.rollData) if (elements.confrontationModif) rollData.confrontationModif = Number(elements.confrontationModif.value)
this.data.content = content
this.render(true)
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('#bonusMalusSituation').change((event) => {
this.rollData.bonusMalusSituation = Number(event.currentTarget.value)
})
html.find('#bonusMalusPerso').change((event) => {
this.rollData.bonusMalusPerso = Number(event.currentTarget.value)
})
html.find('#bonusMalusDef').change((event) => {
this.rollData.bonusMalusDef = Number(event.currentTarget.value)
})
html.find('#bonusMalusPortee').change((event) => {
this.rollData.bonusMalusPortee = Number(event.currentTarget.value)
})
} }
} }

View File

@@ -0,0 +1,133 @@
/* -------------------------------------------- */
import { MaleficesUtility } from "./malefices-utility.js";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
/* -------------------------------------------- */
export class MaleficesCharacterSummary extends HandlebarsApplicationMixin(ApplicationV2) {
/* -------------------------------------------- */
static DEFAULT_OPTIONS = {
id: "malefices-character-summary",
classes: ["MaleficesDialog"],
window: { title: "Résumé des Personnages", resizable: true },
position: { width: 960, height: "auto" },
dragDrop: [{ dragSelector: null, dropSelector: ".character-summary-container" }],
}
static PARTS = {
form: { template: "systems/fvtt-malefices/templates/dialogs/character-summary.hbs" }
}
/* -------------------------------------------- */
static displayPCSummary() {
if (game.user.isGM) {
game.system.malefices.charSummary.render({ force: true })
} else {
ui.notifications.info("Commande /resume réservée au MJ !")
}
}
/* -------------------------------------------- */
updatePCSummary() {
if (this.element?.isConnected) {
this.render({ force: true })
}
}
/* -------------------------------------------- */
static ready() {
if (!game.user.isGM) {
return
}
game.system.malefices.charSummary = new MaleficesCharacterSummary()
}
/* -------------------------------------------- */
constructor(options = {}) {
super(options)
const saved = game.settings.get("world", "character-summary-data")
this.extraList = saved.extraList ?? saved.npcList ?? []
}
/* -------------------------------------------- */
async _prepareContext(_options) {
const pcs = game.actors.filter(ac => ac.type === "personnage" && ac.hasPlayerOwner)
const extras = []
const validList = []
for (const actorId of this.extraList) {
const actor = game.actors.get(actorId)
if (actor) { extras.push(actor); validList.push(actorId) }
}
if (validList.length !== this.extraList.length) {
this.extraList = validList
this._persist()
}
return { pcs, extras, config: game.system.malefices.config }
}
/* -------------------------------------------- */
_onRender(_context, _options) {
const el = this.element
el.querySelectorAll('.actor-open').forEach(a => {
a.addEventListener('click', event => {
const li = event.currentTarget.closest('.item')
const actor = game.actors.get(li.dataset.actorId)
actor?.sheet.render(true)
})
})
el.querySelectorAll('.summary-roll').forEach(a => {
a.addEventListener('click', event => {
const li = event.currentTarget.closest('.item')
const actor = game.actors.get(li.dataset.actorId)
const key = event.currentTarget.dataset.key
actor?.rollAttribut(key)
})
})
el.querySelectorAll('.actor-delete').forEach(a => {
a.addEventListener('click', event => {
const li = event.currentTarget.closest('.item')
this.extraList = this.extraList.filter(id => id !== li.dataset.actorId)
this._persist()
this.render({ force: true })
})
})
const dropZone = el.querySelector('.character-summary-container')
if (dropZone) {
dropZone.addEventListener('dragover', ev => ev.preventDefault())
dropZone.addEventListener('drop', ev => { ev.stopPropagation(); this._onDrop(ev) })
}
}
/* -------------------------------------------- */
_canDragDrop(_selector) { return true }
/* -------------------------------------------- */
_persist() {
const saved = game.settings.get("world", "character-summary-data")
game.settings.set("world", "character-summary-data", { ...saved, extraList: this.extraList })
}
/* -------------------------------------------- */
_saveAndRefresh() {
this.render({ force: true })
}
/* -------------------------------------------- */
async _onDrop(event) {
try {
const dataItem = JSON.parse(event.dataTransfer.getData('text/plain'))
const actor = fromUuidSync(dataItem.uuid)
if (actor && !this.extraList.includes(actor.id)) {
this.extraList.push(actor.id)
this._persist()
this.render({ force: true })
}
} catch(e) { /* not a valid drag payload */ }
}
}

View File

@@ -0,0 +1,132 @@
import { MaleficesUtility } from "./malefices-utility.js";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
export class MaleficesTirageTarotDialog extends HandlebarsApplicationMixin(ApplicationV2) {
/* -------------------------------------------- */
static DEFAULT_OPTIONS = {
id: "malefices-tirage-tarot",
classes: ["MaleficesDialog"],
window: { title: "Tirage des Tarots", resizable: true },
position: { width: 720, height: 740 },
}
static PARTS = {
form: { template: 'systems/fvtt-malefices/templates/dialogs/tirage-tarot-dialog.hbs' }
}
/* -------------------------------------------- */
constructor(actor, tirageData, options = {}) {
super(options)
this.actor = actor
this.tirageData = tirageData
}
/* -------------------------------------------- */
static async create(actor, tirageData) {
const app = new MaleficesTirageTarotDialog(actor, tirageData)
app.render({ force: true })
return app
}
/* -------------------------------------------- */
async _prepareContext(_options) {
return { ...this.tirageData }
}
/* -------------------------------------------- */
_onRender(_context, _options) {
const el = this.element
el.querySelector('#playerId')?.addEventListener('change', (event) => {
if (event.currentTarget.value !== "none") {
this.tirageData.playerId = event.currentTarget.value
this.processSelectedPlayer()
}
})
el.querySelector('#actorId')?.addEventListener('change', (event) => {
if (event.currentTarget.value !== "none") {
this.attributeToActor(event.currentTarget.value)
}
})
el.querySelector('.tirage-close-btn')?.addEventListener('click', () => this.close())
}
/* -------------------------------------------- */
async sendCardRequest() {
this.tirageData.state = 'waiting-user-card'
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "useronly", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/request-tarot-card.hbs`, this.tirageData)
})
}
/* -------------------------------------------- */
drawCard() {
let index = Math.round(Math.random() * (this.tirageData.deck.length - 1))
let selectedCard = this.tirageData.deck[index]
selectedCard.system.ispositif = true
if (selectedCard.system.isdualside) {
selectedCard.system.ispositif = (Math.random() > 0.5)
}
// Cas spécial de la Roue de la Fortune
if (selectedCard.name.toLowerCase().includes("fortune")) {
this.tirageData.maxPlayerCard += 1
this.tirageData.maxSecretCard += 1
}
this.tirageData.deck = this.tirageData.deck.filter(c => c.name !== selectedCard.name)
return selectedCard
}
/* -------------------------------------------- */
async addCard(msgId) {
MaleficesUtility.removeChatMessageId(msgId)
let selectedCard = this.drawCard()
selectedCard.system.isgm = false
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "gmroll", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
})
if (this.tirageData.cards[0].name == "???") this.tirageData.cards.shift()
this.tirageData.cards.push(selectedCard)
this.tirageData.nbCard++
if (this.tirageData.nbCard == this.tirageData.maxPlayerCard) {
for (let i = 0; i < this.tirageData.maxSecretCard; i++) {
let secretCard = this.drawCard()
secretCard.system.isgm = true
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "blindroll", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, secretCard)
})
if (this.tirageData.secretCards[0].name == "???") this.tirageData.secretCards.shift()
this.tirageData.secretCards.push(secretCard)
}
this.tirageData.actors = foundry.utils.duplicate(game.actors)
this.tirageData.state = 'attribute-to-actor'
} else {
this.sendCardRequest()
}
this.render({ force: true })
}
/* -------------------------------------------- */
async processSelectedPlayer() {
this.tirageData.user = game.users.get(this.tirageData.playerId)
this.tirageData.players = null
game.system.malefices.currentTirage = this
this.render({ force: true })
this.sendCardRequest()
}
/* -------------------------------------------- */
attributeToActor(actorId) {
const actor = game.actors.get(actorId)
if (actor) {
actor.createEmbeddedDocuments('Item', this.tirageData.cards)
actor.createEmbeddedDocuments('Item', this.tirageData.secretCards)
ui.notifications.info("Les cartes ont été attribuées à " + actor.name)
}
}
}

View File

@@ -1,23 +1,22 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { MaleficesCombat } from "./malefices-combat.js";
import { MaleficesCommands } from "./malefices-commands.js"; import { MaleficesCommands } from "./malefices-commands.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
export class MaleficesUtility { export class MaleficesUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async init() { static async init() {
Hooks.on('renderChatLog', (log, html, data) => MaleficesUtility.chatListeners(html)); Hooks.on('renderChatLog', (log, html, data) => MaleficesUtility.chatListeners(html));
/*Hooks.on("dropCanvasData", (canvas, data) => {
MaleficesUtility.dropItemOnToken(canvas, data)
});*/
this.rollDataStore = {} this.rollDataStore = {}
this.defenderStore = {} this.defenderStore = {}
MaleficesCommands.init(); MaleficesCommands.init();
}
/* -------------------------------------------- */
static async ready() {
Handlebars.registerHelper('count', function (list) { Handlebars.registerHelper('count', function (list) {
return list.length; return list.length;
@@ -44,6 +43,26 @@ export class MaleficesUtility {
Handlebars.registerHelper('add', function (a, b) { Handlebars.registerHelper('add', function (a, b) {
return parseInt(a) + parseInt(b); return parseInt(a) + parseInt(b);
}) })
// Handle v12 removal of this helper
Handlebars.registerHelper('select', function (selected, options) {
const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected));
const rgx = new RegExp(' value=[\"\']' + escapedValue + '[\"\']');
const html = options.fn(this);
return html.replace(rgx, "$& selected");
});
game.settings.register("world", "character-summary-data", {
name: "character-summary-data",
scope: "world",
config: false,
default: { extraList: [], x: 200, y: 200 },
type: Object
})
const tarots = await MaleficesUtility.loadCompendium("fvtt-malefices.malefices-tarots")
this.tarots = tarots.map(i => i.toObject())
} }
/*-------------------------------------------- */ /*-------------------------------------------- */
@@ -54,16 +73,10 @@ export class MaleficesUtility {
/*-------------------------------------------- */ /*-------------------------------------------- */
static getTarots() { static getTarots() {
return duplicate(this.tarots) return foundry.utils.duplicate(this.tarots)
} }
static getTarot(tId) { static getTarot(tId) {
return this.tarots.find(t => t._id == tId) return this.tarots.find(t => t._id == tId)
}
/* -------------------------------------------- */
static async ready() {
const tarots = await MaleficesUtility.loadCompendium("fvtt-malefices.malefices-tarots")
this.tarots = tarots.map(i => i.toObject())
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -89,11 +102,19 @@ export class MaleficesUtility {
} }
return actor return actor
} }
/* -------------------------------------------- */
static drawDeckCard(msgId) {
if (game.user.isGM) {
game.system.malefices.currentTirage.addCard(msgId)
} else {
game.socket.emit("system.fvtt-malefices", { name: "msg-draw-card", data: { msgId: msgId } })
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static async chatListeners(html) { static async chatListeners(html) {
html.on("click", '.roll-destin', event => { $(html).on("click", '.roll-destin', event => {
let messageId = MaleficesUtility.findChatMessageId(event.currentTarget) let messageId = MaleficesUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId) let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "rolldata") let rollData = message.getFlag("world", "rolldata")
@@ -102,6 +123,11 @@ export class MaleficesUtility {
rollData.isReroll = true rollData.isReroll = true
this.rollMalefices(rollData) this.rollMalefices(rollData)
}) })
$(html).on("click", '.draw-tarot-card', event => {
let messageId = MaleficesUtility.findChatMessageId(event.currentTarget)
this.drawDeckCard(messageId)
})
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -109,10 +135,15 @@ export class MaleficesUtility {
const templatePaths = [ const templatePaths = [
'systems/fvtt-malefices/templates/actors/editor-notes-gm.hbs', 'systems/fvtt-malefices/templates/actors/editor-notes-gm.hbs',
'systems/fvtt-malefices/templates/items/partial-item-header.hbs',
'systems/fvtt-malefices/templates/items/partial-item-nav.hbs', 'systems/fvtt-malefices/templates/items/partial-item-nav.hbs',
'systems/fvtt-malefices/templates/items/partial-item-description.hbs' 'systems/fvtt-malefices/templates/items/partial-item-description.hbs',
'systems/fvtt-malefices/templates/post-item.hbs',
'systems/fvtt-malefices/templates/actors/npc-sheet.hbs',
'systems/fvtt-malefices/templates/chat/welcome-message.hbs',
'systems/fvtt-malefices/templates/dialogs/character-summary.hbs'
] ]
return loadTemplates(templatePaths); return foundry.applications.handlebars.loadTemplates(templatePaths);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -178,28 +209,17 @@ export class MaleficesUtility {
let id = rollData.rollId let id = rollData.rollId
let oldRollData = this.rollDataStore[id] || {} let oldRollData = this.rollDataStore[id] || {}
let newRollData = mergeObject(oldRollData, rollData) let newRollData = foundry.utils.mergeObject(oldRollData, rollData)
this.rollDataStore[id] = newRollData this.rollDataStore[id] = newRollData
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async onSocketMesssage(msg) { static async onSocketMesssage(msg) {
console.log("SOCKET MESSAGE", msg.name) console.log("SOCKET MESSAGE", msg.name)
if (msg.name == "msg_update_roll") { if (msg.name == "msg-draw-card") {
this.updateRollData(msg.data) if (game.user.isGM && game.system.malefices.currentTirage) {
} game.system.malefices.currentTirage.addCard(msg.data.msgId)
if (msg.name == "msg_gm_process_attack_defense") {
this.processSuccessResult(msg.data)
}
if (msg.name == "msg_gm_item_drop" && game.user.isGM) {
let actor = game.actors.get(msg.data.actorId)
let item
if (msg.data.isPack) {
item = await fromUuid("Compendium." + msg.data.isPack + "." + msg.data.itemId)
} else {
item = game.items.get(msg.data.itemId)
} }
this.addItemDropToActor(actor, item)
} }
} }
@@ -261,6 +281,133 @@ export class MaleficesUtility {
} }
} }
/* -------------------------------------------- */
static processSpecialCard(actor, rollData) {
if (rollData.selectedCard.name.toLowerCase().includes("archange")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("archange"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>L'Archange : ${actor.name} gagne 1 point de Spiritualité.`
})
actor.incDecAttr("spiritualite", 1)
}
}
if (rollData.selectedCard.name.toLowerCase().includes("vicaire")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("vicaire"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", {
content: `Conséquence supplémentaire ! <br>Le Vicaire : ${actor.name} vient de gagner 1 point en Pratique de la Magie Blanche (MPMB, secret).`
})
actor.incDecMPMB(1)
}
}
if (rollData.selectedCard.name.toLowerCase().includes("chance")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("chance"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>La Chance : ${actor.name} a gagné 1 point de Destin.`
})
actor.incDecDestin(1)
}
}
if (rollData.selectedCard.name.toLowerCase().includes("mort")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("mort"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>La Mort : ${actor.name} est pétrifié par la peur.`
})
actor.incDecDestin(1)
}
}
if (rollData.selectedCard.name.toLowerCase().includes("diable")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("diable"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>Le Diable : ${actor.name} gagne 1 point de Rationnalité.`
})
actor.incDecAttr("rationnalite", 1)
}
}
if (rollData.selectedCard.name.toLowerCase().includes("lune noire")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("lune noire"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", {
content: `Conséquence supplémentaire ! <br>La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).`
})
actor.incDecFluide(1)
}
}
if (rollData.selectedCard.name.toLowerCase().includes("grand livre")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("grand livre"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", {
content: `Conséquence supplémentaire ! <br>La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).`
})
actor.incDecFluide(1)
}
}
if (rollData.selectedCard.name.toLowerCase().includes("sorcier")) {
let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("sorcier"))
if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", {
content: `Conséquence supplémentaire ! <br>Le Vicaire : ${actor.name} vient de gagner 1 point en Pratique de la Magie Noire (MPMN, secret).`
})
actor.incDecMPMN(1)
}
}
}
/* -------------------------------------------- */
static computeResults(rollData) {
rollData.isSuccess = false
if (rollData.total <= rollData.target) {
rollData.isSuccess = true
}
if (rollData.total == 1) {
rollData.isSuccess = true
rollData.isCritical = true
}
if (rollData.total == 20) {
rollData.isSuccess = false
rollData.isFumble = true
}
if (rollData.total <= Math.floor(rollData.target / 3)) {
rollData.isPart = true
}
}
/* -------------------------------------------- */
static async tirageConfrontationMalefices(rollData) {
let actor = game.actors.get(rollData.actorId)
rollData.target = rollData.attr.value - rollData.confrontationDegre + rollData.confrontationModif
let deck = this.getTarots()
let index = Math.round(Math.random() * (deck.length - 1))
let selectedCard = deck[index]
selectedCard.system.ispositif = (Math.random() > 0.5)
selectedCard.value = (selectedCard.system.ispositif) ? selectedCard.system.numericvalueup : selectedCard.system.numericvaluedown
rollData.total = selectedCard.value
rollData.selectedCard = selectedCard
await MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
})
this.computeResults(rollData)
if (rollData.isSuccess) {
rollData.gainAttr = Math.ceil(rollData.confrontationDegre / 2) + ((rollData.isCritical) ? 1 : 0)
actor.incDecAttr(rollData.attr.abbrev, rollData.gainAttr)
} else {
rollData.gainAttr = rollData.confrontationDegre
actor.incDecAttr(rollData.attr.abbrev, -rollData.gainAttr)
}
await MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/chat-confrontation-result.hbs`, rollData)
})
this.processSpecialCard(actor, rollData)
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static async rollMalefices(rollData) { static async rollMalefices(rollData) {
@@ -276,28 +423,15 @@ export class MaleficesUtility {
// Performs roll // Performs roll
console.log("Roll formula", diceFormula) console.log("Roll formula", diceFormula)
let myRoll = new Roll(diceFormula).roll({ async: false }) let myRoll = await new Roll(diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = myRoll rollData.roll = foundry.utils.duplicate(myRoll)
rollData.total = myRoll.total
rollData.isSuccess = false this.computeResults(rollData)
if (myRoll.total <= rollData.target) {
rollData.isSuccess = true
}
if (myRoll.total == 1) {
rollData.isSuccess = true
rollData.isCritical = true
}
if (myRoll.total == 20) {
rollData.isSuccess = false
rollData.isFumble = true
}
if (myRoll.total <= Math.floor(rollData.target / 3)) {
rollData.isPart = true
}
let msg = await this.createChatWithRollMode(rollData.alias, { let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/chat-generic-result.hbs`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/chat-generic-result.hbs`, rollData)
}) })
msg.setFlag("world", "rolldata", rollData) msg.setFlag("world", "rolldata", rollData)
if (rollData.mode == "initiative") { if (rollData.mode == "initiative") {
@@ -331,11 +465,17 @@ export class MaleficesUtility {
switch (rollMode) { switch (rollMode) {
case "blindroll": return this.getUsers(user => user.isGM); case "blindroll": return this.getUsers(user => user.isGM);
case "gmroll": return this.getWhisperRecipientsAndGMs(name); case "gmroll": return this.getWhisperRecipientsAndGMs(name);
case "useronly": return this.getWhisperRecipientsOnly(name);
case "selfroll": return [game.user.id]; case "selfroll": return [game.user.id];
} }
return undefined; return undefined;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static getWhisperRecipientsOnly(name) {
let recep1 = ChatMessage.getWhisperRecipients(name) || [];
return recep1
}
/* -------------------------------------------- */
static getWhisperRecipientsAndGMs(name) { static getWhisperRecipientsAndGMs(name) {
let recep1 = ChatMessage.getWhisperRecipients(name) || []; let recep1 = ChatMessage.getWhisperRecipients(name) || [];
return recep1.concat(ChatMessage.getWhisperRecipients('GM')); return recep1.concat(ChatMessage.getWhisperRecipients('GM'));
@@ -343,7 +483,7 @@ export class MaleficesUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static blindMessageToGM(chatOptions) { static blindMessageToGM(chatOptions) {
let chatGM = duplicate(chatOptions); let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM); chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content; chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM); console.log("blindMessageToGM", chatGM);
@@ -369,7 +509,7 @@ export class MaleficesUtility {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static createChatMessage(name, rollMode, chatOptions) { static async createChatMessage(name, rollMode, chatOptions) {
switch (rollMode) { switch (rollMode) {
case "blindroll": // GM only case "blindroll": // GM only
if (!game.user.isGM) { if (!game.user.isGM) {
@@ -387,13 +527,13 @@ export class MaleficesUtility {
break; break;
} }
chatOptions.alias = chatOptions.alias || name; chatOptions.alias = chatOptions.alias || name;
return ChatMessage.create(chatOptions); return await ChatMessage.create(chatOptions);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static getBasicRollData() { static getBasicRollData() {
let rollData = { let rollData = {
rollId: randomID(16), rollId: foundry.utils.randomID(16),
bonusMalusPerso: 0, bonusMalusPerso: 0,
bonusMalusSituation: 0, bonusMalusSituation: 0,
bonusMalusDef: 0, bonusMalusDef: 0,
@@ -413,36 +553,22 @@ export class MaleficesUtility {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static createChatWithRollMode(name, chatOptions) { static async createChatWithRollMode(name, chatOptions) {
return this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions) return await this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async confirmDelete(actorSheet, li) { static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id"); const itemId = li.dataset.itemId;
let msgTxt = "<p>Are you sure to remove this Item ?"; const confirmed = await foundry.applications.api.DialogV2.confirm({
let buttons = { window: { title: "Confirmer la suppression" },
delete: { content: "<p>Supprimer cet objet ?</p>",
icon: '<i class="fas fa-check"></i>', yes: { label: "Supprimer", icon: "fas fa-trash" },
label: "Yes, remove it", no: { label: "Annuler", icon: "fas fa-times" },
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false));
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Cancel"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirm removal",
content: msgTxt,
buttons: buttons,
default: "cancel"
}); });
d.render(true); if (confirmed) {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
}
} }
} }

View File

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

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

@@ -0,0 +1,19 @@
/**
* Data model pour les armes
*/
export default class ArmeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
armetype: new fields.StringField({ initial: "" }),
porteecourte: new fields.StringField({ initial: "" }),
porteemoyenne: new fields.StringField({ initial: "" }),
dommagenormale: new fields.NumberField({ initial: 0, integer: true }),
dommagepart: new fields.NumberField({ initial: 0, integer: true }),
dommagecritique: new fields.NumberField({ initial: 0, integer: true }),
dommagecritiqueKO: new fields.BooleanField({ initial: false }),
dommagecritiquemort: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

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

View File

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

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

@@ -0,0 +1,15 @@
/**
* Index des DataModels pour Maléfices
*/
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as PnjDataModel } from './pnj.mjs';
// Modèles d'items
export { default as ArmeDataModel } from './arme.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as ArchetypeDataModel } from './archetype.mjs';
export { default as TarotDataModel } from './tarot.mjs';
export { default as SortilegeDataModel } from './sortilege.mjs';
export { default as ElementbioDataModel } from './elementbio.mjs';

View File

@@ -0,0 +1,95 @@
/**
* Data model pour les personnages joueurs (type "personnage")
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
// Template biodata
biodata: new fields.SchemaField({
age: new fields.NumberField({ initial: 0, integer: true }),
size: new fields.StringField({ initial: "" }),
lieunaissance: new fields.StringField({ initial: "" }),
nationalite: new fields.StringField({ initial: "" }),
profession: new fields.StringField({ initial: "" }),
residence: new fields.StringField({ initial: "" }),
milieusocial: new fields.StringField({ initial: "" }),
poids: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
sexe: new fields.StringField({ initial: "" }),
yeux: new fields.StringField({ initial: "" }),
enfance: new fields.StringField({ initial: "" }),
adulte: new fields.StringField({ initial: "" }),
loisirs: new fields.StringField({ initial: "" }),
singularite: new fields.StringField({ initial: "" }),
politique: new fields.StringField({ initial: "" }),
religion: new fields.StringField({ initial: "" }),
fantastique: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
// Template core
subactors: new fields.ArrayField(new fields.StringField(), { initial: [] }),
lamesdestin: new fields.ArrayField(new fields.StringField(), { initial: [] }),
pointdestin: new fields.NumberField({ initial: 1, integer: true }),
fluide: new fields.NumberField({ initial: 5, integer: true }),
mpmb: new fields.NumberField({ initial: 0, integer: true }),
mpmn: new fields.NumberField({ initial: 0, integer: true }),
equipementlibre: new fields.HTMLField({ initial: "" }),
attributs: new fields.SchemaField({
constitution: new fields.SchemaField({
label: new fields.StringField({ initial: "Constitution" }),
abbrev: new fields.StringField({ initial: "constitution" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
physique: new fields.SchemaField({
label: new fields.StringField({ initial: "Aptitudes Physiques" }),
abbrev: new fields.StringField({ initial: "physique" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
culturegenerale: new fields.SchemaField({
label: new fields.StringField({ initial: "Culture Générale" }),
abbrev: new fields.StringField({ initial: "culturegenerale" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
habilite: new fields.SchemaField({
label: new fields.StringField({ initial: "Habilité" }),
abbrev: new fields.StringField({ initial: "habilite" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
perception: new fields.SchemaField({
label: new fields.StringField({ initial: "Perception" }),
abbrev: new fields.StringField({ initial: "perception" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
spiritualite: new fields.SchemaField({
label: new fields.StringField({ initial: "Spiritualite" }),
abbrev: new fields.StringField({ initial: "spiritualite" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
iscard: new fields.BooleanField({ initial: true }),
max: new fields.NumberField({ initial: 0, integer: true })
}),
rationnalite: new fields.SchemaField({
label: new fields.StringField({ initial: "Rationnalite" }),
abbrev: new fields.StringField({ initial: "rationnalite" }),
value: new fields.NumberField({ initial: 0, integer: true }),
hasmax: new fields.BooleanField({ initial: false }),
iscard: new fields.BooleanField({ initial: true }),
max: new fields.NumberField({ initial: 0, integer: true })
})
})
};
}
}

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

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

View File

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

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

@@ -0,0 +1,17 @@
/**
* Data model pour les lames de tarot
*/
export default class TarotDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
tarottype: new fields.StringField({ initial: "" }),
numericvalueup: new fields.NumberField({ initial: 0, integer: true }),
numericvaluedown: new fields.NumberField({ initial: 0, integer: true }),
isdualside: new fields.BooleanField({ initial: false }),
ispositif: new fields.BooleanField({ initial: true }),
isgm: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ initial: "" })
};
}
}

4982
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

17
package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "fvtt-malefices",
"version": "13.0.0",
"description": "Maléfices RPG for FoundryVTT - French",
"scripts": {
"build": "gulp build",
"watch": "gulp watch"
},
"author": "Uberwald/LeRatierBretonnien",
"license": "SEE LICENSE IN LICENCE.txt",
"devDependencies": {
"gulp": "^4.0.2",
"gulp-less": "^5.0.0",
"gulp-rename": "^2.0.0",
"gulp-sourcemaps": "^3.0.0"
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

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