IMplémentation de la ajorité des remarques de Nepherius
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation de l'Esprit</strong> (Instruction, Merveilleux technologique, Raisonnement, Traitement), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation de l'Esprit</strong> (Instruction, Merveilleux technologique, Raisonnement, Traitement), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
||||||
"narratif": "<p>Le personnage entre <strong>en contact avec l'esprit d'un défunt</strong>. Il peut lui poser une <em>question fermée</em> (réponse par oui ou par non uniquement). Le contact est bref et les réponses peuvent être fragmentées ou métaphoriques, à la discrétion du narrateur.</p>",
|
"narratif": "<p>Le protagoniste entre <strong>en contact avec l'esprit d'un défunt</strong>. Il peut lui poser une <em>question fermée</em> (réponse par oui ou par non uniquement). Le contact est bref et les réponses peuvent être fragmentées ou métaphoriques, à la discrétion du narrateur.</p>",
|
||||||
"exemples": "<ul><li>Interroger la victime d'un meurtre sur l'identité de son agresseur.</li><li>Consulter l'esprit d'un ancêtre pour retrouver un objet caché.</li><li>Demander à un fantôme si quelqu'un l'a aidé à mourir.</li></ul>"
|
"exemples": "<ul><li>Interroger la victime d'un meurtre sur l'identité de son agresseur.</li><li>Consulter l'esprit d'un ancêtre pour retrouver un objet caché.</li><li>Demander à un fantôme si quelqu'un l'a aidé à mourir.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomCommMorts001"
|
"_key": "!items!anomCommMorts001"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, le protagoniste gagne la possibilité de <strong>relancer le Dé de Lune</strong> un nombre de fois égal à son Niveau d'Anomalie. Il peut choisir de conserver le résultat préféré.</p><p>Cette capacité ne s'applique pas aux tests de chance.</p>",
|
"technique": "<p>Durant un scénario, le protagoniste gagne la possibilité de <strong>relancer le Dé de Lune</strong> un nombre de fois égal à son Niveau d'Anomalie. Il peut choisir de conserver le résultat préféré.</p><p>Cette capacité ne s'applique pas aux tests de chance.</p>",
|
||||||
"narratif": "<p>Le personnage peut <strong>influencer le hasard</strong> à sa façon, en déclenchant ou en évitant de petits événements aléatoires dans son environnement proche. Ces manifestations sont mineures, subtiles, et ne semblent jamais surnaturelles aux yeux des témoins.</p>",
|
"narratif": "<p>Le protagoniste peut <strong>influencer le hasard</strong> à sa façon, en déclenchant ou en évitant de petits événements aléatoires dans son environnement proche. Ces manifestations sont mineures, subtiles, et ne semblent jamais surnaturelles aux yeux des témoins.</p>",
|
||||||
"exemples": "<ul><li>Obtenir une bonne main au jeu de cartes lors de la distribution.</li><li>Voir le feu passer au vert en tournant le coin de la rue.</li><li>Faire tomber le verre d'un convive gênant au bon moment.</li></ul>"
|
"exemples": "<ul><li>Obtenir une bonne main au jeu de cartes lors de la distribution.</li><li>Voir le feu passer au vert en tournant le coin de la rue.</li><li>Faire tomber le verre d'un convive gênant au bon moment.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomEntropie0001"
|
"_key": "!items!anomEntropie0001"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, lors d'un test de <strong>Coercition, Échauffourée, Effacement ou Traque</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
"technique": "<p>Durant un scénario, lors d'un test de <strong>Coercition, Échauffourée, Effacement ou Traque</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
||||||
"narratif": "<p>Le personnage peut <strong>générer une petite illusion mineure</strong> — visuelle, auditive ou olfactive, au choix — sans détail ni précision, pour une durée d'<strong>une minute</strong>. L'illusion ne peut représenter un être vivant en détail et ne résiste pas à un examen rapproché.</p>",
|
"narratif": "<p>Le protagoniste peut <strong>générer une petite illusion mineure</strong> — visuelle, auditive ou olfactive, au choix — sans détail ni précision, pour une durée d'<strong>une minute</strong>. L'illusion ne peut représenter un être vivant en détail et ne résiste pas à un examen rapproché.</p>",
|
||||||
"exemples": "<ul><li>Le son d'un chat qui miaule ou d'un livre qui tombe dans la pièce voisine.</li><li>Un reflet métallique ou une ombre fugace au bout d'un couloir.</li><li>L'odeur de la pluie ou d'une fumée inquiétante.</li></ul>"
|
"exemples": "<ul><li>Le son d'un chat qui miaule ou d'un livre qui tombe dans la pièce voisine.</li><li>Un reflet métallique ou une ombre fugace au bout d'un couloir.</li><li>L'odeur de la pluie ou d'une fumée inquiétante.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomIllusion0001"
|
"_key": "!items!anomIllusion0001"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation de l'Âme</strong> (Artifice, Attraction, Coercition, Faveur), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation de l'Âme</strong> (Artifice, Attraction, Coercition, Faveur), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
||||||
"narratif": "<p>Le personnage est capable d'<strong>influencer la prise de décision</strong> d'une personne en lui parlant à voix haute et en la regardant dans les yeux. Cette décision doit avoir un <em>impact immédiat</em> sur l'action de la personne concernée.</p><p>Cette capacité fonctionne également sur les <strong>automates sophistiqués de 4e et 5e génération</strong>.</p>",
|
"narratif": "<p>Le protagoniste est capable d'<strong>influencer la prise de décision</strong> d'une personne en lui parlant à voix haute et en la regardant dans les yeux. Cette décision doit avoir un <em>impact immédiat</em> sur l'action de la personne concernée.</p><p>Cette capacité fonctionne également sur les <strong>automates sophistiqués de 4e et 5e génération</strong>.</p>",
|
||||||
"exemples": "<ul><li>Convaincre un garde de laisser passer sans vérifier les laissez-passer.</li><li>Pousser un prisonnier à donner son nom ou à s'asseoir.</li><li>Inciter un chauffeur de taxi à emprunter un itinéraire détourné.</li></ul>"
|
"exemples": "<ul><li>Convaincre un garde de laisser passer sans vérifier les laissez-passer.</li><li>Pousser un prisonnier à donner son nom ou à s'asseoir.</li><li>Inciter un chauffeur de taxi à emprunter un itinéraire détourné.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomSuggestion01"
|
"_key": "!items!anomSuggestion01"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation du Cœur</strong> (Appréciation, Arts, Inspiration, Traque), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation du Cœur</strong> (Appréciation, Arts, Inspiration, Traque), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
||||||
"narratif": "<p>En <strong>tirant les cartes</strong>, le personnage peut apprendre une information sur une personne concernant son <em>passé, son présent ou son avenir</em>. L'information reste sujette à interprétation et le narrateur peut choisir de la formuler de façon symbolique ou métaphorique.</p>",
|
"narratif": "<p>En <strong>tirant les cartes</strong>, le protagoniste peut apprendre une information sur une personne concernant son <em>passé, son présent ou son avenir</em>. L'information reste sujette à interprétation et le narrateur peut choisir de la formuler de façon symbolique ou métaphorique.</p>",
|
||||||
"exemples": "<ul><li>Deviner où se trouvera une cible le lendemain.</li><li>Connaître les antécédents douloureux d'un voisin mystérieux.</li><li>Obtenir une image symbolique du danger qui attend un allié.</li></ul>"
|
"exemples": "<ul><li>Deviner où se trouvera une cible le lendemain.</li><li>Connaître les antécédents douloureux d'un voisin mystérieux.</li><li>Obtenir une image symbolique du danger qui attend un allié.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomTarot00001"
|
"_key": "!items!anomTarot00001"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation du Corps</strong> (Échauffourée, Effacement, Mobilité, Prouesse), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation du Corps</strong> (Échauffourée, Effacement, Mobilité, Prouesse), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
||||||
"narratif": "<p>Dans un rayon de <strong>8 mètres</strong>, le personnage peut <strong>déplacer par la pensée</strong> un petit objet léger sans attaches, sur <strong>4 mètres</strong> (dans n'importe quelle direction) pendant <strong>2 tours</strong>. L'objet doit être visible et accessible par le regard.</p>",
|
"narratif": "<p>Dans un rayon de <strong>8 mètres</strong>, le protagoniste peut <strong>déplacer par la pensée</strong> un petit objet léger sans attaches, sur <strong>4 mètres</strong> (dans n'importe quelle direction) pendant <strong>2 tours</strong>. L'objet doit être visible et accessible par le regard.</p>",
|
||||||
"exemples": "<ul><li>Déplacer une cuillère pour la faire tomber d'une table au bon moment.</li><li>Faire léviter un jeu de tarot ou un trousseau de clés.</li><li>Pousser doucement un verre pour attirer l'attention d'un interlocuteur.</li></ul>"
|
"exemples": "<ul><li>Déplacer une cuillère pour la faire tomber d'une table au bon moment.</li><li>Faire léviter un jeu de tarot ou un trousseau de clés.</li><li>Pousser doucement un verre pour attirer l'attention d'un interlocuteur.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomTelekines01"
|
"_key": "!items!anomTelekines01"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, lors d'un test d'<strong>Appréciation, Attraction, Échauffourée ou Faveur</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p><p>Cette capacité fonctionne également sur les <strong>automates sophistiqués de 4e et 5e génération</strong>.</p>",
|
"technique": "<p>Durant un scénario, lors d'un test d'<strong>Appréciation, Attraction, Échauffourée ou Faveur</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p><p>Cette capacité fonctionne également sur les <strong>automates sophistiqués de 4e et 5e génération</strong>.</p>",
|
||||||
"narratif": "<p>Le personnage est capable de <strong>percevoir les pensées superficielles</strong> d'un tiers. Il peut comprendre l'état émotionnel d'une personne ou capter une image ou un mot dans son esprit (à la discrétion du narrateur), simplement en <em>l'observant</em>.</p>",
|
"narratif": "<p>Le protagoniste est capable de <strong>percevoir les pensées superficielles</strong> d'un tiers. Il peut comprendre l'état émotionnel d'une personne ou capter une image ou un mot dans son esprit (à la discrétion du narrateur), simplement en <em>l'observant</em>.</p>",
|
||||||
"exemples": "<ul><li>Percevoir l'image d'un cristal de cyanure dans l'esprit d'un serviteur soupçonné de tentative de meurtre.</li><li>Détecter, malgré un visage parfaitement contrôlé, qu'un magistrat est en réalité terrifié.</li><li>Ressentir la culpabilité d'un homme qui ment avec aplomb.</li></ul>"
|
"exemples": "<ul><li>Percevoir l'image d'un cristal de cyanure dans l'esprit d'un serviteur soupçonné de tentative de meurtre.</li><li>Détecter, malgré un visage parfaitement contrôlé, qu'un magistrat est en réalité terrifié.</li><li>Ressentir la culpabilité d'un homme qui ment avec aplomb.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomTelepathi01"
|
"_key": "!items!anomTelepathi01"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"level": 2,
|
"level": 2,
|
||||||
"usesRemaining": 2,
|
"usesRemaining": 2,
|
||||||
"technique": "<p>Durant un scénario, lors d'un test d'<strong>Appréciation, Merveilleux technologique, Traitement ou Traque</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
"technique": "<p>Durant un scénario, lors d'un test d'<strong>Appréciation, Merveilleux technologique, Traitement ou Traque</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
|
||||||
"narratif": "<p>L'<strong>esprit du personnage quitte son enveloppe corporelle</strong> et se déplace de <strong>8 mètres par tour</strong> pendant <strong>4 tours</strong>, dans n'importe quelle direction. L'esprit est <em>invisible</em> et peut traverser tous les obstacles matériels. Les sens du personnage restent les mêmes durant le voyage.</p><p>Le corps reste immobile et vulnérable durant le voyage.</p>",
|
"narratif": "<p>L'<strong>esprit du protagoniste quitte son enveloppe corporelle</strong> et se déplace de <strong>8 mètres par tour</strong> pendant <strong>4 tours</strong>, dans n'importe quelle direction. L'esprit est <em>invisible</em> et peut traverser tous les obstacles matériels. Les sens du protagoniste restent les mêmes durant le voyage.</p><p>Le corps reste immobile et vulnérable durant le voyage.</p>",
|
||||||
"exemples": "<ul><li>Accéder aux toits d'une maison pour effectuer une reconnaissance sans risque physique.</li><li>Inspecter une pièce adjacente verrouillée avant d'y pénétrer.</li><li>Voir à quelle distance un éboulement bloque le passage dans un tunnel et s'il y a des survivants.</li></ul>"
|
"exemples": "<ul><li>Accéder aux toits d'une maison pour effectuer une reconnaissance sans risque physique.</li><li>Inspecter une pièce adjacente verrouillée avant d'y pénétrer.</li><li>Voir à quelle distance un éboulement bloque le passage dans un tunnel et s'il y a des survivants.</li></ul>"
|
||||||
},
|
},
|
||||||
"_key": "!items!anomVoyAstral01"
|
"_key": "!items!anomVoyAstral01"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
CelestopolActor,
|
CelestopolActor,
|
||||||
CelestopolItem,
|
CelestopolItem,
|
||||||
CelestopolChatMessage,
|
CelestopolChatMessage,
|
||||||
|
CelestopolCombat,
|
||||||
CelestopolRoll,
|
CelestopolRoll,
|
||||||
} from "./module/documents/_module.mjs"
|
} from "./module/documents/_module.mjs"
|
||||||
import {
|
import {
|
||||||
@@ -47,8 +48,11 @@ Hooks.once("init", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose SYSTEM constants in game.system namespace
|
// Expose SYSTEM constants + utilities globales
|
||||||
game.celestopol = { SYSTEM }
|
game.celestopol = {
|
||||||
|
SYSTEM,
|
||||||
|
rollMoonStandalone: (actor = null) => CelestopolRoll.rollMoonStandalone(actor),
|
||||||
|
}
|
||||||
|
|
||||||
// ── DataModels ──────────────────────────────────────────────────────────
|
// ── DataModels ──────────────────────────────────────────────────────────
|
||||||
CONFIG.Actor.dataModels.character = CelestopolCharacter
|
CONFIG.Actor.dataModels.character = CelestopolCharacter
|
||||||
@@ -64,8 +68,13 @@ Hooks.once("init", () => {
|
|||||||
CONFIG.Actor.documentClass = CelestopolActor
|
CONFIG.Actor.documentClass = CelestopolActor
|
||||||
CONFIG.Item.documentClass = CelestopolItem
|
CONFIG.Item.documentClass = CelestopolItem
|
||||||
CONFIG.ChatMessage.documentClass = CelestopolChatMessage
|
CONFIG.ChatMessage.documentClass = CelestopolChatMessage
|
||||||
|
CONFIG.Combat.documentClass = CelestopolCombat
|
||||||
CONFIG.Dice.rolls.push(CelestopolRoll)
|
CONFIG.Dice.rolls.push(CelestopolRoll)
|
||||||
|
|
||||||
|
// ── Initiative déterministe (pas de dé) ─────────────────────────────────
|
||||||
|
// Formule de secours si Combat.rollInitiative est appelé sans passer par notre override
|
||||||
|
CONFIG.Combat.initiative = { formula: "@initiative", decimals: 0 }
|
||||||
|
|
||||||
// ── Token display defaults ───────────────────────────────────────────────
|
// ── Token display defaults ───────────────────────────────────────────────
|
||||||
CONFIG.Actor.trackableAttributes = {
|
CONFIG.Actor.trackableAttributes = {
|
||||||
character: {
|
character: {
|
||||||
@@ -131,8 +140,6 @@ Hooks.once("init", () => {
|
|||||||
|
|
||||||
/* ─── Ready hook ─────────────────────────────────────────────────────────── */
|
/* ─── Ready hook ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
/* ─── Ready hook ─────────────────────────────────────────────────────────── */
|
|
||||||
|
|
||||||
Hooks.once("ready", () => {
|
Hooks.once("ready", () => {
|
||||||
console.log(`${SYSTEM_ID} | System ready`)
|
console.log(`${SYSTEM_ID} | System ready`)
|
||||||
|
|
||||||
@@ -279,6 +286,14 @@ function _registerHandlebarsHelpers() {
|
|||||||
// Helper : add two numbers
|
// Helper : add two numbers
|
||||||
Handlebars.registerHelper("add", (a, b) => a + b)
|
Handlebars.registerHelper("add", (a, b) => a + b)
|
||||||
|
|
||||||
|
// Helper : vrai si le dot lvl correspond au seuil de résistance de la spécialisation
|
||||||
|
Handlebars.registerHelper("isResThreshold", (skillId, lvl) => {
|
||||||
|
for (const group of Object.values(SYSTEM.SKILLS)) {
|
||||||
|
if (group[skillId]) return group[skillId].resThreshold === lvl
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
Handlebars.registerHelper("let", function(value, options) {
|
Handlebars.registerHelper("let", function(value, options) {
|
||||||
return options.fn({ value })
|
return options.fn({ value })
|
||||||
})
|
})
|
||||||
@@ -318,6 +333,8 @@ function _preloadTemplates() {
|
|||||||
`${base}/npc-main.hbs`,
|
`${base}/npc-main.hbs`,
|
||||||
`${base}/npc-competences.hbs`,
|
`${base}/npc-competences.hbs`,
|
||||||
`${base}/npc-blessures.hbs`,
|
`${base}/npc-blessures.hbs`,
|
||||||
|
`${base}/npc-equipement.hbs`,
|
||||||
|
`${base}/npc-biographie.hbs`,
|
||||||
`${base}/anomaly.hbs`,
|
`${base}/anomaly.hbs`,
|
||||||
`${base}/aspect.hbs`,
|
`${base}/aspect.hbs`,
|
||||||
`${base}/equipment.hbs`,
|
`${base}/equipment.hbs`,
|
||||||
@@ -325,6 +342,7 @@ function _preloadTemplates() {
|
|||||||
`${base}/armure.hbs`,
|
`${base}/armure.hbs`,
|
||||||
`${base}/roll-dialog.hbs`,
|
`${base}/roll-dialog.hbs`,
|
||||||
`${base}/chat-message.hbs`,
|
`${base}/chat-message.hbs`,
|
||||||
|
`${base}/moon-standalone.hbs`,
|
||||||
`${base}/partials/item-scores.hbs`,
|
`${base}/partials/item-scores.hbs`,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
194
lang/fr.json
194
lang/fr.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
"Actor": {
|
"Actor": {
|
||||||
"character": "Personnage",
|
"character": "Protagoniste",
|
||||||
"npc": "PNJ"
|
"npc": "PNJ"
|
||||||
},
|
},
|
||||||
"Item": {
|
"Item": {
|
||||||
@@ -18,12 +18,14 @@
|
|||||||
"concept": "Concept / Profession",
|
"concept": "Concept / Profession",
|
||||||
"initiative": "Initiative",
|
"initiative": "Initiative",
|
||||||
"anomaly": "Anomalie",
|
"anomaly": "Anomalie",
|
||||||
"description": "Biographie",
|
"descriptionPhysique": "Description physique",
|
||||||
|
"descriptionPsychologique": "Description psychologique",
|
||||||
"notes": "Notes",
|
"notes": "Notes",
|
||||||
"metier": "Métier",
|
"metier": "Métier",
|
||||||
"origine": "Origine",
|
"origine": "Origine",
|
||||||
"age": "Âge",
|
"age": "Âge",
|
||||||
"faction": "Faction"
|
"faction": "Faction",
|
||||||
|
"description": "Description"
|
||||||
},
|
},
|
||||||
"Stat": {
|
"Stat": {
|
||||||
"res": "Résistance",
|
"res": "Résistance",
|
||||||
@@ -58,7 +60,7 @@
|
|||||||
"resetUses": "Réinitialiser les utilisations (nouveau scénario)",
|
"resetUses": "Réinitialiser les utilisations (nouveau scénario)",
|
||||||
"noAnomaly": "Aucune anomalie",
|
"noAnomaly": "Aucune anomalie",
|
||||||
"noUsesLeft": "Plus d'utilisations disponibles pour ce scénario",
|
"noUsesLeft": "Plus d'utilisations disponibles pour ce scénario",
|
||||||
"maxAnomaly": "Un personnage ne peut avoir qu'une seule anomalie",
|
"maxAnomaly": "Un protagoniste ne peut avoir qu'une seule anomalie",
|
||||||
"applicableSkills": "Domaines applicables",
|
"applicableSkills": "Domaines applicables",
|
||||||
"moonDie": "Dé de lune",
|
"moonDie": "Dé de lune",
|
||||||
"none": "Aucune",
|
"none": "Aucune",
|
||||||
@@ -79,16 +81,16 @@
|
|||||||
},
|
},
|
||||||
"Faction": {
|
"Faction": {
|
||||||
"label": "Faction",
|
"label": "Faction",
|
||||||
"score": "Score",
|
"relation": "Niveau de Relation",
|
||||||
"custom": "Faction personnalisée…",
|
"custom": "Faction personnalisée…",
|
||||||
"pinkerton": "Pinkerton",
|
"pinkerton": "Agence Pinkerton",
|
||||||
"police": "Police",
|
"police": "Police secrète du duc",
|
||||||
"okhrana": "Okhrana",
|
"okhrana": "Okhrana",
|
||||||
"lunanovatek": "LunaNovaTek",
|
"lunanovatek": "Luna NovaTek",
|
||||||
"oto": "OTO",
|
"oto": "Société théosophique OTO",
|
||||||
"syndicats": "Syndicats",
|
"syndicats": "Syndicats clandestins",
|
||||||
"vorovskoymir": "Vorovskoymir",
|
"vorovskoymir": "Vorovskoy Mir",
|
||||||
"cour": "Cour"
|
"cour": "Cour des merveilles"
|
||||||
},
|
},
|
||||||
"Track": {
|
"Track": {
|
||||||
"blessures": "Blessures",
|
"blessures": "Blessures",
|
||||||
@@ -96,52 +98,66 @@
|
|||||||
"spleen": "Spleen",
|
"spleen": "Spleen",
|
||||||
"level": "Niveau",
|
"level": "Niveau",
|
||||||
"currentMalus": "Malus actuel",
|
"currentMalus": "Malus actuel",
|
||||||
|
"blessuresTooltip": "Niveaux de blessures :\n1–2 : Anodin / Négligeable → aucun malus (1 min)\n3–4 : Dérisoire / Superficiel → −1 (10 min)\n5–6 : Léger / Modéré → −2 (30 min)\n7 : Grave → −3 (1 journée)\n8 : Dramatique → hors combat",
|
||||||
|
"spleenTooltip": "Le Spleen représente l'usure morale du protagoniste.\nLorsqu'il atteint son maximum, le protagoniste sombre dans la mélancolie et peut se retirer du scénario.",
|
||||||
"destinTooltip": "Usages du Destin :\n• Réaliser un test avec 3d8\n• Gagner l'initiative lors d'un combat\n• Trouver l'ensemble des indices\n• Éviter une blessure\n• Sortir de l'inconscience\n• Obtenir un Triomphe"
|
"destinTooltip": "Usages du Destin :\n• Réaliser un test avec 3d8\n• Gagner l'initiative lors d'un combat\n• Trouver l'ensemble des indices\n• Éviter une blessure\n• Sortir de l'inconscience\n• Obtenir un Triomphe"
|
||||||
},
|
},
|
||||||
"Wound": {
|
"Wound": {
|
||||||
"none": "Aucune blessure",
|
"none": "Aucune blessure",
|
||||||
"anodin": "Anodin",
|
"anodin": "Anodin",
|
||||||
"derisoire": "Dérisoire",
|
"derisoire": "Dérisoire",
|
||||||
"negligeable": "Négligeable",
|
"negligeable": "Négligeable",
|
||||||
"superficiel": "Superficiel",
|
"superficiel": "Superficiel",
|
||||||
"leger": "Léger",
|
"leger": "Léger",
|
||||||
"modere": "Modéré",
|
"modere": "Modéré",
|
||||||
"grave": "Grave",
|
"grave": "Grave",
|
||||||
"dramatique": "Dramatique (hors combat)",
|
"dramatique": "Dramatique (hors combat)",
|
||||||
"duration1min": "1 min",
|
"duration1min": "1 min",
|
||||||
"duration10min": "10 min",
|
"duration10min": "10 min",
|
||||||
"duration30min": "30 min",
|
"duration30min": "30 min",
|
||||||
"duration1jour": "1 journée",
|
"duration1jour": "1 journée",
|
||||||
"status": "État : "
|
"status": "État : "
|
||||||
},
|
},
|
||||||
"Combat": {
|
"Combat": {
|
||||||
"attack": "Attaquer",
|
"initiative": "Initiative",
|
||||||
"corpsPnj": "Corps du PNJ",
|
"attack": "Attaquer",
|
||||||
"tie": "ÉGALITÉ",
|
"corpsPnj": "Corps du PNJ",
|
||||||
"tieDesc": "Personne n'est blessé",
|
"tie": "ÉGALITÉ",
|
||||||
"successHit": "PNJ touché — 1 blessure",
|
"tieDesc": "Personne n'est blessé",
|
||||||
"failureHit": "Joueur touché — 1 blessure (mêlée)",
|
"successHit": "PNJ touché — 1 blessure",
|
||||||
"distanceNoWound": "Raté — pas de riposte",
|
"failureHit": "Joueur touché — 1 blessure (mêlée)",
|
||||||
"weaponDamage": "dégâts supplémentaires",
|
"distanceNoWound": "Raté — pas de riposte",
|
||||||
"playerWounded": "Blessure infligée au joueur (mêlée)",
|
"weaponDamage": "dégâts supplémentaires",
|
||||||
"rangedDefenseTitle": "Esquiver (Mobilité)",
|
"playerWounded": "Blessure infligée au joueur (mêlée)",
|
||||||
"rangedDefenseTag": "Défense à distance",
|
"rangedDefenseTitle": "Esquiver (Mobilité)",
|
||||||
"rangedDefenseSuccess": "Attaque esquivée — pas de blessure",
|
"rangedDefenseTag": "Défense à distance",
|
||||||
"rangedDefenseFailure": "Touché par le PNJ — 1 blessure",
|
"rangedDefenseSuccess": "Attaque esquivée — pas de blessure",
|
||||||
"rangedDefensePlayerWounded":"Blessure infligée par attaque à distance"
|
"rangedDefenseFailure": "Touché par le PNJ — 1 blessure",
|
||||||
|
"rangedDefensePlayerWounded": "Blessure infligée par attaque à distance",
|
||||||
|
"targetLabel": "Cible",
|
||||||
|
"targetAuto": "Saisir manuellement",
|
||||||
|
"rangedMod": "Modificateur de tir",
|
||||||
|
"rangedModNone": "Aucun modificateur",
|
||||||
|
"rangedModAim": "Visée (dépense 1 tour) +2",
|
||||||
|
"rangedModMoving": "Cible mouvante −2",
|
||||||
|
"rangedModEngaged": "Engagé au contact −4",
|
||||||
|
"rangedModLongRange": "Longue portée −4"
|
||||||
},
|
},
|
||||||
"Tab": {
|
"Tab": {
|
||||||
"main": "Principal",
|
"main": "Principal",
|
||||||
"competences": "Domaines",
|
"competences": "Domaines",
|
||||||
"blessures": "Blessures",
|
"blessures": "Jauges",
|
||||||
"factions": "Factions",
|
"factions": "Factions",
|
||||||
"equipement": "Équipement",
|
"equipement": "Équipement",
|
||||||
"biography": "Biographie",
|
"biography": "Biographie",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"technique": "Technique"
|
"technique": "Technique",
|
||||||
|
"biographie": "Biographie",
|
||||||
|
"aspects": "Aspects"
|
||||||
},
|
},
|
||||||
"Roll": {
|
"Roll": {
|
||||||
"clickToRoll": "Cliquer pour lancer",
|
"clickToRoll": "Cliquer pour lancer",
|
||||||
|
"resThresholdHint": "Cette case déclenche la Résistance de la spécialisation",
|
||||||
"difficulty": "Difficulté",
|
"difficulty": "Difficulté",
|
||||||
"modifier": "Modificateur",
|
"modifier": "Modificateur",
|
||||||
"nbDice": "Nombre de dés",
|
"nbDice": "Nombre de dés",
|
||||||
@@ -169,10 +185,15 @@
|
|||||||
"criticalFailureDesc": "Marge ≤ −5 — résultat désastreux !",
|
"criticalFailureDesc": "Marge ≤ −5 — résultat désastreux !",
|
||||||
"woundLevel": "Niveau de blessures",
|
"woundLevel": "Niveau de blessures",
|
||||||
"threshold": "Seuil",
|
"threshold": "Seuil",
|
||||||
|
"opposition": "Test en opposition",
|
||||||
|
"oppositionDesc": "Le MJ détermine le résultat",
|
||||||
|
"oppositionResolved": "Le MJ détermine si le test est réussi ou non",
|
||||||
|
"oppositionVs": "En opposition",
|
||||||
"baseDice": "2d8 de base",
|
"baseDice": "2d8 de base",
|
||||||
"formula": "Formule",
|
"formula": "Formule",
|
||||||
"rollMoonDie": "Lancer le Dé de la Lune",
|
"rollMoonDie": "Lancer le Dé de la Lune",
|
||||||
"moonDieResult": "Dé de la Lune",
|
"moonDieResult": "Dé de la Lune",
|
||||||
|
"armorMalus": "Malus armure équipée",
|
||||||
"visibility": "Visibilité",
|
"visibility": "Visibilité",
|
||||||
"visibilityPublic": "Public",
|
"visibilityPublic": "Public",
|
||||||
"visibilityGM": "MJ uniquement",
|
"visibilityGM": "MJ uniquement",
|
||||||
@@ -185,17 +206,18 @@
|
|||||||
"puiser": "Puiser dans ses ressources",
|
"puiser": "Puiser dans ses ressources",
|
||||||
"puiserDesc": "Ignore tous les malus — coche 1 case de Spleen",
|
"puiserDesc": "Ignore tous les malus — coche 1 case de Spleen",
|
||||||
"usedPuiser": "Ressources puisées — malus ignorés, +1 Spleen",
|
"usedPuiser": "Ressources puisées — malus ignorés, +1 Spleen",
|
||||||
"situationMod": "Mod. de situation",
|
"situationMod": "Modificateurs Situationnels",
|
||||||
"resistanceTest": "Test de résistance",
|
"resistanceTest": "Test de résistance",
|
||||||
"resistanceClickToRoll": "Lancer un test de résistance",
|
"resistanceClickToRoll": "Lancer un test de résistance",
|
||||||
"woundTaken": "Blessure cochée suite à l'échec"
|
"woundTaken": "Blessure cochée suite à l'échec",
|
||||||
|
"autoSuccess": "Réussite automatique"
|
||||||
},
|
},
|
||||||
"Modifier": {
|
"Modifier": {
|
||||||
"evident": "Évident — Réussite automatique",
|
"evident": "Évident — Réussite automatique",
|
||||||
"malaise": "Malaisé (0)",
|
"malaise": "Malaisé (0)",
|
||||||
"difficile": "Difficile (−2)",
|
"difficile": "Difficile (−2)",
|
||||||
"tres": "Très difficile (−4)",
|
"tres": "Très difficile (−4)",
|
||||||
"extreme": "Extrêmement difficile (−6)",
|
"extreme": "Extrêmement difficile (−6)",
|
||||||
"incroyable": "Incroyablement difficile (−8)"
|
"incroyable": "Incroyablement difficile (−8)"
|
||||||
},
|
},
|
||||||
"Moon": {
|
"Moon": {
|
||||||
@@ -215,7 +237,14 @@
|
|||||||
"contrecoup": "Contrecoup",
|
"contrecoup": "Contrecoup",
|
||||||
"contrecoupFull": "Complication négative significative, perte de 2 Destin, ou −4 au prochain jet",
|
"contrecoupFull": "Complication négative significative, perte de 2 Destin, ou −4 au prochain jet",
|
||||||
"catastrophe": "Catastrophe",
|
"catastrophe": "Catastrophe",
|
||||||
"catastropheFull": "Échec catastrophique, perte d'1 utilisation d'Anomalie, ou gain d'1 Spleen"
|
"catastropheFull": "Échec catastrophique, perte d'1 utilisation d'Anomalie, ou gain d'1 Spleen",
|
||||||
|
"standalone": "Dé de la Lune",
|
||||||
|
"standaloneTitle": "Dé de la Lune",
|
||||||
|
"bonneFortune": "🟢 Bonne Fortune",
|
||||||
|
"mauvaiseFortune": "🔴 Mauvaise Fortune",
|
||||||
|
"chanceInterpret": "Chance",
|
||||||
|
"narrativeInterpret": "Narratif",
|
||||||
|
"quantiteHint": "Valeur"
|
||||||
},
|
},
|
||||||
"Difficulty": {
|
"Difficulty": {
|
||||||
"unknown": "Aucun seuil",
|
"unknown": "Aucun seuil",
|
||||||
@@ -251,7 +280,8 @@
|
|||||||
"newArmure": "Nouvelle armure",
|
"newArmure": "Nouvelle armure",
|
||||||
"noWeapons": "Aucune arme",
|
"noWeapons": "Aucune arme",
|
||||||
"noArmures": "Aucune armure",
|
"noArmures": "Aucune armure",
|
||||||
"noEquipments": "Aucun équipement"
|
"noEquipments": "Aucun équipement",
|
||||||
|
"noAspects": "Aucun aspect"
|
||||||
},
|
},
|
||||||
"Equipment": {
|
"Equipment": {
|
||||||
"autre": "Autre",
|
"autre": "Autre",
|
||||||
@@ -263,6 +293,11 @@
|
|||||||
"Sheet": {
|
"Sheet": {
|
||||||
"editMode": "Mode édition",
|
"editMode": "Mode édition",
|
||||||
"playMode": "Mode jeu",
|
"playMode": "Mode jeu",
|
||||||
|
"character": "Fiche Protagoniste",
|
||||||
|
"npc": "Fiche PNJ",
|
||||||
|
"anomaly": "Fiche Anomalie",
|
||||||
|
"aspect": "Fiche Aspect",
|
||||||
|
"equipment": "Fiche Équipement",
|
||||||
"weapon": "Fiche Arme",
|
"weapon": "Fiche Arme",
|
||||||
"armure": "Fiche Armure"
|
"armure": "Fiche Armure"
|
||||||
},
|
},
|
||||||
@@ -301,34 +336,55 @@
|
|||||||
"protection": "Protection",
|
"protection": "Protection",
|
||||||
"protectionHint": "Réduit les blessures subies de ce montant",
|
"protectionHint": "Réduit les blessures subies de ce montant",
|
||||||
"malus": "Malus",
|
"malus": "Malus",
|
||||||
"malusHint": "Malus aux tests de Mobilité et Discrétion (ou Domaine Corps pour PNJ)"
|
"malusHint": "Malus aux tests de Mobilité et Discrétion (ou Domaine Corps pour PNJ)",
|
||||||
|
"malusLabel": "Malus armure",
|
||||||
|
"equipped": "Équipée",
|
||||||
|
"equippedHint": "Si cochée, le malus s'applique à tous les jets",
|
||||||
|
"equippedYes": "Oui — équipée",
|
||||||
|
"equippedNo": "Non — rangée",
|
||||||
|
"equip": "Équiper",
|
||||||
|
"unequip": "Retirer"
|
||||||
},
|
},
|
||||||
"Aspect": {
|
"Aspect": {
|
||||||
"valeur": "Valeur"
|
"valeur": "Valeur"
|
||||||
},
|
},
|
||||||
"XP": {
|
"XP": {
|
||||||
"title": "Expérience",
|
"title": "Expérience",
|
||||||
"actuel": "XP disponible",
|
"actuel": "XP disponible",
|
||||||
"depense": "XP dépensée",
|
"depense": "XP dépensée",
|
||||||
"depenser": "Dépenser XP",
|
"depenser": "Dépenser XP",
|
||||||
"confirmer": "Confirmer",
|
"confirmer": "Confirmer",
|
||||||
"montant": "Montant",
|
"montant": "Montant",
|
||||||
"raison": "Raison",
|
"raison": "Raison",
|
||||||
"raisonPlaceholder": "Ex : Augmentation Mobilité à 4",
|
"raisonPlaceholder": "Ex : Augmentation Mobilité à 4",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"supprimer": "Annuler cette dépense",
|
"supprimer": "Annuler cette dépense",
|
||||||
"disponible": "{n} XP disponibles",
|
"disponible": "{n} XP disponibles",
|
||||||
"insuffisant": "XP insuffisante — seulement {n} disponibles",
|
"insuffisant": "XP insuffisante — seulement {n} disponibles",
|
||||||
"montantInvalide": "Le montant doit être supérieur à 0",
|
"montantInvalide": "Le montant doit être supérieur à 0",
|
||||||
"refTitle": "Tableau des coûts",
|
"refTitle": "Tableau des coûts",
|
||||||
"refAmelioration": "Amélioration",
|
"refAmelioration": "Amélioration",
|
||||||
"refCout": "Coût (XP)",
|
"refCout": "Coût (XP)",
|
||||||
"refAugmenterSpec": "Augmenter une Spécialisation",
|
"refAugmenterSpec": "Augmenter une Spécialisation",
|
||||||
"refCoutNiveau": "= niveau à atteindre",
|
"refCoutNiveau": "= niveau à atteindre",
|
||||||
"refAcquerirAspect": "Acquérir un nouvel Aspect",
|
"refAcquerirAspect": "Acquérir un nouvel Aspect",
|
||||||
"refAugmenterAspect":"Augmenter / Diminuer un Aspect",
|
"refAugmenterAspect": "Augmenter / Diminuer un Aspect",
|
||||||
"refAcquerirAttribut":"Acquérir ou augmenter un Attribut",
|
"refAcquerirAttribut": "Acquérir ou augmenter un Attribut",
|
||||||
"refCoutAttributTotal":"= total des points × 10"
|
"refCoutAttributTotal": "= total des points × 10"
|
||||||
|
},
|
||||||
|
"NPC": {
|
||||||
|
"typeStandard": "PNJ Standard",
|
||||||
|
"typeAntagoniste": "Antagoniste",
|
||||||
|
"type": "Type de PNJ",
|
||||||
|
"emprise": "Emprise",
|
||||||
|
"peril": "Péril",
|
||||||
|
"menace": "Menace",
|
||||||
|
"danger": "Danger",
|
||||||
|
"histoire": "Histoire",
|
||||||
|
"descriptionPhysique": "Description Physique",
|
||||||
|
"faction": "Faction",
|
||||||
|
"factionLabel": "Faction d'appartenance",
|
||||||
|
"factionNone": "Aucune faction"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
trackBox: CelestopolActorSheet.#onTrackBox,
|
trackBox: CelestopolActorSheet.#onTrackBox,
|
||||||
skillLevel: CelestopolActorSheet.#onSkillLevel,
|
skillLevel: CelestopolActorSheet.#onSkillLevel,
|
||||||
factionLevel: CelestopolActorSheet.#onFactionLevel,
|
factionLevel: CelestopolActorSheet.#onFactionLevel,
|
||||||
|
toggleArmure: CelestopolActorSheet.#onToggleArmure,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,12 +174,21 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Met à jour le score d'une faction par clic sur un point. */
|
/** Met à jour le score d'une faction par clic sur un point. */
|
||||||
|
static async #onToggleArmure(_event, target) {
|
||||||
|
const uuid = target.closest('[data-item-uuid]')?.dataset.itemUuid
|
||||||
|
if (!uuid) return
|
||||||
|
const item = await fromUuid(uuid)
|
||||||
|
if (item?.type === "armure") await item.update({ "system.equipped": !item.system.equipped })
|
||||||
|
}
|
||||||
|
|
||||||
static #onFactionLevel(_event, target) {
|
static #onFactionLevel(_event, target) {
|
||||||
if (!this.isEditable) return
|
if (!this.isEditable) return
|
||||||
const factionId = target.dataset.faction
|
const factionId = target.dataset.faction
|
||||||
const index = parseInt(target.dataset.index)
|
const index = parseInt(target.dataset.index) // 0-8
|
||||||
|
const newValue = index - 4 // -4 à +4
|
||||||
const current = this.document.system.factions[factionId]?.value ?? 0
|
const current = this.document.system.factions[factionId]?.value ?? 0
|
||||||
const newValue = (index <= current) ? index - 1 : index
|
// Cliquer sur le dot actif (sauf neutre) remet à 0
|
||||||
this.document.update({ [`system.factions.${factionId}.value`]: Math.max(0, newValue) })
|
const finalValue = (newValue === current && newValue !== 0) ? 0 : newValue
|
||||||
|
this.document.update({ [`system.factions.${factionId}.value`]: finalValue })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
resetAnomalyUses: CelestopolCharacterSheet.#onResetAnomalyUses,
|
resetAnomalyUses: CelestopolCharacterSheet.#onResetAnomalyUses,
|
||||||
depenseXp: CelestopolCharacterSheet.#onDepenseXp,
|
depenseXp: CelestopolCharacterSheet.#onDepenseXp,
|
||||||
supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog,
|
supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog,
|
||||||
|
rollMoonDie: CelestopolCharacterSheet.#onRollMoonDie,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,15 +94,46 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
|
|
||||||
case "factions":
|
case "factions":
|
||||||
context.tab = context.tabs.factions
|
context.tab = context.tabs.factions
|
||||||
|
context.factionRows = Object.entries(SYSTEM.FACTIONS).map(([id, fDef]) => {
|
||||||
|
const val = this.document.system.factions[id]?.value ?? 0
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
label: fDef.label,
|
||||||
|
value: val,
|
||||||
|
valueStr: val > 0 ? `+${val}` : `${val}`,
|
||||||
|
dots: Array.from({ length: 9 }, (_, i) => ({
|
||||||
|
index: i,
|
||||||
|
filled: i <= val + 4,
|
||||||
|
type: i < 4 ? "neg" : i === 4 ? "neutral" : "pos",
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
context.factionCustom = ["perso1", "perso2"].map(id => {
|
||||||
|
const f = this.document.system.factions[id]
|
||||||
|
const val = f?.value ?? 0
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
label: f?.label ?? "",
|
||||||
|
value: val,
|
||||||
|
valueStr: val > 0 ? `+${val}` : `${val}`,
|
||||||
|
dots: Array.from({ length: 9 }, (_, i) => ({
|
||||||
|
index: i,
|
||||||
|
filled: i <= val + 4,
|
||||||
|
type: i < 4 ? "neg" : i === 4 ? "neutral" : "pos",
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
case "biography":
|
case "biography":
|
||||||
context.tab = context.tabs.biography
|
context.tab = context.tabs.biography
|
||||||
context.xpLogEmpty = (doc.system.xp?.log?.length ?? 0) === 0
|
context.xpLogEmpty = (doc.system.xp?.log?.length ?? 0) === 0
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
context.enrichedDescriptionPhysique = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
doc.system.description, { async: true })
|
doc.system.descriptionPhysique, { relativeTo: this.document })
|
||||||
|
context.enrichedDescriptionPsychologique = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
|
doc.system.descriptionPsychologique, { relativeTo: this.document })
|
||||||
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
doc.system.notes, { async: true })
|
doc.system.notes, { relativeTo: this.document })
|
||||||
break
|
break
|
||||||
|
|
||||||
case "equipement":
|
case "equipement":
|
||||||
@@ -235,4 +267,10 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
"system.xp.log": log,
|
"system.xp.log": log,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Lance le Dé de la Lune de façon autonome depuis le header de la fiche. */
|
||||||
|
static async #onRollMoonDie() {
|
||||||
|
const { CelestopolRoll } = await import("../../documents/roll.mjs")
|
||||||
|
await CelestopolRoll.rollMoonStandalone(this.document)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,34 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
|||||||
/** @override */
|
/** @override */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ["npc"],
|
classes: ["npc"],
|
||||||
position: { width: 760, height: 600 },
|
position: { width: 780, height: 640 },
|
||||||
window: { contentClasses: ["npc-content"] },
|
window: { contentClasses: ["npc-content"] },
|
||||||
|
actions: {
|
||||||
|
createAspect: CelestopolNPCSheet.#onCreateAspect,
|
||||||
|
createWeapon: CelestopolNPCSheet.#onCreateWeapon,
|
||||||
|
createArmure: CelestopolNPCSheet.#onCreateArmure,
|
||||||
|
rollMoonDie: CelestopolNPCSheet.#onRollMoonDie,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
main: { template: "systems/fvtt-celestopol/templates/npc-main.hbs" },
|
main: { template: "systems/fvtt-celestopol/templates/npc-main.hbs" },
|
||||||
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
competences:{ template: "systems/fvtt-celestopol/templates/npc-competences.hbs" },
|
competences: { template: "systems/fvtt-celestopol/templates/npc-competences.hbs" },
|
||||||
blessures: { template: "systems/fvtt-celestopol/templates/npc-blessures.hbs" },
|
blessures: { template: "systems/fvtt-celestopol/templates/npc-blessures.hbs" },
|
||||||
|
equipement: { template: "systems/fvtt-celestopol/templates/npc-equipement.hbs" },
|
||||||
|
biographie: { template: "systems/fvtt-celestopol/templates/npc-biographie.hbs" },
|
||||||
}
|
}
|
||||||
|
|
||||||
tabGroups = { sheet: "competences" }
|
tabGroups = { sheet: "competences" }
|
||||||
|
|
||||||
#getTabs() {
|
#getTabs() {
|
||||||
const tabs = {
|
const tabs = {
|
||||||
competences:{ id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" },
|
competences: { id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" },
|
||||||
blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" },
|
blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" },
|
||||||
|
equipement: { id: "equipement", group: "sheet", icon: "fa-solid fa-shield-halved",label: "CELESTOPOL.Tab.equipement" },
|
||||||
|
biographie: { id: "biographie", group: "sheet", icon: "fa-solid fa-book-open", label: "CELESTOPOL.Tab.biographie" },
|
||||||
}
|
}
|
||||||
for (const v of Object.values(tabs)) {
|
for (const v of Object.values(tabs)) {
|
||||||
v.active = this.tabGroups[v.group] === v.id
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
@@ -33,12 +43,29 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
|||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _prepareContext() {
|
async _prepareContext() {
|
||||||
const context = await super._prepareContext()
|
const context = await super._prepareContext()
|
||||||
context.tabs = this.#getTabs()
|
context.tabs = this.#getTabs()
|
||||||
context.stats = SYSTEM.STATS
|
context.stats = SYSTEM.STATS
|
||||||
context.skills = SYSTEM.SKILLS
|
context.anomalyTypes = SYSTEM.ANOMALY_TYPES
|
||||||
context.anomalyTypes = SYSTEM.ANOMALY_TYPES
|
context.woundLevels = SYSTEM.WOUND_LEVELS
|
||||||
context.woundLevels = SYSTEM.WOUND_LEVELS
|
context.npcTypes = SYSTEM.NPC_TYPES
|
||||||
|
context.factions = SYSTEM.FACTIONS
|
||||||
|
context.antagonisteStats = SYSTEM.ANTAGONISTE_STATS
|
||||||
|
|
||||||
|
const sys = this.document.system
|
||||||
|
context.aspects = this.document.itemTypes.aspect ?? []
|
||||||
|
context.weapons = this.document.itemTypes.weapon ?? []
|
||||||
|
context.armures = this.document.itemTypes.armure ?? []
|
||||||
|
context.armorMalus = sys.armorMalus ?? 0
|
||||||
|
|
||||||
|
// Label effectif de chaque domaine selon le type de PNJ
|
||||||
|
const isAntagoniste = sys.npcType === "antagoniste"
|
||||||
|
context.domainLabels = {
|
||||||
|
ame: isAntagoniste ? "CELESTOPOL.NPC.emprise" : "CELESTOPOL.Stat.ame",
|
||||||
|
corps: isAntagoniste ? "CELESTOPOL.NPC.peril" : "CELESTOPOL.Stat.corps",
|
||||||
|
coeur: isAntagoniste ? "CELESTOPOL.NPC.menace" : "CELESTOPOL.Stat.coeur",
|
||||||
|
esprit: isAntagoniste ? "CELESTOPOL.NPC.danger" : "CELESTOPOL.Stat.esprit",
|
||||||
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,11 +75,59 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
|||||||
switch (partId) {
|
switch (partId) {
|
||||||
case "competences":
|
case "competences":
|
||||||
context.tab = context.tabs.competences
|
context.tab = context.tabs.competences
|
||||||
|
// Enrichissement des aspects
|
||||||
|
context.enrichedAspects = await Promise.all(
|
||||||
|
(context.aspects ?? []).map(async a => ({
|
||||||
|
item: a,
|
||||||
|
enrichedDesc: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
|
a.system.description, { relativeTo: this.document }
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
)
|
||||||
break
|
break
|
||||||
case "blessures":
|
case "blessures":
|
||||||
context.tab = context.tabs.blessures
|
context.tab = context.tabs.blessures
|
||||||
|
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
|
context.system.notes, { relativeTo: this.document }
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case "equipement":
|
||||||
|
context.tab = context.tabs.equipement
|
||||||
|
break
|
||||||
|
case "biographie":
|
||||||
|
context.tab = context.tabs.biographie
|
||||||
|
context.enrichedHistoire = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
|
context.system.histoire, { relativeTo: this.document }
|
||||||
|
)
|
||||||
|
context.enrichedDescriptionPhysique = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
|
context.system.descriptionPhysique, { relativeTo: this.document }
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onCreateAspect() {
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
|
name: game.i18n.localize("CELESTOPOL.Item.newAspect"), type: "aspect",
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onCreateWeapon() {
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
|
name: game.i18n.localize("TYPES.Item.weapon"), type: "weapon",
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onCreateArmure() {
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
|
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lance le Dé de la Lune de façon autonome depuis le header de la fiche PNJ. */
|
||||||
|
static async #onRollMoonDie() {
|
||||||
|
const { CelestopolRoll } = await import("../../documents/roll.mjs")
|
||||||
|
await CelestopolRoll.rollMoonStandalone(this.document)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,35 @@ export const WEAPON_RANGE_TYPES = {
|
|||||||
longue: { id: "longue", label: "CELESTOPOL.Weapon.rangeLongue" },
|
longue: { id: "longue", label: "CELESTOPOL.Weapon.rangeLongue" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Types de PNJ : standard (domaines classiques) ou antagoniste (Emprise/Péril/Menace/Danger). */
|
||||||
|
export const NPC_TYPES = {
|
||||||
|
standard: { id: "standard", label: "CELESTOPOL.NPC.typeStandard" },
|
||||||
|
antagoniste: { id: "antagoniste", label: "CELESTOPOL.NPC.typeAntagoniste" },
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Labels alternatifs des domaines pour les Antagonistes.
|
||||||
|
* Le domaine ame ↔ Emprise, corps ↔ Péril, coeur ↔ Menace, esprit ↔ Danger.
|
||||||
|
*/
|
||||||
|
export const ANTAGONISTE_STATS = {
|
||||||
|
ame: { id: "ame", label: "CELESTOPOL.NPC.emprise" },
|
||||||
|
corps: { id: "corps", label: "CELESTOPOL.NPC.peril" },
|
||||||
|
coeur: { id: "coeur", label: "CELESTOPOL.NPC.menace" },
|
||||||
|
esprit: { id: "esprit", label: "CELESTOPOL.NPC.danger" },
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modificateurs de tir à distance (LdB p.XX).
|
||||||
|
* Affiché dans le dialogue de jet uniquement pour les armes de type "distance".
|
||||||
|
*/
|
||||||
|
export const RANGED_MODIFIERS = [
|
||||||
|
{ id: "none", value: 0, label: "CELESTOPOL.Combat.rangedModNone" },
|
||||||
|
{ id: "aim", value: +2, label: "CELESTOPOL.Combat.rangedModAim" },
|
||||||
|
{ id: "moving", value: -2, label: "CELESTOPOL.Combat.rangedModMoving" },
|
||||||
|
{ id: "engaged", value: -4, label: "CELESTOPOL.Combat.rangedModEngaged" },
|
||||||
|
{ id: "longRange", value: -4, label: "CELESTOPOL.Combat.rangedModLongRange" },
|
||||||
|
]
|
||||||
|
|
||||||
export const SYSTEM = {
|
export const SYSTEM = {
|
||||||
id: SYSTEM_ID,
|
id: SYSTEM_ID,
|
||||||
ASCII,
|
ASCII,
|
||||||
@@ -180,6 +209,8 @@ export const SYSTEM = {
|
|||||||
ANOMALY_TYPES,
|
ANOMALY_TYPES,
|
||||||
ANOMALY_DEFINITIONS,
|
ANOMALY_DEFINITIONS,
|
||||||
FACTIONS,
|
FACTIONS,
|
||||||
|
NPC_TYPES,
|
||||||
|
ANTAGONISTE_STATS,
|
||||||
WOUND_LEVELS,
|
WOUND_LEVELS,
|
||||||
DIFFICULTY_CHOICES,
|
DIFFICULTY_CHOICES,
|
||||||
CONTEXT_MODIFIER_CHOICES,
|
CONTEXT_MODIFIER_CHOICES,
|
||||||
@@ -189,4 +220,5 @@ export const SYSTEM = {
|
|||||||
WEAPON_DAMAGE_TYPES,
|
WEAPON_DAMAGE_TYPES,
|
||||||
WEAPON_RANGE_TYPES,
|
WEAPON_RANGE_TYPES,
|
||||||
WEAPON_COMBAT_TYPES,
|
WEAPON_COMBAT_TYPES,
|
||||||
|
RANGED_MODIFIERS,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { default as CelestopolActor } from "./actor.mjs"
|
export { default as CelestopolActor } from "./actor.mjs"
|
||||||
export { default as CelestopolItem } from "./item.mjs"
|
export { default as CelestopolItem } from "./item.mjs"
|
||||||
export { default as CelestopolChatMessage } from "./chat-message.mjs"
|
export { default as CelestopolChatMessage } from "./chat-message.mjs"
|
||||||
|
export { default as CelestopolCombat } from "./combat.mjs"
|
||||||
export { CelestopolRoll } from "./roll.mjs"
|
export { CelestopolRoll } from "./roll.mjs"
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
export default class CelestopolActor extends Actor {
|
export default class CelestopolActor extends Actor {
|
||||||
/** @override */
|
/** @override */
|
||||||
getRollData() {
|
getRollData() {
|
||||||
return this.toObject(false).system
|
// Inclure les valeurs dérivées (initiative, résistances…) calculées par prepareDerivedData
|
||||||
|
return { ...this.toObject(false).system, initiative: this.system.initiative ?? 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override de l'initiative : valeur déterministe (pas de dé).
|
||||||
|
* Personnage : 4 + Mobilité + Inspiration
|
||||||
|
* PNJ : Corps.res
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
async rollInitiative() {
|
||||||
|
if (!game.combat) return null
|
||||||
|
const combatant = game.combat.combatants.find(c => c.actorId === this.id)
|
||||||
|
if (!combatant) return null
|
||||||
|
const initiative = this.system.initiative ?? 0
|
||||||
|
await combatant.update({ initiative })
|
||||||
|
return combatant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
module/documents/combat.mjs
Normal file
53
module/documents/combat.mjs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const SYSTEM_ID = "fvtt-celestopol"
|
||||||
|
|
||||||
|
export default class CelestopolCombat extends Combat {
|
||||||
|
/** @override — Initiative déterministe, message stylé maison */
|
||||||
|
async rollInitiative(ids, { updateTurn = true } = {}) {
|
||||||
|
ids = typeof ids === "string" ? [ids] : ids
|
||||||
|
const combatants = ids.map(id => this.combatants.get(id)).filter(Boolean)
|
||||||
|
if (!combatants.length) return this
|
||||||
|
|
||||||
|
const updates = []
|
||||||
|
for (const combatant of combatants) {
|
||||||
|
const actor = combatant.actor
|
||||||
|
if (!actor) continue
|
||||||
|
const value = actor.system.initiative ?? 0
|
||||||
|
updates.push({ _id: combatant.id, initiative: value })
|
||||||
|
await CelestopolCombat._postInitiativeMessage(combatant, actor, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.length) await this.updateEmbeddedDocuments("Combatant", updates)
|
||||||
|
if (updateTurn && this.turn !== null) await this.update({ turn: this.turn })
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
static async _postInitiativeMessage(combatant, actor, value) {
|
||||||
|
const sys = actor.system
|
||||||
|
let detail
|
||||||
|
if (actor.type === "character") {
|
||||||
|
const mob = sys.stats?.corps?.mobilite?.value ?? 0
|
||||||
|
const insp = sys.stats?.coeur?.inspiration?.value ?? 0
|
||||||
|
detail = `4 + ${mob} (${game.i18n.localize("CELESTOPOL.Skill.mobilite")}) + ${insp} (${game.i18n.localize("CELESTOPOL.Skill.inspiration")})`
|
||||||
|
} else {
|
||||||
|
const corps = sys.stats?.corps?.res ?? value
|
||||||
|
detail = `${game.i18n.localize("CELESTOPOL.Stat.corps")} : ${corps}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await renderTemplate(
|
||||||
|
`systems/${SYSTEM_ID}/templates/chat-initiative.hbs`,
|
||||||
|
{
|
||||||
|
actorName: combatant.name ?? actor.name,
|
||||||
|
actorImg: actor.img,
|
||||||
|
value,
|
||||||
|
detail,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await ChatMessage.create({
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor }),
|
||||||
|
content,
|
||||||
|
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
|
||||||
|
flags: { [SYSTEM_ID]: { type: "initiative" } },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,18 +35,21 @@ export class CelestopolRoll extends Roll {
|
|||||||
* @returns {Promise<CelestopolRoll|null>}
|
* @returns {Promise<CelestopolRoll|null>}
|
||||||
*/
|
*/
|
||||||
static async prompt(options = {}) {
|
static async prompt(options = {}) {
|
||||||
const woundMalus = options.woundMalus ?? 0
|
const woundMalus = options.woundMalus ?? 0
|
||||||
const skillValue = options.skillValue ?? 0
|
const armorMalus = options.armorMalus ?? 0
|
||||||
const woundLevelId = options.woundLevel ?? 0
|
const skillValue = options.skillValue ?? 0
|
||||||
const destGaugeFull = options.destGaugeFull ?? false
|
const woundLevelId = options.woundLevel ?? 0
|
||||||
const fortuneValue = options.fortuneValue ?? 0
|
const destGaugeFull = options.destGaugeFull ?? false
|
||||||
const isResistance = options.isResistance ?? false
|
const fortuneValue = options.fortuneValue ?? 0
|
||||||
const isCombat = options.isCombat ?? false
|
const isResistance = options.isResistance ?? false
|
||||||
|
const isCombat = options.isCombat ?? false
|
||||||
const isRangedDefense = options.isRangedDefense ?? false
|
const isRangedDefense = options.isRangedDefense ?? false
|
||||||
const weaponType = options.weaponType ?? "melee"
|
const weaponType = options.weaponType ?? "melee"
|
||||||
const weaponName = options.weaponName ?? null
|
const weaponName = options.weaponName ?? null
|
||||||
const weaponDegats = options.weaponDegats ?? "0"
|
const weaponDegats = options.weaponDegats ?? "0"
|
||||||
const woundLabel = woundLevelId > 0
|
const availableTargets = options.availableTargets ?? []
|
||||||
|
const isRangedAttack = isCombat && !isRangedDefense && weaponType === "distance"
|
||||||
|
const woundLabel = woundLevelId > 0
|
||||||
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
|
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
|
||||||
: null
|
: null
|
||||||
|
|
||||||
@@ -63,6 +66,11 @@ export class CelestopolRoll extends Roll {
|
|||||||
const v = i - 8
|
const v = i - 8
|
||||||
return { value: v, label: v > 0 ? `+${v}` : `${v}` }
|
return { value: v, label: v > 0 ? `+${v}` : `${v}` }
|
||||||
})
|
})
|
||||||
|
const rangedModChoices = SYSTEM.RANGED_MODIFIERS.map(m => ({
|
||||||
|
id: m.id,
|
||||||
|
value: m.value,
|
||||||
|
label: game.i18n.localize(m.label),
|
||||||
|
}))
|
||||||
|
|
||||||
const dialogContext = {
|
const dialogContext = {
|
||||||
actorName: options.actorName,
|
actorName: options.actorName,
|
||||||
@@ -71,20 +79,22 @@ export class CelestopolRoll extends Roll {
|
|||||||
skillValue,
|
skillValue,
|
||||||
woundMalus,
|
woundMalus,
|
||||||
woundLabel,
|
woundLabel,
|
||||||
difficultyChoices: SYSTEM.DIFFICULTY_CHOICES,
|
|
||||||
defaultDifficulty: options.difficulty ?? "normal",
|
|
||||||
destGaugeFull,
|
|
||||||
defaultRollMoonDie: options.rollMoonDie ?? false,
|
|
||||||
isResistance,
|
|
||||||
isCombat,
|
isCombat,
|
||||||
isRangedDefense,
|
isRangedDefense,
|
||||||
|
isRangedAttack,
|
||||||
weaponType,
|
weaponType,
|
||||||
weaponName,
|
weaponName,
|
||||||
weaponDegats,
|
weaponDegats,
|
||||||
modifierChoices,
|
modifierChoices,
|
||||||
aspectChoices,
|
aspectChoices,
|
||||||
situationChoices,
|
situationChoices,
|
||||||
|
rangedModChoices,
|
||||||
|
availableTargets,
|
||||||
fortuneValue,
|
fortuneValue,
|
||||||
|
armorMalus,
|
||||||
|
destGaugeFull,
|
||||||
|
defaultRollMoonDie: options.rollMoonDie ?? false,
|
||||||
|
isResistance,
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
@@ -104,8 +114,40 @@ export class CelestopolRoll extends Roll {
|
|||||||
const wrap = dialog.element.querySelector('.roll-dialog-content')
|
const wrap = dialog.element.querySelector('.roll-dialog-content')
|
||||||
if (!wrap) return
|
if (!wrap) return
|
||||||
|
|
||||||
function hasMalus(mod, asp, sit) {
|
// Sélection de cible PNJ : masque le champ Corps PNJ (valeur cachée)
|
||||||
return woundMalus < 0 || mod < 0 || asp < 0 || sit < 0
|
const targetSelect = wrap.querySelector('#targetSelect')
|
||||||
|
const corpsPnjRow = wrap.querySelector('#corps-pnj-row')
|
||||||
|
const targetConfirmedRow = wrap.querySelector('#target-confirmed-row')
|
||||||
|
const targetConfirmedName = wrap.querySelector('#target-confirmed-name')
|
||||||
|
|
||||||
|
function applyTargetSelection() {
|
||||||
|
if (!targetSelect) return
|
||||||
|
const selectedOption = targetSelect.options[targetSelect.selectedIndex]
|
||||||
|
const val = parseFloat(targetSelect.value)
|
||||||
|
const corpsPnjInput = wrap.querySelector('#corpsPnj')
|
||||||
|
if (targetSelect.value && !isNaN(val)) {
|
||||||
|
// Cible sélectionnée : masquer la valeur, afficher le nom
|
||||||
|
if (corpsPnjRow) corpsPnjRow.style.display = 'none'
|
||||||
|
if (targetConfirmedRow) targetConfirmedRow.style.display = ''
|
||||||
|
if (targetConfirmedName) targetConfirmedName.textContent = selectedOption?.text ?? ''
|
||||||
|
if (corpsPnjInput) {
|
||||||
|
corpsPnjInput.value = val
|
||||||
|
corpsPnjInput.dispatchEvent(new Event('input'))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Saisie manuelle
|
||||||
|
if (corpsPnjRow) corpsPnjRow.style.display = ''
|
||||||
|
if (targetConfirmedRow) targetConfirmedRow.style.display = 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetSelect) {
|
||||||
|
targetSelect.addEventListener('change', applyTargetSelection)
|
||||||
|
applyTargetSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMalus(mod, asp, sit, ranged) {
|
||||||
|
return woundMalus < 0 || armorMalus < 0 || mod < 0 || asp < 0 || sit < 0 || ranged < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -114,6 +156,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
const modifier = autoSucc ? 0 : (parseInt(rawMod ?? 0) || 0)
|
const modifier = autoSucc ? 0 : (parseInt(rawMod ?? 0) || 0)
|
||||||
const aspectMod = parseInt(wrap.querySelector('#aspectModifier')?.value ?? 0) || 0
|
const aspectMod = parseInt(wrap.querySelector('#aspectModifier')?.value ?? 0) || 0
|
||||||
const situMod = parseInt(wrap.querySelector('#situationMod')?.value ?? 0) || 0
|
const situMod = parseInt(wrap.querySelector('#situationMod')?.value ?? 0) || 0
|
||||||
|
const rangedMod = parseInt(wrap.querySelector('#rangedMod')?.value ?? 0) || 0
|
||||||
const useDestin = wrap.querySelector('#useDestin')?.checked
|
const useDestin = wrap.querySelector('#useDestin')?.checked
|
||||||
const useFort = wrap.querySelector('#useFortune')?.checked
|
const useFort = wrap.querySelector('#useFortune')?.checked
|
||||||
const puiser = wrap.querySelector('#puiserRessources')?.checked
|
const puiser = wrap.querySelector('#puiserRessources')?.checked
|
||||||
@@ -122,7 +165,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
// En résistance : pas de "Puiser" possible
|
// En résistance : pas de "Puiser" possible
|
||||||
const puiserRow = wrap.querySelector('#puiser-row')
|
const puiserRow = wrap.querySelector('#puiser-row')
|
||||||
if (puiserRow) {
|
if (puiserRow) {
|
||||||
if (!isResistance && hasMalus(modifier, aspectMod, situMod)) {
|
if (!isResistance && hasMalus(modifier, aspectMod, situMod, rangedMod)) {
|
||||||
puiserRow.style.display = ''
|
puiserRow.style.display = ''
|
||||||
} else {
|
} else {
|
||||||
puiserRow.style.display = 'none'
|
puiserRow.style.display = 'none'
|
||||||
@@ -135,7 +178,9 @@ export class CelestopolRoll extends Roll {
|
|||||||
const effMod = puiser ? Math.max(0, modifier) : modifier
|
const effMod = puiser ? Math.max(0, modifier) : modifier
|
||||||
const effAspect = puiser ? Math.max(0, aspectMod) : aspectMod
|
const effAspect = puiser ? Math.max(0, aspectMod) : aspectMod
|
||||||
const effSit = puiser ? Math.max(0, situMod) : situMod
|
const effSit = puiser ? Math.max(0, situMod) : situMod
|
||||||
const totalMod = skillValue + effWound + effMod + effAspect + effSit
|
const effArmor = puiser ? 0 : armorMalus
|
||||||
|
const effRanged = puiser ? Math.max(0, rangedMod) : rangedMod
|
||||||
|
const totalMod = skillValue + effWound + effMod + effAspect + effSit + effArmor + effRanged
|
||||||
|
|
||||||
let formula
|
let formula
|
||||||
if (autoSucc) {
|
if (autoSucc) {
|
||||||
@@ -153,7 +198,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
if (previewEl) previewEl.textContent = formula
|
if (previewEl) previewEl.textContent = formula
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap.querySelectorAll('#modifier, #aspectModifier, #situationMod, #useDestin, #useFortune, #puiserRessources, #corpsPnj')
|
wrap.querySelectorAll('#modifier, #aspectModifier, #situationMod, #rangedMod, #useDestin, #useFortune, #puiserRessources, #corpsPnj')
|
||||||
.forEach(el => {
|
.forEach(el => {
|
||||||
el.addEventListener('change', update)
|
el.addEventListener('change', update)
|
||||||
el.addEventListener('input', update)
|
el.addEventListener('input', update)
|
||||||
@@ -179,16 +224,18 @@ export class CelestopolRoll extends Roll {
|
|||||||
|
|
||||||
if (!rollContext) return null
|
if (!rollContext) return null
|
||||||
|
|
||||||
// En combat : Corps PNJ = seuil direct (pas le sélect difficulté)
|
// En combat : Corps PNJ = seuil direct ; sinon seuil fixe = 11
|
||||||
const corpsPnj = isCombat ? (parseInt(rollContext.corpsPnj ?? 7) || 7) : null
|
const corpsPnj = isCombat ? (parseInt(rollContext.corpsPnj ?? 7) || 7) : null
|
||||||
const difficulty = isCombat ? "combat" : (rollContext.difficulty ?? "normal")
|
const difficulty = isCombat ? "combat" : "standard"
|
||||||
const diffConfig = isCombat
|
const diffConfig = isCombat
|
||||||
? { value: corpsPnj, label: "CELESTOPOL.Combat.corpsPnj" }
|
? { value: corpsPnj, label: "CELESTOPOL.Combat.corpsPnj" }
|
||||||
: (SYSTEM.DIFFICULTY_CHOICES[difficulty] ?? SYSTEM.DIFFICULTY_CHOICES.normal)
|
: { value: 11, label: "CELESTOPOL.Roll.threshold" }
|
||||||
const autoSuccess = rollContext.modifier === "auto"
|
const autoSuccess = rollContext.modifier === "auto"
|
||||||
const modifier = autoSuccess ? 0 : (parseInt(rollContext.modifier ?? 0) || 0)
|
const modifier = autoSuccess ? 0 : (parseInt(rollContext.modifier ?? 0) || 0)
|
||||||
const aspectMod = parseInt(rollContext.aspectModifier ?? 0) || 0
|
const aspectMod = parseInt(rollContext.aspectModifier ?? 0) || 0
|
||||||
const situationMod = parseInt(rollContext.situationMod ?? 0) || 0
|
const situationMod = parseInt(rollContext.situationMod ?? 0) || 0
|
||||||
|
const rangedMod = isRangedAttack ? (parseInt(rollContext.rangedMod ?? 0) || 0) : 0
|
||||||
|
const isOpposition = !isCombat && !isResistance && (rollContext.isOpposition === true || rollContext.isOpposition === "true")
|
||||||
const useDestin = destGaugeFull && (rollContext.useDestin === true || rollContext.useDestin === "true")
|
const useDestin = destGaugeFull && (rollContext.useDestin === true || rollContext.useDestin === "true")
|
||||||
const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true")
|
const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true")
|
||||||
const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
|
const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
|
||||||
@@ -200,13 +247,15 @@ export class CelestopolRoll extends Roll {
|
|||||||
|
|
||||||
// Puiser dans ses ressources → ignorer tous les malus
|
// Puiser dans ses ressources → ignorer tous les malus
|
||||||
const effectiveWoundMalus = effectivePuiser ? 0 : woundMalus
|
const effectiveWoundMalus = effectivePuiser ? 0 : woundMalus
|
||||||
|
const effectiveArmorMalus = effectivePuiser ? 0 : armorMalus
|
||||||
const effectiveModifier = effectivePuiser ? Math.max(0, modifier) : modifier
|
const effectiveModifier = effectivePuiser ? Math.max(0, modifier) : modifier
|
||||||
const effectiveAspectMod = effectivePuiser ? Math.max(0, aspectMod) : aspectMod
|
const effectiveAspectMod = effectivePuiser ? Math.max(0, aspectMod) : aspectMod
|
||||||
const effectiveSituationMod = effectivePuiser ? Math.max(0, situationMod) : situationMod
|
const effectiveSituationMod = effectivePuiser ? Math.max(0, situationMod) : situationMod
|
||||||
|
const effectiveRangedMod = effectivePuiser ? Math.max(0, rangedMod) : rangedMod
|
||||||
|
|
||||||
// Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8
|
// Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8
|
||||||
const nbDice = (!isResistance && useDestin) ? 3 : 2
|
const nbDice = (!isResistance && useDestin) ? 3 : 2
|
||||||
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier + effectiveSituationMod
|
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier + effectiveSituationMod + effectiveArmorMalus + effectiveRangedMod
|
||||||
const formula = (!isResistance && useFortune)
|
const formula = (!isResistance && useFortune)
|
||||||
? buildFormula(1, totalModifier + 8)
|
? buildFormula(1, totalModifier + 8)
|
||||||
: buildFormula(nbDice, totalModifier)
|
: buildFormula(nbDice, totalModifier)
|
||||||
@@ -232,11 +281,13 @@ export class CelestopolRoll extends Roll {
|
|||||||
woundMalus: effectiveWoundMalus,
|
woundMalus: effectiveWoundMalus,
|
||||||
autoSuccess,
|
autoSuccess,
|
||||||
isResistance,
|
isResistance,
|
||||||
|
isOpposition,
|
||||||
isCombat,
|
isCombat,
|
||||||
isRangedDefense,
|
isRangedDefense,
|
||||||
weaponType,
|
weaponType,
|
||||||
weaponName,
|
weaponName,
|
||||||
weaponDegats,
|
weaponDegats,
|
||||||
|
rangedMod: effectiveRangedMod,
|
||||||
useDestin: !isResistance && useDestin,
|
useDestin: !isResistance && useDestin,
|
||||||
useFortune: !isResistance && useFortune,
|
useFortune: !isResistance && useFortune,
|
||||||
puiserRessources: effectivePuiser,
|
puiserRessources: effectivePuiser,
|
||||||
@@ -294,9 +345,11 @@ export class CelestopolRoll extends Roll {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mémoriser les préférences
|
// Mémoriser les préférences (protagonistes uniquement — le modèle NPC n'a pas de champ prefs)
|
||||||
updateData["system.prefs.rollMoonDie"] = rollData.rollMoonDie
|
if (actor.type === "character") {
|
||||||
updateData["system.prefs.difficulty"] = difficulty
|
updateData["system.prefs.rollMoonDie"] = rollData.rollMoonDie
|
||||||
|
updateData["system.prefs.difficulty"] = difficulty
|
||||||
|
}
|
||||||
|
|
||||||
await actor.update(updateData)
|
await actor.update(updateData)
|
||||||
}
|
}
|
||||||
@@ -306,11 +359,8 @@ export class CelestopolRoll extends Roll {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Détermine succès/échec selon la marge (total − seuil).
|
* Détermine succès/échec selon la marge (total − seuil).
|
||||||
* - Marge ≥ 5 → succès critique
|
* Seuil : 11 pour les tests normaux, Corps PNJ pour le combat.
|
||||||
* - Marge > 0 → succès
|
* Pas de succès/échec critique — seul le Dé de la Lune produit des résultats exceptionnels.
|
||||||
* - Marge = 0 → succès (ou égalité en combat)
|
|
||||||
* - Marge ≤ −5 → échec critique
|
|
||||||
* - Marge < 0 → échec
|
|
||||||
*/
|
*/
|
||||||
computeResult() {
|
computeResult() {
|
||||||
if (this.options.autoSuccess) {
|
if (this.options.autoSuccess) {
|
||||||
@@ -318,9 +368,15 @@ export class CelestopolRoll extends Roll {
|
|||||||
this.options.margin = null
|
this.options.margin = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// En test d'opposition : pas de résultat calculé — le MJ décide
|
||||||
|
if (this.options.isOpposition) {
|
||||||
|
this.options.resultType = "opposition"
|
||||||
|
this.options.margin = null
|
||||||
|
return
|
||||||
|
}
|
||||||
const threshold = this.options.isCombat
|
const threshold = this.options.isCombat
|
||||||
? (this.options.difficultyValue ?? 0)
|
? (this.options.difficultyValue ?? 0)
|
||||||
: (SYSTEM.DIFFICULTY_CHOICES[this.options.difficulty]?.value ?? 0)
|
: 11
|
||||||
if (threshold === 0) {
|
if (threshold === 0) {
|
||||||
this.options.resultType = "unknown"
|
this.options.resultType = "unknown"
|
||||||
this.options.margin = null
|
this.options.margin = null
|
||||||
@@ -330,10 +386,11 @@ export class CelestopolRoll extends Roll {
|
|||||||
this.options.margin = margin
|
this.options.margin = margin
|
||||||
if (this.options.isCombat && margin === 0) {
|
if (this.options.isCombat && margin === 0) {
|
||||||
this.options.resultType = "tie"
|
this.options.resultType = "tie"
|
||||||
} else if (margin >= 5) this.options.resultType = "critical-success"
|
} else if (margin >= 0) {
|
||||||
else if (margin >= 0) this.options.resultType = "success"
|
this.options.resultType = "success"
|
||||||
else if (margin <= -5) this.options.resultType = "critical-failure"
|
} else {
|
||||||
else this.options.resultType = "failure"
|
this.options.resultType = "failure"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -350,7 +407,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
const diceSum = diceResults.reduce((a, b) => a + b, 0)
|
const diceSum = diceResults.reduce((a, b) => a + b, 0)
|
||||||
const threshold = this.options.isCombat
|
const threshold = this.options.isCombat
|
||||||
? (this.options.difficultyValue ?? 0)
|
? (this.options.difficultyValue ?? 0)
|
||||||
: (SYSTEM.DIFFICULTY_CHOICES[this.options.difficulty]?.value ?? 0)
|
: 11
|
||||||
const margin = this.options.margin
|
const margin = this.options.margin
|
||||||
const woundMalus = this.options.woundMalus ?? 0
|
const woundMalus = this.options.woundMalus ?? 0
|
||||||
const skillValue = this.options.skillValue ?? 0
|
const skillValue = this.options.skillValue ?? 0
|
||||||
@@ -365,18 +422,21 @@ export class CelestopolRoll extends Roll {
|
|||||||
const moonResultType = this.options.moonResultType ?? null
|
const moonResultType = this.options.moonResultType ?? null
|
||||||
|
|
||||||
const resultClassMap = {
|
const resultClassMap = {
|
||||||
"critical-success": "critical-success",
|
"success": "success",
|
||||||
"success": "success",
|
"tie": "tie",
|
||||||
"tie": "tie",
|
"failure": "failure",
|
||||||
"failure": "failure",
|
"opposition": "opposition",
|
||||||
"critical-failure": "critical-failure",
|
"unknown": "",
|
||||||
"unknown": "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Libellé de difficulté : en combat, afficher "Corps PNJ : N"
|
const isOpposition = this.options.isOpposition ?? false
|
||||||
|
|
||||||
|
// Libellé de difficulté : en combat "Corps PNJ : N", en opposition "vs ?", sinon "Seuil : 11"
|
||||||
const difficultyLabel = this.options.isCombat
|
const difficultyLabel = this.options.isCombat
|
||||||
? `${game.i18n.localize("CELESTOPOL.Combat.corpsPnj")} : ${threshold}`
|
? `${game.i18n.localize("CELESTOPOL.Combat.corpsPnj")} : ${threshold}`
|
||||||
: game.i18n.localize(SYSTEM.DIFFICULTY_CHOICES[this.options.difficulty]?.label ?? "")
|
: isOpposition
|
||||||
|
? `${game.i18n.localize("CELESTOPOL.Roll.oppositionVs")}`
|
||||||
|
: `${game.i18n.localize("CELESTOPOL.Roll.threshold")} : 11`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cssClass: [SYSTEM.id, "dice-roll"].join(" "),
|
cssClass: [SYSTEM.id, "dice-roll"].join(" "),
|
||||||
@@ -394,11 +454,10 @@ export class CelestopolRoll extends Roll {
|
|||||||
isSuccess: this.isSuccess,
|
isSuccess: this.isSuccess,
|
||||||
isFailure: this.isFailure,
|
isFailure: this.isFailure,
|
||||||
isTie: this.isTie,
|
isTie: this.isTie,
|
||||||
isCriticalSuccess: this.isCriticalSuccess,
|
isOpposition,
|
||||||
isCriticalFailure: this.isCriticalFailure,
|
|
||||||
difficulty: this.options.difficulty,
|
difficulty: this.options.difficulty,
|
||||||
difficultyLabel,
|
difficultyLabel,
|
||||||
difficultyValue: threshold,
|
difficultyValue: isOpposition ? null : threshold,
|
||||||
margin,
|
margin,
|
||||||
marginAbs: margin !== null ? Math.abs(margin) : null,
|
marginAbs: margin !== null ? Math.abs(margin) : null,
|
||||||
marginAbove: margin !== null && margin >= 0,
|
marginAbove: margin !== null && margin >= 0,
|
||||||
@@ -419,6 +478,8 @@ export class CelestopolRoll extends Roll {
|
|||||||
weaponType: this.options.weaponType ?? null,
|
weaponType: this.options.weaponType ?? null,
|
||||||
isRangedDefense: this.options.isRangedDefense ?? false,
|
isRangedDefense: this.options.isRangedDefense ?? false,
|
||||||
woundTaken: this.options.woundTaken ?? null,
|
woundTaken: this.options.woundTaken ?? null,
|
||||||
|
situationMod: this.options.situationMod ?? 0,
|
||||||
|
rangedMod: this.options.rangedMod ?? 0,
|
||||||
// Dé de lune
|
// Dé de lune
|
||||||
hasMoonDie: moonDieResult !== null,
|
hasMoonDie: moonDieResult !== null,
|
||||||
moonDieResult,
|
moonDieResult,
|
||||||
@@ -442,4 +503,44 @@ export class CelestopolRoll extends Roll {
|
|||||||
: `<strong>${skillLocalized}</strong>`
|
: `<strong>${skillLocalized}</strong>`
|
||||||
return super.toMessage({ flavor, ...messageData }, { rollMode })
|
return super.toMessage({ flavor, ...messageData }, { rollMode })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lance le dé de la Lune de façon autonome (hors test de spécialisation).
|
||||||
|
* Affiche un carte de chat avec le résultat narratif ET l'interprétation chance.
|
||||||
|
* @param {Actor|null} actor Acteur initiateur (pour le speaker du message)
|
||||||
|
*/
|
||||||
|
static async rollMoonStandalone(actor = null) {
|
||||||
|
const roll = await new Roll("1d8").evaluate()
|
||||||
|
const result = roll.total
|
||||||
|
const face = SYSTEM.MOON_DIE_FACES[result] ?? null
|
||||||
|
const resultType = face ? SYSTEM.MOON_RESULT_TYPES[face.result] ?? null : null
|
||||||
|
const isGoodFortune = result <= 4
|
||||||
|
|
||||||
|
const templateData = {
|
||||||
|
result,
|
||||||
|
moonFaceSymbol: face?.symbol ?? "",
|
||||||
|
moonFaceLabel: face ? game.i18n.localize(face.label) : "",
|
||||||
|
moonResultLabel: resultType ? game.i18n.localize(resultType.label) : "",
|
||||||
|
moonResultDesc: resultType ? game.i18n.localize(resultType.desc) : "",
|
||||||
|
moonResultClass: resultType?.cssClass ?? "",
|
||||||
|
isGoodFortune,
|
||||||
|
actorName: actor?.name ?? null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-celestopol/templates/moon-standalone.hbs",
|
||||||
|
templateData
|
||||||
|
)
|
||||||
|
|
||||||
|
const speaker = actor
|
||||||
|
? ChatMessage.getSpeaker({ actor })
|
||||||
|
: { alias: game.i18n.localize("CELESTOPOL.Moon.standalone") }
|
||||||
|
|
||||||
|
await ChatMessage.create({
|
||||||
|
content,
|
||||||
|
speaker,
|
||||||
|
rolls: [roll],
|
||||||
|
style: CONST.CHAT_MESSAGE_STYLES?.ROLL ?? 5,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
vision: persoAttrField(),
|
vision: persoAttrField(),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Factions — score entier direct (0-9)
|
// Factions — niveau de relation -4 (hostile) à +4 (allié), 0 = neutre
|
||||||
const factionField = () => new fields.SchemaField({
|
const factionField = () => new fields.SchemaField({
|
||||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 9 }),
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: -4, max: 4 }),
|
||||||
})
|
})
|
||||||
schema.factions = new fields.SchemaField({
|
schema.factions = new fields.SchemaField({
|
||||||
pinkerton: factionField(),
|
pinkerton: factionField(),
|
||||||
@@ -89,11 +89,11 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
cour: factionField(),
|
cour: factionField(),
|
||||||
perso1: new fields.SchemaField({
|
perso1: new fields.SchemaField({
|
||||||
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
value: new fields.NumberField({ ...reqInt, initial: 0 }),
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: -4, max: 4 }),
|
||||||
}),
|
}),
|
||||||
perso2: new fields.SchemaField({
|
perso2: new fields.SchemaField({
|
||||||
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
value: new fields.NumberField({ ...reqInt, initial: 0 }),
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: -4, max: 4 }),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -114,8 +114,9 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Description & notes
|
// Description & notes
|
||||||
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
schema.descriptionPhysique = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
schema.descriptionPsychologique = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
|
||||||
// Données biographiques
|
// Données biographiques
|
||||||
schema.biodata = new fields.SchemaField({
|
schema.biodata = new fields.SchemaField({
|
||||||
@@ -152,6 +153,20 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
|
|
||||||
// XP dépensée = somme des montants du log
|
// XP dépensée = somme des montants du log
|
||||||
this.xp.depense = this.xp.log.reduce((sum, entry) => sum + entry.montant, 0)
|
this.xp.depense = this.xp.log.reduce((sum, entry) => sum + entry.montant, 0)
|
||||||
|
|
||||||
|
// Malus d'armure(s) équipée(s)
|
||||||
|
this.armorMalus = this.getArmorMalus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le malus total des armures équipées portées par le protagoniste.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getArmorMalus() {
|
||||||
|
if (!this.parent) return 0
|
||||||
|
return -(this.parent.itemTypes.armure
|
||||||
|
.filter(a => a.system.equipped && a.system.malus > 0)
|
||||||
|
.reduce((sum, a) => sum + a.system.malus, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,6 +198,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
skillLabel: skill.label,
|
skillLabel: skill.label,
|
||||||
skillValue: skill.value,
|
skillValue: skill.value,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
|
armorMalus: this.getArmorMalus(),
|
||||||
woundLevel: this.blessures.lvl,
|
woundLevel: this.blessures.lvl,
|
||||||
difficulty: this.prefs.difficulty,
|
difficulty: this.prefs.difficulty,
|
||||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||||
@@ -213,6 +229,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
skillLabel: "CELESTOPOL.Roll.resistanceTest",
|
skillLabel: "CELESTOPOL.Roll.resistanceTest",
|
||||||
skillValue: statData.res,
|
skillValue: statData.res,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
|
armorMalus: this.getArmorMalus(),
|
||||||
woundLevel: this.blessures.lvl,
|
woundLevel: this.blessures.lvl,
|
||||||
isResistance: true,
|
isResistance: true,
|
||||||
rollMoonDie: false,
|
rollMoonDie: false,
|
||||||
@@ -222,6 +239,38 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collecte les tokens PNJs disponibles comme cibles de combat.
|
||||||
|
* Priorise le combat tracker, sinon les tokens ciblés par l'utilisateur.
|
||||||
|
* @returns {Array<{id:string, name:string, corps:number}>}
|
||||||
|
*/
|
||||||
|
_getCombatTargets() {
|
||||||
|
const toEntry = actor => ({
|
||||||
|
id: actor.id,
|
||||||
|
name: actor.name,
|
||||||
|
corps: actor.system.stats?.corps?.res ?? 0,
|
||||||
|
})
|
||||||
|
// Priorité 1 : PNJs dans le combat actif
|
||||||
|
if (game.combat?.active) {
|
||||||
|
const list = game.combat.combatants
|
||||||
|
.filter(c => c.actor?.type === "npc" && c.actorId !== this.parent.id)
|
||||||
|
.map(c => toEntry(c.actor))
|
||||||
|
if (list.length) return list
|
||||||
|
}
|
||||||
|
// Priorité 2 : Tokens ciblés par le joueur
|
||||||
|
const targeted = [...(game.user?.targets ?? [])]
|
||||||
|
.filter(t => t.actor?.type === "npc")
|
||||||
|
.map(t => toEntry(t.actor))
|
||||||
|
if (targeted.length) return targeted
|
||||||
|
// Priorité 3 : Tous les tokens NPC de la scène active
|
||||||
|
if (canvas?.tokens?.placeables) {
|
||||||
|
return canvas.tokens.placeables
|
||||||
|
.filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id)
|
||||||
|
.map(t => toEntry(t.actor))
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lance une attaque avec une arme.
|
* Lance une attaque avec une arme.
|
||||||
* Mêlée : test Échauffourée vs Corps PNJ ; échec → blessure joueur.
|
* Mêlée : test Échauffourée vs Corps PNJ ; échec → blessure joueur.
|
||||||
@@ -238,24 +287,26 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
if (!echauffouree) return null
|
if (!echauffouree) return null
|
||||||
|
|
||||||
return CelestopolRoll.prompt({
|
return CelestopolRoll.prompt({
|
||||||
actorId: this.parent.id,
|
actorId: this.parent.id,
|
||||||
actorName: this.parent.name,
|
actorName: this.parent.name,
|
||||||
actorImage: this.parent.img,
|
actorImage: this.parent.img,
|
||||||
statId: "corps",
|
statId: "corps",
|
||||||
skillId: "echauffouree",
|
skillId: "echauffouree",
|
||||||
statLabel: SYSTEM.STATS.corps.label,
|
statLabel: SYSTEM.STATS.corps.label,
|
||||||
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
|
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
|
||||||
skillValue: echauffouree.value,
|
skillValue: echauffouree.value,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
woundLevel: this.blessures.lvl,
|
armorMalus: this.getArmorMalus(),
|
||||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
woundLevel: this.blessures.lvl,
|
||||||
destGaugeFull: this.destin.lvl > 0,
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||||
fortuneValue: this.attributs.fortune.value,
|
destGaugeFull: this.destin.lvl > 0,
|
||||||
isCombat: true,
|
fortuneValue: this.attributs.fortune.value,
|
||||||
isRangedDefense: false,
|
isCombat: true,
|
||||||
weaponType: item.system.type,
|
isRangedDefense: false,
|
||||||
weaponName: item.name,
|
weaponType: item.system.type,
|
||||||
weaponDegats: item.system.degats,
|
weaponName: item.name,
|
||||||
|
weaponDegats: item.system.degats,
|
||||||
|
availableTargets: this._getCombatTargets(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,24 +325,26 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
if (!mobilite) return null
|
if (!mobilite) return null
|
||||||
|
|
||||||
return CelestopolRoll.prompt({
|
return CelestopolRoll.prompt({
|
||||||
actorId: this.parent.id,
|
actorId: this.parent.id,
|
||||||
actorName: this.parent.name,
|
actorName: this.parent.name,
|
||||||
actorImage: this.parent.img,
|
actorImage: this.parent.img,
|
||||||
statId: "corps",
|
statId: "corps",
|
||||||
skillId: "mobilite",
|
skillId: "mobilite",
|
||||||
statLabel: SYSTEM.STATS.corps.label,
|
statLabel: SYSTEM.STATS.corps.label,
|
||||||
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
|
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
|
||||||
skillValue: mobilite.value,
|
skillValue: mobilite.value,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
woundLevel: this.blessures.lvl,
|
armorMalus: this.getArmorMalus(),
|
||||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
woundLevel: this.blessures.lvl,
|
||||||
destGaugeFull: this.destin.lvl > 0,
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||||
fortuneValue: this.attributs.fortune.value,
|
destGaugeFull: this.destin.lvl > 0,
|
||||||
isCombat: true,
|
fortuneValue: this.attributs.fortune.value,
|
||||||
isRangedDefense: true,
|
isCombat: true,
|
||||||
weaponType: "distance",
|
isRangedDefense: true,
|
||||||
weaponName: item.name,
|
weaponType: "distance",
|
||||||
weaponDegats: "0",
|
weaponName: item.name,
|
||||||
|
weaponDegats: "0",
|
||||||
|
availableTargets: this._getCombatTargets(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export class CelestopolArmure extends foundry.abstract.TypeDataModel {
|
|||||||
return {
|
return {
|
||||||
protection: new fields.NumberField({ ...reqInt, initial: 1, min: 1, max: 2 }),
|
protection: new fields.NumberField({ ...reqInt, initial: 1, min: 1, max: 2 }),
|
||||||
malus: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 2 }),
|
malus: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 2 }),
|
||||||
|
equipped: new fields.BooleanField({ initial: false }),
|
||||||
description: new fields.HTMLField({ required: true, textSearch: true }),
|
description: new fields.HTMLField({ required: true, textSearch: true }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|||||||
const reqInt = { required: true, nullable: false, integer: true }
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
const schema = {}
|
const schema = {}
|
||||||
|
|
||||||
schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" })
|
schema.npcType = new fields.StringField({ required: true, nullable: false, initial: "standard",
|
||||||
|
choices: Object.keys(SYSTEM.NPC_TYPES) })
|
||||||
|
schema.concept = new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
schema.faction = new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
schema.initiative = new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
schema.initiative = new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
|
||||||
schema.anomaly = new fields.SchemaField({
|
schema.anomaly = new fields.SchemaField({
|
||||||
@@ -15,43 +18,27 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|||||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
})
|
})
|
||||||
|
|
||||||
const skillField = (label) => new fields.SchemaField({
|
// PNJs : 4 domaines uniquement (pas de sous-compétences)
|
||||||
label: new fields.StringField({ required: true, initial: label }),
|
const domainField = (statId) => new fields.SchemaField({
|
||||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
label: new fields.StringField({ required: true, initial: SYSTEM.STATS[statId].label }),
|
||||||
|
res: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
|
actuel: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
})
|
})
|
||||||
|
|
||||||
const statField = (statId) => {
|
|
||||||
const skills = SYSTEM.SKILLS[statId]
|
|
||||||
const skillSchema = {}
|
|
||||||
for (const [key, skill] of Object.entries(skills)) {
|
|
||||||
skillSchema[key] = skillField(skill.label)
|
|
||||||
}
|
|
||||||
return new fields.SchemaField({
|
|
||||||
label: new fields.StringField({ required: true, initial: SYSTEM.STATS[statId].label }),
|
|
||||||
res: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
|
||||||
actuel: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), // res + wound malus
|
|
||||||
...skillSchema,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
schema.stats = new fields.SchemaField({
|
schema.stats = new fields.SchemaField({
|
||||||
ame: statField("ame"),
|
ame: domainField("ame"),
|
||||||
corps: statField("corps"),
|
corps: domainField("corps"),
|
||||||
coeur: statField("coeur"),
|
coeur: domainField("coeur"),
|
||||||
esprit: statField("esprit"),
|
esprit: domainField("esprit"),
|
||||||
})
|
})
|
||||||
|
|
||||||
schema.blessures = new fields.SchemaField({
|
schema.blessures = new fields.SchemaField({
|
||||||
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
lvl: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }),
|
||||||
})
|
})
|
||||||
|
|
||||||
schema.prefs = new fields.SchemaField({
|
schema.histoire = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
rollMoonDie: new fields.BooleanField({ required: true, initial: false }),
|
schema.descriptionPhysique = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
difficulty: new fields.StringField({ required: true, nullable: false, initial: "normal" }),
|
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
})
|
|
||||||
|
|
||||||
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
|
||||||
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
@@ -61,11 +48,12 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
super.prepareDerivedData()
|
super.prepareDerivedData()
|
||||||
const malus = this.getWoundMalus()
|
const malus = this.getWoundMalus()
|
||||||
// Initiative PNJ : valeur du Domaine Corps
|
// Initiative PNJ : valeur du Domaine Corps (avec malus blessures)
|
||||||
this.initiative = Math.max(0, this.stats.corps.res + malus)
|
this.initiative = Math.max(0, this.stats.corps.res + malus)
|
||||||
for (const stat of Object.values(this.stats)) {
|
for (const stat of Object.values(this.stats)) {
|
||||||
stat.actuel = Math.max(0, stat.res + malus)
|
stat.actuel = Math.max(0, stat.res + malus)
|
||||||
}
|
}
|
||||||
|
this.armorMalus = this.getArmorMalus()
|
||||||
}
|
}
|
||||||
|
|
||||||
getWoundMalus() {
|
getWoundMalus() {
|
||||||
@@ -73,22 +61,43 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|||||||
return SYSTEM.WOUND_LEVELS[lvl]?.malus ?? 0
|
return SYSTEM.WOUND_LEVELS[lvl]?.malus ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async roll(statId, skillId) {
|
/** Somme des malus des armures équipées (valeur négative ou 0). */
|
||||||
|
getArmorMalus() {
|
||||||
|
const armures = this.parent?.itemTypes?.armure ?? []
|
||||||
|
return armures
|
||||||
|
.filter(a => a.system.equipped)
|
||||||
|
.reduce((sum, a) => sum + (a.system.malus ? -Math.abs(a.system.malus) : 0), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lance un jet sur un domaine (Âme/Corps/Cœur/Esprit).
|
||||||
|
* Le label affiché tient compte du type de PNJ (standard vs antagoniste).
|
||||||
|
*/
|
||||||
|
async roll(statId) {
|
||||||
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
||||||
const skill = this.stats[statId][skillId]
|
const statData = this.stats[statId]
|
||||||
if (!skill) return null
|
if (!statData) return null
|
||||||
|
|
||||||
|
const isAntagoniste = this.npcType === "antagoniste"
|
||||||
|
const skillLabel = isAntagoniste
|
||||||
|
? SYSTEM.ANTAGONISTE_STATS[statId]?.label
|
||||||
|
: SYSTEM.STATS[statId]?.label
|
||||||
|
|
||||||
return CelestopolRoll.prompt({
|
return CelestopolRoll.prompt({
|
||||||
actorId: this.parent.id,
|
actorId: this.parent.id,
|
||||||
actorName: this.parent.name,
|
actorName: this.parent.name,
|
||||||
actorImage: this.parent.img,
|
actorImage: this.parent.img,
|
||||||
statId,
|
statId,
|
||||||
skillId,
|
skillLabel,
|
||||||
skillLabel: skill.label,
|
skillValue: statData.res,
|
||||||
skillValue: skill.value,
|
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
difficulty: this.prefs.difficulty,
|
armorMalus: this.getArmorMalus(),
|
||||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
woundLevel: this.blessures.lvl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Alias pour compatibilité avec le handler _onRoll (clic sans skillId). */
|
||||||
|
async rollResistance(statId) {
|
||||||
|
return this.roll(statId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
packs-system/anomalies/000017.ldb
Normal file
BIN
packs-system/anomalies/000017.ldb
Normal file
Binary file not shown.
BIN
packs-system/anomalies/000019.log
Normal file
BIN
packs-system/anomalies/000019.log
Normal file
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000033
|
MANIFEST-000018
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
2026/03/31-17:30:25.623001 7ff9fd9ff6c0 Recovering log #30
|
2026/04/06-17:46:52.532955 7f67ebfff6c0 Recovering log #15
|
||||||
2026/03/31-17:30:25.660522 7ff9fd9ff6c0 Delete type=3 #28
|
2026/04/06-17:46:52.543005 7f67ebfff6c0 Delete type=3 #13
|
||||||
2026/03/31-17:30:25.660594 7ff9fd9ff6c0 Delete type=0 #30
|
2026/04/06-17:46:52.543081 7f67ebfff6c0 Delete type=0 #15
|
||||||
2026/03/31-17:32:44.418768 7ff7477ef6c0 Level-0 table #36: started
|
|
||||||
2026/03/31-17:32:44.422584 7ff7477ef6c0 Level-0 table #36: 3529 bytes OK
|
|
||||||
2026/03/31-17:32:44.428754 7ff7477ef6c0 Delete type=0 #34
|
|
||||||
2026/03/31-17:32:44.451369 7ff7477ef6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
|
||||||
2026/03/31-17:32:44.451426 7ff7477ef6c0 Manual compaction at level-1 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 33 : 1
|
|
||||||
2026/03/31-17:32:44.451435 7ff7477ef6c0 Compacting 1@1 + 1@2 files
|
|
||||||
2026/03/31-17:32:44.454717 7ff7477ef6c0 Generated table #37@1: 9 keys, 6638 bytes
|
|
||||||
2026/03/31-17:32:44.454749 7ff7477ef6c0 Compacted 1@1 + 1@2 files => 6638 bytes
|
|
||||||
2026/03/31-17:32:44.461425 7ff7477ef6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
|
||||||
2026/03/31-17:32:44.461630 7ff7477ef6c0 Delete type=2 #32
|
|
||||||
2026/03/31-17:32:44.461862 7ff7477ef6c0 Delete type=2 #36
|
|
||||||
2026/03/31-17:32:44.487417 7ff7477ef6c0 Manual compaction at level-1 from '!items!null' @ 33 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
2026/03/31-14:51:45.342017 7ff9fd1fe6c0 Recovering log #25
|
2026/04/05-21:02:44.634018 7f8249dff6c0 Recovering log #10
|
||||||
2026/03/31-14:51:45.425193 7ff9fd1fe6c0 Delete type=3 #23
|
2026/04/05-21:02:44.729398 7f8249dff6c0 Delete type=3 #8
|
||||||
2026/03/31-14:51:45.425262 7ff9fd1fe6c0 Delete type=0 #25
|
2026/04/05-21:02:44.729470 7f8249dff6c0 Delete type=0 #10
|
||||||
2026/03/31-16:02:12.525970 7ff7477ef6c0 Level-0 table #31: started
|
2026/04/06-00:09:38.933436 7f82177fe6c0 Level-0 table #16: started
|
||||||
2026/03/31-16:02:12.529284 7ff7477ef6c0 Level-0 table #31: 3529 bytes OK
|
2026/04/06-00:09:38.937122 7f82177fe6c0 Level-0 table #16: 3525 bytes OK
|
||||||
2026/03/31-16:02:12.536284 7ff7477ef6c0 Delete type=0 #29
|
2026/04/06-00:09:38.943462 7f82177fe6c0 Delete type=0 #14
|
||||||
2026/03/31-16:02:12.536504 7ff7477ef6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
2026/04/06-00:09:38.943723 7f82177fe6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
||||||
2026/03/31-16:02:12.547124 7ff7477ef6c0 Manual compaction at level-1 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 29 : 1
|
2026/04/06-00:09:38.966124 7f82177fe6c0 Manual compaction at level-1 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 17 : 1
|
||||||
2026/03/31-16:02:12.547133 7ff7477ef6c0 Compacting 1@1 + 1@2 files
|
2026/04/06-00:09:38.966141 7f82177fe6c0 Compacting 1@1 + 1@2 files
|
||||||
2026/03/31-16:02:12.550304 7ff7477ef6c0 Generated table #32@1: 9 keys, 6638 bytes
|
2026/04/06-00:09:38.969869 7f82177fe6c0 Generated table #17@1: 9 keys, 6617 bytes
|
||||||
2026/03/31-16:02:12.550331 7ff7477ef6c0 Compacted 1@1 + 1@2 files => 6638 bytes
|
2026/04/06-00:09:38.969906 7f82177fe6c0 Compacted 1@1 + 1@2 files => 6617 bytes
|
||||||
2026/03/31-16:02:12.557451 7ff7477ef6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
2026/04/06-00:09:38.976148 7f82177fe6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
2026/03/31-16:02:12.557568 7ff7477ef6c0 Delete type=2 #27
|
2026/04/06-00:09:38.976266 7f82177fe6c0 Delete type=2 #12
|
||||||
2026/03/31-16:02:12.557744 7ff7477ef6c0 Delete type=2 #31
|
2026/04/06-00:09:38.976457 7f82177fe6c0 Delete type=2 #16
|
||||||
2026/03/31-16:02:12.557862 7ff7477ef6c0 Manual compaction at level-1 from '!items!null' @ 29 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
2026/04/06-00:09:38.987710 7f82177fe6c0 Manual compaction at level-1 from '!items!null' @ 17 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
BIN
packs-system/anomalies/MANIFEST-000018
Normal file
BIN
packs-system/anomalies/MANIFEST-000018
Normal file
Binary file not shown.
Binary file not shown.
@@ -54,6 +54,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Badge malus armure équipée dans le header
|
||||||
|
.armor-malus-badge {
|
||||||
|
border-color: #b84a2e;
|
||||||
|
.armor-malus-value {
|
||||||
|
color: #e06040;
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 1.05em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
label { color: #e06040; opacity: 0.8; }
|
||||||
|
}
|
||||||
|
|
||||||
// Stats × Domaines grid
|
// Stats × Domaines grid
|
||||||
.stats-grid {
|
.stats-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -100,6 +112,47 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-res-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 8px 3px 6px;
|
||||||
|
border: 1px solid var(--cel-orange);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(224, 123, 0, 0.08);
|
||||||
|
font-size: 0.78em;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
.res-die-icon {
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.res-label {
|
||||||
|
color: var(--cel-orange-light);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.res-value {
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
min-width: 18px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.rollable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, border-color 0.15s;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(224, 123, 0, 0.22);
|
||||||
|
border-color: var(--cel-orange-light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.skills-list {
|
.skills-list {
|
||||||
@@ -146,11 +199,36 @@
|
|||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
background: rgba(255,255,255,0.3);
|
background: rgba(255,255,255,0.3);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
transition: background 0.1s, border-color 0.1s;
|
transition: background 0.1s, border-color 0.1s;
|
||||||
&.filled {
|
&.filled {
|
||||||
background: var(--cel-orange);
|
background: var(--cel-orange);
|
||||||
border-color: var(--cel-border);
|
border-color: var(--cel-border);
|
||||||
}
|
}
|
||||||
|
&.res-threshold {
|
||||||
|
border: 2px solid var(--cel-orange);
|
||||||
|
background: rgba(224, 123, 0, 0.2);
|
||||||
|
// Petit indicateur orange sous le dot
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--cel-orange);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
&.filled {
|
||||||
|
background: var(--cel-orange);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 0 0 1.5px var(--cel-orange);
|
||||||
|
&::after {
|
||||||
|
background: var(--cel-orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
&[data-action] { cursor: pointer; }
|
&[data-action] { cursor: pointer; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,12 +279,30 @@
|
|||||||
font-family: var(--cel-font-title);
|
font-family: var(--cel-font-title);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9em;
|
font-size: 1.1em;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
.track-title-destin {
|
.track-help {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
font-size: 0.65em;
|
||||||
|
font-family: var(--cel-font-body);
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: 0;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
border-bottom: 1px dashed currentColor;
|
opacity: 0.7;
|
||||||
text-decoration: none;
|
transition: opacity 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
&:hover { opacity: 1; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +366,7 @@
|
|||||||
background-image: url("../assets/ui/fond_cadrille.jpg");
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
background-blend-mode: soft-light;
|
background-blend-mode: soft-light;
|
||||||
color: var(--cel-orange);
|
color: var(--cel-orange);
|
||||||
th { padding: 5px 8px; font-family: var(--cel-font-title); letter-spacing: 0.06em; }
|
th { padding: 5px 8px; font-family: var(--cel-font-title); font-size: 1.05em; letter-spacing: 0.06em; text-transform: uppercase; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.faction-row {
|
.faction-row {
|
||||||
@@ -299,8 +395,14 @@
|
|||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
background: rgba(255,255,255,0.3);
|
background: rgba(255,255,255,0.3);
|
||||||
transition: background 0.1s;
|
transition: background 0.1s;
|
||||||
&.filled { background: var(--cel-orange); border-color: var(--cel-orange); }
|
|
||||||
&[data-action] { cursor: pointer; }
|
&[data-action] { cursor: pointer; }
|
||||||
|
// Dot neutre (centre, index 4)
|
||||||
|
&.neutral { border-color: #888; }
|
||||||
|
&.neutral.filled { background: #aaa; border-color: #888; }
|
||||||
|
// Dots positifs (alliés) → or
|
||||||
|
&.pos.filled { background: var(--cel-orange); border-color: var(--cel-orange); }
|
||||||
|
// Dots négatifs (hostiles) → rouge terracotta
|
||||||
|
&.neg.filled { background: #b84a2e; border-color: #b84a2e; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.faction-count {
|
.faction-count {
|
||||||
@@ -345,6 +447,12 @@
|
|||||||
.item-row {
|
.item-row {
|
||||||
.cel-item-row();
|
.cel-item-row();
|
||||||
|
|
||||||
|
&.is-equipped {
|
||||||
|
background: rgba(12, 76, 12, 0.12);
|
||||||
|
border-left: 3px solid var(--cel-green);
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.item-tag {
|
.item-tag {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
padding: 1px 7px;
|
padding: 1px 7px;
|
||||||
@@ -355,6 +463,12 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
&.malus { background: rgba(192,68,68,0.1); border-color: rgba(192,68,68,0.35); color: #922; }
|
&.malus { background: rgba(192,68,68,0.1); border-color: rgba(192,68,68,0.35); color: #922; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.equip-toggle {
|
||||||
|
color: var(--cel-border);
|
||||||
|
&.equipped { color: var(--cel-green); }
|
||||||
|
&:hover { color: var(--cel-orange); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.equip-empty {
|
.equip-empty {
|
||||||
@@ -489,34 +603,50 @@
|
|||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
summary {
|
summary {
|
||||||
font-size: 0.78em;
|
font-size: 0.78em;
|
||||||
color: var(--cel-border);
|
color: var(--cel-orange-light);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
&:hover { color: var(--cel-green); }
|
&:hover { color: var(--cel-orange); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.xp-ref-table {
|
.xp-ref-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 0.78em;
|
font-size: 0.82em;
|
||||||
margin-top: 5px;
|
margin-top: 6px;
|
||||||
opacity: 0.85;
|
|
||||||
|
|
||||||
|
thead tr {
|
||||||
|
background: var(--cel-green);
|
||||||
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
|
background-blend-mode: soft-light;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
}
|
||||||
th {
|
th {
|
||||||
color: var(--cel-border);
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 0.9em;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.85em;
|
padding: 4px 8px;
|
||||||
letter-spacing: 0.03em;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-bottom: 1px solid rgba(196,154,26,0.25);
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
border-bottom: 2px solid var(--cel-orange);
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
padding: 2px 6px;
|
padding: 4px 8px;
|
||||||
border-bottom: 1px solid rgba(196,154,26,0.1);
|
color: var(--cel-text, #2a1a00);
|
||||||
color: var(--cel-text-dark, #3a2a0a);
|
border-bottom: 1px solid rgba(196,154,26,0.25);
|
||||||
|
}
|
||||||
|
tbody tr {
|
||||||
|
&:nth-child(odd) { background: rgba(255,248,230,0.7); }
|
||||||
|
&:nth-child(even) { background: rgba(240,228,195,0.5); }
|
||||||
|
&:last-child td { border-bottom: none; }
|
||||||
|
}
|
||||||
|
td:last-child {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
text-align: center;
|
||||||
|
width: 60px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Boutons d'action du header (toggle mode, dé de lune, etc.)
|
||||||
|
.header-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding-left: 4px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--cel-orange-light);
|
||||||
|
font-size: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(196,154,26,0.18);
|
||||||
|
color: var(--cel-orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-standalone-btn {
|
||||||
|
font-size: 1.3em;
|
||||||
|
line-height: 1;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Tabs ────────────────────────────────────────────────────────────────
|
// ─── Tabs ────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -387,12 +387,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Armure-specific
|
// Armure-specific
|
||||||
&.armure {
|
&.armure {
|
||||||
.armure-stats {
|
.armure-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.armure-stat-box {
|
.armure-stat-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -405,12 +406,37 @@
|
|||||||
min-width: 110px;
|
min-width: 110px;
|
||||||
label { font-size: 0.72em; text-transform: uppercase; color: var(--cel-orange-light); letter-spacing: 0.05em; }
|
label { font-size: 0.72em; text-transform: uppercase; color: var(--cel-orange-light); letter-spacing: 0.05em; }
|
||||||
.armure-stat-value {
|
.armure-stat-value {
|
||||||
input, span {
|
input[type="number"], span {
|
||||||
font-family: var(--cel-font-title); font-size: 1.8em; font-weight: bold; color: var(--cel-orange);
|
font-family: var(--cel-font-title); font-size: 1.8em; font-weight: bold; color: var(--cel-orange);
|
||||||
text-align: center; background: transparent; border: none; width: 40px;
|
text-align: center; background: transparent; border: none; width: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.armure-stat-hint { font-size: 0.7em; color: var(--cel-cream); font-style: italic; text-align: center; margin-top: 4px; }
|
.armure-stat-hint { font-size: 0.7em; color: var(--cel-cream); font-style: italic; text-align: center; margin-top: 4px; }
|
||||||
}
|
}
|
||||||
|
.equipped-box {
|
||||||
|
border-color: var(--cel-green);
|
||||||
|
.equipped-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
input[type="checkbox"] { display: none; }
|
||||||
|
.switch-label {
|
||||||
|
font-family: var(--cel-font-ui);
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: var(--cel-border);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border: 1px solid var(--cel-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
&.on {
|
||||||
|
color: var(--cel-green-light);
|
||||||
|
border-color: var(--cel-green);
|
||||||
|
background: rgba(12,76,12,0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
253
styles/npc.less
253
styles/npc.less
@@ -4,75 +4,250 @@
|
|||||||
|
|
||||||
.fvtt-celestopol.npc {
|
.fvtt-celestopol.npc {
|
||||||
|
|
||||||
.stats-grid {
|
// ── Sélecteur type PNJ (en-tête) ────────────────────────────────────────
|
||||||
|
.npc-type-row {
|
||||||
|
margin: 3px 0;
|
||||||
|
|
||||||
|
.npc-type-select {
|
||||||
|
background: rgba(12,76,12,0.15);
|
||||||
|
border: 1px solid var(--cel-border);
|
||||||
|
color: var(--cel-orange);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.npc-type-badge {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 0.8em;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
|
||||||
|
&.antagoniste {
|
||||||
|
background: rgba(120, 30, 30, 0.25);
|
||||||
|
border: 1px solid rgba(200, 60, 60, 0.5);
|
||||||
|
color: #e06060;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Grille 2×2 des domaines ──────────────────────────────────────────────
|
||||||
|
.npc-domains-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
|
|
||||||
.stat-block {
|
.npc-domain-block {
|
||||||
border: 1px solid var(--cel-border);
|
border: 1px solid var(--cel-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.stat-header {
|
.npc-domain-header {
|
||||||
background: var(--cel-green);
|
background: var(--cel-green);
|
||||||
background-image: url("../assets/ui/fond_cadrille.jpg");
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
background-blend-mode: soft-light;
|
background-blend-mode: soft-light;
|
||||||
color: var(--cel-orange);
|
color: var(--cel-orange);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
min-height: 60px;
|
||||||
|
|
||||||
|
.npc-domain-labels {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: center;
|
gap: 2px;
|
||||||
padding: 5px 8px;
|
|
||||||
border-bottom: 1px solid rgba(196,154,26,0.4);
|
|
||||||
|
|
||||||
.stat-name {
|
.domain-label-primary {
|
||||||
font-family: var(--cel-font-title);
|
font-family: var(--cel-font-title);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.9em;
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--cel-orange);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-res {
|
.domain-label-secondary {
|
||||||
display: flex;
|
font-size: 0.75em;
|
||||||
align-items: center;
|
color: rgba(220,170,80,0.7);
|
||||||
gap: 4px;
|
font-style: italic;
|
||||||
font-size: 0.8em;
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
label { color: var(--cel-orange-light); }
|
|
||||||
.stat-res-value { font-weight: bold; color: var(--cel-orange); }
|
|
||||||
.stat-actuel {
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: rgba(255,200,0,0.7);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
input[type="number"] { width: 30px; .cel-input-std(); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.skills-list {
|
.npc-domain-value-wrap {
|
||||||
background: var(--cel-cream);
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.skill-row {
|
// Mode édition : input nombre
|
||||||
|
input.domain-value-input {
|
||||||
|
width: 40px;
|
||||||
|
.cel-input-std();
|
||||||
|
font-size: 1.2em;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode jeu : bouton rollable avec dé
|
||||||
|
.npc-domain-roll-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 5px;
|
||||||
padding: 3px 8px;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid rgba(122,92,32,0.18);
|
padding: 4px 8px;
|
||||||
font-size: 0.85em;
|
border-radius: 4px;
|
||||||
|
transition: background 0.15s, box-shadow 0.15s;
|
||||||
|
|
||||||
&:nth-child(even) { background: var(--cel-cream-dark); }
|
&:hover {
|
||||||
|
background: rgba(224,123,0,0.2);
|
||||||
|
box-shadow: 0 0 6px rgba(224,123,0,0.4);
|
||||||
|
.domain-die-icon { color: var(--cel-orange); }
|
||||||
|
}
|
||||||
|
|
||||||
&.rollable { .cel-rollable(); }
|
.domain-die-icon {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: rgba(220,170,80,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
.skill-name { flex: 1; }
|
.domain-value {
|
||||||
.skill-value { font-weight: bold; color: var(--cel-orange); min-width: 24px; text-align: center; }
|
font-family: var(--cel-font-title);
|
||||||
.skill-value-input { width: 36px; .cel-input-std(); text-align: center; }
|
font-size: 1.4em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-value-base {
|
||||||
|
font-size: 0.75em;
|
||||||
|
color: rgba(220,170,80,0.6);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Section Aspects ──────────────────────────────────────────────────────
|
||||||
|
.npc-aspects-section {
|
||||||
|
margin-top: 12px;
|
||||||
|
border: 1px solid var(--cel-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
background: var(--cel-green);
|
||||||
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
|
background-blend-mode: soft-light;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
border-bottom: 1px solid rgba(196,154,26,0.4);
|
||||||
|
|
||||||
|
a { color: var(--cel-orange-light); margin-left: auto; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.aspect-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-bottom: 1px solid rgba(122,92,32,0.18);
|
||||||
|
background: var(--cel-cream);
|
||||||
|
font-size: 0.9em;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&:nth-child(even) { background: var(--cel-cream-dark); }
|
||||||
|
|
||||||
|
.item-name { flex: 1; }
|
||||||
|
.aspect-value {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 28px;
|
||||||
|
text-align: center;
|
||||||
|
&.positive { color: #2a8a2a; }
|
||||||
|
&.negative { color: #c03030; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Onglet Biographie ────────────────────────────────────────────────────
|
||||||
|
.bio-section {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: var(--cel-green);
|
||||||
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
|
background-blend-mode: soft-light;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
border: 1px solid var(--cel-border);
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enriched-html {
|
||||||
|
font-size: 0.9em;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-section {
|
||||||
|
.faction-display {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--cel-cream);
|
||||||
|
border: 1px solid var(--cel-border);
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
|
||||||
|
.faction-name {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
color: var(--cel-orange);
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-none {
|
||||||
|
font-style: italic;
|
||||||
|
color: rgba(122,92,32,0.5);
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-select-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--cel-cream);
|
||||||
|
border: 1px solid var(--cel-border);
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
|
||||||
|
label { font-size: 0.85em; color: var(--cel-brown); }
|
||||||
|
select { flex: 1; .cel-input-std(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Pistes (Blessures) ───────────────────────────────────────────────────
|
||||||
.track-section {
|
.track-section {
|
||||||
border: 1px solid var(--cel-border);
|
border: 1px solid var(--cel-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
341
styles/roll.less
341
styles/roll.less
@@ -857,4 +857,345 @@
|
|||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-target-row {
|
||||||
|
background: rgba(12, 76, 12, 0.12);
|
||||||
|
border: 1px solid rgba(196, 154, 26, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.85em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
i { color: #e07b00; }
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
color: #f0e0c0;
|
||||||
|
border: 1px solid rgba(196, 154, 26, 0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-target-confirmed {
|
||||||
|
background: rgba(12, 76, 12, 0.2);
|
||||||
|
border: 1px solid rgba(196, 154, 26, 0.5);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
.target-confirmed-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-size: 0.88em;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
i { opacity: 0.8; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-ranged-mod {
|
||||||
|
background: rgba(60, 20, 0, 0.12);
|
||||||
|
border: 1px solid rgba(200, 100, 60, 0.35);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #e08060;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
i { color: #e08060; }
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
color: #f0e0c0;
|
||||||
|
border: 1px solid rgba(200, 100, 60, 0.4);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-threshold-fixed {
|
||||||
|
.threshold-value {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
padding: 2px 10px;
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
border: 1px solid rgba(224,123,0,0.4);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-opposition-row {
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid rgba(180, 140, 60, 0.35);
|
||||||
|
border-radius: 5px;
|
||||||
|
background: rgba(60, 30, 0, 0.15);
|
||||||
|
|
||||||
|
.opposition-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
input[type="checkbox"] { flex-shrink: 0; }
|
||||||
|
|
||||||
|
.opposition-icon {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opposition-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
|
||||||
|
.opposition-main {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opposition-sub {
|
||||||
|
font-size: 0.75em;
|
||||||
|
opacity: 0.7;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bandeau opposition dans le chat
|
||||||
|
.fvtt-celestopol .dice-roll .roll-result-banner.opposition {
|
||||||
|
background: linear-gradient(135deg, rgba(60, 60, 80, 0.8), rgba(40, 40, 60, 0.9));
|
||||||
|
border-color: rgba(150, 140, 200, 0.5);
|
||||||
|
color: #c8c0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dé de la Lune — Carte autonome ──────────────────────────────────────────
|
||||||
|
|
||||||
|
.celestopol-roll.moon-standalone-card {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--cel-parchment, #f5eed8);
|
||||||
|
border: 1px solid var(--cel-border, rgba(196,154,26,0.4));
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.moon-standalone-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 6px;
|
||||||
|
border-bottom: 1px solid rgba(196,154,26,0.3);
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
.moon-standalone-title {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-green-dark, #0c4c0c);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-standalone-actor {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: var(--cel-text, #333);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-standalone-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: rgba(0,0,0,0.04);
|
||||||
|
border-radius: 3px;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
|
||||||
|
.moon-standalone-symbol {
|
||||||
|
font-size: 2em;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-standalone-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-standalone-phase {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 0.95em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-text, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-standalone-value {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: var(--cel-text-light, #666);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.moon-triomphe { border-left-color: var(--cel-green, #0c4c0c); }
|
||||||
|
&.moon-brio { border-left-color: var(--cel-border, #7a5c20); }
|
||||||
|
&.moon-contrecoup { border-left-color: #c07800; }
|
||||||
|
&.moon-catastrophe{ border-left-color: #8b1e2e; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-interpret-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.moon-interpret-label {
|
||||||
|
font-size: 0.72em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: #888;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-fortune {
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
|
||||||
|
&.bonne-fortune {
|
||||||
|
background: rgba(12, 76, 12, 0.12);
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
border: 1px solid rgba(12,76,12,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mauvaise-fortune {
|
||||||
|
background: rgba(139, 30, 46, 0.1);
|
||||||
|
color: #8b1e2e;
|
||||||
|
border: 1px solid rgba(139,30,46,0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Réutilise les styles .moon-die-result existants pour le bloc narratif
|
||||||
|
.moon-die-result {
|
||||||
|
margin-top: 2px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Message d'initiative ──────────────────────────────────────────────────────
|
||||||
|
.celestopol.chat-initiative {
|
||||||
|
border: 1px solid var(--cel-orange, #e07b00);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: var(--cel-font-body, "Palatino Linotype", serif);
|
||||||
|
|
||||||
|
.roll-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background-color: var(--cel-green, #0c4c0c);
|
||||||
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
|
background-blend-mode: soft-light;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-bottom: 2px solid var(--cel-orange, #e07b00);
|
||||||
|
|
||||||
|
.actor-img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid var(--cel-orange, #e07b00);
|
||||||
|
border-radius: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roll-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
|
||||||
|
.actor-name {
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
font-size: 0.92em;
|
||||||
|
}
|
||||||
|
.skill-info {
|
||||||
|
color: var(--cel-cream, #f0e8d4);
|
||||||
|
font-size: 0.77em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.initiative-banner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 8px 8px;
|
||||||
|
background-color: var(--cel-green, #0c4c0c);
|
||||||
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
|
background-blend-mode: soft-light;
|
||||||
|
color: #ffd870;
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-shadow: 0 1px 3px rgba(0,0,0,0.7);
|
||||||
|
|
||||||
|
.initiative-score-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initiative-icon {
|
||||||
|
font-size: 1.1em;
|
||||||
|
opacity: 0.9;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initiative-score {
|
||||||
|
font-size: 2.4em;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.initiative-detail {
|
||||||
|
font-size: 0.65em;
|
||||||
|
opacity: 0.75;
|
||||||
|
font-style: italic;
|
||||||
|
font-family: var(--cel-font-body, serif);
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,17 @@
|
|||||||
|
|
||||||
<div class="anomaly-body">
|
<div class="anomaly-body">
|
||||||
<div class="armure-stats">
|
<div class="armure-stats">
|
||||||
|
<div class="armure-stat-box equipped-box">
|
||||||
|
<label>{{localize "CELESTOPOL.Armure.equipped"}}</label>
|
||||||
|
<div class="armure-stat-value">
|
||||||
|
<label class="equipped-switch">
|
||||||
|
<input type="checkbox" name="system.equipped" {{#if system.equipped}}checked{{/if}} {{#unless isEditable}}disabled{{/unless}}>
|
||||||
|
<span class="switch-label {{#if system.equipped}}on{{/if}}">
|
||||||
|
{{#if system.equipped}}{{localize "CELESTOPOL.Armure.equippedYes"}}{{else}}{{localize "CELESTOPOL.Armure.equippedNo"}}{{/if}}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="armure-stat-box">
|
<div class="armure-stat-box">
|
||||||
<label>{{localize "CELESTOPOL.Armure.protection"}}</label>
|
<label>{{localize "CELESTOPOL.Armure.protection"}}</label>
|
||||||
<div class="armure-stat-value">
|
<div class="armure-stat-value">
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
<div class="tab biography {{tab.cssClass}}" data-group="sheet" data-tab="biography">
|
<div class="tab biography {{tab.cssClass}}" data-group="sheet" data-tab="biography">
|
||||||
|
|
||||||
{{!-- Description / Biographie --}}
|
{{!-- Description Physique --}}
|
||||||
<div class="biography-section">
|
<div class="biography-section">
|
||||||
<div class="section-header">{{localize "CELESTOPOL.Actor.description"}}</div>
|
<div class="section-header">{{localize "CELESTOPOL.Actor.descriptionPhysique"}}</div>
|
||||||
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
{{formInput systemFields.descriptionPhysique enriched=enrichedDescriptionPhysique value=system.descriptionPhysique name="system.descriptionPhysique" toggled=true}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Description Psychologique --}}
|
||||||
|
<div class="biography-section">
|
||||||
|
<div class="section-header">{{localize "CELESTOPOL.Actor.descriptionPsychologique"}}</div>
|
||||||
|
{{formInput systemFields.descriptionPsychologique enriched=enrichedDescriptionPsychologique value=system.descriptionPsychologique name="system.descriptionPsychologique" toggled=true}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{!-- Notes --}}
|
{{!-- Notes --}}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
{{!-- Blessures --}}
|
{{!-- Blessures --}}
|
||||||
<section class="track-section">
|
<section class="track-section">
|
||||||
<div class="track-header">
|
<div class="track-header">
|
||||||
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
|
<span class="track-title">
|
||||||
|
{{localize "CELESTOPOL.Track.blessures"}}
|
||||||
|
<span class="track-help" title="{{localize 'CELESTOPOL.Track.blessuresTooltip'}}">?</span>
|
||||||
|
</span>
|
||||||
<span class="wound-malus">{{localize "CELESTOPOL.Track.currentMalus"}} :
|
<span class="wound-malus">{{localize "CELESTOPOL.Track.currentMalus"}} :
|
||||||
<strong>{{lookup @root.woundLevels system.blessures.lvl 'malus'}}</strong>
|
<strong>{{lookup @root.woundLevels system.blessures.lvl 'malus'}}</strong>
|
||||||
</span>
|
</span>
|
||||||
@@ -24,8 +27,9 @@
|
|||||||
{{!-- Destin --}}
|
{{!-- Destin --}}
|
||||||
<section class="track-section">
|
<section class="track-section">
|
||||||
<div class="track-header">
|
<div class="track-header">
|
||||||
<span class="track-title track-title-destin"
|
<span class="track-title">{{localize "CELESTOPOL.Track.destin"}}
|
||||||
title="{{localize 'CELESTOPOL.Track.destinTooltip'}}">{{localize "CELESTOPOL.Track.destin"}}</span>
|
<span class="track-help" title="{{localize 'CELESTOPOL.Track.destinTooltip'}}">?</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-boxes destin-boxes">
|
<div class="track-boxes destin-boxes">
|
||||||
{{#each (range 8) as |lvl|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
@@ -42,7 +46,10 @@
|
|||||||
{{!-- Spleen --}}
|
{{!-- Spleen --}}
|
||||||
<section class="track-section">
|
<section class="track-section">
|
||||||
<div class="track-header">
|
<div class="track-header">
|
||||||
<span class="track-title">{{localize "CELESTOPOL.Track.spleen"}}</span>
|
<span class="track-title">
|
||||||
|
{{localize "CELESTOPOL.Track.spleen"}}
|
||||||
|
<span class="track-help" title="{{localize 'CELESTOPOL.Track.spleenTooltip'}}">?</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-boxes spleen-boxes">
|
<div class="track-boxes spleen-boxes">
|
||||||
{{#each (range 8) as |lvl|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
<div class="stat-block">
|
<div class="stat-block">
|
||||||
<div class="stat-header">
|
<div class="stat-header">
|
||||||
<span class="stat-name">{{localize stat.label}}</span>
|
<span class="stat-name">{{localize stat.label}}</span>
|
||||||
<div class="stat-res {{#unless ../isEditMode}}rollable{{/unless}}" data-stat-id="{{statId}}"
|
<div class="stat-res-btn {{#unless ../isEditMode}}rollable{{/unless}}" data-stat-id="{{statId}}"
|
||||||
title="{{localize 'CELESTOPOL.Roll.resistanceClickToRoll'}}">
|
title="{{localize 'CELESTOPOL.Roll.resistanceClickToRoll'}}">
|
||||||
<label>{{localize "CELESTOPOL.Stat.res"}}</label>
|
<i class="fas fa-dice-d8 res-die-icon"></i>
|
||||||
<span class="stat-res-value">{{lookup (lookup ../system.stats statId) 'res'}}</span>
|
<span class="res-label">{{localize "CELESTOPOL.Stat.res"}}</span>
|
||||||
|
<span class="res-value">{{lookup (lookup ../system.stats statId) 'res'}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="skills-list">
|
<div class="skills-list">
|
||||||
@@ -19,8 +20,9 @@
|
|||||||
<div class="skill-checkboxes-container">
|
<div class="skill-checkboxes-container">
|
||||||
<div class="skill-checkboxes">
|
<div class="skill-checkboxes">
|
||||||
{{#each (range 8) as |lvl|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}}"
|
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}} {{#if (isResThreshold skillId lvl)}}res-threshold{{/if}}"
|
||||||
data-action="skillLevel" data-stat-id="{{statId}}" data-skill-id="{{skillId}}" data-index="{{lvl}}"></span>
|
data-action="skillLevel" data-stat-id="{{statId}}" data-skill-id="{{skillId}}" data-index="{{lvl}}"
|
||||||
|
title="{{#if (isResThreshold skillId lvl)}}{{localize 'CELESTOPOL.Roll.resThresholdHint'}}{{/if}}"></span>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +35,8 @@
|
|||||||
<div class="skill-checkboxes-container">
|
<div class="skill-checkboxes-container">
|
||||||
<div class="skill-checkboxes">
|
<div class="skill-checkboxes">
|
||||||
{{#each (range 8) as |lvl|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}}"></span>
|
<span class="skill-level-dot {{#if (lte lvl (lookup @root.system.stats statId skillId 'value'))}}filled{{/if}} {{#if (isResThreshold skillId lvl)}}res-threshold{{/if}}"
|
||||||
|
title="{{#if (isResThreshold skillId lvl)}}{{localize 'CELESTOPOL.Roll.resThresholdHint'}}{{/if}}"></span>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,12 +41,17 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#each armures as |item|}}
|
{{#each armures as |item|}}
|
||||||
<div class="item-row armure" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
|
<div class="item-row armure {{#if item.system.equipped}}is-equipped{{/if}}" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
|
||||||
<img src="{{item.img}}" class="item-icon">
|
<img src="{{item.img}}" class="item-icon">
|
||||||
<span class="item-name">{{item.name}}</span>
|
<span class="item-name">{{item.name}}</span>
|
||||||
<span class="item-tag prot"><i class="fas fa-shield"></i> {{item.system.protection}}</span>
|
<span class="item-tag prot"><i class="fas fa-shield"></i> {{item.system.protection}}</span>
|
||||||
{{#if item.system.malus}}<span class="item-tag malus">−{{item.system.malus}} {{localize "CELESTOPOL.Armure.malus"}}</span>{{/if}}
|
{{#if item.system.malus}}<span class="item-tag malus">−{{item.system.malus}} {{localize "CELESTOPOL.Armure.malus"}}</span>{{/if}}
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
|
<a data-action="toggleArmure" data-item-uuid="{{item.uuid}}"
|
||||||
|
class="equip-toggle {{#if item.system.equipped}}equipped{{/if}}"
|
||||||
|
title="{{#if item.system.equipped}}{{localize 'CELESTOPOL.Armure.unequip'}}{{else}}{{localize 'CELESTOPOL.Armure.equip'}}{{/if}}">
|
||||||
|
<i class="fas fa-shield{{#unless item.system.equipped}}-halved{{/unless}}"></i>
|
||||||
|
</a>
|
||||||
<a data-action="edit" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
<a data-action="edit" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||||
{{#if ../isEditMode}}<a data-action="delete" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>{{/if}}
|
{{#if ../isEditMode}}<a data-action="delete" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,72 +3,53 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{localize "CELESTOPOL.Faction.label"}}</th>
|
<th>{{localize "CELESTOPOL.Faction.label"}}</th>
|
||||||
<th>{{localize "CELESTOPOL.Faction.score"}}</th>
|
<th>{{localize "CELESTOPOL.Faction.relation"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#each factions as |faction factionId|}}
|
{{!-- Factions standard --}}
|
||||||
<tr class="faction-row" data-faction="{{factionId}}">
|
{{#each factionRows as |faction|}}
|
||||||
|
<tr class="faction-row" data-faction="{{faction.id}}">
|
||||||
<td class="faction-name">{{localize faction.label}}</td>
|
<td class="faction-name">{{localize faction.label}}</td>
|
||||||
<td class="faction-value">
|
<td class="faction-value">
|
||||||
<div class="faction-checkboxes-container">
|
<div class="faction-checkboxes-container">
|
||||||
<div class="faction-checkboxes">
|
<div class="faction-checkboxes">
|
||||||
{{#each (range 9) as |level|}}
|
{{#each faction.dots as |dot|}}
|
||||||
<span class="faction-dot {{#if (lte level (lookup @root.system.factions factionId 'value'))}}filled{{/if}}"
|
<span class="faction-dot {{dot.type}} {{#if dot.filled}}filled{{/if}}"
|
||||||
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{factionId}}" data-index="{{level}}"{{/if}}></span>
|
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{../id}}" data-index="{{dot.index}}"{{/if}}></span>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<span class="faction-count">{{lookup @root.system.factions factionId 'value'}}</span>
|
<span class="faction-count">{{faction.valueStr}}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
{{!-- Factions personnalisées --}}
|
{{!-- Factions personnalisées --}}
|
||||||
<tr class="faction-row custom" data-faction="perso1">
|
{{#each factionCustom as |faction|}}
|
||||||
|
<tr class="faction-row custom" data-faction="{{faction.id}}">
|
||||||
<td>
|
<td>
|
||||||
{{#if isEditMode}}
|
{{#if @root.isEditMode}}
|
||||||
<input type="text" name="system.factions.perso1.label"
|
<input type="text" name="system.factions.{{faction.id}}.label"
|
||||||
value="{{system.factions.perso1.label}}"
|
value="{{faction.label}}"
|
||||||
placeholder="{{localize 'CELESTOPOL.Faction.custom'}}">
|
placeholder="{{localize 'CELESTOPOL.Faction.custom'}}">
|
||||||
{{else}}
|
{{else}}
|
||||||
<span>{{#if system.factions.perso1.label}}{{system.factions.perso1.label}}{{else}}—{{/if}}</span>
|
<span>{{#if faction.label}}{{faction.label}}{{else}}—{{/if}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="faction-checkboxes-container">
|
<div class="faction-checkboxes-container">
|
||||||
<div class="faction-checkboxes">
|
<div class="faction-checkboxes">
|
||||||
{{#each (range 9) as |level|}}
|
{{#each faction.dots as |dot|}}
|
||||||
<span class="faction-dot {{#if (lte level ../system.factions.perso1.value)}}filled{{/if}}"
|
<span class="faction-dot {{dot.type}} {{#if dot.filled}}filled{{/if}}"
|
||||||
{{#if ../isEditable}}data-action="factionLevel" data-faction="perso1" data-index="{{level}}"{{/if}}></span>
|
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{../id}}" data-index="{{dot.index}}"{{/if}}></span>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<span class="faction-count">{{system.factions.perso1.value}}</span>
|
<span class="faction-count">{{faction.valueStr}}</span>
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="faction-row custom" data-faction="perso2">
|
|
||||||
<td>
|
|
||||||
{{#if isEditMode}}
|
|
||||||
<input type="text" name="system.factions.perso2.label"
|
|
||||||
value="{{system.factions.perso2.label}}"
|
|
||||||
placeholder="{{localize 'CELESTOPOL.Faction.custom'}}">
|
|
||||||
{{else}}
|
|
||||||
<span>{{#if system.factions.perso2.label}}{{system.factions.perso2.label}}{{else}}—{{/if}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="faction-checkboxes-container">
|
|
||||||
<div class="faction-checkboxes">
|
|
||||||
{{#each (range 9) as |level|}}
|
|
||||||
<span class="faction-dot {{#if (lte level ../system.factions.perso2.value)}}filled{{/if}}"
|
|
||||||
{{#if ../isEditable}}data-action="factionLevel" data-faction="perso2" data-index="{{level}}"{{/if}}></span>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
<span class="faction-count">{{system.factions.perso2.value}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{/each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -82,10 +82,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/with}}
|
{{/with}}
|
||||||
|
{{#if system.armorMalus}}
|
||||||
|
<div class="header-stat armor-malus-badge">
|
||||||
|
<label>{{localize "CELESTOPOL.Armure.malusLabel"}}</label>
|
||||||
|
<span class="armor-malus-value">{{system.armorMalus}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
|
<a class="moon-standalone-btn" data-action="rollMoonDie" title="{{localize 'CELESTOPOL.Moon.standaloneTitle'}}">
|
||||||
|
🌙
|
||||||
|
</a>
|
||||||
<a class="toggle-sheet" data-action="toggleSheet" title="{{#if isEditMode}}{{localize 'CELESTOPOL.Sheet.playMode'}}{{else}}{{localize 'CELESTOPOL.Sheet.editMode'}}{{/if}}">
|
<a class="toggle-sheet" data-action="toggleSheet" title="{{#if isEditMode}}{{localize 'CELESTOPOL.Sheet.playMode'}}{{else}}{{localize 'CELESTOPOL.Sheet.editMode'}}{{/if}}">
|
||||||
<i class="fas {{#if isEditMode}}fa-eye{{else}}fa-edit{{/if}}"></i>
|
<i class="fas {{#if isEditMode}}fa-eye{{else}}fa-edit{{/if}}"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
27
templates/chat-initiative.hbs
Normal file
27
templates/chat-initiative.hbs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<div class="celestopol chat-initiative">
|
||||||
|
|
||||||
|
{{!-- En-tête : acteur --}}
|
||||||
|
<div class="roll-header">
|
||||||
|
{{#if actorImg}}
|
||||||
|
<img src="{{actorImg}}" class="actor-img" alt="{{actorName}}">
|
||||||
|
{{/if}}
|
||||||
|
<div class="roll-info">
|
||||||
|
<span class="actor-name">{{actorName}}</span>
|
||||||
|
<span class="skill-info">
|
||||||
|
<span class="skill-lbl">⚡ {{localize "CELESTOPOL.Combat.initiative"}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Bandeau initiative --}}
|
||||||
|
<div class="initiative-banner">
|
||||||
|
<div class="initiative-score-wrap">
|
||||||
|
<span class="initiative-icon">⚡</span>
|
||||||
|
<span class="initiative-score">{{value}}</span>
|
||||||
|
</div>
|
||||||
|
{{#if detail}}
|
||||||
|
<span class="initiative-detail">{{detail}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -71,7 +71,8 @@
|
|||||||
<span class="fl-total">{{total}}</span>
|
<span class="fl-total">{{total}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{!-- Seuil et marge --}}
|
{{!-- Seuil et marge (masqué en opposition) --}}
|
||||||
|
{{#unless isOpposition}}
|
||||||
<div class="threshold-line">
|
<div class="threshold-line">
|
||||||
<span class="vs-wrap">
|
<span class="vs-wrap">
|
||||||
<span class="vs-label">vs</span>
|
<span class="vs-label">vs</span>
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
{{!-- Infos bonus (Destin, Fortune, Aspect) --}}
|
{{!-- Infos bonus (Destin, Fortune, Aspect) --}}
|
||||||
{{#if useDestin}}
|
{{#if useDestin}}
|
||||||
@@ -119,22 +121,14 @@
|
|||||||
{{#if autoSuccess}}
|
{{#if autoSuccess}}
|
||||||
<span class="result-icon">★</span>
|
<span class="result-icon">★</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.autoSuccess"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.autoSuccess"}}</span>
|
||||||
|
{{else if isOpposition}}
|
||||||
|
<span class="result-icon">⚔</span>
|
||||||
|
<span class="result-label">{{localize "CELESTOPOL.Roll.opposition"}}</span>
|
||||||
|
<span class="result-desc">{{localize "CELESTOPOL.Roll.oppositionResolved"}}</span>
|
||||||
{{else if isTie}}
|
{{else if isTie}}
|
||||||
<span class="result-icon">⚖</span>
|
<span class="result-icon">⚖</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Combat.tie"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Combat.tie"}}</span>
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.tieDesc"}}</span>
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.tieDesc"}}</span>
|
||||||
{{else if isCriticalSuccess}}
|
|
||||||
<span class="result-icon">✦✦</span>
|
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalSuccess"}}</span>
|
|
||||||
{{#if isCombat}}
|
|
||||||
{{#if isRangedDefense}}
|
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseSuccess"}}</span>
|
|
||||||
{{else}}
|
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.successHit"}}{{#if (gt weaponDegats "0")}} +{{weaponDegats}} {{localize "CELESTOPOL.Combat.weaponDamage"}}{{/if}}</span>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalSuccessDesc"}}</span>
|
|
||||||
{{/if}}
|
|
||||||
{{else if isSuccess}}
|
{{else if isSuccess}}
|
||||||
<span class="result-icon">✦</span>
|
<span class="result-icon">✦</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.success"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.success"}}</span>
|
||||||
@@ -145,20 +139,6 @@
|
|||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.successHit"}}{{#if (gt weaponDegats "0")}} +{{weaponDegats}} {{localize "CELESTOPOL.Combat.weaponDamage"}}{{/if}}</span>
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.successHit"}}{{#if (gt weaponDegats "0")}} +{{weaponDegats}} {{localize "CELESTOPOL.Combat.weaponDamage"}}{{/if}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else if isCriticalFailure}}
|
|
||||||
<span class="result-icon">✖✖</span>
|
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.criticalFailure"}}</span>
|
|
||||||
{{#if isCombat}}
|
|
||||||
{{#if (eq weaponType "melee")}}
|
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.failureHit"}}</span>
|
|
||||||
{{else if isRangedDefense}}
|
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseFailure"}}</span>
|
|
||||||
{{else}}
|
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.distanceNoWound"}}</span>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Roll.criticalFailureDesc"}}</span>
|
|
||||||
{{/if}}
|
|
||||||
{{else if isFailure}}
|
{{else if isFailure}}
|
||||||
<span class="result-icon">✖</span>
|
<span class="result-icon">✖</span>
|
||||||
<span class="result-label">{{localize "CELESTOPOL.Roll.failure"}}</span>
|
<span class="result-label">{{localize "CELESTOPOL.Roll.failure"}}</span>
|
||||||
|
|||||||
39
templates/moon-standalone.hbs
Normal file
39
templates/moon-standalone.hbs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="celestopol-roll moon-standalone-card">
|
||||||
|
|
||||||
|
{{!-- En-tête --}}
|
||||||
|
<div class="moon-standalone-header">
|
||||||
|
<span class="moon-standalone-title">{{localize "CELESTOPOL.Moon.standaloneTitle"}}</span>
|
||||||
|
{{#if actorName}}
|
||||||
|
<span class="moon-standalone-actor">— {{actorName}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Résultat principal : phase + valeur --}}
|
||||||
|
<div class="moon-standalone-main {{moonResultClass}}">
|
||||||
|
<span class="moon-standalone-symbol">{{moonFaceSymbol}}</span>
|
||||||
|
<div class="moon-standalone-info">
|
||||||
|
<span class="moon-standalone-phase">{{moonFaceLabel}}</span>
|
||||||
|
<span class="moon-standalone-value">{{localize "CELESTOPOL.Moon.quantiteHint"}} : {{result}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Interprétation Chance --}}
|
||||||
|
<div class="moon-interpret-row">
|
||||||
|
<span class="moon-interpret-label">{{localize "CELESTOPOL.Moon.chanceInterpret"}}</span>
|
||||||
|
{{#if isGoodFortune}}
|
||||||
|
<span class="moon-fortune bonne-fortune">{{localize "CELESTOPOL.Moon.bonneFortune"}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="moon-fortune mauvaise-fortune">{{localize "CELESTOPOL.Moon.mauvaiseFortune"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Interprétation Narrative --}}
|
||||||
|
<div class="moon-die-result {{moonResultClass}}">
|
||||||
|
<div class="moon-die-info">
|
||||||
|
<span class="moon-interpret-label">{{localize "CELESTOPOL.Moon.narrativeInterpret"}}</span>
|
||||||
|
<span class="moon-die-type">{{moonResultLabel}}</span>
|
||||||
|
<span class="moon-die-desc">{{moonResultDesc}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
48
templates/npc-biographie.hbs
Normal file
48
templates/npc-biographie.hbs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<div class="tab biographie {{tab.cssClass}}" data-group="sheet" data-tab="biographie">
|
||||||
|
|
||||||
|
{{!-- Faction --}}
|
||||||
|
<div class="bio-section faction-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-flag"></i>
|
||||||
|
<span>{{localize "CELESTOPOL.NPC.faction"}}</span>
|
||||||
|
</div>
|
||||||
|
{{#if isEditMode}}
|
||||||
|
<div class="form-row-line faction-select-row">
|
||||||
|
<label for="system.faction">{{localize "CELESTOPOL.NPC.factionLabel"}}</label>
|
||||||
|
<select name="system.faction" id="faction-select">
|
||||||
|
<option value="" {{#unless system.faction}}selected{{/unless}}>— {{localize "CELESTOPOL.NPC.factionNone"}} —</option>
|
||||||
|
{{#each factions as |faction key|}}
|
||||||
|
<option value="{{key}}" {{#if (eq key ../system.faction)}}selected{{/if}}>{{localize faction.label}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="faction-display">
|
||||||
|
{{#if system.faction}}
|
||||||
|
<span class="faction-name">{{localize (lookup (lookup factions system.faction) 'label')}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="faction-none">{{localize "CELESTOPOL.NPC.factionNone"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Histoire --}}
|
||||||
|
<div class="bio-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-scroll"></i>
|
||||||
|
<span>{{localize "CELESTOPOL.NPC.histoire"}}</span>
|
||||||
|
</div>
|
||||||
|
{{formInput systemFields.histoire enriched=enrichedHistoire value=system.histoire name="system.histoire" toggled=true}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Description Physique --}}
|
||||||
|
<div class="bio-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
<span>{{localize "CELESTOPOL.NPC.descriptionPhysique"}}</span>
|
||||||
|
</div>
|
||||||
|
{{formInput systemFields.descriptionPhysique enriched=enrichedDescriptionPhysique value=system.descriptionPhysique name="system.descriptionPhysique" toggled=true}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
<div class="tab blessures {{tab.cssClass}}" data-group="sheet" data-tab="blessures">
|
<div class="tab blessures {{tab.cssClass}}" data-group="sheet" data-tab="blessures">
|
||||||
<section class="track-section">
|
<section class="track-section">
|
||||||
<div class="track-header">
|
<div class="track-header">
|
||||||
<span class="track-title">{{localize "CELESTOPOL.Track.blessures"}}</span>
|
<span class="track-title">
|
||||||
|
{{localize "CELESTOPOL.Track.blessures"}}
|
||||||
|
<span class="track-help" title="{{localize 'CELESTOPOL.Track.blessuresTooltip'}}">?</span>
|
||||||
|
</span>
|
||||||
|
<span class="wound-malus">{{localize "CELESTOPOL.Track.currentMalus"}} :
|
||||||
|
<strong>{{lookup @root.woundLevels system.blessures.lvl 'malus'}}</strong>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-boxes">
|
<div class="track-boxes">
|
||||||
{{#each (range 8) as |lvl|}}
|
{{#each (range 8) as |lvl|}}
|
||||||
@@ -17,8 +23,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{!-- Description --}}
|
{{!-- Notes (libres) --}}
|
||||||
<div class="description-section">
|
<div class="description-section notes-section">
|
||||||
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
<div class="section-header"><i class="fas fa-note-sticky"></i> {{localize "CELESTOPOL.Actor.notes"}}</div>
|
||||||
|
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,37 +1,65 @@
|
|||||||
<div class="tab competences {{tab.cssClass}}" data-group="sheet" data-tab="competences">
|
<div class="tab competences {{tab.cssClass}}" data-group="sheet" data-tab="competences">
|
||||||
<div class="stats-grid">
|
|
||||||
|
{{!-- Grille des 4 domaines --}}
|
||||||
|
<div class="npc-domains-grid">
|
||||||
{{#each stats as |stat statId|}}
|
{{#each stats as |stat statId|}}
|
||||||
<div class="stat-block">
|
<div class="npc-domain-block">
|
||||||
<div class="stat-header">
|
<div class="npc-domain-header">
|
||||||
<span class="stat-name">{{localize stat.label}}</span>
|
{{!-- Double label : Âme / Emprise --}}
|
||||||
<div class="stat-res">
|
<div class="npc-domain-labels">
|
||||||
<label>{{localize "CELESTOPOL.Stat.res"}}</label>
|
<span class="domain-label-primary">{{localize (lookup ../domainLabels statId)}}</span>
|
||||||
{{#if ../isEditMode}}
|
{{#if (eq ../system.npcType "antagoniste")}}
|
||||||
<input type="number" name="system.stats.{{statId}}.res"
|
<span class="domain-label-secondary">{{localize (lookup ../stats statId 'label')}}</span>
|
||||||
value="{{lookup ../system.stats statId 'res'}}" min="0" max="8">
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="stat-res-value">
|
<span class="domain-label-secondary">{{localize (lookup ../antagonisteStats statId 'label')}}</span>
|
||||||
{{lookup ../system.stats statId 'actuel'}} / {{lookup ../system.stats statId 'res'}}
|
|
||||||
</span>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{!-- Valeur du domaine --}}
|
||||||
<div class="skills-list">
|
<div class="npc-domain-value-wrap">
|
||||||
{{#each (lookup ../skills statId) as |skill skillId|}}
|
|
||||||
<div class="skill-row {{#unless ../isEditMode}}rollable{{/unless}}"
|
|
||||||
data-stat-id="{{statId}}" data-skill-id="{{skillId}}">
|
|
||||||
<span class="skill-name">{{localize skill.label}}</span>
|
|
||||||
{{#if ../isEditMode}}
|
{{#if ../isEditMode}}
|
||||||
<input type="number" name="system.stats.{{statId}}.{{skillId}}.value"
|
<input type="number" name="system.stats.{{statId}}.res"
|
||||||
value="{{lookup (lookup ../system.stats statId) skillId 'value'}}"
|
value="{{lookup ../system.stats statId 'res'}}" min="0" max="8" class="domain-value-input">
|
||||||
min="0" max="8" class="skill-value-input">
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="skill-value">{{lookup (lookup ../system.stats statId) skillId 'value'}}</span>
|
<div class="npc-domain-roll-btn rollable" data-stat-id="{{statId}}"
|
||||||
|
title="{{localize 'CELESTOPOL.Roll.clickToRoll'}}">
|
||||||
|
<i class="fas fa-dice-d8 domain-die-icon"></i>
|
||||||
|
<span class="domain-value">{{lookup ../system.stats statId 'actuel'}}</span>
|
||||||
|
<span class="domain-value-base">/{{lookup ../system.stats statId 'res'}}</span>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{!-- Aspects --}}
|
||||||
|
{{#if (or aspects.length isEditMode)}}
|
||||||
|
<div class="npc-aspects-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-star"></i>
|
||||||
|
<span>{{localize "CELESTOPOL.Tab.aspects"}}</span>
|
||||||
|
{{#if isEditMode}}
|
||||||
|
<a data-action="createAspect" title="{{localize 'CELESTOPOL.Item.newAspect'}}"><i class="fas fa-plus"></i></a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{#each aspects as |item|}}
|
||||||
|
<div class="item-row aspect-row" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
|
||||||
|
<span class="item-name">{{item.name}}</span>
|
||||||
|
<span class="aspect-value {{#if (gt item.system.valeur 0)}}positive{{else if (lt item.system.valeur 0)}}negative{{/if}}">
|
||||||
|
{{#if (gt item.system.valeur 0)}}+{{/if}}{{item.system.valeur}}
|
||||||
|
</span>
|
||||||
|
<div class="item-controls">
|
||||||
|
<a data-action="edit" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||||
|
{{#if ../isEditMode}}<a data-action="delete" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{#unless ../isEditMode}}
|
||||||
|
<p class="equip-empty">{{localize "CELESTOPOL.Item.noAspects"}}</p>
|
||||||
|
{{/unless}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
58
templates/npc-equipement.hbs
Normal file
58
templates/npc-equipement.hbs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<div class="tab equipement {{tab.cssClass}}" data-group="sheet" data-tab="equipement">
|
||||||
|
|
||||||
|
{{!-- ── Armes ─────────────────────────────────────────────────────────── --}}
|
||||||
|
<div class="equip-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-khanda"></i>
|
||||||
|
<span>{{localize "CELESTOPOL.Item.weapons"}}</span>
|
||||||
|
{{#if isEditMode}}
|
||||||
|
<a data-action="createWeapon" title="{{localize 'CELESTOPOL.Item.newWeapon'}}"><i class="fas fa-plus"></i></a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{#each weapons as |item|}}
|
||||||
|
<div class="item-row weapon" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
|
||||||
|
<img src="{{item.img}}" class="item-icon">
|
||||||
|
<span class="item-name">{{item.name}}</span>
|
||||||
|
<span class="item-tag type">{{#if (eq item.system.type "melee")}}{{localize "CELESTOPOL.Weapon.typeMelee"}}{{else}}{{localize "CELESTOPOL.Weapon.typeDistance"}}{{/if}}</span>
|
||||||
|
<span class="item-tag dmg">{{localize "CELESTOPOL.Weapon.degats"}} {{item.system.degats}}</span>
|
||||||
|
<div class="item-controls">
|
||||||
|
<a data-action="edit" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||||
|
{{#if ../isEditMode}}<a data-action="delete" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<p class="equip-empty">{{localize "CELESTOPOL.Item.noWeapons"}}</p>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- ── Armures ───────────────────────────────────────────────────────── --}}
|
||||||
|
<div class="equip-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-shield-halved"></i>
|
||||||
|
<span>{{localize "CELESTOPOL.Item.armures"}}</span>
|
||||||
|
{{#if isEditMode}}
|
||||||
|
<a data-action="createArmure" title="{{localize 'CELESTOPOL.Item.newArmure'}}"><i class="fas fa-plus"></i></a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{#each armures as |item|}}
|
||||||
|
<div class="item-row armure {{#if item.system.equipped}}is-equipped{{/if}}" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
|
||||||
|
<img src="{{item.img}}" class="item-icon">
|
||||||
|
<span class="item-name">{{item.name}}</span>
|
||||||
|
<span class="item-tag prot"><i class="fas fa-shield"></i> {{item.system.protection}}</span>
|
||||||
|
{{#if item.system.malus}}<span class="item-tag malus">−{{item.system.malus}} {{localize "CELESTOPOL.Armure.malus"}}</span>{{/if}}
|
||||||
|
<div class="item-controls">
|
||||||
|
<a data-action="toggleArmure" data-item-uuid="{{item.uuid}}"
|
||||||
|
title="{{#if item.system.equipped}}{{localize 'CELESTOPOL.Armure.unequip'}}{{else}}{{localize 'CELESTOPOL.Armure.equip'}}{{/if}}"
|
||||||
|
class="equip-toggle {{#if item.system.equipped}}equipped{{/if}}">
|
||||||
|
<i class="fas fa-shield{{#unless item.system.equipped}}-halved{{/unless}}"></i>
|
||||||
|
</a>
|
||||||
|
<a data-action="edit" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||||
|
{{#if ../isEditMode}}<a data-action="delete" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<p class="equip-empty">{{localize "CELESTOPOL.Item.noArmures"}}</p>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -19,6 +19,22 @@
|
|||||||
<span class="concept-display">{{system.concept}}</span>
|
<span class="concept-display">{{system.concept}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{!-- Type PNJ (standard / antagoniste) --}}
|
||||||
|
<div class="npc-type-row">
|
||||||
|
{{#if isEditMode}}
|
||||||
|
<select name="system.npcType" class="npc-type-select">
|
||||||
|
{{#each npcTypes as |ntype key|}}
|
||||||
|
<option value="{{key}}" {{#if (eq key ../system.npcType)}}selected{{/if}}>{{localize ntype.label}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
{{else}}
|
||||||
|
{{#if (eq system.npcType "antagoniste")}}
|
||||||
|
<span class="npc-type-badge antagoniste">{{localize "CELESTOPOL.NPC.typeAntagoniste"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="header-stats-row">
|
<div class="header-stats-row">
|
||||||
<div class="header-stat">
|
<div class="header-stat">
|
||||||
<label>{{localize "CELESTOPOL.Actor.initiative"}}</label>
|
<label>{{localize "CELESTOPOL.Actor.initiative"}}</label>
|
||||||
@@ -49,9 +65,20 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/with}}
|
{{/with}}
|
||||||
|
|
||||||
|
{{!-- Badge malus armure équipée --}}
|
||||||
|
{{#if armorMalus}}
|
||||||
|
<div class="header-stat armor-malus-badge">
|
||||||
|
<label>{{localize "CELESTOPOL.Armure.malusLabel"}}</label>
|
||||||
|
<span>{{armorMalus}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
|
<a class="moon-standalone-btn" data-action="rollMoonDie" title="{{localize 'CELESTOPOL.Moon.standaloneTitle'}}">
|
||||||
|
🌙
|
||||||
|
</a>
|
||||||
<a data-action="toggleSheet">
|
<a data-action="toggleSheet">
|
||||||
<i class="fas {{#if isEditMode}}fa-eye{{else}}fa-edit{{/if}}"></i>
|
<i class="fas {{#if isEditMode}}fa-eye{{else}}fa-edit{{/if}}"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -27,38 +27,83 @@
|
|||||||
{{#if woundMalus}}
|
{{#if woundMalus}}
|
||||||
<span class="dminus"> − {{abs woundMalus}}</span>
|
<span class="dminus"> − {{abs woundMalus}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if armorMalus}}
|
||||||
|
<span class="dminus armor-malus"> − {{abs armorMalus}}</span>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if woundLabel}}
|
{{#if woundLabel}}
|
||||||
<div class="wound-info">⚠ {{woundLabel}}</div>
|
<div class="wound-info">⚠ {{woundLabel}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if armorMalus}}
|
||||||
|
<div class="wound-info armor-info">🛡 {{localize "CELESTOPOL.Roll.armorMalus"}}</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="roll-form-rows">
|
<div class="roll-form-rows">
|
||||||
|
|
||||||
{{!-- Difficulté : sélect standard OU input Corps PNJ en combat --}}
|
{{!-- Difficulté : Corps PNJ en combat, fixe 11 en test normal --}}
|
||||||
{{#if isCombat}}
|
{{#if isCombat}}
|
||||||
<div class="form-row-line form-corps-pnj">
|
|
||||||
<label for="corpsPnj">{{localize "CELESTOPOL.Combat.corpsPnj"}}</label>
|
{{!-- Sélecteur de cible PNJ (si des tokens NPCs sont disponibles) --}}
|
||||||
<input type="number" id="corpsPnj" name="corpsPnj" value="7" min="1" max="30" class="corps-pnj-input">
|
{{#if availableTargets.length}}
|
||||||
</div>
|
<div class="form-row-line form-target-row">
|
||||||
{{else}}
|
<label for="targetSelect"><i class="fas fa-crosshairs"></i> {{localize "CELESTOPOL.Combat.targetLabel"}}</label>
|
||||||
<div class="form-row-line">
|
<select id="targetSelect" name="targetSelect">
|
||||||
<label for="difficulty">{{localize "CELESTOPOL.Roll.difficulty"}}</label>
|
<option value="">— {{localize "CELESTOPOL.Combat.targetAuto"}} —</option>
|
||||||
<select id="difficulty" name="difficulty">
|
{{#each availableTargets as |t|}}
|
||||||
{{#each difficultyChoices as |diff key|}}
|
<option value="{{t.corps}}">{{t.name}}</option>
|
||||||
<option value="{{key}}" {{#if (eq key ../defaultDifficulty)}}selected{{/if}}>
|
|
||||||
{{localize diff.label}}{{#if diff.value}} ({{diff.value}}){{/if}}
|
|
||||||
</option>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="form-row-line form-corps-pnj" id="corps-pnj-row">
|
||||||
|
<label for="corpsPnj">{{localize "CELESTOPOL.Combat.corpsPnj"}}</label>
|
||||||
|
<input type="number" id="corpsPnj" name="corpsPnj" value="7" min="1" max="30" class="corps-pnj-input">
|
||||||
|
</div>
|
||||||
|
<div class="form-row-line form-target-confirmed" id="target-confirmed-row" style="display:none">
|
||||||
|
<span class="target-confirmed-badge"><i class="fas fa-crosshairs"></i> <span id="target-confirmed-name"></span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Modificateurs tir (distance uniquement) --}}
|
||||||
|
{{#if isRangedAttack}}
|
||||||
|
<div class="form-row-line form-ranged-mod">
|
||||||
|
<label for="rangedMod"><i class="fas fa-bullseye"></i> {{localize "CELESTOPOL.Combat.rangedMod"}}</label>
|
||||||
|
<select id="rangedMod" name="rangedMod">
|
||||||
|
{{#each rangedModChoices as |choice|}}
|
||||||
|
<option value="{{choice.value}}" {{#if (eq choice.id "none")}}selected{{/if}}>{{choice.label}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
<div class="form-row-line form-threshold-fixed">
|
||||||
|
<label>{{localize "CELESTOPOL.Roll.threshold"}}</label>
|
||||||
|
<span class="threshold-value" id="threshold-display">11</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- Test en opposition : le résultat sera masqué, MJ décide --}}
|
||||||
|
{{#unless isResistance}}
|
||||||
|
<div class="form-opposition-row">
|
||||||
|
<label class="opposition-toggle" for="isOpposition">
|
||||||
|
<input type="checkbox" id="isOpposition" name="isOpposition">
|
||||||
|
<span class="opposition-icon">⚔</span>
|
||||||
|
<span class="opposition-text">
|
||||||
|
<span class="opposition-main">{{localize "CELESTOPOL.Roll.opposition"}}</span>
|
||||||
|
<span class="opposition-sub">{{localize "CELESTOPOL.Roll.oppositionDesc"}}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{!-- Options non disponibles en test de résistance --}}
|
{{!-- Options non disponibles en test de résistance --}}
|
||||||
{{#unless isResistance}}
|
{{#unless isResistance}}
|
||||||
|
|
||||||
{{!-- Modificateur & Aspect côte à côte --}}
|
{{!-- Modificateur & Aspect côte à côte (tests normaux) --}}
|
||||||
<div class="form-two-col">
|
<div class="form-two-col">
|
||||||
<div class="form-row-line">
|
<div class="form-row-line">
|
||||||
<label for="modifier">{{localize "CELESTOPOL.Roll.modifier"}}</label>
|
<label for="modifier">{{localize "CELESTOPOL.Roll.modifier"}}</label>
|
||||||
@@ -78,6 +123,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
|
||||||
|
{{!-- En résistance : Bonus/Malus d'Aspect disponible --}}
|
||||||
|
<div class="form-row-line form-resistance-aspect">
|
||||||
|
<label for="aspectModifier">{{localize "CELESTOPOL.Roll.aspect"}}</label>
|
||||||
|
<select id="aspectModifier" name="aspectModifier">
|
||||||
|
{{#each aspectChoices as |choice|}}
|
||||||
|
<option value="{{choice.value}}" {{#if (eq choice.value 0)}}selected{{/if}}>{{choice.label}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{/unless}}{{!-- /isResistance aspect --}}
|
||||||
|
|
||||||
|
{{!-- Options non disponibles en test de résistance (lune, destin, puiser, fortune) --}}
|
||||||
|
{{#unless isResistance}}
|
||||||
|
|
||||||
{{!-- Dé de la Lune --}}
|
{{!-- Dé de la Lune --}}
|
||||||
<div class="form-moon-row">
|
<div class="form-moon-row">
|
||||||
<label class="moon-toggle" for="rollMoonDie">
|
<label class="moon-toggle" for="rollMoonDie">
|
||||||
@@ -132,7 +194,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{/unless}}{{!-- /isResistance --}}
|
{{/unless}}{{!-- /isResistance (lune, destin, puiser, fortune) --}}
|
||||||
|
|
||||||
{{!-- Modificateur de situation (-8 à +8) — tous les jets --}}
|
{{!-- Modificateur de situation (-8 à +8) — tous les jets --}}
|
||||||
<div class="form-row-line form-situation-mod">
|
<div class="form-row-line form-situation-mod">
|
||||||
|
|||||||
Reference in New Issue
Block a user