Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a2be0cc0e | |||
| 3358dea306 |
BIN
assets/ui/help/aide-aspects-dialogue.png
Normal file
BIN
assets/ui/help/aide-aspects-dialogue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
assets/ui/help/aide-combat-chat.png
Normal file
BIN
assets/ui/help/aide-combat-chat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 KiB |
BIN
assets/ui/help/aide-domaines.png
Normal file
BIN
assets/ui/help/aide-domaines.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
BIN
assets/ui/help/aide-factions.png
Normal file
BIN
assets/ui/help/aide-factions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
assets/ui/help/aide-jet.png
Normal file
BIN
assets/ui/help/aide-jet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
@@ -1,6 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* fvtt-celestopol.mjs — Point d'entrée principal du système Célestopol 1922
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
* FoundryVTT v13+ / DataModels / ApplicationV2
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SYSTEM, SYSTEM_ID, ASCII } from "./module/config/system.mjs"
|
import { SYSTEM, SYSTEM_ID, ASCII } from "./module/config/system.mjs"
|
||||||
@@ -30,6 +38,10 @@ import {
|
|||||||
CelestopolArmureSheet,
|
CelestopolArmureSheet,
|
||||||
} from "./module/applications/_module.mjs"
|
} from "./module/applications/_module.mjs"
|
||||||
|
|
||||||
|
const DAMAGE_APPLICATION_FLAG = "damageApplication"
|
||||||
|
const FACTION_ASPECT_STATE_SETTING = "factionAspectState"
|
||||||
|
const WELCOME_SCENE_IMPORTED_SETTING = "welcomeSceneImported"
|
||||||
|
|
||||||
/* ─── Init hook ──────────────────────────────────────────────────────────── */
|
/* ─── Init hook ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
Hooks.once("init", () => {
|
Hooks.once("init", () => {
|
||||||
@@ -52,6 +64,11 @@ Hooks.once("init", () => {
|
|||||||
game.celestopol = {
|
game.celestopol = {
|
||||||
SYSTEM,
|
SYSTEM,
|
||||||
rollMoonStandalone: (actor = null) => CelestopolRoll.rollMoonStandalone(actor),
|
rollMoonStandalone: (actor = null) => CelestopolRoll.rollMoonStandalone(actor),
|
||||||
|
manageFactionAspects: (actor = null) => _manageFactionAspects(actor),
|
||||||
|
getFactionAspectState: () => _getFactionAspectState(),
|
||||||
|
getFactionAspectSummary: (actor = null) => _getFactionAspectSummary(actor),
|
||||||
|
getFactionDisplayLabel: (value) => _getFactionDisplayLabel(value),
|
||||||
|
normalizeFactionId: (value) => _normalizeFactionId(value),
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── DataModels ──────────────────────────────────────────────────────────
|
// ── DataModels ──────────────────────────────────────────────────────────
|
||||||
@@ -140,7 +157,7 @@ Hooks.once("init", () => {
|
|||||||
|
|
||||||
/* ─── Ready hook ─────────────────────────────────────────────────────────── */
|
/* ─── Ready hook ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
Hooks.once("ready", () => {
|
Hooks.once("ready", async () => {
|
||||||
console.log(`${SYSTEM_ID} | System ready`)
|
console.log(`${SYSTEM_ID} | System ready`)
|
||||||
|
|
||||||
// Socket handler for GM-only operations (e.g. wound application)
|
// Socket handler for GM-only operations (e.g. wound application)
|
||||||
@@ -148,12 +165,25 @@ Hooks.once("ready", () => {
|
|||||||
game.socket.on(`system.${SYSTEM_ID}`, _onSocketMessage)
|
game.socket.on(`system.${SYSTEM_ID}`, _onSocketMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Hooks.on("renderChatMessageHTML", (message, html) => {
|
||||||
|
_activateChatCardListeners(message, html)
|
||||||
|
})
|
||||||
|
Hooks.on("updateChatMessage", (message, changed) => {
|
||||||
|
if (foundry.utils.hasProperty(changed, `flags.${SYSTEM_ID}.${DAMAGE_APPLICATION_FLAG}`)) {
|
||||||
|
_updateRenderedChatMessageState(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
_activateExistingChatCards()
|
||||||
|
|
||||||
// Migration : supprime les items de types obsolètes (ex: "attribute")
|
// Migration : supprime les items de types obsolètes (ex: "attribute")
|
||||||
if (game.user.isGM) {
|
if (game.user.isGM) {
|
||||||
_migrateObsoleteItems()
|
_migrateObsoleteItems()
|
||||||
_migrateIntegerTracks()
|
_migrateIntegerTracks()
|
||||||
_setupAnomaliesFolder()
|
_setupAnomaliesFolder()
|
||||||
|
await _setupWelcomeScene()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _createWelcomeChatMessage()
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Supprime les items dont le type n'est plus reconnu par le système. */
|
/** Supprime les items dont le type n'est plus reconnu par le système. */
|
||||||
@@ -317,6 +347,85 @@ function _registerSettings() {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
game.settings.register(SYSTEM_ID, FACTION_ASPECT_STATE_SETTING, {
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: Object,
|
||||||
|
default: _getDefaultFactionAspectState(),
|
||||||
|
})
|
||||||
|
game.settings.register(SYSTEM_ID, WELCOME_SCENE_IMPORTED_SETTING, {
|
||||||
|
scope: "world",
|
||||||
|
config: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _createWelcomeChatMessage() {
|
||||||
|
const activeGM = game.users.activeGM
|
||||||
|
if (!game.user.isGM || (activeGM && activeGM.id !== game.user.id)) return
|
||||||
|
|
||||||
|
const helpPack = game.packs.get(`${SYSTEM_ID}.aides-de-jeu`)
|
||||||
|
const helpDocs = helpPack ? await helpPack.getDocuments() : []
|
||||||
|
const helpEntry = helpDocs[0] ?? null
|
||||||
|
const helpReference = helpEntry
|
||||||
|
? `@UUID[${helpEntry.uuid}]{${helpEntry.name}}`
|
||||||
|
: `**${helpPack?.metadata?.label ?? game.i18n.localize("CELESTOPOL.Welcome.helpFallback")}**`
|
||||||
|
const rawContent = `
|
||||||
|
<div class="cel-welcome-message chat-system-card">
|
||||||
|
<div class="welcome-header">
|
||||||
|
<span class="welcome-mark">✦</span>
|
||||||
|
<span class="welcome-title">${game.i18n.localize("CELESTOPOL.Welcome.title")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-body">
|
||||||
|
<p>${game.i18n.localize("CELESTOPOL.Welcome.intro")}</p>
|
||||||
|
<div class="welcome-note">
|
||||||
|
<span class="welcome-label">${game.i18n.localize("CELESTOPOL.Welcome.helpLabel")}</span>
|
||||||
|
<span class="welcome-value">${game.i18n.format("CELESTOPOL.Welcome.helpCompendium", { help: helpReference })}</span>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-note">
|
||||||
|
<span class="welcome-label">${game.i18n.localize("CELESTOPOL.Welcome.bookLabel")}</span>
|
||||||
|
<span class="welcome-value"><a href="https://antre-monde.com/produit/celestopol-1922-le-jeu-de-role-livre-de-base/" target="_blank" rel="noopener noreferrer">${game.i18n.localize("CELESTOPOL.Welcome.bookLinkLabel")}</a></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
await ChatMessage.create({
|
||||||
|
style: CONST.CHAT_MESSAGE_STYLES.OOC,
|
||||||
|
speaker: { alias: game.system.title },
|
||||||
|
content: await foundry.applications.ux.TextEditor.implementation.enrichHTML(rawContent, { async: true }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _setupWelcomeScene() {
|
||||||
|
const activeGM = game.users.activeGM
|
||||||
|
if (!game.user.isGM || (activeGM && activeGM.id !== game.user.id)) return
|
||||||
|
if (game.settings.get(SYSTEM_ID, WELCOME_SCENE_IMPORTED_SETTING)) return
|
||||||
|
|
||||||
|
const sceneName = "Accueil Celestopol 1922"
|
||||||
|
let scene = game.scenes.getName(sceneName)
|
||||||
|
|
||||||
|
if (!scene) {
|
||||||
|
const pack = game.packs.get(`${SYSTEM_ID}.scenes`)
|
||||||
|
if (!pack) {
|
||||||
|
console.warn(`${SYSTEM_ID} | Compendium de scènes introuvable`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await pack.getDocuments()
|
||||||
|
const sourceScene = docs.find(doc => doc.name === sceneName)
|
||||||
|
if (!sourceScene) {
|
||||||
|
console.warn(`${SYSTEM_ID} | Scène d'accueil introuvable dans le compendium`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scene = await Scene.create(sourceScene.toObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
await scene.activate()
|
||||||
|
await scene.view()
|
||||||
|
await game.settings.set(SYSTEM_ID, WELCOME_SCENE_IMPORTED_SETTING, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Template preload ───────────────────────────────────────────────────── */
|
/* ─── Template preload ───────────────────────────────────────────────────── */
|
||||||
@@ -349,17 +458,702 @@ function _preloadTemplates() {
|
|||||||
|
|
||||||
/* ─── Socket handler ─────────────────────────────────────────────────────── */
|
/* ─── Socket handler ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function _onSocketMessage(data) {
|
async function _onSocketMessage(data) {
|
||||||
if (!game.user.isGM) return
|
const activeGM = game.users.activeGM
|
||||||
|
if (!game.user.isGM || (activeGM && activeGM.id !== game.user.id)) return
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "applyWound": {
|
case "applyWound": {
|
||||||
const actor = game.actors.get(data.actorId)
|
const actor = game.actors.get(data.actorId)
|
||||||
if (actor) actor.update({ "system.blessures.lvl": data.level })
|
if (actor) await actor.update({ "system.blessures.lvl": data.level })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "applyWeaponDamage": {
|
||||||
|
await _applyWeaponDamage(data)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _getChatHtmlRoot(html) {
|
||||||
|
if (html instanceof HTMLElement) return html
|
||||||
|
if (html?.[0] instanceof HTMLElement) return html[0]
|
||||||
|
if (html?.element instanceof HTMLElement) return html.element
|
||||||
|
if (html?.element?.[0] instanceof HTMLElement) return html.element[0]
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function _activateExistingChatCards() {
|
||||||
|
document.querySelectorAll(".message[data-message-id]").forEach(messageEl => {
|
||||||
|
const messageId = messageEl.dataset.messageId
|
||||||
|
const message = game.messages.get(messageId)
|
||||||
|
const root = messageEl.querySelector(".celestopol.chat-roll")
|
||||||
|
if (!message || !root) return
|
||||||
|
_activateChatCardListeners(message, root)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function _activateChatCardListeners(message, html) {
|
||||||
|
const root = _getChatHtmlRoot(html)
|
||||||
|
if (!root) return
|
||||||
|
|
||||||
|
_renderWeaponDamageState(message, root)
|
||||||
|
|
||||||
|
root.querySelectorAll('[data-action="apply-weapon-damage"]').forEach(button => {
|
||||||
|
if (button.dataset.bound === "true") return
|
||||||
|
button.dataset.bound = "true"
|
||||||
|
button.addEventListener("click", event => _onApplyWeaponDamageClick(event, message))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _onApplyWeaponDamageClick(event, message) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const button = event.currentTarget
|
||||||
|
const card = button.closest(".celestopol.chat-roll")
|
||||||
|
const select = button.closest(".weapon-damage-actions")?.querySelector('select[name="targetActorId"]')
|
||||||
|
const actorId = button.dataset.actorId || select?.value || ""
|
||||||
|
const incomingWounds = Number.parseInt(button.dataset.incomingWounds ?? "", 10)
|
||||||
|
const currentState = _getDamageApplicationState(message)
|
||||||
|
|
||||||
|
if (currentState?.applied) {
|
||||||
|
if (card) _renderWeaponDamageState(message, card)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!actorId) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Combat.selectCharacterFirst"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(incomingWounds) || incomingWounds < 0) return
|
||||||
|
|
||||||
|
if (card) _renderPendingWeaponDamageState(card)
|
||||||
|
button.disabled = true
|
||||||
|
await _requestWeaponDamageApplication({
|
||||||
|
actorId,
|
||||||
|
incomingWounds,
|
||||||
|
chatMessageId: message?.id ?? null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _requestWeaponDamageApplication({ actorId, incomingWounds, chatMessageId = null }) {
|
||||||
|
if (game.user.isGM) {
|
||||||
|
return _applyWeaponDamage({ actorId, incomingWounds, chatMessageId })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!game.socket) return
|
||||||
|
|
||||||
|
game.socket.emit(`system.${SYSTEM_ID}`, {
|
||||||
|
type: "applyWeaponDamage",
|
||||||
|
actorId,
|
||||||
|
incomingWounds,
|
||||||
|
chatMessageId,
|
||||||
|
})
|
||||||
|
|
||||||
|
ui.notifications.info(game.i18n.localize("CELESTOPOL.Combat.damageRequestSent"))
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getDamageApplicationState(message) {
|
||||||
|
return message?.getFlag(SYSTEM_ID, DAMAGE_APPLICATION_FLAG) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updateRenderedChatMessageState(message) {
|
||||||
|
const root = document.querySelector(`.message[data-message-id="${message.id}"] .celestopol.chat-roll`)
|
||||||
|
if (!root) return
|
||||||
|
_renderWeaponDamageState(message, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _removeDamageStatus(root) {
|
||||||
|
root.querySelector(".damage-application-status")?.remove()
|
||||||
|
root.querySelector(".weapon-damage-summary")?.classList.remove("is-applied", "is-pending")
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setDamageStatus(root, { text, cssClass = "" }) {
|
||||||
|
const summary = root.querySelector(".weapon-damage-summary")
|
||||||
|
if (!summary) return
|
||||||
|
|
||||||
|
_removeDamageStatus(root)
|
||||||
|
summary.classList.add(cssClass)
|
||||||
|
|
||||||
|
const status = document.createElement("div")
|
||||||
|
status.className = `damage-application-status ${cssClass}`.trim()
|
||||||
|
status.textContent = text
|
||||||
|
summary.append(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _renderPendingWeaponDamageState(root) {
|
||||||
|
const button = root.querySelector('[data-action="apply-weapon-damage"]')
|
||||||
|
const select = root.querySelector('select[name="targetActorId"]')
|
||||||
|
if (button) {
|
||||||
|
button.disabled = true
|
||||||
|
button.textContent = game.i18n.localize("CELESTOPOL.Combat.damageApplying")
|
||||||
|
}
|
||||||
|
if (select) select.disabled = true
|
||||||
|
_setDamageStatus(root, {
|
||||||
|
text: game.i18n.localize("CELESTOPOL.Combat.damageApplyingNotice"),
|
||||||
|
cssClass: "is-pending",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function _renderWeaponDamageState(message, root) {
|
||||||
|
const button = root.querySelector('[data-action="apply-weapon-damage"]')
|
||||||
|
const select = root.querySelector('select[name="targetActorId"]')
|
||||||
|
const state = _getDamageApplicationState(message)
|
||||||
|
|
||||||
|
if (!state?.applied) {
|
||||||
|
if (button) button.textContent = game.i18n.localize("CELESTOPOL.Combat.applyDamage")
|
||||||
|
if (button) button.disabled = false
|
||||||
|
if (select) select.disabled = false
|
||||||
|
_removeDamageStatus(root)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
button.disabled = true
|
||||||
|
button.textContent = game.i18n.localize("CELESTOPOL.Combat.damageAppliedDone")
|
||||||
|
}
|
||||||
|
if (select) select.disabled = true
|
||||||
|
|
||||||
|
const text = state.appliedWounds > 0
|
||||||
|
? game.i18n.format("CELESTOPOL.Combat.damageAppliedCard", {
|
||||||
|
actor: state.actorName,
|
||||||
|
wounds: state.appliedWounds,
|
||||||
|
armor: state.armorProtection,
|
||||||
|
})
|
||||||
|
: game.i18n.format("CELESTOPOL.Combat.damageNoEffectCard", {
|
||||||
|
actor: state.actorName,
|
||||||
|
armor: state.armorProtection,
|
||||||
|
})
|
||||||
|
|
||||||
|
_setDamageStatus(root, { text, cssClass: "is-applied" })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _markChatMessageDamageApplied(chatMessageId, data) {
|
||||||
|
if (!chatMessageId) return
|
||||||
|
const message = game.messages.get(chatMessageId)
|
||||||
|
if (!message) return
|
||||||
|
await message.setFlag(SYSTEM_ID, DAMAGE_APPLICATION_FLAG, {
|
||||||
|
applied: true,
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
_updateRenderedChatMessageState(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _applyWeaponDamage({ actorId, incomingWounds, chatMessageId = null }) {
|
||||||
|
const actor = game.actors.get(actorId)
|
||||||
|
if (!actor) return null
|
||||||
|
|
||||||
|
const message = chatMessageId ? game.messages.get(chatMessageId) : null
|
||||||
|
if (_getDamageApplicationState(message)?.applied) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const armorProtection = CelestopolRoll.getActorArmorProtection(actor)
|
||||||
|
const appliedWounds = Math.max(0, incomingWounds - armorProtection)
|
||||||
|
const currentWounds = actor.system?.blessures?.lvl ?? 0
|
||||||
|
const nextWounds = Math.min(8, currentWounds + appliedWounds)
|
||||||
|
|
||||||
|
if (appliedWounds > 0 && nextWounds !== currentWounds) {
|
||||||
|
await actor.update({ "system.blessures.lvl": nextWounds })
|
||||||
|
}
|
||||||
|
|
||||||
|
await _markChatMessageDamageApplied(chatMessageId, {
|
||||||
|
actorId,
|
||||||
|
actorName: actor.name,
|
||||||
|
appliedWounds,
|
||||||
|
armorProtection,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (appliedWounds > 0) {
|
||||||
|
ui.notifications.info(game.i18n.format("CELESTOPOL.Combat.damageAppliedNotify", {
|
||||||
|
actor: actor.name,
|
||||||
|
wounds: appliedWounds,
|
||||||
|
armor: armorProtection,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
ui.notifications.info(game.i18n.format("CELESTOPOL.Combat.damageNoEffectNotify", {
|
||||||
|
actor: actor.name,
|
||||||
|
armor: armorProtection,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return { actorName: actor.name, appliedWounds, armorProtection }
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getDefaultFactionAspectState() {
|
||||||
|
return {
|
||||||
|
pointsMax: 8,
|
||||||
|
activatedAspects: [],
|
||||||
|
customCell: {
|
||||||
|
enabled: false,
|
||||||
|
mode: "replace",
|
||||||
|
name: "",
|
||||||
|
aspectIds: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _normalizeFactionId(value) {
|
||||||
|
const raw = `${value ?? ""}`.trim()
|
||||||
|
if (!raw) return ""
|
||||||
|
const direct = raw.toLowerCase()
|
||||||
|
if (SYSTEM.FACTIONS[direct]) return direct
|
||||||
|
|
||||||
|
for (const [id, faction] of Object.entries(SYSTEM.FACTIONS)) {
|
||||||
|
if (game.i18n.localize(faction.label).trim().toLowerCase() === direct) return id
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getFactionDisplayLabel(value) {
|
||||||
|
const factionId = _normalizeFactionId(value)
|
||||||
|
if (!factionId) return `${value ?? ""}`.trim()
|
||||||
|
return game.i18n.localize(SYSTEM.FACTIONS[factionId].label)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sanitizeFactionAspectState(state = {}) {
|
||||||
|
const base = foundry.utils.mergeObject(_getDefaultFactionAspectState(), foundry.utils.deepClone(state), {
|
||||||
|
inplace: false,
|
||||||
|
insertKeys: true,
|
||||||
|
insertValues: true,
|
||||||
|
overwrite: true,
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
base.pointsMax = Math.max(0, Number.parseInt(base.pointsMax ?? 8, 10) || 0)
|
||||||
|
base.customCell.enabled = Boolean(base.customCell?.enabled)
|
||||||
|
base.customCell.mode = base.customCell?.mode === "extend" ? "extend" : "replace"
|
||||||
|
base.customCell.name = `${base.customCell?.name ?? ""}`.trim()
|
||||||
|
base.customCell.aspectIds = Array.from(new Set((base.customCell?.aspectIds ?? [])
|
||||||
|
.filter(id => SYSTEM.FACTION_ASPECTS[id])))
|
||||||
|
|
||||||
|
base.activatedAspects = (base.activatedAspects ?? [])
|
||||||
|
.map(entry => {
|
||||||
|
const id = `${entry?.id ?? ""}`.trim()
|
||||||
|
const aspect = SYSTEM.FACTION_ASPECTS[id]
|
||||||
|
if (!aspect) return null
|
||||||
|
const value = Math.max(1, Math.min(4, Number.parseInt(entry.value ?? 1, 10) || 1))
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
label: game.i18n.localize(aspect.label),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getFactionAspectState() {
|
||||||
|
const stored = game.settings.get(SYSTEM_ID, FACTION_ASPECT_STATE_SETTING) ?? {}
|
||||||
|
return _sanitizeFactionAspectState(stored)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _setFactionAspectState(state) {
|
||||||
|
const cleanState = _sanitizeFactionAspectState(state)
|
||||||
|
await game.settings.set(SYSTEM_ID, FACTION_ASPECT_STATE_SETTING, cleanState)
|
||||||
|
_refreshFactionAspectSheets()
|
||||||
|
return cleanState
|
||||||
|
}
|
||||||
|
|
||||||
|
function _refreshFactionAspectSheets() {
|
||||||
|
for (const actor of game.actors.contents) {
|
||||||
|
if (actor.type !== "character") continue
|
||||||
|
if (!actor.sheet?.rendered) continue
|
||||||
|
actor.sheet.render(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getRepresentedFactionIds() {
|
||||||
|
return Array.from(new Set(
|
||||||
|
game.actors.contents
|
||||||
|
.filter(actor => actor.type === "character")
|
||||||
|
.map(actor => _normalizeFactionId(actor.system?.faction))
|
||||||
|
.filter(Boolean)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getFactionAspectSourceData(state = _getFactionAspectState()) {
|
||||||
|
const representedFactionIds = _getRepresentedFactionIds()
|
||||||
|
const sourceFactionIds = state.customCell.enabled && state.customCell.mode === "replace"
|
||||||
|
? []
|
||||||
|
: representedFactionIds
|
||||||
|
|
||||||
|
const sourceAspectIds = new Set()
|
||||||
|
const officialSourceLabels = []
|
||||||
|
const sourceLabels = []
|
||||||
|
|
||||||
|
for (const factionId of sourceFactionIds) {
|
||||||
|
const label = game.i18n.localize(SYSTEM.FACTIONS[factionId].label)
|
||||||
|
officialSourceLabels.push(label)
|
||||||
|
sourceLabels.push(label)
|
||||||
|
for (const aspectId of SYSTEM.FACTION_ASPECTS_BY_FACTION[factionId] ?? []) {
|
||||||
|
sourceAspectIds.add(aspectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let customCellLabel = ""
|
||||||
|
if (state.customCell.enabled) {
|
||||||
|
customCellLabel = state.customCell.name || game.i18n.localize("CELESTOPOL.FactionAspect.customCell")
|
||||||
|
sourceLabels.push(customCellLabel)
|
||||||
|
for (const aspectId of state.customCell.aspectIds ?? []) {
|
||||||
|
sourceAspectIds.add(aspectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
representedFactionIds,
|
||||||
|
officialSourceLabels,
|
||||||
|
availableAspectIds: Array.from(sourceAspectIds),
|
||||||
|
sourceLabels,
|
||||||
|
customCellLabel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getActorAvailableFactionAspectIds(actor, state = _getFactionAspectState()) {
|
||||||
|
const ids = new Set()
|
||||||
|
const factionId = _normalizeFactionId(actor?.system?.faction)
|
||||||
|
|
||||||
|
if (state.customCell.enabled && state.customCell.mode === "replace") {
|
||||||
|
for (const aspectId of state.customCell.aspectIds ?? []) ids.add(aspectId)
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
if (factionId) {
|
||||||
|
for (const aspectId of SYSTEM.FACTION_ASPECTS_BY_FACTION[factionId] ?? []) ids.add(aspectId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.customCell.enabled && state.customCell.mode === "extend") {
|
||||||
|
for (const aspectId of state.customCell.aspectIds ?? []) ids.add(aspectId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getFactionAspectSummary(actor = null) {
|
||||||
|
const state = _getFactionAspectState()
|
||||||
|
const sourceData = _getFactionAspectSourceData(state)
|
||||||
|
const primaryFactionId = actor ? _normalizeFactionId(actor.system?.faction) : ""
|
||||||
|
const actorAvailableIds = actor ? _getActorAvailableFactionAspectIds(actor, state) : new Set(sourceData.availableAspectIds)
|
||||||
|
const pointsSpent = state.activatedAspects.reduce((sum, aspect) => sum + aspect.value, 0)
|
||||||
|
const pointsRemaining = Math.max(0, state.pointsMax - pointsSpent)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pointsSpent,
|
||||||
|
pointsRemaining,
|
||||||
|
sourceLabels: sourceData.sourceLabels,
|
||||||
|
officialSourceLabels: sourceData.officialSourceLabels,
|
||||||
|
customCellLabel: sourceData.customCellLabel,
|
||||||
|
hasOfficialSources: sourceData.officialSourceLabels.length > 0,
|
||||||
|
needsSourceConfiguration: !sourceData.officialSourceLabels.length && !state.customCell.enabled,
|
||||||
|
representedFactions: sourceData.representedFactionIds.map(id => ({
|
||||||
|
id,
|
||||||
|
label: game.i18n.localize(SYSTEM.FACTIONS[id].label),
|
||||||
|
})),
|
||||||
|
primaryFactionId,
|
||||||
|
primaryFactionLabel: primaryFactionId ? game.i18n.localize(SYSTEM.FACTIONS[primaryFactionId].label) : _getFactionDisplayLabel(actor?.system?.faction),
|
||||||
|
availableAspectChoices: state.activatedAspects.map(aspect => ({
|
||||||
|
id: aspect.id,
|
||||||
|
value: aspect.value,
|
||||||
|
label: aspect.label,
|
||||||
|
})),
|
||||||
|
activatableAspectChoices: sourceData.availableAspectIds
|
||||||
|
.filter(id => !state.activatedAspects.some(aspect => aspect.id === id))
|
||||||
|
.map(id => ({
|
||||||
|
id,
|
||||||
|
label: game.i18n.localize(SYSTEM.FACTION_ASPECTS[id].label),
|
||||||
|
})),
|
||||||
|
availableAspectLabels: sourceData.availableAspectIds.map(id => game.i18n.localize(SYSTEM.FACTION_ASPECTS[id].label)),
|
||||||
|
activatedAspects: state.activatedAspects.map(aspect => ({
|
||||||
|
...aspect,
|
||||||
|
relevantToActor: actor ? actorAvailableIds.has(aspect.id) : true,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _parseFactionAspectManagerForm(form) {
|
||||||
|
return {
|
||||||
|
pointsMax: Math.max(0, Number.parseInt(form.querySelector('[name="pointsMax"]')?.value ?? 8, 10) || 0),
|
||||||
|
customCellEnabled: form.querySelector('[name="customCellEnabled"]')?.checked ?? false,
|
||||||
|
customCellMode: form.querySelector('[name="customCellMode"]')?.value === "extend" ? "extend" : "replace",
|
||||||
|
customCellName: `${form.querySelector('[name="customCellName"]')?.value ?? ""}`.trim(),
|
||||||
|
customCellAspectIds: Array.from(form.querySelectorAll('input[name="customCellAspectIds"]:checked')).map(input => input.value),
|
||||||
|
activateAspectId: `${form.querySelector('[name="activateAspectId"]')?.value ?? ""}`.trim(),
|
||||||
|
activateAspectValue: Math.max(1, Math.min(4, Number.parseInt(form.querySelector('[name="activateAspectValue"]')?.value ?? 1, 10) || 1)),
|
||||||
|
removeAspectId: `${form.querySelector('[name="removeAspectId"]')?.value ?? ""}`.trim(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _renderFactionAspectManagerContent(summary) {
|
||||||
|
const i18n = game.i18n
|
||||||
|
const hint = (text) => ` <span class="faction-aspect-help-tip" title="${foundry.utils.escapeHTML(text)}">?</span>`
|
||||||
|
const checkedAspectIds = new Set(summary.customCell.aspectIds ?? [])
|
||||||
|
const customAspectCheckboxes = Object.values(SYSTEM.FACTION_ASPECTS).map(aspect => `
|
||||||
|
<label class="faction-aspect-cell-option">
|
||||||
|
<input type="checkbox" name="customCellAspectIds" value="${aspect.id}" ${checkedAspectIds.has(aspect.id) ? "checked" : ""}>
|
||||||
|
<span>${i18n.localize(aspect.label)}</span>
|
||||||
|
</label>
|
||||||
|
`).join("")
|
||||||
|
|
||||||
|
const activatableOptions = summary.activatableAspectChoices.length
|
||||||
|
? summary.activatableAspectChoices.map(aspect => `<option value="${aspect.id}">${aspect.label}</option>`).join("")
|
||||||
|
: `<option value="">${i18n.localize("CELESTOPOL.FactionAspect.noAspectAvailable")}</option>`
|
||||||
|
|
||||||
|
const activatedRows = summary.activatedAspects.length
|
||||||
|
? summary.activatedAspects.map(aspect => `
|
||||||
|
<div class="faction-aspect-active-row ${aspect.relevantToActor ? "is-relevant" : ""}">
|
||||||
|
<span class="faction-aspect-active-name">${aspect.label}</span>
|
||||||
|
<span class="faction-aspect-active-value">+${aspect.value}</span>
|
||||||
|
</div>
|
||||||
|
`).join("")
|
||||||
|
: `<div class="faction-aspect-empty">${i18n.localize("CELESTOPOL.FactionAspect.noneActive")}</div>`
|
||||||
|
|
||||||
|
const sourceLabels = summary.sourceLabels.length
|
||||||
|
? summary.sourceLabels.join(" • ")
|
||||||
|
: i18n.localize("CELESTOPOL.FactionAspect.noSource")
|
||||||
|
|
||||||
|
const availableAspectList = summary.availableAspectLabels.length
|
||||||
|
? summary.availableAspectLabels.map(label => `<span class="faction-aspect-tag">${label}</span>`).join("")
|
||||||
|
: `<div class="faction-aspect-empty">${i18n.localize("CELESTOPOL.FactionAspect.noAspectAvailable")}</div>`
|
||||||
|
|
||||||
|
const removeOptions = summary.activatedAspects.length
|
||||||
|
? summary.activatedAspects.map(aspect => `<option value="${aspect.id}">${aspect.label} (+${aspect.value})</option>`).join("")
|
||||||
|
: `<option value="">${i18n.localize("CELESTOPOL.FactionAspect.noneActive")}</option>`
|
||||||
|
|
||||||
|
const officialSourcesBlock = summary.hasOfficialSources
|
||||||
|
? `
|
||||||
|
<div class="faction-aspect-source-list">
|
||||||
|
${summary.officialSourceLabels.map(label => `<span class="faction-aspect-tag">${label}</span>`).join("")}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: `<div class="faction-aspect-warning">${i18n.localize("CELESTOPOL.FactionAspect.officialSourcesEmpty")}</div>`
|
||||||
|
|
||||||
|
const customCellOpen = summary.customCell.enabled ? "open" : ""
|
||||||
|
|
||||||
|
return `
|
||||||
|
<form class="cel-dialog-form faction-aspect-manager">
|
||||||
|
<div class="faction-aspect-box">
|
||||||
|
<div class="faction-aspect-box-title">${i18n.localize("CELESTOPOL.FactionAspect.managerState")}</div>
|
||||||
|
<div class="faction-aspect-points">
|
||||||
|
<span class="faction-aspect-point-card"><strong>${i18n.localize("CELESTOPOL.FactionAspect.pointsMax")}</strong><em>${summary.pointsMax}</em></span>
|
||||||
|
<span class="faction-aspect-point-card"><strong>${i18n.localize("CELESTOPOL.FactionAspect.pointsSpent")}</strong><em>${summary.pointsSpent}</em></span>
|
||||||
|
<span class="faction-aspect-point-card"><strong>${i18n.localize("CELESTOPOL.FactionAspect.pointsRemaining")}</strong><em>${summary.pointsRemaining}</em></span>
|
||||||
|
</div>
|
||||||
|
${game.user.isGM ? `
|
||||||
|
<div class="form-group faction-aspect-pool-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.FactionAspect.globalPoolLabel")}${hint(i18n.localize("CELESTOPOL.FactionAspect.globalPoolHint"))}</label>
|
||||||
|
<input type="number" name="pointsMax" min="0" value="${summary.pointsMax}">
|
||||||
|
</div>
|
||||||
|
` : ""}
|
||||||
|
<div class="faction-aspect-source-line"><strong>${i18n.localize("CELESTOPOL.FactionAspect.sources")}</strong> ${sourceLabels}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faction-aspect-box">
|
||||||
|
<div class="faction-aspect-box-title">${i18n.localize("CELESTOPOL.FactionAspect.officialSourcesTitle")}${hint(i18n.localize("CELESTOPOL.FactionAspect.officialSourcesHint"))}</div>
|
||||||
|
${officialSourcesBlock}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faction-aspect-box">
|
||||||
|
<div class="faction-aspect-box-title">${i18n.localize("CELESTOPOL.FactionAspect.availablePoolTitle")}${hint(i18n.localize("CELESTOPOL.FactionAspect.availablePoolHint"))}</div>
|
||||||
|
<div class="faction-aspect-tag-list">${availableAspectList}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faction-aspect-box">
|
||||||
|
<div class="faction-aspect-box-title">${i18n.localize("CELESTOPOL.FactionAspect.managerSettings")}</div>
|
||||||
|
<details class="faction-aspect-advanced" ${customCellOpen}>
|
||||||
|
<summary>${i18n.localize("CELESTOPOL.FactionAspect.customCellSection")}${hint(i18n.localize("CELESTOPOL.FactionAspect.customCellHint"))}</summary>
|
||||||
|
<div class="form-group faction-aspect-checkbox-line">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="customCellEnabled" ${summary.customCell.enabled ? "checked" : ""}>
|
||||||
|
${i18n.localize("CELESTOPOL.FactionAspect.customCellEnabled")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.FactionAspect.customCellName")}</label>
|
||||||
|
<input type="text" name="customCellName" value="${foundry.utils.escapeHTML(summary.customCell.name ?? "")}" placeholder="${i18n.localize("CELESTOPOL.FactionAspect.customCell")}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.FactionAspect.customCellMode")}</label>
|
||||||
|
<select name="customCellMode">
|
||||||
|
<option value="replace" ${summary.customCell.mode === "replace" ? "selected" : ""}>${i18n.localize("CELESTOPOL.FactionAspect.modeReplace")}</option>
|
||||||
|
<option value="extend" ${summary.customCell.mode === "extend" ? "selected" : ""}>${i18n.localize("CELESTOPOL.FactionAspect.modeExtend")}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="faction-aspect-cell-grid">${customAspectCheckboxes}</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faction-aspect-box">
|
||||||
|
<div class="faction-aspect-box-title">${i18n.localize("CELESTOPOL.FactionAspect.activateTitle")}${hint(i18n.localize("CELESTOPOL.FactionAspect.activateHint"))}</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.FactionAspect.activateAspect")}</label>
|
||||||
|
<select name="activateAspectId">${activatableOptions}</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.FactionAspect.activateValue")}</label>
|
||||||
|
<select name="activateAspectValue">
|
||||||
|
<option value="1">+1</option>
|
||||||
|
<option value="2">+2</option>
|
||||||
|
<option value="3">+3</option>
|
||||||
|
<option value="4">+4</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faction-aspect-box">
|
||||||
|
<div class="faction-aspect-box-title">${i18n.localize("CELESTOPOL.FactionAspect.activeTitle")}</div>
|
||||||
|
<div class="faction-aspect-active-list">${activatedRows}</div>
|
||||||
|
<div class="faction-aspect-remove-block">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>${i18n.localize("CELESTOPOL.FactionAspect.removeAspect")}</label>
|
||||||
|
<select name="removeAspectId">${removeOptions}</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _saveFactionAspectManagerSettings(formData, currentState) {
|
||||||
|
if (!game.user.isGM) {
|
||||||
|
formData.pointsMax = currentState.pointsMax
|
||||||
|
}
|
||||||
|
|
||||||
|
const activatedCost = currentState.activatedAspects.reduce((sum, aspect) => sum + aspect.value, 0)
|
||||||
|
if (formData.pointsMax < activatedCost) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.FactionAspect.pointsBelowSpent"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.customCellEnabled) {
|
||||||
|
const count = formData.customCellAspectIds.length
|
||||||
|
if (count < 4 || count > 8) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.FactionAspect.customCellAspectCount"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _setFactionAspectState({
|
||||||
|
...currentState,
|
||||||
|
pointsMax: formData.pointsMax,
|
||||||
|
customCell: {
|
||||||
|
enabled: formData.customCellEnabled,
|
||||||
|
mode: formData.customCellMode,
|
||||||
|
name: formData.customCellName,
|
||||||
|
aspectIds: formData.customCellAspectIds,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _manageFactionAspects(actor = null) {
|
||||||
|
const summary = _getFactionAspectSummary(actor)
|
||||||
|
const result = await foundry.applications.api.DialogV2.wait({
|
||||||
|
window: { title: game.i18n.localize("CELESTOPOL.FactionAspect.managerTitle") },
|
||||||
|
classes: ["fvtt-celestopol", "faction-aspect-dialog"],
|
||||||
|
content: _renderFactionAspectManagerContent(summary),
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
action: "save",
|
||||||
|
label: game.i18n.localize("CELESTOPOL.FactionAspect.save"),
|
||||||
|
callback: (_event, button) => ({ action: "save", ..._parseFactionAspectManagerForm(button.form) }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "activate",
|
||||||
|
label: game.i18n.localize("CELESTOPOL.FactionAspect.activateButton"),
|
||||||
|
callback: (_event, button) => ({ action: "activate", ..._parseFactionAspectManagerForm(button.form) }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "remove",
|
||||||
|
label: game.i18n.localize("CELESTOPOL.FactionAspect.removeButton"),
|
||||||
|
callback: (_event, button) => ({ action: "remove", ..._parseFactionAspectManagerForm(button.form) }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "reset",
|
||||||
|
label: game.i18n.localize("CELESTOPOL.FactionAspect.resetScenario"),
|
||||||
|
callback: () => ({ action: "reset" }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejectClose: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result?.action) return null
|
||||||
|
|
||||||
|
if (result.action === "save") {
|
||||||
|
const updatedState = await _saveFactionAspectManagerSettings(result, _getFactionAspectState())
|
||||||
|
if (updatedState) ui.notifications.info(game.i18n.localize("CELESTOPOL.FactionAspect.saved"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.action === "reset") {
|
||||||
|
const currentState = _getFactionAspectState()
|
||||||
|
await _setFactionAspectState({ ...currentState, activatedAspects: [] })
|
||||||
|
ui.notifications.info(game.i18n.localize("CELESTOPOL.FactionAspect.resetDone"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.action === "activate") {
|
||||||
|
const savedState = await _saveFactionAspectManagerSettings(result, _getFactionAspectState())
|
||||||
|
if (!savedState) return null
|
||||||
|
|
||||||
|
const aspectId = result.activateAspectId
|
||||||
|
const aspectValue = result.activateAspectValue
|
||||||
|
const availableIds = new Set(_getFactionAspectSourceData(savedState).availableAspectIds)
|
||||||
|
const alreadyActive = savedState.activatedAspects.some(aspect => aspect.id === aspectId)
|
||||||
|
const currentSpent = savedState.activatedAspects.reduce((sum, aspect) => sum + aspect.value, 0)
|
||||||
|
|
||||||
|
if (!aspectId || !SYSTEM.FACTION_ASPECTS[aspectId] || !availableIds.has(aspectId)) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.FactionAspect.invalidAspect"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (alreadyActive) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.FactionAspect.alreadyActive"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if ((currentSpent + aspectValue) > savedState.pointsMax) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.FactionAspect.notEnoughPoints"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const aspectLabel = game.i18n.localize(SYSTEM.FACTION_ASPECTS[aspectId].label)
|
||||||
|
await _setFactionAspectState({
|
||||||
|
...savedState,
|
||||||
|
activatedAspects: [
|
||||||
|
...savedState.activatedAspects,
|
||||||
|
{ id: aspectId, value: aspectValue, label: aspectLabel },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
ui.notifications.info(game.i18n.format("CELESTOPOL.FactionAspect.activated", {
|
||||||
|
aspect: aspectLabel,
|
||||||
|
value: aspectValue,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.action === "remove") {
|
||||||
|
const savedState = await _saveFactionAspectManagerSettings(result, _getFactionAspectState())
|
||||||
|
if (!savedState) return null
|
||||||
|
|
||||||
|
const aspectId = result.removeAspectId
|
||||||
|
const removedAspect = savedState.activatedAspects.find(aspect => aspect.id === aspectId)
|
||||||
|
if (!removedAspect) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("CELESTOPOL.FactionAspect.invalidRemove"))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
await _setFactionAspectState({
|
||||||
|
...savedState,
|
||||||
|
activatedAspects: savedState.activatedAspects.filter(aspect => aspect.id !== aspectId),
|
||||||
|
})
|
||||||
|
ui.notifications.info(game.i18n.format("CELESTOPOL.FactionAspect.removed", {
|
||||||
|
aspect: removedAspect.label,
|
||||||
|
value: removedAspect.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Import initial des anomalies du compendium dans le monde ─────────── */
|
/* ─── Import initial des anomalies du compendium dans le monde ─────────── */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
110
lang/fr.json
110
lang/fr.json
@@ -82,7 +82,17 @@
|
|||||||
"Faction": {
|
"Faction": {
|
||||||
"label": "Faction",
|
"label": "Faction",
|
||||||
"relation": "Niveau de Relation",
|
"relation": "Niveau de Relation",
|
||||||
|
"legendTitle": "Rappel des relations",
|
||||||
"custom": "Faction personnalisée…",
|
"custom": "Faction personnalisée…",
|
||||||
|
"levelAllies": "Alliés",
|
||||||
|
"levelAmicaux": "Amicaux",
|
||||||
|
"levelPartenaires": "Partenaires",
|
||||||
|
"levelBienveillants": "Bienveillants",
|
||||||
|
"levelNeutres": "Neutres",
|
||||||
|
"levelMefiants": "Méfiants",
|
||||||
|
"levelHostiles": "Hostiles",
|
||||||
|
"levelRivaux": "Rivaux",
|
||||||
|
"levelEnnemis": "Ennemis",
|
||||||
"pinkerton": "Agence Pinkerton",
|
"pinkerton": "Agence Pinkerton",
|
||||||
"police": "Police secrète du duc",
|
"police": "Police secrète du duc",
|
||||||
"okhrana": "Okhrana",
|
"okhrana": "Okhrana",
|
||||||
@@ -92,6 +102,69 @@
|
|||||||
"vorovskoymir": "Vorovskoy Mir",
|
"vorovskoymir": "Vorovskoy Mir",
|
||||||
"cour": "Cour des merveilles"
|
"cour": "Cour des merveilles"
|
||||||
},
|
},
|
||||||
|
"FactionAspect": {
|
||||||
|
"title": "Aspects de faction",
|
||||||
|
"manage": "Gérer",
|
||||||
|
"managerTitle": "Gestion des Aspects de faction",
|
||||||
|
"managerState": "État du scénario",
|
||||||
|
"managerSettings": "Paramètres de groupe",
|
||||||
|
"officialSourcesTitle": "Factions officielles reconnues",
|
||||||
|
"officialSourcesHint": "En usage normal, les aspects disponibles proviennent automatiquement des factions principales des protagonistes.",
|
||||||
|
"officialSourcesEmpty": "Aucune faction officielle reconnue. Renseignez la faction principale des PJ sur leur fiche, ou activez une cellule indépendante si le groupe joue sa propre structure.",
|
||||||
|
"availablePoolTitle": "Aspects actuellement mobilisables",
|
||||||
|
"availablePoolHint": "Les joueurs choisissent collectivement un aspect disponible et la valeur qu'ils veulent lui attribuer, dans la limite de la réserve du scénario.",
|
||||||
|
"globalPoolLabel": "Pool global du scénario (MJ)",
|
||||||
|
"globalPoolHint": "Le MJ ajuste ici uniquement la réserve totale disponible pour le groupe au cours du scénario.",
|
||||||
|
"pointsMax": "Points disponibles",
|
||||||
|
"pointsSpent": "Points mobilisés",
|
||||||
|
"pointsRemaining": "Points restants",
|
||||||
|
"sources": "Sources disponibles",
|
||||||
|
"noSource": "Aucune faction de protagoniste reconnue pour le moment.",
|
||||||
|
"activeTitle": "Aspects mobilisés",
|
||||||
|
"noneActive": "Aucun aspect de faction mobilisé.",
|
||||||
|
"activateTitle": "Mobiliser un aspect",
|
||||||
|
"activateHint": "Une fois choisi par les joueurs et mobilisé ici, l'aspect reste figé pour tout le scénario, jusqu'au bouton « Nouveau scénario » ou à une correction manuelle du MJ.",
|
||||||
|
"activateAspect": "Aspect à mobiliser",
|
||||||
|
"activateValue": "Valeur",
|
||||||
|
"activateButton": "Mobiliser",
|
||||||
|
"removeAspect": "Retirer un aspect mobilisé",
|
||||||
|
"removeButton": "Retirer",
|
||||||
|
"customCell": "Cellule indépendante",
|
||||||
|
"customCellSection": "Cellule indépendante (optionnel)",
|
||||||
|
"customCellEnabled": "Utiliser une cellule indépendante",
|
||||||
|
"customCellHint": "Utilisez cette option uniquement si le groupe agit comme sa propre cellule ou si vous voulez compléter ou remplacer les factions officielles reconnues.",
|
||||||
|
"customCellName": "Nom de la cellule",
|
||||||
|
"customCellMode": "Mode de combinaison",
|
||||||
|
"modeReplace": "Remplace les factions officielles",
|
||||||
|
"modeExtend": "S'ajoute aux factions officielles",
|
||||||
|
"save": "Enregistrer",
|
||||||
|
"saved": "Paramètres des Aspects de faction enregistrés.",
|
||||||
|
"resetScenario": "Nouveau scénario",
|
||||||
|
"resetDone": "Les Aspects de faction mobilisés ont été réinitialisés.",
|
||||||
|
"activated": "{aspect} mobilisé à +{value}.",
|
||||||
|
"removed": "{aspect} (+{value}) retiré de la réserve mobilisée.",
|
||||||
|
"alreadyActive": "Cet aspect de faction est déjà mobilisé pour ce scénario.",
|
||||||
|
"invalidAspect": "Choisissez un aspect de faction disponible à mobiliser.",
|
||||||
|
"invalidRemove": "Choisissez un aspect de faction mobilisé à retirer.",
|
||||||
|
"notEnoughPoints": "La réserve d'Aspects de faction ne suffit pas pour cette mobilisation.",
|
||||||
|
"pointsBelowSpent": "Le total disponible ne peut pas être inférieur aux points déjà mobilisés.",
|
||||||
|
"customCellAspectCount": "Une cellule indépendante doit proposer entre 4 et 8 aspects.",
|
||||||
|
"gmOnly": "Seul le MJ peut gérer les Aspects de faction.",
|
||||||
|
"noAspectAvailable": "Aucun aspect disponible à mobiliser",
|
||||||
|
"legacyFactionValue": "Valeur héritée",
|
||||||
|
"rollLabel": "Aspect de faction",
|
||||||
|
"noneOption": "Aucun aspect de faction",
|
||||||
|
"bonnesadresses": "Bonnes adresses",
|
||||||
|
"contrebande": "Contrebande",
|
||||||
|
"corruption": "Corruption",
|
||||||
|
"diversion": "Diversion",
|
||||||
|
"falsification": "Falsification",
|
||||||
|
"passedroit": "Passe-droit",
|
||||||
|
"renforts": "Renforts",
|
||||||
|
"renseignements": "Renseignements",
|
||||||
|
"ressources": "Ressources",
|
||||||
|
"surveillance": "Surveillance"
|
||||||
|
},
|
||||||
"Track": {
|
"Track": {
|
||||||
"blessures": "Blessures",
|
"blessures": "Blessures",
|
||||||
"destin": "Destin",
|
"destin": "Destin",
|
||||||
@@ -99,7 +172,7 @@
|
|||||||
"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",
|
"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.",
|
"spleenTooltip": "Le Spleen représente l'usure morale du protagoniste.\nLa jauge de Spleen augmente avec les actions suivantes :\n• Lors d’un test de Spécialisation, en obtenant un échec et une Pleine lune sur le dé de la Lune.\n• Après un test de Spécialisation, pour transformer un échec en réussite, même après l’utilisation éventuelle d’une Anomalie.\n• Avant un test de résistance, pour réussir automatiquement le test.\n• Pour ne pas subir une blessure Dramatique.\n• En choisissant de puiser dans ses ressources.\nLorsque la jauge est remplie, le Protagoniste subit une Séquelle Dramatique et risque, à terme, de passer définitivement Hors Fiction.",
|
||||||
"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": {
|
||||||
@@ -124,7 +197,7 @@
|
|||||||
"corpsPnj": "Corps du PNJ",
|
"corpsPnj": "Corps du PNJ",
|
||||||
"tie": "ÉGALITÉ",
|
"tie": "ÉGALITÉ",
|
||||||
"tieDesc": "Personne n'est blessé",
|
"tieDesc": "Personne n'est blessé",
|
||||||
"successHit": "PNJ touché — 1 blessure",
|
"successHit": "Attaque réussie — cible touchée",
|
||||||
"failureHit": "Joueur touché — 1 blessure (mêlée)",
|
"failureHit": "Joueur touché — 1 blessure (mêlée)",
|
||||||
"distanceNoWound": "Raté — pas de riposte",
|
"distanceNoWound": "Raté — pas de riposte",
|
||||||
"weaponDamage": "dégâts supplémentaires",
|
"weaponDamage": "dégâts supplémentaires",
|
||||||
@@ -136,6 +209,25 @@
|
|||||||
"rangedDefensePlayerWounded": "Blessure infligée par attaque à distance",
|
"rangedDefensePlayerWounded": "Blessure infligée par attaque à distance",
|
||||||
"targetLabel": "Cible",
|
"targetLabel": "Cible",
|
||||||
"targetAuto": "Saisir manuellement",
|
"targetAuto": "Saisir manuellement",
|
||||||
|
"targetCharacterLabel": "Personnage visé",
|
||||||
|
"targetCharacterAuto": "Aucun personnage présélectionné",
|
||||||
|
"damageLabel": "Dégâts infligés",
|
||||||
|
"damageUnit": "blessure(s)",
|
||||||
|
"damageManual": "Dégâts variables : application manuelle par le MJ.",
|
||||||
|
"damageArmorReduction": "Protection d'armure",
|
||||||
|
"damageApplied": "Blessures après armure",
|
||||||
|
"applyDamage": "Appliquer les blessures",
|
||||||
|
"damageApplying": "Application...",
|
||||||
|
"damageApplyingNotice": "Application des blessures en cours...",
|
||||||
|
"damageAppliedDone": "Blessures appliquées",
|
||||||
|
"damageAppliedCard": "{actor} subit {wounds} blessure(s) après armure ({armor}).",
|
||||||
|
"damageNoEffectCard": "{actor} ne subit aucune blessure : l'armure absorbe l'attaque ({armor}).",
|
||||||
|
"selectCharacter": "Choisir une cible",
|
||||||
|
"selectCharacterFirst": "Sélectionnez une cible avant d'appliquer les blessures.",
|
||||||
|
"noCharacterTargetAvailable": "Aucune cible de la scène active disponible pour appliquer les blessures.",
|
||||||
|
"damageRequestSent": "Demande d'application des blessures envoyée au MJ.",
|
||||||
|
"damageAppliedNotify": "{actor} : {wounds} blessure(s) appliquée(s) après armure ({armor}).",
|
||||||
|
"damageNoEffectNotify": "{actor} : l'armure absorbe entièrement l'attaque ({armor}).",
|
||||||
"rangedMod": "Modificateur de tir",
|
"rangedMod": "Modificateur de tir",
|
||||||
"rangedModNone": "Aucun modificateur",
|
"rangedModNone": "Aucun modificateur",
|
||||||
"rangedModAim": "Visée (dépense 1 tour) +2",
|
"rangedModAim": "Visée (dépense 1 tour) +2",
|
||||||
@@ -210,7 +302,8 @@
|
|||||||
"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"
|
"autoSuccess": "Réussite automatique",
|
||||||
|
"usedFactionAspect": "Aspect de faction mobilisé"
|
||||||
},
|
},
|
||||||
"Modifier": {
|
"Modifier": {
|
||||||
"evident": "Évident — Réussite automatique",
|
"evident": "Évident — Réussite automatique",
|
||||||
@@ -311,6 +404,15 @@
|
|||||||
"hint": "Cocher automatiquement 'Lancer le dé de la lune' dans les fenêtres de jet"
|
"hint": "Cocher automatiquement 'Lancer le dé de la lune' dans les fenêtres de jet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Welcome": {
|
||||||
|
"title": "Bienvenue dans Célestopol 1922",
|
||||||
|
"intro": "Bienvenue dans le système FoundryVTT de Célestopol 1922.",
|
||||||
|
"helpLabel": "Aide de jeu",
|
||||||
|
"helpCompendium": "Une aide de jeu est disponible dans le compendium : {help}.",
|
||||||
|
"bookLabel": "Livre de base",
|
||||||
|
"helpFallback": "Célestopol 1922 — Aides de jeu",
|
||||||
|
"bookLinkLabel": "Voir le livre de base sur le site d’Antre-Monde Éditions"
|
||||||
|
},
|
||||||
"ChatCard": {
|
"ChatCard": {
|
||||||
"rollFor": "Jet de {skill} ({stat})"
|
"rollFor": "Jet de {skill} ({stat})"
|
||||||
},
|
},
|
||||||
@@ -387,4 +489,4 @@
|
|||||||
"factionNone": "Aucune faction"
|
"factionNone": "Aucune faction"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
export { default as CelestopolCharacterSheet } from "./sheets/character-sheet.mjs"
|
export { default as CelestopolCharacterSheet } from "./sheets/character-sheet.mjs"
|
||||||
export { default as CelestopolNPCSheet } from "./sheets/npc-sheet.mjs"
|
export { default as CelestopolNPCSheet } from "./sheets/npc-sheet.mjs"
|
||||||
export { CelestopolAnomalySheet, CelestopolAspectSheet, CelestopolEquipmentSheet, CelestopolWeaponSheet, CelestopolArmureSheet } from "./sheets/item-sheets.mjs"
|
export { CelestopolAnomalySheet, CelestopolAspectSheet, CelestopolEquipmentSheet, CelestopolWeaponSheet, CelestopolArmureSheet } from "./sheets/item-sheets.mjs"
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import { SYSTEM } from "../../config/system.mjs"
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
@@ -46,6 +59,7 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
|||||||
actor: this.document,
|
actor: this.document,
|
||||||
system: this.document.system,
|
system: this.document.system,
|
||||||
source: this.document.toObject(),
|
source: this.document.toObject(),
|
||||||
|
isGM: game.user.isGM,
|
||||||
isEditMode: this.isEditMode,
|
isEditMode: this.isEditMode,
|
||||||
isPlayMode: this.isPlayMode,
|
isPlayMode: this.isPlayMode,
|
||||||
isEditable: this.isEditable,
|
isEditable: this.isEditable,
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
export default class CelestopolItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
export default class CelestopolItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import CelestopolActorSheet from "./base-actor-sheet.mjs"
|
import CelestopolActorSheet from "./base-actor-sheet.mjs"
|
||||||
import { SYSTEM } from "../../config/system.mjs"
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
@@ -18,6 +31,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
depenseXp: CelestopolCharacterSheet.#onDepenseXp,
|
depenseXp: CelestopolCharacterSheet.#onDepenseXp,
|
||||||
supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog,
|
supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog,
|
||||||
rollMoonDie: CelestopolCharacterSheet.#onRollMoonDie,
|
rollMoonDie: CelestopolCharacterSheet.#onRollMoonDie,
|
||||||
|
manageFactionAspects: CelestopolCharacterSheet.#onManageFactionAspects,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +72,11 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
context.anomalyTypes = SYSTEM.ANOMALY_TYPES
|
context.anomalyTypes = SYSTEM.ANOMALY_TYPES
|
||||||
context.factions = SYSTEM.FACTIONS
|
context.factions = SYSTEM.FACTIONS
|
||||||
context.woundLevels = SYSTEM.WOUND_LEVELS
|
context.woundLevels = SYSTEM.WOUND_LEVELS
|
||||||
|
context.selectedPrimaryFactionId = game.celestopol?.normalizeFactionId(this.document.system.faction) || ""
|
||||||
|
context.legacyPrimaryFactionValue = this.document.system.faction && !context.selectedPrimaryFactionId
|
||||||
|
? `${this.document.system.faction}`.trim()
|
||||||
|
: ""
|
||||||
|
context.primaryFactionLabel = game.celestopol?.getFactionDisplayLabel(this.document.system.faction) || this.document.system.faction
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +113,18 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
|
|
||||||
case "factions":
|
case "factions":
|
||||||
context.tab = context.tabs.factions
|
context.tab = context.tabs.factions
|
||||||
|
context.factionAspectSummary = game.celestopol?.getFactionAspectSummary(this.document) ?? null
|
||||||
|
context.factionLegend = [
|
||||||
|
{ value: "+4", label: game.i18n.localize("CELESTOPOL.Faction.levelAllies") },
|
||||||
|
{ value: "+3", label: game.i18n.localize("CELESTOPOL.Faction.levelAmicaux") },
|
||||||
|
{ value: "+2", label: game.i18n.localize("CELESTOPOL.Faction.levelPartenaires") },
|
||||||
|
{ value: "+1", label: game.i18n.localize("CELESTOPOL.Faction.levelBienveillants") },
|
||||||
|
{ value: "0", label: game.i18n.localize("CELESTOPOL.Faction.levelNeutres") },
|
||||||
|
{ value: "-1", label: game.i18n.localize("CELESTOPOL.Faction.levelMefiants") },
|
||||||
|
{ value: "-2", label: game.i18n.localize("CELESTOPOL.Faction.levelHostiles") },
|
||||||
|
{ value: "-3", label: game.i18n.localize("CELESTOPOL.Faction.levelRivaux") },
|
||||||
|
{ value: "-4", label: game.i18n.localize("CELESTOPOL.Faction.levelEnnemis") },
|
||||||
|
]
|
||||||
context.factionRows = Object.entries(SYSTEM.FACTIONS).map(([id, fDef]) => {
|
context.factionRows = Object.entries(SYSTEM.FACTIONS).map(([id, fDef]) => {
|
||||||
const val = this.document.system.factions[id]?.value ?? 0
|
const val = this.document.system.factions[id]?.value ?? 0
|
||||||
return {
|
return {
|
||||||
@@ -177,6 +208,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
static async #onCreateArmure() {
|
static async #onCreateArmure() {
|
||||||
await this.document.createEmbeddedDocuments("Item", [{
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
|
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
|
||||||
|
system: { protection: 1, malus: 1 },
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +231,10 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
|||||||
await anomaly.update({ "system.usesRemaining": anomaly.system.level })
|
await anomaly.update({ "system.usesRemaining": anomaly.system.level })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onManageFactionAspects() {
|
||||||
|
await game.celestopol?.manageFactionAspects(this.document)
|
||||||
|
}
|
||||||
|
|
||||||
/** Ouvre un dialogue pour dépenser de l'XP. */
|
/** Ouvre un dialogue pour dépenser de l'XP. */
|
||||||
static async #onDepenseXp() {
|
static async #onDepenseXp() {
|
||||||
const actor = this.document
|
const actor = this.document
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import CelestopolItemSheet from "./base-item-sheet.mjs"
|
import CelestopolItemSheet from "./base-item-sheet.mjs"
|
||||||
import { SYSTEM } from "../../config/system.mjs"
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
@@ -97,4 +110,22 @@ export class CelestopolArmureSheet extends CelestopolItemSheet {
|
|||||||
this.document.system.description, { async: true })
|
this.document.system.description, { async: true })
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onRender(context, options) {
|
||||||
|
super._onRender(context, options)
|
||||||
|
|
||||||
|
const protectionInput = this.element.querySelector('[name="system.protection"]')
|
||||||
|
const malusInput = this.element.querySelector('[name="system.malus"]')
|
||||||
|
const malusValue = this.element.querySelector('[data-armure-malus-value]')
|
||||||
|
if (!protectionInput || !malusInput || !malusValue) return
|
||||||
|
|
||||||
|
const syncMalus = () => {
|
||||||
|
malusInput.value = protectionInput.value
|
||||||
|
malusValue.textContent = protectionInput.value
|
||||||
|
}
|
||||||
|
|
||||||
|
syncMalus()
|
||||||
|
protectionInput.addEventListener("input", syncMalus)
|
||||||
|
protectionInput.addEventListener("change", syncMalus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import CelestopolActorSheet from "./base-actor-sheet.mjs"
|
import CelestopolActorSheet from "./base-actor-sheet.mjs"
|
||||||
import { SYSTEM } from "../../config/system.mjs"
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
@@ -9,6 +22,7 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
|||||||
window: { contentClasses: ["npc-content"] },
|
window: { contentClasses: ["npc-content"] },
|
||||||
actions: {
|
actions: {
|
||||||
createAspect: CelestopolNPCSheet.#onCreateAspect,
|
createAspect: CelestopolNPCSheet.#onCreateAspect,
|
||||||
|
createEquipment: CelestopolNPCSheet.#onCreateEquipment,
|
||||||
createWeapon: CelestopolNPCSheet.#onCreateWeapon,
|
createWeapon: CelestopolNPCSheet.#onCreateWeapon,
|
||||||
createArmure: CelestopolNPCSheet.#onCreateArmure,
|
createArmure: CelestopolNPCSheet.#onCreateArmure,
|
||||||
rollMoonDie: CelestopolNPCSheet.#onRollMoonDie,
|
rollMoonDie: CelestopolNPCSheet.#onRollMoonDie,
|
||||||
@@ -53,9 +67,10 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
|||||||
context.antagonisteStats = SYSTEM.ANTAGONISTE_STATS
|
context.antagonisteStats = SYSTEM.ANTAGONISTE_STATS
|
||||||
|
|
||||||
const sys = this.document.system
|
const sys = this.document.system
|
||||||
context.aspects = this.document.itemTypes.aspect ?? []
|
context.aspects = this.document.itemTypes.aspect ?? []
|
||||||
context.weapons = this.document.itemTypes.weapon ?? []
|
context.weapons = this.document.itemTypes.weapon.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
context.armures = this.document.itemTypes.armure ?? []
|
context.armures = this.document.itemTypes.armure.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
context.equipments = this.document.itemTypes.equipment.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
context.armorMalus = sys.armorMalus ?? 0
|
context.armorMalus = sys.armorMalus ?? 0
|
||||||
|
|
||||||
// Label effectif de chaque domaine selon le type de PNJ
|
// Label effectif de chaque domaine selon le type de PNJ
|
||||||
@@ -119,9 +134,16 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
|||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onCreateEquipment() {
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
|
name: game.i18n.localize("TYPES.Item.equipment"), type: "equipment",
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
static async #onCreateArmure() {
|
static async #onCreateArmure() {
|
||||||
await this.document.createEmbeddedDocuments("Item", [{
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
|
name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
|
||||||
|
system: { protection: 1, malus: 1 },
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
export const SYSTEM_ID = "fvtt-celestopol"
|
export const SYSTEM_ID = "fvtt-celestopol"
|
||||||
|
|
||||||
export const ASCII = `
|
export const ASCII = `
|
||||||
@@ -88,6 +101,52 @@ export const FACTIONS = {
|
|||||||
cour: { id: "cour", label: "CELESTOPOL.Faction.cour" },
|
cour: { id: "cour", label: "CELESTOPOL.Faction.cour" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Aspects de faction mobilisables au niveau du groupe. */
|
||||||
|
export const FACTION_ASPECTS = {
|
||||||
|
bonnesadresses: { id: "bonnesadresses", label: "CELESTOPOL.FactionAspect.bonnesadresses" },
|
||||||
|
contrebande: { id: "contrebande", label: "CELESTOPOL.FactionAspect.contrebande" },
|
||||||
|
corruption: { id: "corruption", label: "CELESTOPOL.FactionAspect.corruption" },
|
||||||
|
diversion: { id: "diversion", label: "CELESTOPOL.FactionAspect.diversion" },
|
||||||
|
falsification: { id: "falsification", label: "CELESTOPOL.FactionAspect.falsification" },
|
||||||
|
passedroit: { id: "passedroit", label: "CELESTOPOL.FactionAspect.passedroit" },
|
||||||
|
renforts: { id: "renforts", label: "CELESTOPOL.FactionAspect.renforts" },
|
||||||
|
renseignements: { id: "renseignements", label: "CELESTOPOL.FactionAspect.renseignements" },
|
||||||
|
ressources: { id: "ressources", label: "CELESTOPOL.FactionAspect.ressources" },
|
||||||
|
surveillance: { id: "surveillance", label: "CELESTOPOL.FactionAspect.surveillance" },
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tableau p.111 : aspects de faction disponibles selon l'organisation. */
|
||||||
|
export const FACTION_ASPECTS_BY_FACTION = {
|
||||||
|
police: [
|
||||||
|
"diversion", "passedroit", "renforts", "renseignements", "ressources", "surveillance",
|
||||||
|
],
|
||||||
|
vorovskoymir: [
|
||||||
|
"bonnesadresses", "contrebande", "corruption", "diversion", "falsification",
|
||||||
|
"renforts", "renseignements", "ressources", "surveillance",
|
||||||
|
],
|
||||||
|
okhrana: [
|
||||||
|
"corruption", "diversion", "falsification", "passedroit", "renforts",
|
||||||
|
"renseignements", "ressources",
|
||||||
|
],
|
||||||
|
oto: [
|
||||||
|
"contrebande", "corruption", "falsification", "renseignements", "surveillance",
|
||||||
|
],
|
||||||
|
syndicats: [
|
||||||
|
"bonnesadresses", "contrebande", "corruption", "falsification", "renseignements", "surveillance",
|
||||||
|
],
|
||||||
|
pinkerton: [
|
||||||
|
"bonnesadresses", "diversion", "falsification", "renforts",
|
||||||
|
"renseignements", "ressources", "surveillance",
|
||||||
|
],
|
||||||
|
cour: [
|
||||||
|
"bonnesadresses", "contrebande", "diversion", "renforts", "renseignements", "surveillance",
|
||||||
|
],
|
||||||
|
lunanovatek: [
|
||||||
|
"contrebande", "corruption", "falsification", "renforts",
|
||||||
|
"renseignements", "ressources", "surveillance",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
/** Niveaux de blessures avec leur malus associé. */
|
/** Niveaux de blessures avec leur malus associé. */
|
||||||
export const WOUND_LEVELS = [
|
export const WOUND_LEVELS = [
|
||||||
{ id: 0, label: "CELESTOPOL.Wound.none", malus: 0, duration: "" },
|
{ id: 0, label: "CELESTOPOL.Wound.none", malus: 0, duration: "" },
|
||||||
@@ -209,6 +268,8 @@ export const SYSTEM = {
|
|||||||
ANOMALY_TYPES,
|
ANOMALY_TYPES,
|
||||||
ANOMALY_DEFINITIONS,
|
ANOMALY_DEFINITIONS,
|
||||||
FACTIONS,
|
FACTIONS,
|
||||||
|
FACTION_ASPECTS,
|
||||||
|
FACTION_ASPECTS_BY_FACTION,
|
||||||
NPC_TYPES,
|
NPC_TYPES,
|
||||||
ANTAGONISTE_STATS,
|
ANTAGONISTE_STATS,
|
||||||
WOUND_LEVELS,
|
WOUND_LEVELS,
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
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"
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
export default class CelestopolActor extends Actor {
|
export default class CelestopolActor extends Actor {
|
||||||
/** @override */
|
/** @override */
|
||||||
getRollData() {
|
getRollData() {
|
||||||
|
|||||||
@@ -1 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
export default class CelestopolChatMessage extends ChatMessage {}
|
export default class CelestopolChatMessage extends ChatMessage {}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
const SYSTEM_ID = "fvtt-celestopol"
|
const SYSTEM_ID = "fvtt-celestopol"
|
||||||
|
|
||||||
export default class CelestopolCombat extends Combat {
|
export default class CelestopolCombat extends Combat {
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
export default class CelestopolItem extends Item {
|
export default class CelestopolItem extends Item {
|
||||||
/** @override */
|
/** @override */
|
||||||
getRollData() {
|
getRollData() {
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import { SYSTEM } from "../config/system.mjs"
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
/** Construit la formule de jet à partir du nombre de dés et du modificateur total. */
|
/** Construit la formule de jet à partir du nombre de dés et du modificateur total. */
|
||||||
@@ -29,6 +42,42 @@ export class CelestopolRoll extends Roll {
|
|||||||
get skillLabel() { return this.options.skillLabel }
|
get skillLabel() { return this.options.skillLabel }
|
||||||
get difficulty() { return this.options.difficulty }
|
get difficulty() { return this.options.difficulty }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit le niveau de dégâts d'une arme en nombre de blessures de base.
|
||||||
|
* Règle : une attaque réussie inflige toujours 1 blessure, plus le bonus de dégâts.
|
||||||
|
* @param {string|number|null} weaponDegats
|
||||||
|
* @returns {number|null}
|
||||||
|
*/
|
||||||
|
static getIncomingWounds(weaponDegats) {
|
||||||
|
const raw = `${weaponDegats ?? "0"}`
|
||||||
|
const bonus = Number.parseInt(raw, 10)
|
||||||
|
if (!Number.isFinite(bonus)) return null
|
||||||
|
return Math.max(0, 1 + bonus)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la protection totale de l'armure équipée pour un acteur.
|
||||||
|
* @param {Actor|null} actor
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
static getActorArmorProtection(actor) {
|
||||||
|
if (!actor) return 0
|
||||||
|
|
||||||
|
if (typeof actor.system?.getArmorMalus === "function") {
|
||||||
|
return Math.abs(actor.system.getArmorMalus())
|
||||||
|
}
|
||||||
|
|
||||||
|
const derivedArmorMalus = actor.system?.armorMalus
|
||||||
|
if (Number.isFinite(derivedArmorMalus)) {
|
||||||
|
return Math.abs(derivedArmorMalus)
|
||||||
|
}
|
||||||
|
|
||||||
|
const armures = actor.itemTypes?.armure ?? []
|
||||||
|
return armures
|
||||||
|
.filter(a => a.system.equipped)
|
||||||
|
.reduce((sum, a) => sum + Math.abs(a.system.protection ?? a.system.malus ?? 0), 0)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ouvre le dialogue de configuration du jet via DialogV2 et exécute le jet.
|
* Ouvre le dialogue de configuration du jet via DialogV2 et exécute le jet.
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
@@ -71,6 +120,8 @@ export class CelestopolRoll extends Roll {
|
|||||||
value: m.value,
|
value: m.value,
|
||||||
label: game.i18n.localize(m.label),
|
label: game.i18n.localize(m.label),
|
||||||
}))
|
}))
|
||||||
|
const factionAspectChoices = game.celestopol?.getFactionAspectSummary(options.actorId ? game.actors.get(options.actorId) : null)
|
||||||
|
?.availableAspectChoices ?? []
|
||||||
|
|
||||||
const dialogContext = {
|
const dialogContext = {
|
||||||
actorName: options.actorName,
|
actorName: options.actorName,
|
||||||
@@ -89,6 +140,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
aspectChoices,
|
aspectChoices,
|
||||||
situationChoices,
|
situationChoices,
|
||||||
rangedModChoices,
|
rangedModChoices,
|
||||||
|
factionAspectChoices,
|
||||||
availableTargets,
|
availableTargets,
|
||||||
fortuneValue,
|
fortuneValue,
|
||||||
armorMalus,
|
armorMalus,
|
||||||
@@ -123,7 +175,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
function applyTargetSelection() {
|
function applyTargetSelection() {
|
||||||
if (!targetSelect) return
|
if (!targetSelect) return
|
||||||
const selectedOption = targetSelect.options[targetSelect.selectedIndex]
|
const selectedOption = targetSelect.options[targetSelect.selectedIndex]
|
||||||
const val = parseFloat(targetSelect.value)
|
const val = parseFloat(selectedOption?.dataset.corps ?? "")
|
||||||
const corpsPnjInput = wrap.querySelector('#corpsPnj')
|
const corpsPnjInput = wrap.querySelector('#corpsPnj')
|
||||||
if (targetSelect.value && !isNaN(val)) {
|
if (targetSelect.value && !isNaN(val)) {
|
||||||
// Cible sélectionnée : masquer la valeur, afficher le nom
|
// Cible sélectionnée : masquer la valeur, afficher le nom
|
||||||
@@ -155,6 +207,8 @@ export class CelestopolRoll extends Roll {
|
|||||||
const autoSucc = rawMod === "auto"
|
const autoSucc = rawMod === "auto"
|
||||||
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 selectedFactionAspect = wrap.querySelector('#factionAspectId')?.selectedOptions?.[0]
|
||||||
|
const factionAspectBonus = parseInt(selectedFactionAspect?.dataset.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 rangedMod = parseInt(wrap.querySelector('#rangedMod')?.value ?? 0) || 0
|
||||||
const useDestin = wrap.querySelector('#useDestin')?.checked
|
const useDestin = wrap.querySelector('#useDestin')?.checked
|
||||||
@@ -180,7 +234,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
const effSit = puiser ? Math.max(0, situMod) : situMod
|
const effSit = puiser ? Math.max(0, situMod) : situMod
|
||||||
const effArmor = puiser ? 0 : armorMalus
|
const effArmor = puiser ? 0 : armorMalus
|
||||||
const effRanged = puiser ? Math.max(0, rangedMod) : rangedMod
|
const effRanged = puiser ? Math.max(0, rangedMod) : rangedMod
|
||||||
const totalMod = skillValue + effWound + effMod + effAspect + effSit + effArmor + effRanged
|
const totalMod = skillValue + effWound + effMod + effAspect + factionAspectBonus + effSit + effArmor + effRanged
|
||||||
|
|
||||||
let formula
|
let formula
|
||||||
if (autoSucc) {
|
if (autoSucc) {
|
||||||
@@ -198,7 +252,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
if (previewEl) previewEl.textContent = formula
|
if (previewEl) previewEl.textContent = formula
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap.querySelectorAll('#modifier, #aspectModifier, #situationMod, #rangedMod, #useDestin, #useFortune, #puiserRessources, #corpsPnj')
|
wrap.querySelectorAll('#modifier, #aspectModifier, #factionAspectId, #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)
|
||||||
@@ -233,13 +287,23 @@ export class CelestopolRoll extends Roll {
|
|||||||
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 factionAspectId = typeof rollContext.factionAspectId === "string" ? rollContext.factionAspectId : ""
|
||||||
|
const selectedFactionAspect = factionAspectChoices.find(choice => choice.id === factionAspectId) ?? null
|
||||||
|
const factionAspectBonus = selectedFactionAspect?.value ?? 0
|
||||||
|
const factionAspectLabel = selectedFactionAspect?.label ?? ""
|
||||||
const situationMod = parseInt(rollContext.situationMod ?? 0) || 0
|
const situationMod = parseInt(rollContext.situationMod ?? 0) || 0
|
||||||
const rangedMod = isRangedAttack ? (parseInt(rollContext.rangedMod ?? 0) || 0) : 0
|
const rangedMod = isRangedAttack ? (parseInt(rollContext.rangedMod ?? 0) || 0) : 0
|
||||||
const isOpposition = !isCombat && !isResistance && (rollContext.isOpposition === true || rollContext.isOpposition === "true")
|
const isOpposition = !isCombat && (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"
|
||||||
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true"
|
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true"
|
||||||
|
const selectedCombatTargetId = typeof rollContext.targetSelect === "string" ? rollContext.targetSelect : ""
|
||||||
|
const selectedCombatTarget = selectedCombatTargetId
|
||||||
|
? availableTargets.find(t => t.id === selectedCombatTargetId) ?? null
|
||||||
|
: null
|
||||||
|
const targetActorId = selectedCombatTarget?.id || ""
|
||||||
|
const targetActorName = selectedCombatTarget?.name || ""
|
||||||
|
|
||||||
// En résistance : forcer puiser=false, lune=false, fortune=false, destin=false
|
// En résistance : forcer puiser=false, lune=false, fortune=false, destin=false
|
||||||
const effectivePuiser = isResistance ? false : puiserRessources
|
const effectivePuiser = isResistance ? false : puiserRessources
|
||||||
@@ -255,7 +319,7 @@ export class CelestopolRoll extends Roll {
|
|||||||
|
|
||||||
// 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 + effectiveArmorMalus + effectiveRangedMod
|
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier + factionAspectBonus + 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)
|
||||||
@@ -277,6 +341,9 @@ export class CelestopolRoll extends Roll {
|
|||||||
difficultyValue: diffConfig.value,
|
difficultyValue: diffConfig.value,
|
||||||
modifier: effectiveModifier,
|
modifier: effectiveModifier,
|
||||||
aspectMod: effectiveAspectMod,
|
aspectMod: effectiveAspectMod,
|
||||||
|
factionAspectId,
|
||||||
|
factionAspectLabel,
|
||||||
|
factionAspectBonus,
|
||||||
situationMod: effectiveSituationMod,
|
situationMod: effectiveSituationMod,
|
||||||
woundMalus: effectiveWoundMalus,
|
woundMalus: effectiveWoundMalus,
|
||||||
autoSuccess,
|
autoSuccess,
|
||||||
@@ -287,6 +354,9 @@ export class CelestopolRoll extends Roll {
|
|||||||
weaponType,
|
weaponType,
|
||||||
weaponName,
|
weaponName,
|
||||||
weaponDegats,
|
weaponDegats,
|
||||||
|
targetActorId,
|
||||||
|
targetActorName,
|
||||||
|
availableTargets,
|
||||||
rangedMod: effectiveRangedMod,
|
rangedMod: effectiveRangedMod,
|
||||||
useDestin: !isResistance && useDestin,
|
useDestin: !isResistance && useDestin,
|
||||||
useFortune: !isResistance && useFortune,
|
useFortune: !isResistance && useFortune,
|
||||||
@@ -410,8 +480,10 @@ export class CelestopolRoll extends Roll {
|
|||||||
: 11
|
: 11
|
||||||
const margin = this.options.margin
|
const margin = this.options.margin
|
||||||
const woundMalus = this.options.woundMalus ?? 0
|
const woundMalus = this.options.woundMalus ?? 0
|
||||||
|
const armorMalus = this.options.armorMalus ?? 0
|
||||||
const skillValue = this.options.skillValue ?? 0
|
const skillValue = this.options.skillValue ?? 0
|
||||||
const woundLevelId = this.options.woundLevel ?? 0
|
const woundLevelId = this.options.woundLevel ?? 0
|
||||||
|
const weaponDegats = `${this.options.weaponDegats ?? "0"}`
|
||||||
const woundLabel = woundLevelId > 0
|
const woundLabel = woundLevelId > 0
|
||||||
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
|
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
|
||||||
: null
|
: null
|
||||||
@@ -430,6 +502,22 @@ export class CelestopolRoll extends Roll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isOpposition = this.options.isOpposition ?? false
|
const isOpposition = this.options.isOpposition ?? false
|
||||||
|
const isWeaponHit = (this.options.isCombat ?? false) && !(this.options.isRangedDefense ?? false) && this.isSuccess
|
||||||
|
const incomingWounds = isWeaponHit ? this.constructor.getIncomingWounds(weaponDegats) : null
|
||||||
|
const hasVariableDamage = isWeaponHit && incomingWounds === null
|
||||||
|
const targetActorId = this.options.targetActorId ?? ""
|
||||||
|
const targetActorName = this.options.targetActorName ?? ""
|
||||||
|
const availableTargets = (this.options.availableTargets ?? []).map(target => ({
|
||||||
|
...target,
|
||||||
|
selected: target.id === targetActorId,
|
||||||
|
}))
|
||||||
|
const selectedTargetActor = targetActorId ? game.actors.get(targetActorId) : null
|
||||||
|
const selectedTargetProtection = selectedTargetActor
|
||||||
|
? this.constructor.getActorArmorProtection(selectedTargetActor)
|
||||||
|
: null
|
||||||
|
const selectedTargetAppliedWounds = (incomingWounds !== null && selectedTargetActor)
|
||||||
|
? Math.max(0, incomingWounds - selectedTargetProtection)
|
||||||
|
: null
|
||||||
|
|
||||||
// Libellé de difficulté : en combat "Corps PNJ : N", en opposition "vs ?", sinon "Seuil : 11"
|
// Libellé de difficulté : en combat "Corps PNJ : N", en opposition "vs ?", sinon "Seuil : 11"
|
||||||
const difficultyLabel = this.options.isCombat
|
const difficultyLabel = this.options.isCombat
|
||||||
@@ -464,22 +552,35 @@ export class CelestopolRoll extends Roll {
|
|||||||
modifier: this.options.modifier ?? 0,
|
modifier: this.options.modifier ?? 0,
|
||||||
autoSuccess: this.options.autoSuccess ?? false,
|
autoSuccess: this.options.autoSuccess ?? false,
|
||||||
aspectMod: this.options.aspectMod ?? 0,
|
aspectMod: this.options.aspectMod ?? 0,
|
||||||
|
factionAspectLabel: this.options.factionAspectLabel ?? "",
|
||||||
|
factionAspectBonus: this.options.factionAspectBonus ?? 0,
|
||||||
skillValue,
|
skillValue,
|
||||||
useDestin: this.options.useDestin ?? false,
|
useDestin: this.options.useDestin ?? false,
|
||||||
useFortune: this.options.useFortune ?? false,
|
useFortune: this.options.useFortune ?? false,
|
||||||
puiserRessources: this.options.puiserRessources ?? false,
|
puiserRessources: this.options.puiserRessources ?? false,
|
||||||
nbDice: this.options.nbDice ?? diceResults.length,
|
nbDice: this.options.nbDice ?? diceResults.length,
|
||||||
woundMalus,
|
woundMalus,
|
||||||
|
armorMalus,
|
||||||
woundLabel,
|
woundLabel,
|
||||||
isResistance: this.options.isResistance ?? false,
|
isResistance: this.options.isResistance ?? false,
|
||||||
isCombat: this.options.isCombat ?? false,
|
isCombat: this.options.isCombat ?? false,
|
||||||
weaponName: this.options.weaponName ?? null,
|
weaponName: this.options.weaponName ?? null,
|
||||||
weaponDegats: this.options.weaponDegats ?? null,
|
weaponDegats,
|
||||||
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,
|
situationMod: this.options.situationMod ?? 0,
|
||||||
rangedMod: this.options.rangedMod ?? 0,
|
rangedMod: this.options.rangedMod ?? 0,
|
||||||
|
hasDamageSummary: isWeaponHit,
|
||||||
|
incomingWounds,
|
||||||
|
incomingWoundsDisplay: incomingWounds ?? "1 + X",
|
||||||
|
hasVariableDamage,
|
||||||
|
canApplyWeaponDamage: incomingWounds !== null,
|
||||||
|
targetActorId,
|
||||||
|
targetActorName,
|
||||||
|
selectedTargetProtection,
|
||||||
|
selectedTargetAppliedWounds,
|
||||||
|
availableTargets,
|
||||||
// Dé de lune
|
// Dé de lune
|
||||||
hasMoonDie: moonDieResult !== null,
|
hasMoonDie: moonDieResult !== null,
|
||||||
moonDieResult,
|
moonDieResult,
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
export { default as CelestopolCharacter } from "./character.mjs"
|
export { default as CelestopolCharacter } from "./character.mjs"
|
||||||
export { default as CelestopolNPC } from "./npc.mjs"
|
export { default as CelestopolNPC } from "./npc.mjs"
|
||||||
export { CelestopolAnomaly, CelestopolAspect, CelestopolEquipment, CelestopolWeapon, CelestopolArmure } from "./items.mjs"
|
export { CelestopolAnomaly, CelestopolAspect, CelestopolEquipment, CelestopolWeapon, CelestopolArmure } from "./items.mjs"
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import { SYSTEM } from "../config/system.mjs"
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
export default class CelestopolCharacter extends foundry.abstract.TypeDataModel {
|
export default class CelestopolCharacter extends foundry.abstract.TypeDataModel {
|
||||||
@@ -165,8 +178,21 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
getArmorMalus() {
|
getArmorMalus() {
|
||||||
if (!this.parent) return 0
|
if (!this.parent) return 0
|
||||||
return -(this.parent.itemTypes.armure
|
return -(this.parent.itemTypes.armure
|
||||||
.filter(a => a.system.equipped && a.system.malus > 0)
|
.filter(a => a.system.equipped && (a.system.protection ?? a.system.malus) > 0)
|
||||||
.reduce((sum, a) => sum + a.system.malus, 0))
|
.reduce((sum, a) => sum + (a.system.protection ?? a.system.malus), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le malus d'armure applicable pour un jet PJ.
|
||||||
|
* Règle : uniquement sur Mobilité et Effacement si l'armure est équipée.
|
||||||
|
* @param {string} statId
|
||||||
|
* @param {string|null} skillId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getArmorMalusForRoll(statId, skillId = null) {
|
||||||
|
if (statId !== "corps") return 0
|
||||||
|
if (!["mobilite", "effacement"].includes(skillId)) return 0
|
||||||
|
return this.getArmorMalus()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,7 +224,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(),
|
armorMalus: this.getArmorMalusForRoll(statId, skillId),
|
||||||
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,
|
||||||
@@ -229,7 +255,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(),
|
armorMalus: 0,
|
||||||
woundLevel: this.blessures.lvl,
|
woundLevel: this.blessures.lvl,
|
||||||
isResistance: true,
|
isResistance: true,
|
||||||
rollMoonDie: false,
|
rollMoonDie: false,
|
||||||
@@ -240,8 +266,8 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collecte les tokens PNJs disponibles comme cibles de combat.
|
* Collecte les cibles de combat sur la scène active.
|
||||||
* Priorise le combat tracker, sinon les tokens ciblés par l'utilisateur.
|
* Pour un PJ attaquant, seules les cibles PNJ présentes sur la scène sont proposées.
|
||||||
* @returns {Array<{id:string, name:string, corps:number}>}
|
* @returns {Array<{id:string, name:string, corps:number}>}
|
||||||
*/
|
*/
|
||||||
_getCombatTargets() {
|
_getCombatTargets() {
|
||||||
@@ -250,25 +276,13 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
name: actor.name,
|
name: actor.name,
|
||||||
corps: actor.system.stats?.corps?.res ?? 0,
|
corps: actor.system.stats?.corps?.res ?? 0,
|
||||||
})
|
})
|
||||||
// Priorité 1 : PNJs dans le combat actif
|
const sceneTokens = canvas?.scene?.isView ? (canvas.tokens?.placeables ?? []) : []
|
||||||
if (game.combat?.active) {
|
return [...new Map(sceneTokens
|
||||||
const list = game.combat.combatants
|
.filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id)
|
||||||
.filter(c => c.actor?.type === "npc" && c.actorId !== this.parent.id)
|
.map(t => {
|
||||||
.map(c => toEntry(c.actor))
|
const actor = t.actor
|
||||||
if (list.length) return list
|
return [actor.id, toEntry(actor)]
|
||||||
}
|
})).values()]
|
||||||
// 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 []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,7 +310,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
|
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
|
||||||
skillValue: echauffouree.value,
|
skillValue: echauffouree.value,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
armorMalus: this.getArmorMalus(),
|
armorMalus: this.getArmorMalusForRoll("corps", "echauffouree"),
|
||||||
woundLevel: this.blessures.lvl,
|
woundLevel: this.blessures.lvl,
|
||||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||||
destGaugeFull: this.destin.lvl > 0,
|
destGaugeFull: this.destin.lvl > 0,
|
||||||
@@ -334,7 +348,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
|||||||
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
|
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
|
||||||
skillValue: mobilite.value,
|
skillValue: mobilite.value,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
armorMalus: this.getArmorMalus(),
|
armorMalus: this.getArmorMalusForRoll("corps", "mobilite"),
|
||||||
woundLevel: this.blessures.lvl,
|
woundLevel: this.blessures.lvl,
|
||||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||||
destGaugeFull: this.destin.lvl > 0,
|
destGaugeFull: this.destin.lvl > 0,
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import { SYSTEM } from "../config/system.mjs"
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
/** Schéma partagé pour les bonus/malus par domaine (utilisé dans anomaly/aspect). */
|
/** Schéma partagé pour les bonus/malus par domaine (utilisé dans anomaly/aspect). */
|
||||||
@@ -83,9 +96,14 @@ export class CelestopolArmure extends foundry.abstract.TypeDataModel {
|
|||||||
const reqInt = { required: true, nullable: false, integer: true }
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
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: 1, min: 0, max: 2 }),
|
||||||
equipped: new fields.BooleanField({ initial: false }),
|
equipped: new fields.BooleanField({ initial: false }),
|
||||||
description: new fields.HTMLField({ required: true, textSearch: true }),
|
description: new fields.HTMLField({ required: true, textSearch: true }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
this.malus = this.protection
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Célestopol 1922 — Système FoundryVTT
|
||||||
|
*
|
||||||
|
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
|
||||||
|
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||||||
|
* affilié à Antre-Monde Éditions,
|
||||||
|
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||||||
|
*
|
||||||
|
* @author LeRatierBretonnien
|
||||||
|
* @copyright 2025–2026 LeRatierBretonnien
|
||||||
|
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
*/
|
||||||
|
|
||||||
import { SYSTEM } from "../config/system.mjs"
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
||||||
@@ -66,7 +79,20 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|||||||
const armures = this.parent?.itemTypes?.armure ?? []
|
const armures = this.parent?.itemTypes?.armure ?? []
|
||||||
return armures
|
return armures
|
||||||
.filter(a => a.system.equipped)
|
.filter(a => a.system.equipped)
|
||||||
.reduce((sum, a) => sum + (a.system.malus ? -Math.abs(a.system.malus) : 0), 0)
|
.reduce((sum, a) => {
|
||||||
|
const value = a.system.protection ?? a.system.malus
|
||||||
|
return sum + (value ? -Math.abs(value) : 0)
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le malus d'armure applicable pour un jet PNJ.
|
||||||
|
* Règle : sur tous les jets de Corps uniquement.
|
||||||
|
* @param {string} statId
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getArmorMalusForRoll(statId) {
|
||||||
|
return statId === "corps" ? this.getArmorMalus() : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,7 +117,7 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
|||||||
skillLabel,
|
skillLabel,
|
||||||
skillValue: statData.res,
|
skillValue: statData.res,
|
||||||
woundMalus: this.getWoundMalus(),
|
woundMalus: this.getWoundMalus(),
|
||||||
armorMalus: this.getArmorMalus(),
|
armorMalus: this.getArmorMalusForRoll(statId),
|
||||||
woundLevel: this.blessures.lvl,
|
woundLevel: this.blessures.lvl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
packs-system/aides-de-jeu/000005.ldb
Normal file
BIN
packs-system/aides-de-jeu/000005.ldb
Normal file
Binary file not shown.
0
packs-system/aides-de-jeu/000024.log
Normal file
0
packs-system/aides-de-jeu/000024.log
Normal file
1
packs-system/aides-de-jeu/CURRENT
Normal file
1
packs-system/aides-de-jeu/CURRENT
Normal file
@@ -0,0 +1 @@
|
|||||||
|
MANIFEST-000022
|
||||||
0
packs-system/aides-de-jeu/LOCK
Normal file
0
packs-system/aides-de-jeu/LOCK
Normal file
8
packs-system/aides-de-jeu/LOG
Normal file
8
packs-system/aides-de-jeu/LOG
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
2026/04/11-15:27:57.620400 7ff3bebfd6c0 Recovering log #20
|
||||||
|
2026/04/11-15:27:57.630743 7ff3bebfd6c0 Delete type=3 #18
|
||||||
|
2026/04/11-15:27:57.630810 7ff3bebfd6c0 Delete type=0 #20
|
||||||
|
2026/04/11-15:29:26.022154 7ff3bdbfb6c0 Level-0 table #25: started
|
||||||
|
2026/04/11-15:29:26.022184 7ff3bdbfb6c0 Level-0 table #25: 0 bytes OK
|
||||||
|
2026/04/11-15:29:26.028372 7ff3bdbfb6c0 Delete type=0 #23
|
||||||
|
2026/04/11-15:29:26.034985 7ff3bdbfb6c0 Manual compaction at level-0 from '!journal!eNYstmPK0mMmVJYC' @ 72057594037927935 : 1 .. '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 0 : 0; will stop at (end)
|
||||||
|
2026/04/11-15:29:26.054681 7ff3bdbfb6c0 Manual compaction at level-1 from '!journal!eNYstmPK0mMmVJYC' @ 72057594037927935 : 1 .. '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 0 : 0; will stop at (end)
|
||||||
8
packs-system/aides-de-jeu/LOG.old
Normal file
8
packs-system/aides-de-jeu/LOG.old
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
2026/04/11-15:27:20.564248 7f20edbfe6c0 Recovering log #16
|
||||||
|
2026/04/11-15:27:20.573933 7f20edbfe6c0 Delete type=3 #14
|
||||||
|
2026/04/11-15:27:20.573986 7f20edbfe6c0 Delete type=0 #16
|
||||||
|
2026/04/11-15:27:31.323603 7f1e4ffff6c0 Level-0 table #21: started
|
||||||
|
2026/04/11-15:27:31.323630 7f1e4ffff6c0 Level-0 table #21: 0 bytes OK
|
||||||
|
2026/04/11-15:27:31.330503 7f1e4ffff6c0 Delete type=0 #19
|
||||||
|
2026/04/11-15:27:31.336782 7f1e4ffff6c0 Manual compaction at level-0 from '!journal!eNYstmPK0mMmVJYC' @ 72057594037927935 : 1 .. '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 0 : 0; will stop at (end)
|
||||||
|
2026/04/11-15:27:31.347179 7f1e4ffff6c0 Manual compaction at level-1 from '!journal!eNYstmPK0mMmVJYC' @ 72057594037927935 : 1 .. '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 0 : 0; will stop at (end)
|
||||||
BIN
packs-system/aides-de-jeu/MANIFEST-000022
Normal file
BIN
packs-system/aides-de-jeu/MANIFEST-000022
Normal file
Binary file not shown.
Binary file not shown.
0
packs-system/anomalies/000060.log
Normal file
0
packs-system/anomalies/000060.log
Normal file
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000018
|
MANIFEST-000058
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
2026/04/06-17:46:52.532955 7f67ebfff6c0 Recovering log #15
|
2026/04/11-15:27:57.608051 7ff3bf3fe6c0 Recovering log #55
|
||||||
2026/04/06-17:46:52.543005 7f67ebfff6c0 Delete type=3 #13
|
2026/04/11-15:27:57.617618 7ff3bf3fe6c0 Delete type=3 #53
|
||||||
2026/04/06-17:46:52.543081 7f67ebfff6c0 Delete type=0 #15
|
2026/04/11-15:27:57.617668 7ff3bf3fe6c0 Delete type=0 #55
|
||||||
|
2026/04/11-15:29:26.012403 7ff3bdbfb6c0 Level-0 table #61: started
|
||||||
|
2026/04/11-15:29:26.015819 7ff3bdbfb6c0 Level-0 table #61: 3524 bytes OK
|
||||||
|
2026/04/11-15:29:26.022024 7ff3bdbfb6c0 Delete type=0 #59
|
||||||
|
2026/04/11-15:29:26.034975 7ff3bdbfb6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
||||||
|
2026/04/11-15:29:26.044411 7ff3bdbfb6c0 Manual compaction at level-1 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 53 : 1
|
||||||
|
2026/04/11-15:29:26.044420 7ff3bdbfb6c0 Compacting 1@1 + 1@2 files
|
||||||
|
2026/04/11-15:29:26.047715 7ff3bdbfb6c0 Generated table #62@1: 9 keys, 6617 bytes
|
||||||
|
2026/04/11-15:29:26.047736 7ff3bdbfb6c0 Compacted 1@1 + 1@2 files => 6617 bytes
|
||||||
|
2026/04/11-15:29:26.054365 7ff3bdbfb6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
|
2026/04/11-15:29:26.054474 7ff3bdbfb6c0 Delete type=2 #57
|
||||||
|
2026/04/11-15:29:26.054607 7ff3bdbfb6c0 Delete type=2 #61
|
||||||
|
2026/04/11-15:29:26.064047 7ff3bdbfb6c0 Manual compaction at level-1 from '!items!null' @ 53 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
2026/04/05-21:02:44.634018 7f8249dff6c0 Recovering log #10
|
2026/04/11-15:27:20.551914 7f20ee3ff6c0 Recovering log #50
|
||||||
2026/04/05-21:02:44.729398 7f8249dff6c0 Delete type=3 #8
|
2026/04/11-15:27:20.562310 7f20ee3ff6c0 Delete type=3 #48
|
||||||
2026/04/05-21:02:44.729470 7f8249dff6c0 Delete type=0 #10
|
2026/04/11-15:27:20.562396 7f20ee3ff6c0 Delete type=0 #50
|
||||||
2026/04/06-00:09:38.933436 7f82177fe6c0 Level-0 table #16: started
|
2026/04/11-15:27:31.313674 7f1e4ffff6c0 Level-0 table #56: started
|
||||||
2026/04/06-00:09:38.937122 7f82177fe6c0 Level-0 table #16: 3525 bytes OK
|
2026/04/11-15:27:31.317287 7f1e4ffff6c0 Level-0 table #56: 3524 bytes OK
|
||||||
2026/04/06-00:09:38.943462 7f82177fe6c0 Delete type=0 #14
|
2026/04/11-15:27:31.323440 7f1e4ffff6c0 Delete type=0 #54
|
||||||
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/04/11-15:27:31.336770 7f1e4ffff6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
||||||
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/04/11-15:27:31.336813 7f1e4ffff6c0 Manual compaction at level-1 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 49 : 1
|
||||||
2026/04/06-00:09:38.966141 7f82177fe6c0 Compacting 1@1 + 1@2 files
|
2026/04/11-15:27:31.336819 7f1e4ffff6c0 Compacting 1@1 + 1@2 files
|
||||||
2026/04/06-00:09:38.969869 7f82177fe6c0 Generated table #17@1: 9 keys, 6617 bytes
|
2026/04/11-15:27:31.340181 7f1e4ffff6c0 Generated table #57@1: 9 keys, 6617 bytes
|
||||||
2026/04/06-00:09:38.969906 7f82177fe6c0 Compacted 1@1 + 1@2 files => 6617 bytes
|
2026/04/11-15:27:31.340207 7f1e4ffff6c0 Compacted 1@1 + 1@2 files => 6617 bytes
|
||||||
2026/04/06-00:09:38.976148 7f82177fe6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
2026/04/11-15:27:31.346883 7f1e4ffff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
2026/04/06-00:09:38.976266 7f82177fe6c0 Delete type=2 #12
|
2026/04/11-15:27:31.346998 7f1e4ffff6c0 Delete type=2 #52
|
||||||
2026/04/06-00:09:38.976457 7f82177fe6c0 Delete type=2 #16
|
2026/04/11-15:27:31.347109 7f1e4ffff6c0 Delete type=2 #56
|
||||||
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)
|
2026/04/11-15:27:31.353403 7f1e4ffff6c0 Manual compaction at level-1 from '!items!null' @ 49 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
BIN
packs-system/anomalies/MANIFEST-000058
Normal file
BIN
packs-system/anomalies/MANIFEST-000058
Normal file
Binary file not shown.
0
packs-system/scenes/000017.log
Normal file
0
packs-system/scenes/000017.log
Normal file
BIN
packs-system/scenes/000019.ldb
Normal file
BIN
packs-system/scenes/000019.ldb
Normal file
Binary file not shown.
1
packs-system/scenes/CURRENT
Normal file
1
packs-system/scenes/CURRENT
Normal file
@@ -0,0 +1 @@
|
|||||||
|
MANIFEST-000015
|
||||||
0
packs-system/scenes/LOCK
Normal file
0
packs-system/scenes/LOCK
Normal file
15
packs-system/scenes/LOG
Normal file
15
packs-system/scenes/LOG
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
2026/04/11-15:27:57.633319 7ff3bf3fe6c0 Recovering log #13
|
||||||
|
2026/04/11-15:27:57.644646 7ff3bf3fe6c0 Delete type=3 #11
|
||||||
|
2026/04/11-15:27:57.644712 7ff3bf3fe6c0 Delete type=0 #13
|
||||||
|
2026/04/11-15:29:26.001386 7ff3bdbfb6c0 Level-0 table #18: started
|
||||||
|
2026/04/11-15:29:26.005135 7ff3bdbfb6c0 Level-0 table #18: 3095 bytes OK
|
||||||
|
2026/04/11-15:29:26.012219 7ff3bdbfb6c0 Delete type=0 #16
|
||||||
|
2026/04/11-15:29:26.034958 7ff3bdbfb6c0 Manual compaction at level-0 from '!scenes!Jr7lGxYk2RETlXRv' @ 72057594037927935 : 1 .. '!scenes.tokens.delta.items!Jr7lGxYk2RETlXRv.6urwC5SVcou6UOAG.CTg4yBE12iMee1RU.BYT1CrA37R3Og0nu' @ 0 : 0; will stop at (end)
|
||||||
|
2026/04/11-15:29:26.035011 7ff3bdbfb6c0 Manual compaction at level-1 from '!scenes!Jr7lGxYk2RETlXRv' @ 72057594037927935 : 1 .. '!scenes.tokens.delta.items!Jr7lGxYk2RETlXRv.6urwC5SVcou6UOAG.CTg4yBE12iMee1RU.BYT1CrA37R3Og0nu' @ 0 : 0; will stop at '!scenes.tokens.delta.items!Jr7lGxYk2RETlXRv.6urwC5SVcou6UOAG.CTg4yBE12iMee1RU.BYT1CrA37R3Og0nu' @ 31 : 1
|
||||||
|
2026/04/11-15:29:26.035018 7ff3bdbfb6c0 Compacting 1@1 + 1@2 files
|
||||||
|
2026/04/11-15:29:26.038199 7ff3bdbfb6c0 Generated table #19@1: 7 keys, 3095 bytes
|
||||||
|
2026/04/11-15:29:26.038215 7ff3bdbfb6c0 Compacted 1@1 + 1@2 files => 3095 bytes
|
||||||
|
2026/04/11-15:29:26.044152 7ff3bdbfb6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
|
2026/04/11-15:29:26.044236 7ff3bdbfb6c0 Delete type=2 #10
|
||||||
|
2026/04/11-15:29:26.044341 7ff3bdbfb6c0 Delete type=2 #18
|
||||||
|
2026/04/11-15:29:26.064030 7ff3bdbfb6c0 Manual compaction at level-1 from '!scenes.tokens.delta.items!Jr7lGxYk2RETlXRv.6urwC5SVcou6UOAG.CTg4yBE12iMee1RU.BYT1CrA37R3Og0nu' @ 31 : 1 .. '!scenes.tokens.delta.items!Jr7lGxYk2RETlXRv.6urwC5SVcou6UOAG.CTg4yBE12iMee1RU.BYT1CrA37R3Og0nu' @ 0 : 0; will stop at (end)
|
||||||
8
packs-system/scenes/LOG.old
Normal file
8
packs-system/scenes/LOG.old
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
2026/04/11-15:27:20.576678 7f20ecbfc6c0 Recovering log #8
|
||||||
|
2026/04/11-15:27:20.586717 7f20ecbfc6c0 Delete type=3 #6
|
||||||
|
2026/04/11-15:27:20.586780 7f20ecbfc6c0 Delete type=0 #8
|
||||||
|
2026/04/11-15:27:31.330627 7f1e4ffff6c0 Level-0 table #14: started
|
||||||
|
2026/04/11-15:27:31.330649 7f1e4ffff6c0 Level-0 table #14: 0 bytes OK
|
||||||
|
2026/04/11-15:27:31.336642 7f1e4ffff6c0 Delete type=0 #12
|
||||||
|
2026/04/11-15:27:31.336792 7f1e4ffff6c0 Manual compaction at level-0 from '!scenes!Jr7lGxYk2RETlXRv' @ 72057594037927935 : 1 .. '!scenes.tokens.delta.items!Jr7lGxYk2RETlXRv.6urwC5SVcou6UOAG.CTg4yBE12iMee1RU.BYT1CrA37R3Og0nu' @ 0 : 0; will stop at (end)
|
||||||
|
2026/04/11-15:27:31.347189 7f1e4ffff6c0 Manual compaction at level-1 from '!scenes!Jr7lGxYk2RETlXRv' @ 72057594037927935 : 1 .. '!scenes.tokens.delta.items!Jr7lGxYk2RETlXRv.6urwC5SVcou6UOAG.CTg4yBE12iMee1RU.BYT1CrA37R3Og0nu' @ 0 : 0; will stop at (end)
|
||||||
BIN
packs-system/scenes/MANIFEST-000015
Normal file
BIN
packs-system/scenes/MANIFEST-000015
Normal file
Binary file not shown.
@@ -355,6 +355,145 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.factions-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 210px;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-summary {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
border: 1px solid rgba(122,92,32,0.35);
|
||||||
|
background: linear-gradient(180deg, rgba(255,248,232,0.95), rgba(240,229,209,0.9));
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.35);
|
||||||
|
padding: 7px 9px;
|
||||||
|
|
||||||
|
.faction-aspect-summary-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-summary-title {
|
||||||
|
color: var(--cel-green);
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 1em;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-manage {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: var(--cel-orange);
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.84em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-points {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-point {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border: 1px solid rgba(122,92,32,0.25);
|
||||||
|
background: rgba(255,255,255,0.45);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.66em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
color: var(--cel-border);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: var(--cel-orange);
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 1.02em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-source-line {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--cel-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-source {
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: rgba(12,76,12,0.08);
|
||||||
|
border: 1px solid rgba(12,76,12,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-active-title {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: var(--cel-border);
|
||||||
|
font-size: 0.68em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-active-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(122,92,32,0.25);
|
||||||
|
background: rgba(255,255,255,0.55);
|
||||||
|
line-height: 1.1;
|
||||||
|
|
||||||
|
&.is-relevant {
|
||||||
|
border-color: rgba(12,76,12,0.35);
|
||||||
|
background: rgba(12,76,12,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: var(--cel-green);
|
||||||
|
font-size: 0.88em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: var(--cel-orange);
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-empty {
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.84em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Factions table
|
// Factions table
|
||||||
.factions-table {
|
.factions-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -396,12 +535,9 @@
|
|||||||
background: rgba(255,255,255,0.3);
|
background: rgba(255,255,255,0.3);
|
||||||
transition: background 0.1s;
|
transition: background 0.1s;
|
||||||
&[data-action] { cursor: pointer; }
|
&[data-action] { cursor: pointer; }
|
||||||
// Dot neutre (centre, index 4)
|
|
||||||
&.neutral { border-color: #888; }
|
&.neutral { border-color: #888; }
|
||||||
&.neutral.filled { background: #aaa; 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); }
|
&.pos.filled { background: var(--cel-orange); border-color: var(--cel-orange); }
|
||||||
// Dots négatifs (hostiles) → rouge terracotta
|
|
||||||
&.neg.filled { background: #b84a2e; border-color: #b84a2e; }
|
&.neg.filled { background: #b84a2e; border-color: #b84a2e; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +557,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.factions-legend {
|
||||||
|
border: 1px solid rgba(122,92,32,0.35);
|
||||||
|
background: linear-gradient(180deg, rgba(255,248,232,0.95), rgba(240,229,209,0.9));
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.35);
|
||||||
|
padding: 8px 10px;
|
||||||
|
|
||||||
|
.factions-legend-title {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid rgba(122,92,32,0.25);
|
||||||
|
color: var(--cel-green);
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-size: 1.05em;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factions-legend-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 0.88em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factions-legend-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 28px minmax(0, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factions-legend-value {
|
||||||
|
color: var(--cel-orange);
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.factions-legend-label {
|
||||||
|
color: var(--cel-green);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Biography / Equipment
|
// Biography / Equipment
|
||||||
.equipments-section {
|
.equipments-section {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
@@ -446,6 +624,7 @@
|
|||||||
|
|
||||||
.item-row {
|
.item-row {
|
||||||
.cel-item-row();
|
.cel-item-row();
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
&.is-equipped {
|
&.is-equipped {
|
||||||
background: rgba(12, 76, 12, 0.12);
|
background: rgba(12, 76, 12, 0.12);
|
||||||
@@ -469,6 +648,22 @@
|
|||||||
&.equipped { color: var(--cel-green); }
|
&.equipped { color: var(--cel-green); }
|
||||||
&:hover { color: var(--cel-orange); }
|
&:hover { color: var(--cel-orange); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-controls {
|
||||||
|
opacity: 1;
|
||||||
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.equip-empty {
|
.equip-empty {
|
||||||
|
|||||||
@@ -221,6 +221,10 @@
|
|||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.manage-faction-aspects-btn {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -415,13 +415,18 @@
|
|||||||
}
|
}
|
||||||
.equipped-box {
|
.equipped-box {
|
||||||
border-color: var(--cel-green);
|
border-color: var(--cel-green);
|
||||||
|
min-width: 170px;
|
||||||
.equipped-switch {
|
.equipped-switch {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
input[type="checkbox"] { display: none; }
|
input[type="checkbox"] { display: none; }
|
||||||
.switch-label {
|
.switch-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
font-family: var(--cel-font-ui);
|
font-family: var(--cel-font-ui);
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: var(--cel-border);
|
color: var(--cel-border);
|
||||||
@@ -429,7 +434,13 @@
|
|||||||
border: 1px solid var(--cel-border);
|
border: 1px solid var(--cel-border);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
white-space: nowrap;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 34px;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: center;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
&.on {
|
&.on {
|
||||||
color: var(--cel-green-light);
|
color: var(--cel-green-light);
|
||||||
border-color: var(--cel-green);
|
border-color: var(--cel-green);
|
||||||
|
|||||||
142
styles/npc.less
142
styles/npc.less
@@ -251,7 +251,7 @@
|
|||||||
.track-section {
|
.track-section {
|
||||||
border: 1px solid var(--cel-border);
|
border: 1px solid var(--cel-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.track-header {
|
.track-header {
|
||||||
@@ -259,27 +259,78 @@
|
|||||||
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: 5px 8px;
|
padding: 5px 8px;
|
||||||
font-family: var(--cel-font-title);
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.9em;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
border-bottom: 1px solid rgba(196,154,26,0.4);
|
border-bottom: 1px solid rgba(196,154,26,0.4);
|
||||||
|
|
||||||
|
.track-title {
|
||||||
|
font-family: var(--cel-font-title);
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 1.1em;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
&:hover { opacity: 1; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-boxes {
|
.track-boxes {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
background: var(--cel-cream);
|
background: var(--cel-cream);
|
||||||
|
|
||||||
.track-box {
|
.track-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
.box-label { font-size: 0.65em; color: var(--cel-border); }
|
width: 22px;
|
||||||
|
min-height: 22px;
|
||||||
|
border: 2px solid var(--cel-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
background: rgba(255,255,255,0.45);
|
||||||
|
transition: background 0.1s, border-color 0.1s;
|
||||||
|
|
||||||
|
&.filled {
|
||||||
|
background: var(--cel-orange);
|
||||||
|
border-color: var(--cel-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-action] { cursor: pointer; }
|
||||||
|
|
||||||
|
.box-label {
|
||||||
|
font-size: 0.6em;
|
||||||
|
color: var(--cel-border);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.filled .box-label { color: rgba(30,10,0,0.65); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,11 +341,84 @@
|
|||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
background: rgba(139,115,85,0.1);
|
background: rgba(139,115,85,0.1);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
|
label { color: var(--cel-border); }
|
||||||
input[type="number"] { width: 40px; .cel-input-std(); }
|
input[type="number"] { width: 40px; .cel-input-std(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.description-section {
|
.tab.equipement {
|
||||||
|
.equip-section {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
.cel-section-header();
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
i { opacity: 0.75; }
|
||||||
|
span { flex: 1; }
|
||||||
|
a { color: var(--cel-orange); cursor: pointer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-row {
|
||||||
|
.cel-item-row();
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
&.is-equipped {
|
||||||
|
background: rgba(12, 76, 12, 0.12);
|
||||||
|
border-left: 3px solid var(--cel-green);
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-tag {
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 1px 7px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(12,76,12,0.15);
|
||||||
|
border: 1px solid rgba(12,76,12,0.3);
|
||||||
|
color: #3a5a1e;
|
||||||
|
white-space: nowrap;
|
||||||
|
&.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); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-controls {
|
||||||
|
opacity: 1;
|
||||||
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.equip-empty {
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--cel-border);
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-section {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
.section-header { .cel-section-header(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-section, .notes-section {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
.enriched-html { font-size: 0.9em; line-height: 1.6; }
|
.enriched-html { font-size: 0.9em; line-height: 1.6; }
|
||||||
}
|
}
|
||||||
|
|||||||
486
styles/roll.less
486
styles/roll.less
@@ -285,6 +285,11 @@
|
|||||||
|
|
||||||
.form-visibility label { color: #888; }
|
.form-visibility label { color: #888; }
|
||||||
|
|
||||||
|
.form-faction-aspect select {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Ligne Puiser dans ses ressources ──
|
// ── Ligne Puiser dans ses ressources ──
|
||||||
.form-puiser-row {
|
.form-puiser-row {
|
||||||
border: 1px solid rgba(139,62,72,0.4);
|
border: 1px solid rgba(139,62,72,0.4);
|
||||||
@@ -581,6 +586,7 @@
|
|||||||
.fl-mod.fortune { color: var(--cel-green, #0c4c0c); font-weight: bold; }
|
.fl-mod.fortune { color: var(--cel-green, #0c4c0c); font-weight: bold; }
|
||||||
.fl-mod.wound { color: #922; }
|
.fl-mod.wound { color: #922; }
|
||||||
.fl-asp { color: var(--cel-orange, #e07b00); font-weight: bold; }
|
.fl-asp { color: var(--cel-orange, #e07b00); font-weight: bold; }
|
||||||
|
.fl-faction { color: var(--cel-green, #0c4c0c); font-weight: bold; }
|
||||||
.fl-sep { font-weight: bold; color: var(--cel-border, #7a5c20); margin: 0 2px; }
|
.fl-sep { font-weight: bold; color: var(--cel-border, #7a5c20); margin: 0 2px; }
|
||||||
.fl-eq { color: #aaa; }
|
.fl-eq { color: #aaa; }
|
||||||
.fl-op { color: #aaa; }
|
.fl-op { color: #aaa; }
|
||||||
@@ -646,6 +652,116 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.used-info.used-faction-aspect {
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
background: rgba(12,76,12,0.08);
|
||||||
|
border-top-color: rgba(12,76,12,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.weapon-damage-summary {
|
||||||
|
padding: 8px 12px 10px;
|
||||||
|
background: linear-gradient(180deg, rgba(224,123,0,0.08), rgba(224,123,0,0.02));
|
||||||
|
border-top: 1px solid rgba(122,92,32,0.25);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.is-applied {
|
||||||
|
background: linear-gradient(180deg, rgba(12,76,12,0.1), rgba(12,76,12,0.03));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-pending {
|
||||||
|
background: linear-gradient(180deg, rgba(122,92,32,0.14), rgba(122,92,32,0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-header {
|
||||||
|
font-size: 0.72em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--cel-border, #7a5c20);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-main {
|
||||||
|
margin-top: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-value {
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
font-size: 1.9em;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-unit {
|
||||||
|
font-size: 0.82em;
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-breakdown,
|
||||||
|
.damage-note {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 0.76em;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #5c4630;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weapon-damage-actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
select {
|
||||||
|
min-width: 170px;
|
||||||
|
border: 1px solid rgba(122,92,32,0.45);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 3px 7px;
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-apply-button {
|
||||||
|
border: 1px solid var(--cel-green, #0c4c0c);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--cel-green, #0c4c0c);
|
||||||
|
color: var(--cel-orange-light, #ddb84a);
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 0.78em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-application-status {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.76em;
|
||||||
|
line-height: 1.4;
|
||||||
|
|
||||||
|
&.is-applied {
|
||||||
|
background: rgba(12,76,12,0.12);
|
||||||
|
border: 1px solid rgba(12,76,12,0.28);
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-pending {
|
||||||
|
background: rgba(122,92,32,0.12);
|
||||||
|
border: 1px solid rgba(122,92,32,0.28);
|
||||||
|
color: #6b4b12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Fortune fixe badge dans zone dés ──
|
// ── Fortune fixe badge dans zone dés ──
|
||||||
.fortune-fixed-badge {
|
.fortune-fixed-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -783,6 +899,376 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-message .cel-welcome-message {
|
||||||
|
border: 1px solid var(--cel-border, #7a5c20);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(180deg, rgba(255,248,232,0.98), rgba(240,229,209,0.95));
|
||||||
|
font-family: var(--cel-font-body, "Palatino Linotype", serif);
|
||||||
|
|
||||||
|
.welcome-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
background: var(--cel-green, #0c4c0c);
|
||||||
|
background-image: url("../assets/ui/fond_cadrille.jpg");
|
||||||
|
background-blend-mode: soft-light;
|
||||||
|
border-bottom: 2px solid var(--cel-orange, #e07b00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-mark {
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-size: 1.05em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-size: 0.98em;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-body {
|
||||||
|
padding: 9px 11px 10px;
|
||||||
|
color: #3f3623;
|
||||||
|
font-size: 0.84em;
|
||||||
|
line-height: 1.45;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-note {
|
||||||
|
margin-top: 7px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid rgba(122,92,32,0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
color: var(--cel-border, #7a5c20);
|
||||||
|
font-size: 0.72em;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-value {
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.content-link,
|
||||||
|
a[href] {
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.content-link:hover,
|
||||||
|
a[href]:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.application.faction-aspect-dialog {
|
||||||
|
width: min(540px, 92vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.application.faction-aspect-dialog .window-content {
|
||||||
|
padding: 0 !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: min(70vh, 680px);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--cel-cream, #f0e8d4);
|
||||||
|
border-top: 2px solid var(--cel-orange, #e07b00);
|
||||||
|
|
||||||
|
> form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content.standard-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 10px 6px !important;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-footer {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 6px 10px 8px;
|
||||||
|
margin: 0;
|
||||||
|
border-top: 1px solid rgba(122,92,32,0.18);
|
||||||
|
background: rgba(255,255,255,0.28);
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border: 1px solid rgba(122,92,32,0.35);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: linear-gradient(180deg, rgba(12,76,12,0.1), rgba(12,76,12,0.03));
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--cel-orange, #e07b00);
|
||||||
|
background: linear-gradient(180deg, rgba(224,123,0,0.18), rgba(224,123,0,0.06));
|
||||||
|
color: #7a3e00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-box {
|
||||||
|
border: 1px solid rgba(122,92,32,0.22);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,0.55), rgba(255,255,255,0.34));
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.55);
|
||||||
|
padding: 6px 7px;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
background: linear-gradient(180deg, rgba(12,76,12,0.08), rgba(255,255,255,0.42));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-box-title {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
font-size: 0.88em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-points {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-point-card {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 92px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
border: 1px solid rgba(122,92,32,0.18);
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-size: 0.72em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--cel-border, #7a5c20);
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: normal;
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
font-size: 1.05em;
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-source-line {
|
||||||
|
font-size: 0.78em;
|
||||||
|
color: var(--cel-border, #7a5c20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-warning {
|
||||||
|
padding: 7px 9px;
|
||||||
|
border-left: 3px solid #b84a2e;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(184, 74, 46, 0.08);
|
||||||
|
color: #8b3e2b;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-tag-list,
|
||||||
|
.faction-aspect-source-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 3px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(12, 76, 12, 0.08);
|
||||||
|
border: 1px solid rgba(12, 76, 12, 0.18);
|
||||||
|
color: var(--cel-green, #0c4c0c);
|
||||||
|
font-size: 0.72em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-size: 0.68em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--cel-border, #7a5c20);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"],
|
||||||
|
input[type="text"],
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 28px;
|
||||||
|
border: 1px solid rgba(122,92,32,0.32);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
background: rgba(255,255,255,0.9);
|
||||||
|
font-size: 0.78em;
|
||||||
|
color: #2f2413;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
select option {
|
||||||
|
color: #2f2413;
|
||||||
|
background: #fffaf0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-pool-group {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
max-width: 180px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-help-tip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(122,92,32,0.28);
|
||||||
|
background: rgba(224,123,0,0.12);
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
font-size: 0.72em;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: help;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-cell-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-advanced {
|
||||||
|
margin-top: 2px;
|
||||||
|
padding-top: 3px;
|
||||||
|
border-top: 1px dashed rgba(122,92,32,0.18);
|
||||||
|
|
||||||
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 0.82em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-cell-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 0.74em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-active-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-active-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255,255,255,0.7);
|
||||||
|
color: #2f2413;
|
||||||
|
font-size: 0.76em;
|
||||||
|
|
||||||
|
&.is-relevant {
|
||||||
|
border-left: 3px solid var(--cel-green, #0c4c0c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-active-name {
|
||||||
|
color: #2f2413;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-active-value {
|
||||||
|
color: var(--cel-orange, #e07b00);
|
||||||
|
font-family: var(--cel-font-title, "CopaseticNF", serif);
|
||||||
|
font-size: 0.92em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-empty {
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.74em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faction-aspect-remove-block {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding-top: 6px;
|
||||||
|
border-top: 1px solid rgba(122,92,32,0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.faction-aspect-cell-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Notification de blessure cochée lors d'un test de résistance raté
|
// Notification de blessure cochée lors d'un test de résistance raté
|
||||||
.celestopol.chat-roll {
|
.celestopol.chat-roll {
|
||||||
|
|||||||
19
system.json
19
system.json
@@ -6,7 +6,7 @@
|
|||||||
"download": "#{DOWNLOAD}#",
|
"download": "#{DOWNLOAD}#",
|
||||||
"url": "https://www.uberwald.me/gitea/public/fvtt-celestopol",
|
"url": "https://www.uberwald.me/gitea/public/fvtt-celestopol",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"version": "13.0.0",
|
"version": "14.0.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Uberwald",
|
"name": "Uberwald",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13",
|
"minimum": "13",
|
||||||
"verified": "13"
|
"verified": "14"
|
||||||
},
|
},
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
"fvtt-celestopol.mjs"
|
"fvtt-celestopol.mjs"
|
||||||
@@ -97,7 +97,22 @@
|
|||||||
"system": "fvtt-celestopol",
|
"system": "fvtt-celestopol",
|
||||||
"path": "packs-system/anomalies",
|
"path": "packs-system/anomalies",
|
||||||
"type": "Item"
|
"type": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aides-de-jeu",
|
||||||
|
"label": "Célestopol 1922 — Aides de jeu",
|
||||||
|
"system": "fvtt-celestopol",
|
||||||
|
"path": "packs-system/aides-de-jeu",
|
||||||
|
"type": "JournalEntry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "scenes",
|
||||||
|
"label": "Célestopol 1922 — Scènes",
|
||||||
|
"system": "fvtt-celestopol",
|
||||||
|
"path": "packs-system/scenes",
|
||||||
|
"type": "Scene"
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
"grid": {
|
"grid": {
|
||||||
"distance": 5,
|
"distance": 5,
|
||||||
|
|||||||
@@ -35,11 +35,8 @@
|
|||||||
<div class="armure-stat-box">
|
<div class="armure-stat-box">
|
||||||
<label>{{localize "CELESTOPOL.Armure.malus"}}</label>
|
<label>{{localize "CELESTOPOL.Armure.malus"}}</label>
|
||||||
<div class="armure-stat-value">
|
<div class="armure-stat-value">
|
||||||
{{#if isEditable}}
|
<input type="hidden" name="system.malus" value="{{system.protection}}">
|
||||||
<input type="number" name="system.malus" value="{{system.malus}}" min="0" max="2">
|
<span data-armure-malus-value>{{system.protection}}</span>
|
||||||
{{else}}
|
|
||||||
<span>{{system.malus}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="armure-stat-hint">{{localize "CELESTOPOL.Armure.malusHint"}}</div>
|
<div class="armure-stat-hint">{{localize "CELESTOPOL.Armure.malusHint"}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<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.protection}}<span class="item-tag malus">−{{item.system.protection}} {{localize "CELESTOPOL.Armure.malus"}}</span>{{/if}}
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a data-action="toggleArmure" data-item-uuid="{{item.uuid}}"
|
<a data-action="toggleArmure" data-item-uuid="{{item.uuid}}"
|
||||||
class="equip-toggle {{#if item.system.equipped}}equipped{{/if}}"
|
class="equip-toggle {{#if item.system.equipped}}equipped{{/if}}"
|
||||||
|
|||||||
@@ -1,55 +1,124 @@
|
|||||||
<div class="tab factions {{tab.cssClass}}" data-group="sheet" data-tab="factions">
|
<div class="tab factions {{tab.cssClass}}" data-group="sheet" data-tab="factions">
|
||||||
<table class="factions-table">
|
<div class="factions-layout">
|
||||||
<thead>
|
<section class="faction-aspect-summary">
|
||||||
<tr>
|
<div class="faction-aspect-summary-header">
|
||||||
<th>{{localize "CELESTOPOL.Faction.label"}}</th>
|
<div class="faction-aspect-summary-title">{{localize "CELESTOPOL.FactionAspect.title"}}</div>
|
||||||
<th>{{localize "CELESTOPOL.Faction.relation"}}</th>
|
{{#if isGM}}
|
||||||
</tr>
|
<a class="faction-aspect-manage" data-action="manageFactionAspects">
|
||||||
</thead>
|
<i class="fa-solid fa-sliders"></i> {{localize "CELESTOPOL.FactionAspect.manage"}}
|
||||||
<tbody>
|
</a>
|
||||||
{{!-- Factions standard --}}
|
{{/if}}
|
||||||
{{#each factionRows as |faction|}}
|
</div>
|
||||||
<tr class="faction-row" data-faction="{{faction.id}}">
|
|
||||||
<td class="faction-name">{{localize faction.label}}</td>
|
|
||||||
<td class="faction-value">
|
|
||||||
<div class="faction-checkboxes-container">
|
|
||||||
<div class="faction-checkboxes">
|
|
||||||
{{#each faction.dots as |dot|}}
|
|
||||||
<span class="faction-dot {{dot.type}} {{#if dot.filled}}filled{{/if}}"
|
|
||||||
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{../id}}" data-index="{{dot.index}}"{{/if}}></span>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
<span class="faction-count">{{faction.valueStr}}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
{{!-- Factions personnalisées --}}
|
{{#if factionAspectSummary}}
|
||||||
{{#each factionCustom as |faction|}}
|
<div class="faction-aspect-points">
|
||||||
<tr class="faction-row custom" data-faction="{{faction.id}}">
|
<div class="faction-aspect-point">
|
||||||
<td>
|
<span class="label">{{localize "CELESTOPOL.FactionAspect.pointsMax"}}</span>
|
||||||
{{#if @root.isEditMode}}
|
<span class="value">{{factionAspectSummary.pointsMax}}</span>
|
||||||
<input type="text" name="system.factions.{{faction.id}}.label"
|
</div>
|
||||||
value="{{faction.label}}"
|
<div class="faction-aspect-point">
|
||||||
placeholder="{{localize 'CELESTOPOL.Faction.custom'}}">
|
<span class="label">{{localize "CELESTOPOL.FactionAspect.pointsSpent"}}</span>
|
||||||
{{else}}
|
<span class="value">{{factionAspectSummary.pointsSpent}}</span>
|
||||||
<span>{{#if faction.label}}{{faction.label}}{{else}}—{{/if}}</span>
|
</div>
|
||||||
{{/if}}
|
<div class="faction-aspect-point">
|
||||||
</td>
|
<span class="label">{{localize "CELESTOPOL.FactionAspect.pointsRemaining"}}</span>
|
||||||
<td>
|
<span class="value">{{factionAspectSummary.pointsRemaining}}</span>
|
||||||
<div class="faction-checkboxes-container">
|
</div>
|
||||||
<div class="faction-checkboxes">
|
</div>
|
||||||
{{#each faction.dots as |dot|}}
|
|
||||||
<span class="faction-dot {{dot.type}} {{#if dot.filled}}filled{{/if}}"
|
{{#if factionAspectSummary.sourceLabels.length}}
|
||||||
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{../id}}" data-index="{{dot.index}}"{{/if}}></span>
|
<div class="faction-aspect-source-line">
|
||||||
{{/each}}
|
<strong>{{localize "CELESTOPOL.FactionAspect.sources"}} :</strong>
|
||||||
</div>
|
{{#each factionAspectSummary.sourceLabels as |label|}}
|
||||||
<span class="faction-count">{{faction.valueStr}}</span>
|
<span class="faction-aspect-source">{{label}}</span>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="faction-aspect-empty">{{localize "CELESTOPOL.FactionAspect.officialSourcesEmpty"}}</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="faction-aspect-active-block">
|
||||||
|
<div class="faction-aspect-active-title">{{localize "CELESTOPOL.FactionAspect.activeTitle"}}</div>
|
||||||
|
{{#if factionAspectSummary.activatedAspects.length}}
|
||||||
|
<div class="faction-aspect-active-list">
|
||||||
|
{{#each factionAspectSummary.activatedAspects as |aspect|}}
|
||||||
|
<div class="faction-aspect-chip {{#if aspect.relevantToActor}}is-relevant{{/if}}">
|
||||||
|
<span class="name">{{aspect.label}}</span>
|
||||||
|
<span class="value">+{{aspect.value}}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
{{/each}}
|
||||||
</tr>
|
</div>
|
||||||
{{/each}}
|
{{else}}
|
||||||
</tbody>
|
<div class="faction-aspect-empty">{{localize "CELESTOPOL.FactionAspect.noneActive"}}</div>
|
||||||
</table>
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<table class="factions-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{localize "CELESTOPOL.Faction.label"}}</th>
|
||||||
|
<th>{{localize "CELESTOPOL.Faction.relation"}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{!-- Factions standard --}}
|
||||||
|
{{#each factionRows as |faction|}}
|
||||||
|
<tr class="faction-row" data-faction="{{faction.id}}">
|
||||||
|
<td class="faction-name">{{localize faction.label}}</td>
|
||||||
|
<td class="faction-value">
|
||||||
|
<div class="faction-checkboxes-container">
|
||||||
|
<div class="faction-checkboxes">
|
||||||
|
{{#each faction.dots as |dot|}}
|
||||||
|
<span class="faction-dot {{dot.type}} {{#if dot.filled}}filled{{/if}}"
|
||||||
|
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{../id}}" data-index="{{dot.index}}"{{/if}}></span>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
<span class="faction-count">{{faction.valueStr}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{!-- Factions personnalisées --}}
|
||||||
|
{{#each factionCustom as |faction|}}
|
||||||
|
<tr class="faction-row custom" data-faction="{{faction.id}}">
|
||||||
|
<td>
|
||||||
|
{{#if @root.isEditMode}}
|
||||||
|
<input type="text" name="system.factions.{{faction.id}}.label"
|
||||||
|
value="{{faction.label}}"
|
||||||
|
placeholder="{{localize 'CELESTOPOL.Faction.custom'}}">
|
||||||
|
{{else}}
|
||||||
|
<span>{{#if faction.label}}{{faction.label}}{{else}}—{{/if}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="faction-checkboxes-container">
|
||||||
|
<div class="faction-checkboxes">
|
||||||
|
{{#each faction.dots as |dot|}}
|
||||||
|
<span class="faction-dot {{dot.type}} {{#if dot.filled}}filled{{/if}}"
|
||||||
|
{{#if @root.isEditable}}data-action="factionLevel" data-faction="{{../id}}" data-index="{{dot.index}}"{{/if}}></span>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
<span class="faction-count">{{faction.valueStr}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<aside class="factions-legend">
|
||||||
|
<div class="factions-legend-title">{{localize "CELESTOPOL.Faction.legendTitle"}}</div>
|
||||||
|
<div class="factions-legend-list">
|
||||||
|
{{#each factionLegend as |entry|}}
|
||||||
|
<div class="factions-legend-row">
|
||||||
|
<span class="factions-legend-value">{{entry.value}}</span>
|
||||||
|
<span class="factions-legend-label">{{entry.label}}</span>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,10 +45,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="identity-field">
|
<div class="identity-field">
|
||||||
<label>{{localize "CELESTOPOL.Actor.faction"}}</label>
|
<label>{{localize "CELESTOPOL.Actor.faction"}}</label>
|
||||||
{{#if isEditMode}}
|
{{#if isEditMode}}
|
||||||
<input type="text" name="system.faction" value="{{system.faction}}" placeholder="{{localize 'CELESTOPOL.Actor.faction'}}">
|
<select name="system.faction">
|
||||||
|
<option value="" {{#unless selectedPrimaryFactionId}}{{#unless legacyPrimaryFactionValue}}selected{{/unless}}{{/unless}}>— {{localize "CELESTOPOL.NPC.factionNone"}} —</option>
|
||||||
|
{{#if legacyPrimaryFactionValue}}
|
||||||
|
<option value="{{legacyPrimaryFactionValue}}" selected>{{localize "CELESTOPOL.FactionAspect.legacyFactionValue"}} : {{legacyPrimaryFactionValue}}</option>
|
||||||
|
{{/if}}
|
||||||
|
{{#each factions as |faction key|}}
|
||||||
|
<option value="{{key}}" {{#if (eq key ../selectedPrimaryFactionId)}}selected{{/if}}>{{localize faction.label}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span>{{system.faction}}</span>
|
<span>{{primaryFactionLabel}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,6 +100,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
|
{{#if isGM}}
|
||||||
|
<a class="manage-faction-aspects-btn" data-action="manageFactionAspects" title="{{localize 'CELESTOPOL.FactionAspect.managerTitle'}}">
|
||||||
|
<i class="fa-solid fa-people-group"></i>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
<a class="moon-standalone-btn" data-action="rollMoonDie" title="{{localize 'CELESTOPOL.Moon.standaloneTitle'}}">
|
<a class="moon-standalone-btn" data-action="rollMoonDie" title="{{localize 'CELESTOPOL.Moon.standaloneTitle'}}">
|
||||||
🌙
|
🌙
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -55,6 +55,10 @@
|
|||||||
<span class="fl-op">−</span>
|
<span class="fl-op">−</span>
|
||||||
<span class="fl-mod wound" title="{{localize "CELESTOPOL.Roll.woundMalus"}}">{{abs woundMalus}}</span>
|
<span class="fl-mod wound" title="{{localize "CELESTOPOL.Roll.woundMalus"}}">{{abs woundMalus}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if armorMalus}}
|
||||||
|
<span class="fl-op">−</span>
|
||||||
|
<span class="fl-mod armor" title="{{localize "CELESTOPOL.Roll.armorMalus"}}">🛡{{abs armorMalus}}</span>
|
||||||
|
{{/if}}
|
||||||
{{#if modifier}}
|
{{#if modifier}}
|
||||||
<span class="fl-op">{{#if (gt modifier 0)}}+{{else}}−{{/if}}</span>
|
<span class="fl-op">{{#if (gt modifier 0)}}+{{else}}−{{/if}}</span>
|
||||||
<span class="fl-mod">{{abs modifier}}</span>
|
<span class="fl-mod">{{abs modifier}}</span>
|
||||||
@@ -63,6 +67,10 @@
|
|||||||
<span class="fl-op">{{#if (gt aspectMod 0)}}+{{else}}−{{/if}}</span>
|
<span class="fl-op">{{#if (gt aspectMod 0)}}+{{else}}−{{/if}}</span>
|
||||||
<span class="fl-asp" title="{{localize "CELESTOPOL.Roll.usedAspect"}}">✦{{abs aspectMod}}</span>
|
<span class="fl-asp" title="{{localize "CELESTOPOL.Roll.usedAspect"}}">✦{{abs aspectMod}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if factionAspectBonus}}
|
||||||
|
<span class="fl-op">+</span>
|
||||||
|
<span class="fl-faction" title="{{localize "CELESTOPOL.FactionAspect.rollLabel"}}">⚑{{factionAspectBonus}}</span>
|
||||||
|
{{/if}}
|
||||||
{{#if situationMod}}
|
{{#if situationMod}}
|
||||||
<span class="fl-op">{{#if (gt situationMod 0)}}+{{else}}−{{/if}}</span>
|
<span class="fl-op">{{#if (gt situationMod 0)}}+{{else}}−{{/if}}</span>
|
||||||
<span class="fl-mod sit" title="{{localize "CELESTOPOL.Roll.situationMod"}}">◈{{abs situationMod}}</span>
|
<span class="fl-mod sit" title="{{localize "CELESTOPOL.Roll.situationMod"}}">◈{{abs situationMod}}</span>
|
||||||
@@ -103,6 +111,16 @@
|
|||||||
<span>💪 {{localize "CELESTOPOL.Roll.usedPuiser"}}</span>
|
<span>💪 {{localize "CELESTOPOL.Roll.usedPuiser"}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if armorMalus}}
|
||||||
|
<div class="used-info">
|
||||||
|
<span class="used-armor">🛡 {{localize "CELESTOPOL.Roll.armorMalus"}} (−{{abs armorMalus}})</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if factionAspectBonus}}
|
||||||
|
<div class="used-info used-faction-aspect">
|
||||||
|
<span>⚑ {{factionAspectLabel}} (+{{factionAspectBonus}})</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{!-- Résultat du Dé de la Lune (narratif) --}}
|
{{!-- Résultat du Dé de la Lune (narratif) --}}
|
||||||
{{#if hasMoonDie}}
|
{{#if hasMoonDie}}
|
||||||
@@ -136,7 +154,7 @@
|
|||||||
{{#if isRangedDefense}}
|
{{#if isRangedDefense}}
|
||||||
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseSuccess"}}</span>
|
<span class="result-desc">{{localize "CELESTOPOL.Combat.rangedDefenseSuccess"}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<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"}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else if isFailure}}
|
{{else if isFailure}}
|
||||||
@@ -154,6 +172,47 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if hasDamageSummary}}
|
||||||
|
<div class="weapon-damage-summary">
|
||||||
|
<div class="damage-header">{{localize "CELESTOPOL.Combat.damageLabel"}}</div>
|
||||||
|
<div class="damage-main">
|
||||||
|
<span class="damage-value">{{incomingWoundsDisplay}}</span>
|
||||||
|
<span class="damage-unit">{{localize "CELESTOPOL.Combat.damageUnit"}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if hasVariableDamage}}
|
||||||
|
<div class="damage-note">{{localize "CELESTOPOL.Combat.damageManual"}}</div>
|
||||||
|
{{else}}
|
||||||
|
{{#if targetActorId}}
|
||||||
|
<div class="damage-breakdown">
|
||||||
|
<div><strong>{{localize "CELESTOPOL.Combat.targetLabel"}} :</strong> {{targetActorName}}</div>
|
||||||
|
<div><strong>{{localize "CELESTOPOL.Combat.damageArmorReduction"}} :</strong> −{{selectedTargetProtection}}</div>
|
||||||
|
<div><strong>{{localize "CELESTOPOL.Combat.damageApplied"}} :</strong> {{selectedTargetAppliedWounds}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="weapon-damage-actions">
|
||||||
|
<button type="button" class="damage-apply-button" data-action="apply-weapon-damage" data-actor-id="{{targetActorId}}" data-incoming-wounds="{{incomingWounds}}">
|
||||||
|
{{localize "CELESTOPOL.Combat.applyDamage"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{else if availableTargets.length}}
|
||||||
|
<div class="weapon-damage-actions">
|
||||||
|
<select name="targetActorId">
|
||||||
|
<option value="">{{localize "CELESTOPOL.Combat.targetAuto"}}</option>
|
||||||
|
{{#each availableTargets as |target|}}
|
||||||
|
<option value="{{target.id}}" {{#if target.selected}}selected{{/if}}>{{target.name}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
<button type="button" class="damage-apply-button" data-action="apply-weapon-damage" data-incoming-wounds="{{incomingWounds}}">
|
||||||
|
{{localize "CELESTOPOL.Combat.applyDamage"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="damage-note">{{localize "CELESTOPOL.Combat.noCharacterTargetAvailable"}}</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{!-- Blessure auto-cochée (résistance ratée ou combat mêlée raté) --}}
|
{{!-- Blessure auto-cochée (résistance ratée ou combat mêlée raté) --}}
|
||||||
{{#if woundTaken}}
|
{{#if woundTaken}}
|
||||||
<div class="resistance-wound-notice">
|
<div class="resistance-wound-notice">
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<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.protection}}<span class="item-tag malus">−{{item.system.protection}} {{localize "CELESTOPOL.Armure.malus"}}</span>{{/if}}
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a data-action="toggleArmure" data-item-uuid="{{item.uuid}}"
|
<a data-action="toggleArmure" data-item-uuid="{{item.uuid}}"
|
||||||
title="{{#if item.system.equipped}}{{localize 'CELESTOPOL.Armure.unequip'}}{{else}}{{localize 'CELESTOPOL.Armure.equip'}}{{/if}}"
|
title="{{#if item.system.equipped}}{{localize 'CELESTOPOL.Armure.unequip'}}{{else}}{{localize 'CELESTOPOL.Armure.equip'}}{{/if}}"
|
||||||
@@ -55,4 +55,27 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{!-- ── Équipements ──────────────────────────────────────────────────── --}}
|
||||||
|
<div class="equip-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<i class="fas fa-briefcase"></i>
|
||||||
|
<span>{{localize "CELESTOPOL.Item.equipments"}}</span>
|
||||||
|
{{#if isEditMode}}
|
||||||
|
<a data-action="createEquipment" title="{{localize 'CELESTOPOL.Item.newEquipment'}}"><i class="fas fa-plus"></i></a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{#each equipments as |item|}}
|
||||||
|
<div class="item-row equipment" 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>
|
||||||
|
<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.noEquipments"}}</p>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<select id="targetSelect" name="targetSelect">
|
<select id="targetSelect" name="targetSelect">
|
||||||
<option value="">— {{localize "CELESTOPOL.Combat.targetAuto"}} —</option>
|
<option value="">— {{localize "CELESTOPOL.Combat.targetAuto"}} —</option>
|
||||||
{{#each availableTargets as |t|}}
|
{{#each availableTargets as |t|}}
|
||||||
<option value="{{t.corps}}">{{t.name}}</option>
|
<option value="{{t.id}}" data-corps="{{t.corps}}">{{t.name}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,7 +85,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{!-- Test en opposition : le résultat sera masqué, MJ décide --}}
|
{{!-- Test en opposition : le résultat sera masqué, MJ décide --}}
|
||||||
{{#unless isResistance}}
|
|
||||||
<div class="form-opposition-row">
|
<div class="form-opposition-row">
|
||||||
<label class="opposition-toggle" for="isOpposition">
|
<label class="opposition-toggle" for="isOpposition">
|
||||||
<input type="checkbox" id="isOpposition" name="isOpposition">
|
<input type="checkbox" id="isOpposition" name="isOpposition">
|
||||||
@@ -96,7 +95,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
@@ -137,6 +135,18 @@
|
|||||||
|
|
||||||
{{/unless}}{{!-- /isResistance aspect --}}
|
{{/unless}}{{!-- /isResistance aspect --}}
|
||||||
|
|
||||||
|
{{#if factionAspectChoices.length}}
|
||||||
|
<div class="form-row-line form-faction-aspect">
|
||||||
|
<label for="factionAspectId">{{localize "CELESTOPOL.FactionAspect.rollLabel"}}</label>
|
||||||
|
<select id="factionAspectId" name="factionAspectId">
|
||||||
|
<option value="">{{localize "CELESTOPOL.FactionAspect.noneOption"}}</option>
|
||||||
|
{{#each factionAspectChoices as |choice|}}
|
||||||
|
<option value="{{choice.id}}" data-value="{{choice.value}}">{{choice.label}} (+{{choice.value}})</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{!-- Options non disponibles en test de résistance (lune, destin, puiser, fortune) --}}
|
{{!-- Options non disponibles en test de résistance (lune, destin, puiser, fortune) --}}
|
||||||
{{#unless isResistance}}
|
{{#unless isResistance}}
|
||||||
|
|
||||||
@@ -225,5 +235,3 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user