Compare commits

...

26 Commits

Author SHA1 Message Date
c61179a790 Corrections sur commande /tirage
All checks were successful
Release Creation / build (release) Successful in 1m3s
2026-04-24 21:05:02 +02:00
956ed9e916 Corrections CSS sur fiche archetype et système
All checks were successful
Release Creation / build (release) Successful in 50s
2026-04-21 13:43:29 +02:00
5cc060b102 Foundryv14 migration 2026-04-01 22:21:54 +02:00
a816380679 Foundryv14 migration
All checks were successful
Release Creation / build (release) Successful in 47s
2026-04-01 21:57:26 +02:00
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
124 changed files with 11137 additions and 1626 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: '13'
compatibility-verified: '14'

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# IDE
.idea/
.vs/
styles/*.css
# Node Modules
node_modules/
.github/

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/resume.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

View File

@@ -1,13 +1,15 @@
{ {
"ACTOR": { "TYPES": {
"TypePersonnage": "Personnage" "Actor": {
"personnage" : "Personnage"
}, },
"ITEM": { "Item": {
"TypeArme": "Arme", "arme" : "Arme",
"TypeEquipement": "Equipement", "equipement" : "Equipement",
"TypeTarot": "Tarot", "tarot" : "Tarot",
"TypeElementbio": "Element Biographique", "elementbio" : "Elément Biographique",
"TypeArchetype": "Archetype", "archetype" : "Archetype",
"TypeSortilege": "Sortilège" "sortilege" : "Sortilège"
}
} }
} }

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;
}
}
}

168
less/base.less Normal file
View File

@@ -0,0 +1,168 @@
/* ==================== (A) Fonts ==================== */
@font-face {
font-family: "Rivanna";
src: url('../fonts/rivanna.ttf') format("truetype");
}
/* Global styles scoped to Maléfices windows only */
.fvtt-malefices .window-app {
text-align: justify;
font-size: 16px;
letter-spacing: 1px;
}
/* Fonts — scoped to system sheets */
.fvtt-malefices .sheet header.sheet-header h1 input,
#actors .directory-list,
#navigation #scene-list .scene.nav-item {
font-size: 1.0rem;
}
.fvtt-malefices .sheet nav.sheet-tabs {
font-size: 0.8rem;
}
.fvtt-malefices .item-form,
.fvtt-malefices .sheet header.sheet-header .flex-group-center.flex-compteurs,
.fvtt-malefices .sheet header.sheet-header .flex-group-center.flex-fatigue,
.fvtt-malefices .item-checkbox,
#sidebar, #players, #navigation #nav-toggle {
font-size: 0.8rem;
}
.strong-text {
font-weight: bold;
}
.fvtt-malefices .tabs .item.active,
.blessures-list li ul li:first-child:hover {
text-shadow: 1px 0px 0px @color-accent;
}
.rollable:hover, .rollable:focus {
color: #000;
text-shadow: 0 0 10px red;
cursor: pointer;
}
.fvtt-malefices input:hover, .fvtt-malefices select:hover {
border-width: 4px;
border-color: rgb(85, 65, 130);
}
.fvtt-malefices input:disabled {
color:#1c2058;
}
.fvtt-malefices 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;
}
}
}
}

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

