Message de bienvenue

This commit is contained in:
2026-05-06 20:26:31 +02:00
parent ee6fecbcef
commit 454f8de412
73 changed files with 323 additions and 184 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

+12
View File
@@ -115,6 +115,18 @@
"tie": "Égalité : la fiction tranche." "tie": "Égalité : la fiction tranche."
} }
}, },
"welcome": {
"title": "Bienvenue dans Les Oubliés",
"eyebrow": "Système",
"intro": "Bienvenue dans le système FoundryVTT des Oubliés.",
"developerLabel": "Développement du système :",
"publisherLabel": "Jeu édité par",
"helpLabel": "Aide intégrée :",
"helpLinkLabel": "Ouvrir laide du système",
"helpUnavailable": "le compendium daide nest pas disponible pour le moment.",
"openHelp": "Ouvrir laide",
"close": "Fermer"
},
"labels": { "labels": {
"race": "Race", "race": "Race",
"tribu": "Tribu", "tribu": "Tribu",
+53
View File
@@ -7,6 +7,8 @@ import * as models from "./models/index.mjs"
import * as sheets from "./applications/sheets/_module.mjs" import * as sheets from "./applications/sheets/_module.mjs"
const DEFAULT_PERSONNAGE_TOKEN_TEXTURE = "systems/fvtt-les-oublies/assets/tokens/border_token_oublies.webp" const DEFAULT_PERSONNAGE_TOKEN_TEXTURE = "systems/fvtt-les-oublies/assets/tokens/border_token_oublies.webp"
const UBERWALD_URL = "https://www.uberwald.me"
const XII_SINGES_URL = "https://www.les12singes.com/84-les-oublies"
function ensureSystemStyles() { function ensureSystemStyles() {
const href = `systems/${game.system.id}/css/les-oublies.css` const href = `systems/${game.system.id}/css/les-oublies.css`
@@ -28,6 +30,53 @@ function usesFoundryDefaultTokenTexture(actor, data) {
return !tokenTexture || tokenTexture === CONST.DEFAULT_TOKEN || tokenTexture === "icons/svg/mystery-man.svg" return !tokenTexture || tokenTexture === CONST.DEFAULT_TOKEN || tokenTexture === "icons/svg/mystery-man.svg"
} }
async function getHelpJournalEntry() {
const pack = game.packs.get(`${game.system.id}.aide-systeme`)
if (!pack) return null
const documents = await pack.getDocuments()
return documents[0] ?? null
}
async function buildWelcomeMessageContent(helpJournal) {
const helpContent = helpJournal
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(
`@UUID[${helpJournal.uuid}]{${game.i18n.localize("LESOUBLIES.welcome.helpLinkLabel")}}`,
{ async: true },
)
: game.i18n.localize("LESOUBLIES.welcome.helpUnavailable")
return `
<div class="les-oublies-chat-card les-oublies-welcome-chat">
<div class="chat-card-header">
<div>
<p class="chat-card-eyebrow">${game.i18n.localize("LESOUBLIES.welcome.eyebrow")}</p>
<h3>${game.i18n.localize("LESOUBLIES.welcome.title")}</h3>
</div>
</div>
<div class="chat-card-body">
<p>${game.i18n.localize("LESOUBLIES.welcome.intro")}</p>
<p>${game.i18n.localize("LESOUBLIES.welcome.developerLabel")} <a href="${UBERWALD_URL}" target="_blank" rel="noopener noreferrer">Uberwald</a>.</p>
<p>${game.i18n.localize("LESOUBLIES.welcome.publisherLabel")} <a href="${XII_SINGES_URL}" target="_blank" rel="noopener noreferrer">Les XII Singes</a>.</p>
<p><strong>${game.i18n.localize("LESOUBLIES.welcome.helpLabel")}</strong> ${helpContent}</p>
</div>
</div>
`
}
async function showWelcomeMessage() {
const helpJournal = await getHelpJournalEntry()
const content = await buildWelcomeMessageContent(helpJournal)
await ChatMessage.create({
speaker: {
alias: game.system.title,
},
content,
whisper: [game.user.id],
})
}
Hooks.once("init", function () { Hooks.once("init", function () {
console.info("Les Oubliés | Initialisation du système") console.info("Les Oubliés | Initialisation du système")
ensureSystemStyles() ensureSystemStyles()
@@ -77,6 +126,10 @@ Hooks.once("init", function () {
LesOubliesUtility.registerHandlebarsHelpers() LesOubliesUtility.registerHandlebarsHelpers()
}) })
Hooks.once("ready", function () {
showWelcomeMessage()
})
Hooks.on("preCreateActor", function (actor, data) { Hooks.on("preCreateActor", function (actor, data) {
if (actor.type !== "personnage") return if (actor.type !== "personnage") return
if (!usesFoundryDefaultTokenTexture(actor, data)) return if (!usesFoundryDefaultTokenTexture(actor, data)) return
+98
View File
@@ -0,0 +1,98 @@
[
{
"name": "Aide du système",
"type": "JournalEntry",
"ownership": {
"default": 2
},
"flags": {
"core": {}
},
"pages": [
{
"name": "Bienvenue",
"type": "text",
"title": {
"show": true,
"level": 1
},
"text": {
"format": 1,
"content": "<h1>Les Oubliés dans Foundry</h1><p>Cette aide de jeu présente le fonctionnement concret du système <strong>fvtt-les-oublies</strong> dans Foundry VTT. Elle est pensée pour une prise en main rapide autour de la fiche, des jets, du combat, de la magie et des compendiums fournis.</p><h2>Ce que fait le système</h2><ul><li>gère les acteurs <strong>Personnage</strong>, <strong>Compagnie</strong> et <strong>Créature</strong> ;</li><li>calcule les valeurs dérivées utiles, dont les points de vie liés à la taille ;</li><li>propose des dialogues dédiés pour les tests, confrontations, initiatives et dégâts ;</li><li>fournit des compendiums techniques pour les races, tribus, métiers, compétences, armes, armures, équipements, pouvoirs de compagnie et sortilèges.</li></ul><h2>Par où commencer ?</h2><ol><li>Créez un <strong>Personnage</strong>.</li><li>Assignez-lui une <strong>Race</strong>, une <strong>Tribu</strong> et un <strong>Métier</strong> par glisser-déposer.</li><li>Complétez ou ajustez ses compétences et son équipement.</li><li>Utilisez les boutons de la fiche pour lancer les jets utiles en jeu.</li></ol><p>Les pages suivantes détaillent chaque zone importante avec des captures d'écran prises dans le système lui-même.</p>"
}
},
{
"name": "Portrait et identité",
"type": "text",
"title": {
"show": true,
"level": 1
},
"text": {
"format": 1,
"content": "<h1>Lire la fiche personnage</h1><p>L'onglet <strong>Portrait</strong> concentre l'identité du personnage et les éléments de création qui structurent le reste de la fiche.</p><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-sheet-portrait.png\" alt=\"Fiche personnage des Oubliés, onglet Portrait.\" /><figcaption>La fiche personnage affiche immédiatement les références de race, de tribu et de métier.</figcaption></figure><h2>À retenir</h2><ul><li>La zone <strong>Race / Tribu / Métier</strong> accepte le glisser-déposer depuis les compendiums et remplace proprement la référence existante.</li><li>Un nouveau personnage reçoit automatiquement le token système <code>border_token_oublies.webp</code> tant qu'aucun token personnalisé n'a déjà été défini.</li><li>Les informations d'identité, les notes et les ressources principales restent modifiables directement sur la fiche.</li></ul><p>Le portrait sert surtout de synthèse et de point d'entrée avant de passer aux compétences et aux actions de jeu.</p>"
}
},
{
"name": "Compétences et profils",
"type": "text",
"title": {
"show": true,
"level": 1
},
"text": {
"format": 1,
"content": "<h1>Compétences et profils</h1><p>L'onglet <strong>Compétences</strong> présente les compétences regroupées par profil. Le système rappelle en permanence la logique <strong>Base + Profil = Valeur finale</strong>.</p><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-sheet-competences.png\" alt=\"Fiche personnage des Oubliés, onglet Compétences.\" /><figcaption>Chaque groupe affiche sa valeur de profil et les compétences qui en dépendent.</figcaption></figure><h2>Fonctionnement</h2><ul><li>Les compétences du personnage proviennent en pratique de la création, des compendiums ou d'ajouts manuels.</li><li>Les compétences fermées restent identifiables et peuvent demander une activation fictionnelle ou un apprentissage préalable.</li><li>Les domaines utiles (<em>Arts</em>, <em>Artisanat</em>, <em>Érudition</em>, <em>Langues</em>) peuvent être saisis sur les items concernés.</li></ul><p>Quand vous préparez un jet, c'est généralement la <strong>valeur finale</strong> affichée ici qu'il faut reporter dans le dialogue de résolution.</p>"
}
},
{
"name": "Jets de test, confrontation et initiative",
"type": "text",
"title": {
"show": true,
"level": 1
},
"text": {
"format": 1,
"content": "<h1>Les jets principaux</h1><p>Le système embarque trois dialogues génériques : <strong>test</strong>, <strong>confrontation</strong> et <strong>initiative</strong>. Ils respectent le principe Songes / Cauchemar avec choix du dé retenu quand plusieurs dés sont en concurrence.</p><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-dialog-test.png\" alt=\"Dialogue de jet de test dans le système Les Oubliés.\" /><figcaption>Le jet de test permet de choisir la difficulté, le mode de jet et un éventuel dé supplémentaire.</figcaption></figure><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-dialog-confrontation.png\" alt=\"Dialogue de confrontation dans le système Les Oubliés.\" /><figcaption>La confrontation gère les deux camps, avec saisie manuelle ou sélection d'un adversaire cible.</figcaption></figure><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-dialog-initiative.png\" alt=\"Dialogue d'initiative dans le système Les Oubliés.\" /><figcaption>L'initiative s'appuie sur Rapidité puis applique l'arrondi supérieur avec plafond à 12.</figcaption></figure><h2>Comportements utiles</h2><ul><li>Le système gère les modes <strong>1d12</strong>, <strong>2d12</strong> Songes / Cauchemar et les variantes avec dé supplémentaire.</li><li>Le <strong>12 explosif</strong> et le <strong>1 naturel</strong> sont pris en compte dans la résolution.</li><li>La confrontation peut récupérer une cible sélectionnée sur la scène, mais reste utilisable en saisie libre si aucune cible n'est active.</li></ul><p>Après le lancer, le chat produit une carte de résultat dédiée avec le détail utile à la table.</p>"
}
},
{
"name": "Combat, dégâts et protections",
"type": "text",
"title": {
"show": true,
"level": 1
},
"text": {
"format": 1,
"content": "<h1>Combattre dans le système</h1><p>L'onglet <strong>Combat & Magie</strong> regroupe les actions de combat, les armes équipées et les aides à la résolution associées.</p><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-sheet-combat-magie.png\" alt=\"Fiche personnage des Oubliés, onglet Combat et magie.\" /><figcaption>Les actions de combat, les réserves de fils et la magie partagent le même onglet pour limiter les allers-retours.</figcaption></figure><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-dialog-degats.png\" alt=\"Dialogue de résolution des dégâts dans le système Les Oubliés.\" /><figcaption>Le helper de dégâts applique la base de l'arme, les modificateurs et la protection ciblée.</figcaption></figure><h2>Points importants</h2><ul><li>Le bouton <strong>Attaque</strong> ouvre une action contextualisée depuis l'arme portée.</li><li>Le bouton <strong>Dégâts</strong> ouvre un helper plutôt qu'un jet classique, conformément aux règles du jeu.</li><li>Les armes à taille variable tiennent compte de la <strong>taille réelle du porteur</strong> pour calculer les dégâts de base.</li><li>La protection de la cible peut être appliquée directement dans la résolution finale.</li></ul><p>Les cartes de chat qui en résultent sont compactes et conçues pour un usage fréquent en partie.</p>"
}
},
{
"name": "Magie, fils et globes",
"type": "text",
"title": {
"show": true,
"level": 1
},
"text": {
"format": 1,
"content": "<h1>Magie et réserves</h1><p>La moitié basse de l'onglet <strong>Combat & Magie</strong> gère les fils, les globes et les sortilèges du personnage.</p><figure><img src=\"systems/fvtt-les-oublies/assets/ui/help-sheet-combat-magie.png\" alt=\"Réserves de fils, globes et sortilèges dans la fiche personnage des Oubliés.\" /><figcaption>Les réserves personnelle et de compagnie sont directement manipulables depuis la fiche.</figcaption></figure><h2>Ce que permet la fiche</h2><ul><li>déplacer des <strong>fils de Songes</strong>, des <strong>fils de Cauchemar</strong> et des <strong>globes vides</strong> entre la réserve personnelle et la compagnie ;</li><li>lancer la <strong>récolte de fils</strong> depuis le bouton dédié ;</li><li>activer un <strong>sortilège</strong> depuis sa ligne sans passer par un jet générique quand les règles ne le demandent pas.</li></ul><p>Les dépenses de fils rendent automatiquement autant de globes vides à la réserve utilisée. Le système met ainsi l'accent sur la circulation des ressources plutôt que sur des sous-feuilles séparées.</p>"
}
},
{
"name": "Compendiums et glisser-déposer",
"type": "text",
"title": {
"show": true,
"level": 1
},
"text": {
"format": 1,
"content": "<h1>Utiliser les compendiums</h1><p>Les compendiums fournis par le système servent de base technique pour créer et enrichir les acteurs.</p><h2>Usages recommandés</h2><ul><li>glisser une <strong>race</strong>, une <strong>tribu</strong> ou un <strong>métier</strong> sur un personnage pour configurer rapidement sa création ;</li><li>ajouter des <strong>armes</strong>, <strong>armures</strong>, <strong>équipements</strong> et <strong>sortilèges</strong> depuis les packs dédiés ;</li><li>faire glisser un item embarqué depuis la fiche vers la sidebar des objets quand vous souhaitez l'extraire comme document autonome.</li></ul><h2>À savoir</h2><ul><li>Les compendiums du système sont volontairement <strong>techniques</strong> : les champs descriptifs riches ont été vidés pour rester dans le périmètre du système.</li><li>Les versions complètes des contenus sont destinées au module frère <strong>fvtt-les-oublies-base</strong>.</li></ul><p>Pour une table de jeu, le plus simple est donc d'utiliser ces compendiums comme bibliothèque de construction rapide, puis d'affiner directement sur la fiche si nécessaire.</p>"
}
}
]
}
]
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
+1
View File
@@ -0,0 +1 @@
2026/05/06-20:00:09.287368 7f612bfff6c0 Delete type=3 #1
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.439890 7fd39d7ec6c0 Recovering log #3 2026/05/06-20:00:09.149296 7f6174dfd6c0 Delete type=3 #1
2026/05/06-10:42:52.439947 7fd39d7ec6c0 Level-0 table #5: started
2026/05/06-10:42:52.443417 7fd39d7ec6c0 Level-0 table #5: 6826 bytes OK
2026/05/06-10:42:52.454459 7fd39d7ec6c0 Delete type=0 #3
2026/05/06-10:42:52.454587 7fd39d7ec6c0 Delete type=3 #2
2026/05/06-10:43:22.990986 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:22.991003 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:22.997495 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.019890 7fd39cfeb6c0 Manual compaction at level-0 from '!items!0wVpxy2XZYx6S5QR' @ 72057594037927935 : 1 .. '!items!wvt5PIveAgIdsK1T' @ 0 : 0; will stop at '!items!wvt5PIveAgIdsK1T' @ 35 : 1
2026/05/06-10:43:23.019900 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.023528 7fd39cfeb6c0 Generated table #9@0: 35 keys, 6826 bytes
2026/05/06-10:43:23.023563 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 6826 bytes
2026/05/06-10:43:23.030165 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.030304 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.050189 7fd39cfeb6c0 Manual compaction at level-0 from '!items!wvt5PIveAgIdsK1T' @ 35 : 1 .. '!items!wvt5PIveAgIdsK1T' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.239714 7f66b3fff6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.456918 7fd39e7ee6c0 Recovering log #3 2026/05/06-20:00:09.165377 7f612bfff6c0 Delete type=3 #1
2026/05/06-10:42:52.456943 7fd39e7ee6c0 Level-0 table #5: started
2026/05/06-10:42:52.460700 7fd39e7ee6c0 Level-0 table #5: 815 bytes OK
2026/05/06-10:42:52.470387 7fd39e7ee6c0 Delete type=0 #3
2026/05/06-10:42:52.470451 7fd39e7ee6c0 Delete type=3 #2
2026/05/06-10:43:22.984965 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:22.985000 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:22.990909 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.009491 7fd39cfeb6c0 Manual compaction at level-0 from '!items!3BnwI245d2H2cttB' @ 72057594037927935 : 1 .. '!items!ouVi1TDDGHMH7wRj' @ 0 : 0; will stop at '!items!ouVi1TDDGHMH7wRj' @ 2 : 1
2026/05/06-10:43:23.009499 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.012514 7fd39cfeb6c0 Generated table #9@0: 3 keys, 815 bytes
2026/05/06-10:43:23.012529 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 815 bytes
2026/05/06-10:43:23.019598 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.019764 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.050179 7fd39cfeb6c0 Manual compaction at level-0 from '!items!ouVi1TDDGHMH7wRj' @ 2 : 1 .. '!items!ouVi1TDDGHMH7wRj' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.316793 7f66b37fe6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.504965 7fd39d7ec6c0 Recovering log #3 2026/05/06-20:00:09.210742 7f6174dfd6c0 Delete type=3 #1
2026/05/06-10:42:52.505011 7fd39d7ec6c0 Level-0 table #5: started
2026/05/06-10:42:52.508141 7fd39d7ec6c0 Level-0 table #5: 4614 bytes OK
2026/05/06-10:42:52.518635 7fd39d7ec6c0 Delete type=0 #3
2026/05/06-10:42:52.518695 7fd39d7ec6c0 Delete type=3 #2
2026/05/06-10:43:23.071936 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:23.071953 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:23.077983 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.111647 7fd39cfeb6c0 Manual compaction at level-0 from '!items!1IhIUjv73YMttYW0' @ 72057594037927935 : 1 .. '!items!zrJvQTctIpfjIz7J' @ 0 : 0; will stop at '!items!zrJvQTctIpfjIz7J' @ 20 : 1
2026/05/06-10:43:23.111655 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.115172 7fd39cfeb6c0 Generated table #9@0: 27 keys, 4614 bytes
2026/05/06-10:43:23.115193 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 4614 bytes
2026/05/06-10:43:23.121954 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.122015 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.122144 7fd39cfeb6c0 Manual compaction at level-0 from '!items!zrJvQTctIpfjIz7J' @ 20 : 1 .. '!items!zrJvQTctIpfjIz7J' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.548963 7f66b3fff6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.472500 7fd39efef6c0 Recovering log #3 2026/05/06-20:00:09.180731 7f6175dff6c0 Delete type=3 #1
2026/05/06-10:42:52.472607 7fd39efef6c0 Level-0 table #5: started
2026/05/06-10:42:52.476439 7fd39efef6c0 Level-0 table #5: 6181 bytes OK
2026/05/06-10:42:52.486963 7fd39efef6c0 Delete type=0 #3
2026/05/06-10:42:52.487018 7fd39efef6c0 Delete type=3 #2
2026/05/06-10:43:22.997591 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:22.997612 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:23.003434 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.030431 7fd39cfeb6c0 Manual compaction at level-0 from '!items!19G5qBEMaflvGK28' @ 72057594037927935 : 1 .. '!items!zoKnkvQK4wPA0cvP' @ 0 : 0; will stop at '!items!zoKnkvQK4wPA0cvP' @ 35 : 1
2026/05/06-10:43:23.030439 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.033703 7fd39cfeb6c0 Generated table #9@0: 35 keys, 6181 bytes
2026/05/06-10:43:23.033732 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 6181 bytes
2026/05/06-10:43:23.039807 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.039906 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.050199 7fd39cfeb6c0 Manual compaction at level-0 from '!items!zoKnkvQK4wPA0cvP' @ 35 : 1 .. '!items!zoKnkvQK4wPA0cvP' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.389376 7f67015ff6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.553462 7fd39e7ee6c0 Recovering log #3 2026/05/06-20:00:09.256606 7f61755fe6c0 Delete type=3 #1
2026/05/06-10:42:52.553504 7fd39e7ee6c0 Level-0 table #5: started
2026/05/06-10:42:52.556942 7fd39e7ee6c0 Level-0 table #5: 4030 bytes OK
2026/05/06-10:42:52.566377 7fd39e7ee6c0 Delete type=0 #3
2026/05/06-10:42:52.566433 7fd39e7ee6c0 Delete type=3 #2
2026/05/06-10:43:23.065397 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:23.065421 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:23.071751 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.100865 7fd39cfeb6c0 Manual compaction at level-0 from '!items!1o5Kw4HzVNxm2LkJ' @ 72057594037927935 : 1 .. '!items!zIdc7oRRURJK1LCb' @ 0 : 0; will stop at '!items!zIdc7oRRURJK1LCb' @ 6 : 1
2026/05/06-10:43:23.100878 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.104865 7fd39cfeb6c0 Generated table #9@0: 8 keys, 4030 bytes
2026/05/06-10:43:23.104896 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 4030 bytes
2026/05/06-10:43:23.111409 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.111517 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.122135 7fd39cfeb6c0 Manual compaction at level-0 from '!items!zIdc7oRRURJK1LCb' @ 6 : 1 .. '!items!zIdc7oRRURJK1LCb' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.785271 7f6700dfe6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.489022 7fd39e7ee6c0 Recovering log #3 2026/05/06-20:00:09.195364 7f61755fe6c0 Delete type=3 #1
2026/05/06-10:42:52.489067 7fd39e7ee6c0 Level-0 table #5: started
2026/05/06-10:42:52.492225 7fd39e7ee6c0 Level-0 table #5: 2059 bytes OK
2026/05/06-10:42:52.502742 7fd39e7ee6c0 Delete type=0 #3
2026/05/06-10:42:52.502805 7fd39e7ee6c0 Delete type=3 #2
2026/05/06-10:43:23.003515 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:23.003534 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:23.009396 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.040005 7fd39cfeb6c0 Manual compaction at level-0 from '!items!FfQwxxtecQRrRAOX' @ 72057594037927935 : 1 .. '!items!uKW7eAe5nA8zgHNQ' @ 0 : 0; will stop at '!items!uKW7eAe5nA8zgHNQ' @ 2 : 1
2026/05/06-10:43:23.040012 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.043526 7fd39cfeb6c0 Generated table #9@0: 8 keys, 2059 bytes
2026/05/06-10:43:23.043551 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 2059 bytes
2026/05/06-10:43:23.049951 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.050079 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.050207 7fd39cfeb6c0 Manual compaction at level-0 from '!items!uKW7eAe5nA8zgHNQ' @ 2 : 1 .. '!items!uKW7eAe5nA8zgHNQ' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.470099 7f6700dfe6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.520730 7fd39e7ee6c0 Recovering log #3 2026/05/06-20:00:09.225835 7f612bfff6c0 Delete type=3 #1
2026/05/06-10:42:52.520772 7fd39e7ee6c0 Level-0 table #5: started
2026/05/06-10:42:52.523941 7fd39e7ee6c0 Level-0 table #5: 3420 bytes OK
2026/05/06-10:42:52.535782 7fd39e7ee6c0 Delete type=0 #3
2026/05/06-10:42:52.535848 7fd39e7ee6c0 Delete type=3 #2
2026/05/06-10:43:23.050346 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:23.050364 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:23.058307 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.078097 7fd39cfeb6c0 Manual compaction at level-0 from '!items!0xfnMEBzcDzn33u1' @ 72057594037927935 : 1 .. '!items!ucpCiBTGiqJ4vPoK' @ 0 : 0; will stop at '!items!ucpCiBTGiqJ4vPoK' @ 4 : 1
2026/05/06-10:43:23.078105 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.081777 7fd39cfeb6c0 Generated table #9@0: 7 keys, 3420 bytes
2026/05/06-10:43:23.081823 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 3420 bytes
2026/05/06-10:43:23.088900 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.089029 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.122115 7fd39cfeb6c0 Manual compaction at level-0 from '!items!ucpCiBTGiqJ4vPoK' @ 4 : 1 .. '!items!ucpCiBTGiqJ4vPoK' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.629799 7f66b37fe6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
View File
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.568751 7fd39d7ec6c0 Recovering log #3 2026/05/06-20:00:09.271737 7f6174dfd6c0 Delete type=3 #1
2026/05/06-10:42:52.568899 7fd39d7ec6c0 Level-0 table #5: started
2026/05/06-10:42:52.572727 7fd39d7ec6c0 Level-0 table #5: 16649 bytes OK
2026/05/06-10:42:52.593776 7fd39d7ec6c0 Delete type=0 #3
2026/05/06-10:42:52.593835 7fd39d7ec6c0 Delete type=3 #2
2026/05/06-10:43:23.122256 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:23.122276 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:23.128449 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.148375 7fd39cfeb6c0 Manual compaction at level-0 from '!items!15iY8vCuisq7fgxC' @ 72057594037927935 : 1 .. '!items!zlacnrDvD6OQqSq9' @ 0 : 0; will stop at '!items!zlacnrDvD6OQqSq9' @ 49 : 1
2026/05/06-10:43:23.148382 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.151996 7fd39cfeb6c0 Generated table #9@0: 72 keys, 16649 bytes
2026/05/06-10:43:23.152009 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 16649 bytes
2026/05/06-10:43:23.158338 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.158406 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.158547 7fd39cfeb6c0 Manual compaction at level-0 from '!items!zlacnrDvD6OQqSq9' @ 49 : 1 .. '!items!zlacnrDvD6OQqSq9' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.858447 7f66b3fff6c0 Delete type=3 #1
Binary file not shown.
Binary file not shown.
View File
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000004 MANIFEST-000002
+1 -15
View File
@@ -1,15 +1 @@
2026/05/06-10:42:52.538075 7fd39d7ec6c0 Recovering log #3 2026/05/06-20:00:09.241097 7f6175dff6c0 Delete type=3 #1
2026/05/06-10:42:52.538162 7fd39d7ec6c0 Level-0 table #5: started
2026/05/06-10:42:52.541922 7fd39d7ec6c0 Level-0 table #5: 6762 bytes OK
2026/05/06-10:42:52.551592 7fd39d7ec6c0 Delete type=0 #3
2026/05/06-10:42:52.551647 7fd39d7ec6c0 Delete type=3 #2
2026/05/06-10:43:23.058483 7fd39cfeb6c0 Level-0 table #8: started
2026/05/06-10:43:23.058516 7fd39cfeb6c0 Level-0 table #8: 0 bytes OK
2026/05/06-10:43:23.065217 7fd39cfeb6c0 Delete type=0 #6
2026/05/06-10:43:23.089170 7fd39cfeb6c0 Manual compaction at level-0 from '!items!4aynfqE4U9hcdgMN' @ 72057594037927935 : 1 .. '!items!yUWIdLKCkSvQcMT7' @ 0 : 0; will stop at '!items!yUWIdLKCkSvQcMT7' @ 9 : 1
2026/05/06-10:43:23.089178 7fd39cfeb6c0 Compacting 1@0 + 0@1 files
2026/05/06-10:43:23.092608 7fd39cfeb6c0 Generated table #9@0: 11 keys, 6762 bytes
2026/05/06-10:43:23.092669 7fd39cfeb6c0 Compacted 1@0 + 0@1 files => 6762 bytes
2026/05/06-10:43:23.100615 7fd39cfeb6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/06-10:43:23.100731 7fd39cfeb6c0 Delete type=2 #5
2026/05/06-10:43:23.122126 7fd39cfeb6c0 Manual compaction at level-0 from '!items!yUWIdLKCkSvQcMT7' @ 9 : 1 .. '!items!yUWIdLKCkSvQcMT7' @ 0 : 0; will stop at (end)
-1
View File
@@ -1 +0,0 @@
2026/05/06-10:41:39.711104 7f67015ff6c0 Delete type=3 #1
Binary file not shown.
+2 -1
View File
@@ -1,7 +1,7 @@
import path from "node:path" import path from "node:path"
import fs from "node:fs" import fs from "node:fs"
import { buildPacks } from "./pack-builder.mjs" import { buildPacks, SYSTEM_PACK_DEFINITIONS } from "./pack-builder.mjs"
const rootDir = path.resolve(import.meta.dirname, "..") const rootDir = path.resolve(import.meta.dirname, "..")
const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8")) const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"))
@@ -10,6 +10,7 @@ const systemJson = JSON.parse(fs.readFileSync(path.join(rootDir, "system.json"),
await buildPacks({ await buildPacks({
sourceRoot: path.join(rootDir, "packs-src"), sourceRoot: path.join(rootDir, "packs-src"),
outputRoot: path.join(rootDir, "packs"), outputRoot: path.join(rootDir, "packs"),
packDefinitions: SYSTEM_PACK_DEFINITIONS,
documentSystemId: systemJson.id, documentSystemId: systemJson.id,
documentSystemVersion: packageJson.version, documentSystemVersion: packageJson.version,
coreVersion: String(systemJson.compatibility?.verified ?? systemJson.compatibility?.minimum ?? ""), coreVersion: String(systemJson.compatibility?.verified ?? systemJson.compatibility?.minimum ?? ""),
+115 -26
View File
@@ -4,7 +4,7 @@ import crypto from "node:crypto"
import { Level } from "level" import { Level } from "level"
export const PACK_DEFINITIONS = [ export const CONTENT_PACK_DEFINITIONS = [
{ sourceFile: "armes.json", outputFolder: "armes", type: "Item" }, { sourceFile: "armes.json", outputFolder: "armes", type: "Item" },
{ sourceFile: "armures.json", outputFolder: "armures", type: "Item" }, { sourceFile: "armures.json", outputFolder: "armures", type: "Item" },
{ sourceFile: "equipements.json", outputFolder: "equipements", type: "Item" }, { sourceFile: "equipements.json", outputFolder: "equipements", type: "Item" },
@@ -16,6 +16,11 @@ export const PACK_DEFINITIONS = [
{ sourceFile: "sortileges.json", outputFolder: "sortileges", type: "Item" }, { sourceFile: "sortileges.json", outputFolder: "sortileges", type: "Item" },
] ]
export const SYSTEM_PACK_DEFINITIONS = [
...CONTENT_PACK_DEFINITIONS,
{ sourceFile: "aide-systeme.json", outputFolder: "aide-systeme", type: "JournalEntry" },
]
function slugId(input) { function slugId(input) {
const hash = crypto.createHash("sha256").update(input).digest() const hash = crypto.createHash("sha256").update(input).digest()
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
@@ -26,41 +31,122 @@ function slugId(input) {
return id return id
} }
function toPackDocument(entry, index, { function createDocumentStats({
documentSystemId, documentSystemId,
documentSystemVersion, documentSystemVersion,
coreVersion, coreVersion,
createdTime, createdTime,
lastModifiedBy = "Copilot", lastModifiedBy = "Copilot",
} = {}) { } = {}) {
return {
systemId: documentSystemId,
systemVersion: documentSystemVersion,
coreVersion,
createdTime,
modifiedTime: createdTime,
lastModifiedBy,
compendiumSource: null,
duplicateSource: null,
exportSource: null,
}
}
function toItemPackDocument(entry, index, options = {}) {
const docId = slugId(`${entry.type}:${entry.name}`) const docId = slugId(`${entry.type}:${entry.name}`)
return { return {
name: entry.name, key: `!items!${docId}`,
type: entry.type, value: {
img: entry.img ?? "icons/svg/item-bag.svg", name: entry.name,
system: entry.system ?? {}, type: entry.type,
effects: Array.isArray(entry.effects) ? entry.effects : [], img: entry.img ?? "icons/svg/item-bag.svg",
flags: entry.flags ?? {}, system: entry.system ?? {},
_stats: { effects: Array.isArray(entry.effects) ? entry.effects : [],
systemId: documentSystemId, flags: entry.flags ?? {},
systemVersion: documentSystemVersion, _stats: createDocumentStats(options),
coreVersion, _id: docId,
createdTime, folder: null,
modifiedTime: createdTime, sort: index * 1000,
lastModifiedBy, ownership: {
compendiumSource: null, default: 0,
duplicateSource: null, },
exportSource: null,
},
_id: docId,
folder: null,
sort: index * 1000,
ownership: {
default: 0,
}, },
embedded: [],
} }
} }
function toJournalPageDocument(entry, journalId, page, pageIndex, options = {}) {
const pageId = slugId(`JournalEntryPage:${entry.name}:${page.name ?? page.title ?? pageIndex}`)
return {
_id: pageId,
name: page.name ?? `Page ${pageIndex + 1}`,
type: page.type ?? "text",
title: {
show: page.title?.show ?? true,
level: page.title?.level ?? 1,
},
text: {
format: page.text?.format ?? 1,
content: page.text?.content ?? "",
},
system: page.system ?? {},
image: page.image ?? {},
video: page.video ?? { controls: true, volume: 0.5 },
src: page.src ?? null,
category: page.category ?? null,
sort: page.sort ?? pageIndex * 1000,
ownership: page.ownership ?? { default: -1 },
flags: page.flags ?? {},
_stats: createDocumentStats(options),
_key: `!journal.pages!${journalId}.${pageId}`,
}
}
function toJournalPackDocument(entry, index, options = {}) {
const docId = slugId(`JournalEntry:${entry.name}`)
const pages = Array.isArray(entry.pages)
? entry.pages.map((page, pageIndex) => toJournalPageDocument(entry, docId, page, pageIndex, options))
: []
return {
key: `!journal!${docId}`,
value: {
name: entry.name,
pages: pages.map((page) => page._id),
ownership: entry.ownership ?? { default: 0 },
flags: entry.flags ?? { core: {} },
_stats: createDocumentStats(options),
folder: null,
sort: entry.sort ?? index * 1000,
_id: docId,
categories: Array.isArray(entry.categories) ? entry.categories : [],
},
embedded: pages.map((page) => ({
key: page._key,
value: {
name: page.name,
type: page.type,
title: page.title,
text: page.text,
system: page.system,
image: page.image,
video: page.video,
src: page.src,
category: page.category,
sort: page.sort,
ownership: page.ownership,
flags: page.flags,
_stats: page._stats,
_id: page._id,
},
})),
}
}
function toPackDocument(entry, index, options = {}) {
if (entry.type === "JournalEntry") return toJournalPackDocument(entry, index, options)
return toItemPackDocument(entry, index, options)
}
async function buildPack({ async function buildPack({
sourcePath, sourcePath,
outputPath, outputPath,
@@ -95,7 +181,10 @@ async function buildPack({
createdTime, createdTime,
lastModifiedBy, lastModifiedBy,
}) })
batch.put(`!items!${doc._id}`, JSON.stringify(doc)) batch.put(doc.key, JSON.stringify(doc.value))
for (const embedded of doc.embedded) {
batch.put(embedded.key, JSON.stringify(embedded.value))
}
}) })
await batch.write() await batch.write()
} finally { } finally {
@@ -106,7 +195,7 @@ async function buildPack({
export async function buildPacks({ export async function buildPacks({
sourceRoot, sourceRoot,
outputRoot, outputRoot,
packDefinitions = PACK_DEFINITIONS, packDefinitions = SYSTEM_PACK_DEFINITIONS,
documentSystemId, documentSystemId,
documentSystemVersion, documentSystemVersion,
coreVersion, coreVersion,
+9 -3
View File
@@ -1,7 +1,7 @@
import fs from "node:fs" import fs from "node:fs"
import path from "node:path" import path from "node:path"
import { PACK_DEFINITIONS, buildPacks } from "./pack-builder.mjs" import { CONTENT_PACK_DEFINITIONS, SYSTEM_PACK_DEFINITIONS, buildPacks } from "./pack-builder.mjs"
const systemRoot = path.resolve(import.meta.dirname, "..") const systemRoot = path.resolve(import.meta.dirname, "..")
const targetRoot = path.resolve( const targetRoot = path.resolve(
@@ -20,10 +20,11 @@ const richFieldMap = Object.fromEntries(
Object.entries(systemManifest.documentTypes?.Item ?? {}).map(([type, data]) => [type, data.htmlFields ?? []]), Object.entries(systemManifest.documentTypes?.Item ?? {}).map(([type, data]) => [type, data.htmlFields ?? []]),
) )
const coreVersion = String(systemManifest.compatibility?.verified ?? systemManifest.compatibility?.minimum ?? "") const coreVersion = String(systemManifest.compatibility?.verified ?? systemManifest.compatibility?.minimum ?? "")
const basePackDefinitions = PACK_DEFINITIONS.map((pack) => ({ const basePackDefinitions = CONTENT_PACK_DEFINITIONS.map((pack) => ({
...pack, ...pack,
outputFolder: `base-${pack.outputFolder}`, outputFolder: `base-${pack.outputFolder}`,
})) }))
const contentPackNames = new Set(CONTENT_PACK_DEFINITIONS.map((pack) => pack.outputFolder))
function setDeepValue(target, propertyPath, value) { function setDeepValue(target, propertyPath, value) {
const segments = String(propertyPath || "").split(".").filter(Boolean) const segments = String(propertyPath || "").split(".").filter(Boolean)
@@ -142,6 +143,11 @@ function ensureTargetModuleScaffold() {
], ],
}, },
packs: (systemManifest.packs ?? []).map((pack) => ({ packs: (systemManifest.packs ?? []).map((pack) => ({
// The base content module only mirrors content packs, not system journals or utility packs.
...pack,
}))
.filter((pack) => contentPackNames.has(pack.name))
.map((pack) => ({
...pack, ...pack,
name: `base-${pack.name}`, name: `base-${pack.name}`,
path: `packs/base-${pack.name}`, path: `packs/base-${pack.name}`,
@@ -201,7 +207,7 @@ pruneStalePackDirectories(targetPacksRoot, basePackDefinitions)
await buildPacks({ await buildPacks({
sourceRoot: systemSourceRoot, sourceRoot: systemSourceRoot,
outputRoot: path.join(systemRoot, "packs"), outputRoot: path.join(systemRoot, "packs"),
packDefinitions: PACK_DEFINITIONS, packDefinitions: SYSTEM_PACK_DEFINITIONS,
documentSystemId: systemManifest.id, documentSystemId: systemManifest.id,
documentSystemVersion: systemPackage.version, documentSystemVersion: systemPackage.version,
coreVersion, coreVersion,
+14 -1
View File
@@ -235,6 +235,18 @@
"PLAYER": "OBSERVER", "PLAYER": "OBSERVER",
"ASSISTANT": "OWNER" "ASSISTANT": "OWNER"
} }
},
{
"type": "JournalEntry",
"label": "Aide du système",
"name": "aide-systeme",
"path": "packs/aide-systeme",
"system": "fvtt-les-oublies",
"flags": {},
"ownership": {
"PLAYER": "OBSERVER",
"ASSISTANT": "OWNER"
}
} }
], ],
"packFolders": [ "packFolders": [
@@ -250,7 +262,8 @@
"races", "races",
"tribus", "tribus",
"metiers", "metiers",
"sortileges" "sortileges",
"aide-systeme"
] ]
} }
], ],