@@ -0,0 +1,145 @@
/* ===================================================================
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;
}
}
/* Description editor: ensure enough vertical room */
.tab.description {
prose-mirror,
.editor,
.ProseMirror {
min-height: 440px;
}
}
/* 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;
}
}
}
}

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

@@ -0,0 +1,349 @@
/* 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;*/
}
.fvtt-malefices input[type="text"], .fvtt-malefices select[type="text"] {
background:white;
color: @color-input-text;
}
.fvtt-malefices select {
background:white;
color: @color-input-text;
}
/* background: #011d33 url("../images/ui/fond1.webp") repeat left top;*/
/*color: rgba(168, 139, 139, 0.5);*/
.fvtt-malefices.window-app.sheet .window-content .sheet-header select[type="text"],
.fvtt-malefices.window-app.sheet .window-content .sheet-header input[type="text"],
.fvtt-malefices.window-app.sheet .window-content .sheet-header input[type="number"],
.fvtt-malefices.window-app.sheet .window-content .sheet-body input[type="text"],
.fvtt-malefices.window-app.sheet .window-content .sheet-body input[type="number"],
.fvtt-malefices.window-app.sheet .window-content .sheet-body select[type="text"] {
color: @color-text;
}
.fvtt-malefices.window-app.sheet .window-content .sheet-header input[type="password"],
.fvtt-malefices.window-app.sheet .window-content .sheet-header input[type="date"],
.fvtt-malefices.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;
}
.fvtt-malefices.window-app.sheet .window-content .sheet-body input[type="password"],
.fvtt-malefices.window-app.sheet .window-content .sheet-body input[type="date"],
.fvtt-malefices.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;
}
.fvtt-malefices.window-app.sheet .window-content .sheet-body select,
.fvtt-malefices.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;
}
.fvtt-malefices 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;
}
.fvtt-malefices nav.sheet-tabs .item {
position: relative;
padding: 0 0.25rem;
color: @color-tab-text;
margin-top: 4px;
margin-bottom: 4px;
}
.fvtt-malefices 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,36 @@
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()
const tarots = MaleficesUtility.getTarots() ?? []
context.tarots = Object.fromEntries(tarots.map(t => [t._id, t.name]))
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,13 +6,13 @@
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:680, height:680,
@@ -33,17 +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()),
tarotsCache: duplicate(this.actor.getHiddenTarots()), tarotsCache: foundry.utils.duplicate(this.actor.getHiddenTarots()),
archetype: duplicate(this.actor.getArchetype()), archetype: foundry.utils.duplicate(this.actor.getArchetype()),
equipements: duplicate(this.actor.getEquipements()), equipements: foundry.utils.duplicate(this.actor.getEquipements()),
subActors: duplicate(this.actor.getSubActors()), subActors: foundry.utils.duplicate(this.actor.getSubActors()),
phyMalus: this.actor.getPhysiqueMalus(), phyMalus: this.actor.getPhysiqueMalus(),
elementsbio: this.actor.getElementsBio(), elementsbio: this.actor.getElementsBio(),
sorts: this.actor.getSorts(), 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,
@@ -51,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;
} }

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;
@@ -43,10 +43,6 @@ export class MaleficesActor extends Actor {
return super.create(data, options); return super.create(data, options);
} }
/* -------------------------------------------- */
prepareBaseData() {
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async prepareData() { async prepareData() {
@@ -92,7 +88,7 @@ export class MaleficesActor extends Actor {
return 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 && comp.system) { if (comp && comp.system) {
comp.tarot = MaleficesUtility.getTarot(comp.system.lametutelaire) comp.tarot = MaleficesUtility.getTarot(comp.system.lametutelaire)
} }
@@ -101,25 +97,25 @@ export class MaleficesActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getElementsBio() { getElementsBio() {
let comp = duplicate(this.items.filter(item => item.type == 'elementbio') || []) let comp = foundry.utils.duplicate(this.items.filter(item => item.type == 'elementbio') || [])
MaleficesUtility.sortArrayObjectsByName(comp) MaleficesUtility.sortArrayObjectsByName(comp)
return comp; return comp;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getTarots() { getTarots() {
let comp = duplicate(this.items.filter(item => item.type == 'tarot' && !item.system.isgm) || []) 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;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getHiddenTarots() { getHiddenTarots() {
let comp = duplicate(this.items.filter(item => item.type == 'tarot' && item.system.isgm) || []) 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;
} }
@@ -127,7 +123,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;
} }
@@ -173,7 +169,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 = []
@@ -224,27 +220,27 @@ export class MaleficesActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
clearInitiative(){ clearInitiative() {
this.getFlag("world", "initiative", -1) this.getFlag("world", "initiative", -1)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
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 });
} }
@@ -275,25 +271,25 @@ export class MaleficesActor extends Actor {
/* -------------------------------------------- */ /* -------------------------------------------- */
incDecFluide(value) { incDecFluide(value) {
let fluide = this.system.fluide + value let fluide = this.system.fluide + value
this.update( {'system.fluide': fluide} ) this.update({ 'system.fluide': fluide })
} }
incDecDestin(value) { incDecDestin(value) {
let destin = this.system.pointdestin + value let destin = this.system.pointdestin + value
this.update( {'system.pointdestin': destin} ) this.update({ 'system.pointdestin': destin })
} }
incDecMPMB(value) { incDecMPMB(value) {
let mpmb = this.system.mpmb + value let mpmb = this.system.mpmb + value
this.update( {'system.mpmb': mpmb} ) this.update({ 'system.mpmb': mpmb })
} }
incDecMPMN(value) { incDecMPMN(value) {
let mpmn = this.system.mpmn + value let mpmn = this.system.mpmn + value
this.update( {'system.mpmn': mpmn} ) this.update({ 'system.mpmn': mpmn })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
incDecAttr(attrKey, value) { incDecAttr(attrKey, value) {
let attr = duplicate(this.system.attributs[attrKey]) let attr = foundry.utils.duplicate(this.system.attributs[attrKey])
attr.value += value attr.value += value
this.update( { [`system.attributs.${attrKey}`]: attr}) this.update({ [`system.attributs.${attrKey}`]: attr })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async incDecQuantity(objetId, incDec = 0) { async incDecQuantity(objetId, incDec = 0) {
@@ -317,14 +313,14 @@ export class MaleficesActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getAtttributImage( attrKey) { getAtttributImage(attrKey) {
return `systems/fvtt-malefices/images/icons/${attrKey}.webp` return `systems/fvtt-malefices/images/icons/${attrKey}.webp`
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
incDecDestin( value) { incDecDestin(value) {
let newValue = Math.max( this.system.pointdestin + value, 0) let newValue = Math.max(this.system.pointdestin + value, 0)
this.update( {'system.pointdestin': newValue}) this.update({ 'system.pointdestin': newValue })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -341,6 +337,7 @@ export class MaleficesActor extends Actor {
rollData.isReroll = false rollData.isReroll = false
rollData.confrontationDegre = 0 rollData.confrontationDegre = 0
rollData.confrontationModif = 0 rollData.confrontationModif = 0
rollData.config = game.system.malefices.config
console.log("ROLLDATA", rollData) console.log("ROLLDATA", rollData)
@@ -348,7 +345,7 @@ export class MaleficesActor extends Actor {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getPhysiqueMalus() { getPhysiqueMalus() {
if ( this.system.attributs.constitution.value <= 8) { if (this.system.attributs.constitution.value <= 8) {
return -(9 - this.system.attributs.constitution.value) return -(9 - this.system.attributs.constitution.value)
} }
return 0 return 0
@@ -358,7 +355,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)
@@ -369,12 +366,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
@@ -388,8 +385,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

@@ -12,6 +12,7 @@ export class MaleficesCommands {
if (!game.system.malefices.commands) { if (!game.system.malefices.commands) {
const commands = new MaleficesCommands(); const commands = new MaleficesCommands();
commands.registerCommand({ path: ["/tirage"], func: (content, msg, params) => MaleficesCommands.createTirage(msg), descr: "Tirage des tarots" }); commands.registerCommand({ path: ["/tirage"], func: (content, msg, params) => MaleficesCommands.createTirage(msg), descr: "Tirage des tarots" });
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!" }); 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;
} }
@@ -79,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)
} }
@@ -88,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);
@@ -105,7 +106,7 @@ export class MaleficesCommands {
ChatMessage.create(msg); ChatMessage.create(msg);
} }
/* -------------------------------------------- */ /* --------------------------------------------- */
static async createTirage(msg) { static async createTirage(msg) {
if (game.user.isGM) { if (game.user.isGM) {
let tirageData = { let tirageData = {
@@ -114,7 +115,7 @@ export class MaleficesCommands {
maxPlayerCard: 4, maxPlayerCard: 4,
maxSecretCard: 1, maxSecretCard: 1,
cards: [], cards: [],
players: duplicate(game.users), players: foundry.utils.duplicate(game.users),
secretCards: [], secretCards: [],
deck: MaleficesUtility.getTarots() deck: MaleficesUtility.getTarots()
} }
@@ -124,8 +125,22 @@ export class MaleficesCommands {
tirageData.secretCards.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) let tirageDialog = await MaleficesTirageTarotDialog.create(this, tirageData)
tirageDialog.render(true)
} }
} }
/* --------------------------------------------- */
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

@@ -27,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,12 +4,12 @@ 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 }],
@@ -19,20 +19,6 @@ export class MaleficesItemSheet extends ItemSheet {
}); });
} }
/* -------------------------------------------- */
_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,13 +43,13 @@ 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
} }
@@ -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)
}); });
} }

View File

@@ -1,7 +1,7 @@
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
export const defaultItemImg = { export const defaultItemImg = {
arme: "systems/fvtt-malefices/images/icons/arme.webp", arme: "systems/fvtt-malefices/images/icons/epee.webp",
equipement: "systems/fvtt-malefices/images/icons/equipement.webp", equipement: "systems/fvtt-malefices/images/icons/equipement.webp",
elementbio: "systems/fvtt-malefices/images/icons/wisdom.webp", elementbio: "systems/fvtt-malefices/images/icons/wisdom.webp",
archetype: "systems/fvtt-malefices/images/icons/archetype.webp", archetype: "systems/fvtt-malefices/images/icons/archetype.webp",

View File

@@ -9,15 +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 { 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 */
@@ -53,56 +57,51 @@ 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() {
if (game.user.isGM) {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/fvtt-malefices/templates/chat/welcome-message.hbs', {}
)
ChatMessage.create({ ChatMessage.create({
user: game.user.id, user: game.user.id,
whisper: [game.user.id], whisper: [game.user.id],
content: `<div id="welcome-message-malefices"><span class="rdd-roll-part"> content
<strong>Bienvenu dans Malefices, le JDR qui sent le souffre !</strong>
<p>Le Livre de Base de Maléfices v4 est nécessaire pour jouer : https://arkhane-asylum.fr/en/malefices/</p>
<p>Maléfices et un jeu de rôle publié par Arkhane Asylum Publishing, tout les droits leur appartiennent.</p>
<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>
` });
}
/* -------------------------------------------- */
// Register world usage statistics
function registerUsageCount( registerKey ) {
if ( game.user.isGM ) {
game.settings.register(registerKey, "world-key", {
name: "Unique world key",
scope: "world",
config: false,
default: "",
type: String
}); });
let worldKey = game.settings.get(registerKey, "world-key")
if ( worldKey == undefined || worldKey == "" ) {
worldKey = randomID(32)
game.settings.set(registerKey, "world-key", worldKey )
}
// Simple API counter
let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"`
//$.ajaxSetup({
//headers: { 'Access-Control-Allow-Origin': '*' }
//})
$.ajax(regURL)
} }
} }
@@ -120,16 +119,9 @@ Hooks.once("ready", function () {
}); });
} }
// CSS patch for v9 ClassCounter.registerUsageCount();
if (game.version) {
let sidebar = document.getElementById("sidebar");
sidebar.style.width = "min-content";
}
registerUsageCount('fvtt-malefices')
welcomeMessage(); welcomeMessage();
MaleficesUtility.ready() MaleficesUtility.ready()
MaleficesUtility.init()
MaleficesCharacterSummary.ready() MaleficesCharacterSummary.ready()
}) })
@@ -148,4 +140,3 @@ Hooks.on("chatMessage", (html, content, msg) => {
} }
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(),

View File

@@ -1,92 +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
if (rollData.attr && rollData.attr.iscard) {
html = await renderTemplate('systems/fvtt-malefices/templates/dialogs/confrontation-dialog.hbs', rollData);
} else {
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 isCard = rollData.attr && rollData.attr.iscard
let conf = {
title: (isCard) ? "Jet" : "Tirage",
content: html,
buttons: {
roll: {
icon: '<i class="fas fa-check"></i>',
label: (isCard) ? "Tirer une carte" : "Lancer le dé",
callback: () => { this.roll() }
}, },
cancel: { classes: ["malefices-roll-dialog"],
icon: '<i class="fas fa-times"></i>', position: { width: 540 },
label: "Annuler", modal: false,
callback: () => { this.close() } rejectClose: false,
} content,
}, buttons: [
close: close {
} action: "roll",
label: isCard ? "Tirer une carte" : "Lancer le dé",
super(conf, options); icon: isCard ? "fa-solid fa-layer-group" : "fa-solid fa-check",
default: true,
this.actor = actor; callback: (event, button, dialog) => {
this.rollData = rollData; MaleficesRollDialog._updateRollDataFromForm(rollData, button.form.elements)
}
/* -------------------------------------------- */
roll() {
let isCard = this.rollData.attr && this.rollData.attr.iscard
if (isCard) { if (isCard) {
MaleficesUtility.tirageConfrontationMalefices(this.rollData) MaleficesUtility.tirageConfrontationMalefices(rollData)
} else { } else {
MaleficesUtility.rollMalefices(this.rollData) MaleficesUtility.rollMalefices(rollData)
} }
} }
},
{
action: "cancel",
label: "Annuler",
icon: "fa-solid fa-times",
}
],
})
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async refreshDialog() { static _updateRollDataFromForm(rollData, elements) {
const content = await renderTemplate("systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs", this.rollData) if (elements.bonusMalusPerso) rollData.bonusMalusPerso = Number(elements.bonusMalusPerso.value)
this.data.content = content if (elements.bonusMalusSituation) rollData.bonusMalusSituation = Number(elements.bonusMalusSituation.value)
this.render(true) if (elements.bonusMalusDef) rollData.bonusMalusDef = Number(elements.bonusMalusDef.value)
} if (elements.bonusMalusPortee) rollData.bonusMalusPortee = Number(elements.bonusMalusPortee.value)
if (elements.confrontationDegre) rollData.confrontationDegre = Number(elements.confrontationDegre.value)
/* -------------------------------------------- */ if (elements.confrontationModif) rollData.confrontationModif = Number(elements.confrontationModif.value)
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)
})
html.find('#confrontationDegre').change((event) => {
this.rollData.confrontationDegre = Number(event.currentTarget.value)
})
html.find('#confrontationModif').change((event) => {
this.rollData.confrontationModif = Number(event.currentTarget.value)
})
} }
} }

View File

@@ -1,129 +1,133 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api
/* -------------------------------------------- */ /* -------------------------------------------- */
export class MaleficesCharacterSummary extends Application { export class MaleficesCharacterSummary extends HandlebarsApplicationMixin(ApplicationV2) {
/* -------------------------------------------- */ /* -------------------------------------------- */
static displayPCSummary(){ static DEFAULT_OPTIONS = {
game.system.malefices.charSummary.render(true) 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" }
}
/* -------------------------------------------- */ /* -------------------------------------------- */
updatePCSummary(){ static displayPCSummary() {
if ( this.rendered) { if (game.user.isGM) {
this.render(true) game.system.malefices.charSummary.render({ force: true })
} else {
ui.notifications.info("Commande /resume réservée au MJ !")
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static createSummaryPos() { updatePCSummary() {
return { top: 200, left: 200 }; if (this.element?.isConnected) {
this.render({ force: true })
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static ready() { static ready() {
if ( !game.user.isGM ) { // Uniquement si GM if (!game.user.isGM) {
return return
} }
let charSummary = new MaleficesCharacterSummary() game.system.malefices.charSummary = new MaleficesCharacterSummary()
game.system.malefices.charSummary = charSummary
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
constructor() { constructor(options = {}) {
super(); super(options)
//game.settings.set("world", "character-summary-data", {npcList: [], x:0, y:0}) const saved = game.settings.get("world", "character-summary-data")
this.settings = game.settings.get("world", "character-summary-data") this.extraList = saved.extraList ?? saved.npcList ?? []
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static get defaultOptions() { async _prepareContext(_options) {
return mergeObject(super.defaultOptions, { const pcs = game.actors.filter(ac => ac.type === "personnage" && ac.hasPlayerOwner)
template: "systems/fvtt-malefices/templates/dialogs/character-summary.hbs", const extras = []
popOut: true, const validList = []
resizable: true, for (const actorId of this.extraList) {
dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }], const actor = game.actors.get(actorId)
classes: ["bol", "dialog"], width: 920, height: 'fit-content' 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) })
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
getData() { _canDragDrop(_selector) { return true }
let formData = super.getData();
formData.pcs = game.actors.filter( ac => ac.type == "personnage" && ac.hasPlayerOwner ) /* -------------------------------------------- */
formData.npcs = [] _persist() {
let newList = [] const saved = game.settings.get("world", "character-summary-data")
let toUpdate = false game.settings.set("world", "character-summary-data", { ...saved, extraList: this.extraList })
for( let actorId of this.settings.npcList ) {
let actor = game.actors.get(actorId)
if (actor) {
formData.npcs.push( actor )
newList.push(actorId)
} else {
toUpdate = true
}
}
formData.config = game.system.malefices.config
if ( toUpdate ) {
this.settings.npcList = newList
//console.log("Going to update ...", this.settings)
game.settings.set("world", "character-summary-data", this.settings)
}
return formData
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
updateNPC() { _saveAndRefresh() {
game.settings.set("world", "character-summary-data", game.system.malefices.charSummary.settings) this.render({ force: true })
game.system.malefices.charSummary.close()
setTimeout( function() { game.system.malefices.charSummary.render(true)}, 500)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async _onDrop(event) { async _onDrop(event) {
//console.log("Dragged data are : ", dragData) try {
let data = event.dataTransfer.getData('text/plain') const dataItem = JSON.parse(event.dataTransfer.getData('text/plain'))
let dataItem = JSON.parse( data) const actor = fromUuidSync(dataItem.uuid)
let actor = fromUuidSync(dataItem.uuid) if (actor && !this.extraList.includes(actor.id)) {
if (actor) { this.extraList.push(actor.id)
game.system.malefices.charSummary.settings.npcList.push( actor.id ) this._persist()
game.system.malefices.charSummary.updateNPC() this.render({ force: true })
} else {
ui.notifications.warn( "Pas d'acteur trouvé" )
} }
} } catch(e) { /* not a valid drag payload */ }
/* -------------------------------------------- */
/** @override */
async activateListeners(html) {
super.activateListeners(html);
html.find('.actor-open').click((event) => {
const li = $(event.currentTarget).parents(".item")
const actor = game.actors.get(li.data("actor-id"))
actor.sheet.render(true)
})
html.find('.summary-roll').click((event) => {
const li = $(event.currentTarget).parents(".item")
const actor = game.actors.get(li.data("actor-id"))
let type = $(event.currentTarget).data("type")
let key = $(event.currentTarget).data("key")
actor.rollAttribut(key)
})
html.find('.actor-delete').click(event => {
const li = $(event.currentTarget).parents(".item");
let actorId = li.data("actor-id")
let newList = game.system.malefices.charSummary.settings.npcList.filter(id => id != actorId)
game.system.malefices.charSummary.settings.npcList = newList
game.system.malefices.charSummary.updateNPC()
})
} }
} }

View File

@@ -1,155 +1,132 @@
import { MaleficesUtility } from "./malefices-utility.js"; import { MaleficesUtility } from "./malefices-utility.js";
export class MaleficesTirageTarotDialog extends Dialog { 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) { static async create(actor, tirageData) {
const app = new MaleficesTirageTarotDialog(actor, tirageData)
let options = { classes: ["MaleficesDialog"], width: 720, height: 740, 'z-index': 99999 }; app.render({ force: true })
let html = await renderTemplate('systems/fvtt-malefices/templates/dialogs/tirage-tarot-dialog.hbs', tirageData); return app
return new MaleficesTirageTarotDialog(actor, tirageData, html, options);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
constructor(actor, tirageData, html, options, close = undefined) { async _prepareContext(_options) {
let conf = { return { ...this.tirageData }
title: "Tirage des tarots",
content: html,
buttons: {
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Fermer/Annuler",
callback: () => { this.close() }
}
},
close: close
} }
super(conf, options); /* -------------------------------------------- */
_onRender(_context, _options) {
const el = this.element
this.actor = actor; el.querySelector('#playerId')?.addEventListener('change', (event) => {
this.tirageData = tirageData; 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() { async sendCardRequest() {
this.tirageData.state = 'waiting-user-card' this.tirageData.state = 'waiting-user-card'
let msg = await MaleficesUtility.createChatMessage(this.tirageData.user.name, "useronly", { await MaleficesUtility.createChatMessage(this.tirageData.user.name, "useronly", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/request-tarot-card.hbs`, this.tirageData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/request-tarot-card.hbs`, this.tirageData)
}) })
//msg.setFlag("world", "tirage-data", this.tirageData)
console.log("MSG IS", msg)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
drawCard() { drawCard() {
let index = Math.round(Math.random() * (this.tirageData.deck.length-1)) let index = Math.round(Math.random() * (this.tirageData.deck.length - 1))
let selectedCard = this.tirageData.deck[index] let selectedCard = this.tirageData.deck[index]
selectedCard.system.ispositif = true selectedCard.system.ispositif = true
if ( selectedCard.system.isdualside) { // Cas des cartes pouvant avoir 2 sens if (selectedCard.system.isdualside) {
selectedCard.system.ispositif = (Math.random() > 0.5) selectedCard.system.ispositif = (Math.random() > 0.5)
} }
console.log("CARD SELECTED:", selectedCard)
// Cas spécial de la Roue de la Fortune // Cas spécial de la Roue de la Fortune
if ( selectedCard.name.toLowerCase().includes("fortune")) { if (selectedCard.name.toLowerCase().includes("fortune")) {
this.tirageData.maxPlayerCard += 1 this.tirageData.maxPlayerCard += 1
this.tirageData.maxSecretCard += 1 this.tirageData.maxSecretCard += 1
} }
let newList = [] this.tirageData.deck = this.tirageData.deck.filter(c => c.name !== selectedCard.name)
for(let card of this.tirageData.deck) {
if (card.name != selectedCard.name) {
newList.push(card)
}
}
this.tirageData.deck = newList
return selectedCard return selectedCard
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async addCard( msgId ) { async addCard(msgId) {
MaleficesUtility.removeChatMessageId(msgId) MaleficesUtility.removeChatMessageId(msgId)
let selectedCard = this.drawCard() let selectedCard = this.drawCard()
selectedCard.system.isgm = false selectedCard.system.isgm = false
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "gmroll", { await MaleficesUtility.createChatMessage(this.tirageData.user.name, "gmroll", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
}) })
if (this.tirageData.cards[0].name == "???") { if (this.tirageData.cards[0].name == "???") this.tirageData.cards.shift()
this.tirageData.cards.shift()
}
this.tirageData.cards.push(selectedCard) this.tirageData.cards.push(selectedCard)
this.tirageData.nbCard++ this.tirageData.nbCard++
if (this.tirageData.nbCard == this.tirageData.maxPlayerCard) { if (this.tirageData.nbCard == this.tirageData.maxPlayerCard) {
for (let i=0; i<this.tirageData.maxSecretCard; i++) { for (let i = 0; i < this.tirageData.maxSecretCard; i++) {
let selectedCard = this.drawCard() let secretCard = this.drawCard()
selectedCard.system.isgm = true secretCard.system.isgm = true
await MaleficesUtility.createChatMessage(this.tirageData.user.name, "blindroll", { await MaleficesUtility.createChatMessage(this.tirageData.user.name, "blindroll", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, secretCard)
}) })
if (this.tirageData.secretCards[0].name == "???") { if (this.tirageData.secretCards[0].name == "???") this.tirageData.secretCards.shift()
this.tirageData.secretCards.shift() this.tirageData.secretCards.push(secretCard)
} }
this.tirageData.secretCards.push(selectedCard) this.tirageData.actors = foundry.utils.duplicate(game.actors)
}
this.tirageData.actors = duplicate(game.actors)
this.tirageData.state = 'attribute-to-actor' this.tirageData.state = 'attribute-to-actor'
}else { } else {
this.sendCardRequest() this.sendCardRequest()
} }
this.refreshDialog() this.render({ force: true })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async processSelectedPlayer() { async processSelectedPlayer() {
let user = game.users.get(this.tirageData.playerId) this.tirageData.user = game.users.get(this.tirageData.playerId)
this.tirageData.user = user
this.tirageData.players = null this.tirageData.players = null
console.log("Going to work with ", user.name)
game.system.malefices.currentTirage = this game.system.malefices.currentTirage = this
this.refreshDialog() this.render({ force: true })
this.sendCardRequest() this.sendCardRequest()
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
attributeToActor(actorId) { attributeToActor(actorId) {
let actor = game.actors.get(actorId) const actor = game.actors.get(actorId)
if (actor) { if (actor) {
actor.createEmbeddedDocuments('Item', this.tirageData.cards) actor.createEmbeddedDocuments('Item', this.tirageData.cards)
actor.createEmbeddedDocuments('Item', this.tirageData.secretCards) actor.createEmbeddedDocuments('Item', this.tirageData.secretCards)
ui.notifications.info("Les cartes ont été attribuées à " + actor.name) ui.notifications.info("Les cartes ont été attribuées à " + actor.name)
} }
} }
/* -------------------------------------------- */
async refreshDialog() {
const content = await renderTemplate("systems/fvtt-malefices/templates/dialogs/tirage-tarot-dialog.hbs", this.tirageData)
this.data.content = content
this.render(true)
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('#playerId').change((event) => {
if ( event.currentTarget.value != "none") {
dialog.tirageData.playerId = event.currentTarget.value
dialog.processSelectedPlayer()
}
})
html.find('#actorId').change((event) => {
if ( event.currentTarget.value != "none") {
let actorId = event.currentTarget.value
dialog.attributeToActor(actorId)
}
})
}
} }

View File

@@ -5,18 +5,18 @@ 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('renderChatMessageHTML', (message, html) => MaleficesUtility.chatListeners(message, 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;
@@ -43,14 +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", { game.settings.register("world", "character-summary-data", {
name: "character-summary-data", name: "character-summary-data",
scope: "world", scope: "world",
config: false, config: false,
default: { npcList: [], x: 200, y: 200 }, default: { extraList: [], x: 200, y: 200 },
type: Object type: Object
}) })
const tarots = await MaleficesUtility.loadCompendium("fvtt-malefices.malefices-tarots")
this.tarots = tarots.map(i => i.toObject())
} }
/*-------------------------------------------- */ /*-------------------------------------------- */
@@ -61,19 +73,12 @@ 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())
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static async loadCompendiumData(compendium) { static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium) const pack = game.packs.get(compendium)
@@ -100,29 +105,32 @@ export class MaleficesUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static drawDeckCard(msgId) { static drawDeckCard(msgId) {
if (game.user.isGM) { if (game.user.isGM) {
game.system.malefices.currentTirage.addCard(msgId) const tirage = game.system.malefices.currentTirage
if (tirage) {
tirage.addCard(msgId)
} else { } else {
game.socket.emit( "system.fvtt-malefices", {name: "msg-draw-card", data: {msgId: msgId}}) ui.notifications.warn("Aucun tirage en cours.")
}
} else {
game.socket.emit("system.fvtt-malefices", { name: "msg-draw-card", data: { msgId: msgId } })
} }
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async chatListeners(html) { static chatListeners(message, html) {
if (!html) return
html.on("click", '.roll-destin', event => { html.querySelector('.roll-destin')?.addEventListener('click', () => {
let messageId = MaleficesUtility.findChatMessageId(event.currentTarget) const rollData = message.getFlag("world", "rolldata")
let message = game.messages.get(messageId) const actor = MaleficesUtility.getActorFromRollData(rollData)
let rollData = message.getFlag("world", "rolldata")
let actor = this.getActorFromRollData(rollData)
actor.incDecDestin(-1) actor.incDecDestin(-1)
rollData.isReroll = true rollData.isReroll = true
this.rollMalefices(rollData) MaleficesUtility.rollMalefices(rollData)
})
html.on("click", '.draw-tarot-card', event => {
let messageId = MaleficesUtility.findChatMessageId(event.currentTarget)
this.drawDeckCard(messageId)
}) })
html.querySelector('.draw-tarot-card')?.addEventListener('click', () => {
MaleficesUtility.drawDeckCard(message.id)
})
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -130,10 +138,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);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -199,7 +212,7 @@ 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
} }
@@ -207,7 +220,7 @@ export class MaleficesUtility {
static async onSocketMesssage(msg) { static async onSocketMesssage(msg) {
console.log("SOCKET MESSAGE", msg.name) console.log("SOCKET MESSAGE", msg.name)
if (msg.name == "msg-draw-card") { if (msg.name == "msg-draw-card") {
if ( game.user.isGM && game.system.malefices.currentTirage) { if (game.user.isGM && game.system.malefices.currentTirage) {
game.system.malefices.currentTirage.addCard(msg.data.msgId) game.system.malefices.currentTirage.addCard(msg.data.msgId)
} }
} }
@@ -274,66 +287,74 @@ export class MaleficesUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static processSpecialCard(actor, rollData) { static processSpecialCard(actor, rollData) {
if (rollData.selectedCard.name.toLowerCase().includes("archange")) { if (rollData.selectedCard.name.toLowerCase().includes("archange")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("archange")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("archange"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", { MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>L'Archange : ${actor.name} gagne 1 point de Spiritualité.` }) content: `Conséquence supplémentaire ! <br>L'Archange : ${actor.name} gagne 1 point de Spiritualité.`
})
actor.incDecAttr("spiritualite", 1) actor.incDecAttr("spiritualite", 1)
} }
} }
if (rollData.selectedCard.name.toLowerCase().includes("vicaire")) { if (rollData.selectedCard.name.toLowerCase().includes("vicaire")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("vicaire")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("vicaire"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", { 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).` }) 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) actor.incDecMPMB(1)
} }
} }
if (rollData.selectedCard.name.toLowerCase().includes("chance")) { if (rollData.selectedCard.name.toLowerCase().includes("chance")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("chance")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("chance"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", { MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>La Chance : ${actor.name} a gagné 1 point de Destin.` }) content: `Conséquence supplémentaire ! <br>La Chance : ${actor.name} a gagné 1 point de Destin.`
})
actor.incDecDestin(1) actor.incDecDestin(1)
} }
} }
if (rollData.selectedCard.name.toLowerCase().includes("mort")) { if (rollData.selectedCard.name.toLowerCase().includes("mort")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("mort")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("mort"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", { MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>La Mort : ${actor.name} est pétrifié par la peur.` }) content: `Conséquence supplémentaire ! <br>La Mort : ${actor.name} est pétrifié par la peur.`
})
actor.incDecDestin(1) actor.incDecDestin(1)
} }
} }
if (rollData.selectedCard.name.toLowerCase().includes("diable")) { if (rollData.selectedCard.name.toLowerCase().includes("diable")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("diable")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("diable"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "gmroll", { MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: `Conséquence supplémentaire ! <br>Le Diable : ${actor.name} gagne 1 point de Rationnalité.` }) content: `Conséquence supplémentaire ! <br>Le Diable : ${actor.name} gagne 1 point de Rationnalité.`
})
actor.incDecAttr("rationnalite", 1) actor.incDecAttr("rationnalite", 1)
} }
} }
if (rollData.selectedCard.name.toLowerCase().includes("lune noire")) { if (rollData.selectedCard.name.toLowerCase().includes("lune noire")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("lune noire")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("lune noire"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", { MaleficesUtility.createChatMessage(actor.name, "blindroll", {
content: `Conséquence supplémentaire ! <br>La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).` }) content: `Conséquence supplémentaire ! <br>La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).`
})
actor.incDecFluide(1) actor.incDecFluide(1)
} }
} }
if (rollData.selectedCard.name.toLowerCase().includes("grand livre")) { if (rollData.selectedCard.name.toLowerCase().includes("grand livre")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("grand livre")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("grand livre"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", { MaleficesUtility.createChatMessage(actor.name, "blindroll", {
content: `Conséquence supplémentaire ! <br>La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).` }) content: `Conséquence supplémentaire ! <br>La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).`
})
actor.incDecFluide(1) actor.incDecFluide(1)
} }
} }
if (rollData.selectedCard.name.toLowerCase().includes("sorcier")) { if (rollData.selectedCard.name.toLowerCase().includes("sorcier")) {
let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("sorcier")) let actorCard = actor.items.find(c => c.type == "tarot" && c.name.toLowerCase().includes("sorcier"))
if (actorCard) { if (actorCard) {
MaleficesUtility.createChatMessage(actor.name, "blindroll", { 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).` }) 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) actor.incDecMPMN(1)
} }
} }
@@ -365,28 +386,28 @@ export class MaleficesUtility {
rollData.target = rollData.attr.value - rollData.confrontationDegre + rollData.confrontationModif rollData.target = rollData.attr.value - rollData.confrontationDegre + rollData.confrontationModif
let deck = this.getTarots() let deck = this.getTarots()
let index = Math.round(Math.random() * (deck.length-1)) let index = Math.round(Math.random() * (deck.length - 1))
let selectedCard = deck[index] let selectedCard = deck[index]
selectedCard.system.ispositif = (Math.random() > 0.5) selectedCard.system.ispositif = (Math.random() > 0.5)
selectedCard.value = (selectedCard.system.ispositif)? selectedCard.system.numericvalueup : selectedCard.system.numericvaluedown selectedCard.value = (selectedCard.system.ispositif) ? selectedCard.system.numericvalueup : selectedCard.system.numericvaluedown
rollData.total = selectedCard.value rollData.total = selectedCard.value
rollData.selectedCard = selectedCard rollData.selectedCard = selectedCard
await MaleficesUtility.createChatMessage(actor.name, "gmroll", { await MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/display-tarot-card.hbs`, selectedCard)
}) })
this.computeResults(rollData) this.computeResults(rollData)
if (rollData.isSuccess) { if (rollData.isSuccess) {
rollData.gainAttr = Math.ceil(rollData.confrontationDegre/2) + ((rollData.isCritical ) ? 1 : 0) rollData.gainAttr = Math.ceil(rollData.confrontationDegre / 2) + ((rollData.isCritical) ? 1 : 0)
actor.incDecAttr(rollData.attr.abbrev, rollData.gainAttr ) actor.incDecAttr(rollData.attr.abbrev, rollData.gainAttr)
} else { } else {
rollData.gainAttr = rollData.confrontationDegre rollData.gainAttr = rollData.confrontationDegre
actor.incDecAttr(rollData.attr.abbrev, -rollData.gainAttr ) actor.incDecAttr(rollData.attr.abbrev, -rollData.gainAttr)
} }
await MaleficesUtility.createChatMessage(actor.name, "gmroll", { await MaleficesUtility.createChatMessage(actor.name, "gmroll", {
content: await renderTemplate(`systems/fvtt-malefices/templates/chat/chat-confrontation-result.hbs`, rollData) content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-malefices/templates/chat/chat-confrontation-result.hbs`, rollData)
}) })
this.processSpecialCard(actor, rollData) this.processSpecialCard(actor, rollData)
} }
@@ -405,15 +426,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.total = myRoll.total
this.computeResults(rollData) this.computeResults(rollData)
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") {
@@ -465,7 +486,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);
@@ -515,7 +536,7 @@ export class MaleficesUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
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,
@@ -541,30 +562,16 @@ export class MaleficesUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
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"
}
}

View File

View File

View File

@@ -0,0 +1 @@
MANIFEST-000076

View File

View File

@@ -0,0 +1,11 @@
2026/04/21-11:52:44.202744 7ff1aaffd6c0 Delete type=3 #1
2026/04/21-13:41:40.820174 7ff1a9ffb6c0 Level-0 table #79: started
2026/04/21-13:41:40.820230 7ff1a9ffb6c0 Level-0 table #79: 0 bytes OK
2026/04/21-13:41:40.826374 7ff1a9ffb6c0 Delete type=0 #77
2026/04/21-13:41:40.846124 7ff1a9ffb6c0 Manual compaction at level-0 from '!items!2HWSdXDSFei9KC6y' @ 72057594037927935 : 1 .. '!items!xtYE2kVIfNtrXSoU' @ 0 : 0; will stop at '!items!xtYE2kVIfNtrXSoU' @ 92 : 1
2026/04/21-13:41:40.846135 7ff1a9ffb6c0 Compacting 1@0 + 0@1 files
2026/04/21-13:41:40.850988 7ff1a9ffb6c0 Generated table #80@0: 23 keys, 50829 bytes
2026/04/21-13:41:40.851009 7ff1a9ffb6c0 Compacted 1@0 + 0@1 files => 50829 bytes
2026/04/21-13:41:40.857011 7ff1a9ffb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/04/21-13:41:40.857165 7ff1a9ffb6c0 Delete type=2 #41
2026/04/21-13:41:40.887698 7ff1a9ffb6c0 Manual compaction at level-0 from '!items!xtYE2kVIfNtrXSoU' @ 92 : 1 .. '!items!xtYE2kVIfNtrXSoU' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,4 @@
2026/04/21-11:52:44.183987 7ff1aaffd6c0 Log #74: 0 ops saved to Table #75 OK
2026/04/21-11:52:44.184084 7ff1aaffd6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-archetypes/000074.log: OK
2026/04/21-11:52:44.186478 7ff1aaffd6c0 Table #41: 23 entries OK
2026/04/21-11:52:44.190025 7ff1aaffd6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-archetypes; recovered 1 files; 50829 bytes. Some data may have been lost. ****

Binary file not shown.

Binary file not shown.

View File

View File

View File

@@ -0,0 +1 @@
MANIFEST-000076

View File

11
packs/malefices-armes/LOG Normal file
View File

@@ -0,0 +1,11 @@
2026/04/21-11:52:44.181427 7ff1aaffd6c0 Delete type=3 #1
2026/04/21-13:41:40.838999 7ff1a9ffb6c0 Level-0 table #79: started
2026/04/21-13:41:40.839022 7ff1a9ffb6c0 Level-0 table #79: 0 bytes OK
2026/04/21-13:41:40.846003 7ff1a9ffb6c0 Delete type=0 #77
2026/04/21-13:41:40.878282 7ff1a9ffb6c0 Manual compaction at level-0 from '!items!5J6qIaWdnhEGMAXJ' @ 72057594037927935 : 1 .. '!items!nkRQU81L1gWOfaeo' @ 0 : 0; will stop at '!items!nkRQU81L1gWOfaeo' @ 36 : 1
2026/04/21-13:41:40.878297 7ff1a9ffb6c0 Compacting 1@0 + 0@1 files
2026/04/21-13:41:40.881453 7ff1a9ffb6c0 Generated table #80@0: 9 keys, 2083 bytes
2026/04/21-13:41:40.881477 7ff1a9ffb6c0 Compacted 1@0 + 0@1 files => 2083 bytes
2026/04/21-13:41:40.887457 7ff1a9ffb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/04/21-13:41:40.887570 7ff1a9ffb6c0 Delete type=2 #41
2026/04/21-13:41:40.887738 7ff1a9ffb6c0 Manual compaction at level-0 from '!items!nkRQU81L1gWOfaeo' @ 36 : 1 .. '!items!nkRQU81L1gWOfaeo' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,4 @@
2026/04/21-11:52:44.164486 7ff1aaffd6c0 Log #74: 0 ops saved to Table #75 OK
2026/04/21-11:52:44.164589 7ff1aaffd6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-armes/000074.log: OK
2026/04/21-11:52:44.164875 7ff1aaffd6c0 Table #41: 9 entries OK
2026/04/21-11:52:44.168545 7ff1aaffd6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-armes; recovered 1 files; 2083 bytes. Some data may have been lost. ****

Binary file not shown.

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,3 @@
{"name":"Résumé des PJs pour le MJ","type":"chat","scope":"global","author":"R9gIh86vXDB4IFn1","img":"systems/fvtt-malefices/images/icons/resume.webp","command":"/resume","flags":{"core":{"sourceId":"Macro.ulj2PgchTQVE1VV4"}},"_stats":{"systemId":"fvtt-malefices","systemVersion":"10.1.1","coreVersion":"10.291","createdTime":1677422022018,"modifiedTime":1677422143283,"lastModifiedBy":"R9gIh86vXDB4IFn1"},"ownership":{"default":0,"R9gIh86vXDB4IFn1":3},"folder":null,"sort":0,"_id":"ESV4er8Hy6liMOC3"}
{"name":"Tirage des tarots","type":"chat","scope":"global","author":"R9gIh86vXDB4IFn1","img":"systems/fvtt-malefices/images/icons/tirage.webp","command":"/tirage","flags":{"core":{"sourceId":"Macro.ulj2PgchTQVE1VV4"}},"_stats":{"systemId":"fvtt-malefices","systemVersion":"10.1.1","coreVersion":"10.291","createdTime":1677422022018,"modifiedTime":1677422144635,"lastModifiedBy":"R9gIh86vXDB4IFn1"},"ownership":{"default":0,"R9gIh86vXDB4IFn1":3},"folder":null,"sort":0,"_id":"sVKXJsiG9KAaBglV"}
{"name":"Tirer une carte","type":"chat","command":"/carte","author":"R9gIh86vXDB4IFn1","img":"systems/fvtt-malefices/images/icons/tirer.webp","scope":"global","flags":{"core":{"sourceId":"Macro.P2dPA3CA5ZjOwDeE"}},"_stats":{"systemId":"fvtt-malefices","systemVersion":"10.1.1","coreVersion":"10.291","createdTime":1677421496447,"modifiedTime":1677422146138,"lastModifiedBy":"R9gIh86vXDB4IFn1"},"ownership":{"default":0,"R9gIh86vXDB4IFn1":3},"folder":null,"sort":0,"_id":"zDPgmHiwNxBWhoYz"}

View File

View File

View File

@@ -0,0 +1 @@
MANIFEST-000076

View File

View File

@@ -0,0 +1,11 @@
2026/04/21-11:52:44.222882 7ff1abfff6c0 Delete type=3 #1
2026/04/21-13:41:40.832543 7ff1a9ffb6c0 Level-0 table #79: started
2026/04/21-13:41:40.832568 7ff1a9ffb6c0 Level-0 table #79: 0 bytes OK
2026/04/21-13:41:40.838884 7ff1a9ffb6c0 Delete type=0 #77
2026/04/21-13:41:40.867899 7ff1a9ffb6c0 Manual compaction at level-0 from '!macros!ESV4er8Hy6liMOC3' @ 72057594037927935 : 1 .. '!macros!zDPgmHiwNxBWhoYz' @ 0 : 0; will stop at '!macros!zDPgmHiwNxBWhoYz' @ 12 : 1
2026/04/21-13:41:40.867910 7ff1a9ffb6c0 Compacting 1@0 + 0@1 files
2026/04/21-13:41:40.871553 7ff1a9ffb6c0 Generated table #80@0: 3 keys, 843 bytes
2026/04/21-13:41:40.871580 7ff1a9ffb6c0 Compacted 1@0 + 0@1 files => 843 bytes
2026/04/21-13:41:40.877546 7ff1a9ffb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/04/21-13:41:40.877655 7ff1a9ffb6c0 Delete type=2 #41
2026/04/21-13:41:40.887725 7ff1a9ffb6c0 Manual compaction at level-0 from '!macros!zDPgmHiwNxBWhoYz' @ 12 : 1 .. '!macros!zDPgmHiwNxBWhoYz' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,4 @@
2026/04/21-11:52:44.205891 7ff1abfff6c0 Log #74: 0 ops saved to Table #75 OK
2026/04/21-11:52:44.206027 7ff1abfff6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-macros/000074.log: OK
2026/04/21-11:52:44.206120 7ff1abfff6c0 Table #41: 3 entries OK
2026/04/21-11:52:44.210039 7ff1abfff6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-macros; recovered 1 files; 843 bytes. Some data may have been lost. ****

Binary file not shown.

View File

View File

View File

Binary file not shown.

View File

View File

View File

@@ -0,0 +1 @@
MANIFEST-000076

View File

View File

@@ -0,0 +1,11 @@
2026/04/21-11:52:44.160569 7ff1aa7fc6c0 Delete type=3 #1
2026/04/21-13:41:40.826507 7ff1a9ffb6c0 Level-0 table #79: started
2026/04/21-13:41:40.826536 7ff1a9ffb6c0 Level-0 table #79: 0 bytes OK
2026/04/21-13:41:40.832425 7ff1a9ffb6c0 Delete type=0 #77
2026/04/21-13:41:40.857305 7ff1a9ffb6c0 Manual compaction at level-0 from '!items!1DRKmbzGzbCRCswc' @ 72057594037927935 : 1 .. '!items!zbGGMEQFdwVdlKAf' @ 0 : 0; will stop at '!items!zbGGMEQFdwVdlKAf' @ 88 : 1
2026/04/21-13:41:40.857314 7ff1a9ffb6c0 Compacting 1@0 + 0@1 files
2026/04/21-13:41:40.860393 7ff1a9ffb6c0 Generated table #80@0: 22 keys, 4074 bytes
2026/04/21-13:41:40.860404 7ff1a9ffb6c0 Compacted 1@0 + 0@1 files => 4074 bytes
2026/04/21-13:41:40.867688 7ff1a9ffb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/04/21-13:41:40.867784 7ff1a9ffb6c0 Delete type=2 #41
2026/04/21-13:41:40.887714 7ff1a9ffb6c0 Manual compaction at level-0 from '!items!zbGGMEQFdwVdlKAf' @ 88 : 1 .. '!items!zbGGMEQFdwVdlKAf' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,4 @@
2026/04/21-11:52:44.059054 7ff1aa7fc6c0 Log #74: 0 ops saved to Table #75 OK
2026/04/21-11:52:44.059194 7ff1aa7fc6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-tarots/000074.log: OK
2026/04/21-11:52:44.059258 7ff1aa7fc6c0 Table #41: 22 entries OK
2026/04/21-11:52:44.063146 7ff1aa7fc6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-malefices/packs/malefices-tarots; recovered 1 files; 4074 bytes. Some data may have been lost. ****

Binary file not shown.

View File

View File

View File

Binary file not shown.

File diff suppressed because it is too large Load Diff

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