Esprit de la Loi + Automaton

This commit is contained in:
2026-05-02 23:16:10 +02:00
parent d6b5891519
commit 0df4a5a9fb
280 changed files with 10668 additions and 419 deletions
+57 -1
View File
@@ -223,6 +223,9 @@
"MNBL.potionHeroique": "Heroic",
"MNBL.potionInefficace": "Ineffective",
"MNBL.potionPoison": "Poison",
"MNBL.potions": "Potions",
"MNBL.potionStatut": "Status",
"MNBL.potionDuree": "Duration",
"MNBL.preparePotion": "Prepare a Potion",
"MNBL.capacites": "Capacities / Powers",
"MNBL.capacite": "Capacity",
@@ -230,6 +233,7 @@
"MNBL.typeCapaciteElue": "Chosen Power",
"MNBL.typeCapaciteElementaire": "Elemental Power",
"MNBL.typeCapaciteDemoniaque": "Demonic Power",
"MNBL.typeCapaciteAutomaton": "Automaton Ability",
"MNBL.typeCapaciteCreature": "Creature Power",
"MNBL.creatureTypeCreature": "Creature",
"MNBL.creatureTypeDemon": "Demon",
@@ -240,6 +244,37 @@
"MNBL.elementTypeEau": "Water",
"MNBL.creatureType": "Creature Type",
"MNBL.elementType": "Element",
"MNBL.demonType": "Demon Type",
"MNBL.demonPuissance": "Power Level",
"MNBL.demonTypeNone": "— Undefined —",
"MNBL.demonTypeCombat": "Combat Demon",
"MNBL.demonTypeDesir": "Desire Demon",
"MNBL.demonTypeSavoir": "Knowledge Demon",
"MNBL.demonTypeProtection": "Protection Demon",
"MNBL.demonTypeVoyage": "Travel Demon",
"MNBL.demonPuissanceNone": "— Undefined —",
"MNBL.demonPuissanceMineur": "Minor",
"MNBL.demonPuissanceMedian": "Median",
"MNBL.demonPuissanceMajeur": "Major",
"MNBL.creatureTypeAutomaton": "Automaton",
"MNBL.automatonType": "Automaton Type",
"MNBL.automatonPuissance": "Power Level",
"MNBL.automatonTypeNone": "— Undefined —",
"MNBL.automatonTypeCombat": "Combat",
"MNBL.automatonTypeVoyage": "Travel",
"MNBL.automatonTypePerception": "Perception",
"MNBL.automatonTypeRestauration": "Restoration",
"MNBL.automatonTypeReparateur": "Repairer",
"MNBL.automatonPuissanceNone": "— Undefined —",
"MNBL.automatonPuissanceMineur": "Minor",
"MNBL.automatonPuissanceMedian": "Median",
"MNBL.automatonPuissanceMajeur": "Major",
"MNBL.automatonVoyageType": "Travel Type",
"MNBL.automatonVoyageTypeNone": "— Undefined —",
"MNBL.automatonVoyageTypeTerrestre": "Terrestrial",
"MNBL.automatonVoyageTypeAquatique": "Aquatic",
"MNBL.automatonVoyageTypeAerien": "Aerial",
"MNBL.automatonVoyageTypeExtradimensionnel": "Extra-dimensional",
"MNBL.invoquerElementaire": "Summon an Elemental",
"MNBL.bannirElementaire": "Banish",
"MNBL.invocationsActives": "Active Invocations",
@@ -262,5 +297,26 @@
"MNBL.invocationConcentrationMineur": "1 round",
"MNBL.invocationConcentrationMedian": "1 minute",
"MNBL.invocationConcentrationMajeur": "1 hour",
"MNBL.invocationAmeBloque": "Blocked Soul"
"MNBL.invocationAmeBloque": "Blocked Soul",
"MNBL.invoquerDemon": "Invoke a Demon",
"MNBL.invoquerEsprit": "Invoke a Law Spirit",
"MNBL.libererDemon": "Release",
"MNBL.invocationsDemons": "Active Demonic Invocations",
"MNBL.invocationDemonSeuil": "Difficulty Threshold",
"MNBL.invocationDemonAme": "Soul Spent",
"MNBL.invocationDemonCoercition": "Coercion",
"MNBL.invocationDemonLoiChaos": "Lore: Law & Chaos",
"MNBL.invocationDemonResultSucces": "The Demon is invoked!",
"MNBL.invocationDemonResultHeroique": "Heroic success! More favorable agreement.",
"MNBL.invocationDemonResultEchec": "Invocation failed. You lose half the Soul points spent.",
"MNBL.invocationDemonResultDramatique": "Dramatic failure! All Soul points are lost.",
"MNBL.enchantementLoi": "Law Enchantment",
"MNBL.enchanter": "Enchant",
"MNBL.enchantementActif": "Enchanted",
"MNBL.enchantementBonus": "Bonus",
"MNBL.enchantementAntiChaos": "Anti-Chaos",
"MNBL.enchantementStandard": "Standard (skill bonus)",
"MNBL.enchantementTypeAntiChaos": "Anti-Chaos (luminous aura)"
}
+59 -2
View File
@@ -22,7 +22,8 @@
"runeeffect": "Effet de Rune",
"bouclier": "Bouclier",
"modifier": "Modificateur",
"traitespece": "Trait d'Espèce"
"traitespece": "Trait d'Espèce",
"potion": "Potion"
}
},
"Adresse": "Adresse",
@@ -232,6 +233,9 @@
"MNBL.potionHeroique": "Héroïque",
"MNBL.potionInefficace": "Inefficace",
"MNBL.potionPoison": "Poison",
"MNBL.potions": "Potions",
"MNBL.potionStatut": "Statut",
"MNBL.potionDuree": "Durée",
"MNBL.preparePotion": "Préparer une Potion",
"MNBL.capacites": "Capacités / Pouvoirs",
"MNBL.capacite": "Capacité",
@@ -239,6 +243,7 @@
"MNBL.typeCapaciteElue": "Pouvoir Élue",
"MNBL.typeCapaciteElementaire": "Pouvoir Élémentaire",
"MNBL.typeCapaciteDemoniaque": "Pouvoir Démoniaque",
"MNBL.typeCapaciteAutomaton": "Capacité Automaton",
"MNBL.typeCapaciteCreature": "Pouvoir Créature",
"MNBL.creatureTypeCreature": "Créature",
"MNBL.creatureTypeDemon": "Démon",
@@ -249,6 +254,37 @@
"MNBL.elementTypeEau": "Eau",
"MNBL.creatureType": "Type de créature",
"MNBL.elementType": "Élément",
"MNBL.demonType": "Type de Démon",
"MNBL.demonPuissance": "Puissance",
"MNBL.demonTypeNone": "— Non défini —",
"MNBL.demonTypeCombat": "Démon de Combat",
"MNBL.demonTypeDesir": "Démon du Désir",
"MNBL.demonTypeSavoir": "Démon du Savoir",
"MNBL.demonTypeProtection": "Démon de Protection",
"MNBL.demonTypeVoyage": "Démon du Voyage",
"MNBL.demonPuissanceNone": "— Non défini —",
"MNBL.demonPuissanceMineur": "Mineur",
"MNBL.demonPuissanceMedian": "Médian",
"MNBL.demonPuissanceMajeur": "Majeur",
"MNBL.creatureTypeAutomaton": "Automaton",
"MNBL.automatonType": "Type d'Automaton",
"MNBL.automatonPuissance": "Puissance",
"MNBL.automatonTypeNone": "— Non défini —",
"MNBL.automatonTypeCombat": "Combat",
"MNBL.automatonTypeVoyage": "Voyage",
"MNBL.automatonTypePerception": "Perception",
"MNBL.automatonTypeRestauration": "Restauration",
"MNBL.automatonTypeReparateur": "Réparateur",
"MNBL.automatonPuissanceNone": "— Non défini —",
"MNBL.automatonPuissanceMineur": "Mineur",
"MNBL.automatonPuissanceMedian": "Médian",
"MNBL.automatonPuissanceMajeur": "Majeur",
"MNBL.automatonVoyageType": "Type de voyage",
"MNBL.automatonVoyageTypeNone": "— Non défini —",
"MNBL.automatonVoyageTypeTerrestre": "Terrestre",
"MNBL.automatonVoyageTypeAquatique": "Aquatique",
"MNBL.automatonVoyageTypeAerien": "Aérien",
"MNBL.automatonVoyageTypeExtradimensionnel": "Extra-dimensionnel",
"MNBL.invoquerElementaire": "Invoquer un Élémentaire",
"MNBL.bannirElementaire": "Bannir",
"MNBL.invocationsActives": "Invocations actives",
@@ -271,5 +307,26 @@
"MNBL.invocationConcentrationMineur": "1 tour",
"MNBL.invocationConcentrationMedian": "1 minute",
"MNBL.invocationConcentrationMajeur": "1 heure",
"MNBL.invocationAmeBloque": "Âme bloquée"
"MNBL.invocationAmeBloque": "Âme bloquée",
"MNBL.invoquerDemon": "Invoquer un Démon",
"MNBL.invoquerEsprit": "Invoquer un Esprit de la Loi",
"MNBL.libererDemon": "Libérer",
"MNBL.invocationsDemons": "Invocations démoniaques actives",
"MNBL.invocationDemonSeuil": "Seuil de difficulté",
"MNBL.invocationDemonAme": "Âme dépensée",
"MNBL.invocationDemonCoercition": "Coercition",
"MNBL.invocationDemonLoiChaos": "Savoir : Loi & Chaos",
"MNBL.invocationDemonResultSucces": "Le Démon est invoqué !",
"MNBL.invocationDemonResultHeroique": "Réussite héroïque ! Accord plus favorable.",
"MNBL.invocationDemonResultEchec": "L'invocation a échoué. Vous perdez la moitié des points d'Âme dépensés.",
"MNBL.invocationDemonResultDramatique": "Échec dramatique ! Tous les points d'Âme sont perdus.",
"MNBL.enchantementLoi": "Enchantement de la Loi",
"MNBL.enchanter": "Enchanter",
"MNBL.enchantementActif": "Enchanté",
"MNBL.enchantementBonus": "Bonus",
"MNBL.enchantementAntiChaos": "Anti-Chaos",
"MNBL.enchantementStandard": "Standard (bonus de compétence)",
"MNBL.enchantementTypeAntiChaos": "Anti-Chaos (aura lumineuse)"
}
+33 -2
View File
@@ -343,8 +343,8 @@
}
.item-controls-fixed {
min-width: 3.2rem;
max-width: 3.2rem;
min-width: 5rem;
max-width: 5rem;
}
// Couleurs pour les labels et textes dans les onglets
@@ -917,3 +917,34 @@
}
}
}
// Chat potion result — rune mini image and haut-parler styling
.mournblade-chat-result {
.detail-row {
.rune-mini-img {
width: 18px;
height: 18px;
border-radius: 2px;
object-fit: cover;
flex-shrink: 0;
border: 1px solid rgba(139, 69, 19, 0.4);
vertical-align: middle;
margin-right: 4px;
}
.potion-haut-parler {
font-style: italic;
color: #5a3a8a;
}
}
}
// Post-item card: danger divider variant (for létal effects)
.mournblade-item-card {
.item-card-divider--danger {
color: #8b0000;
&::before, &::after {
background: linear-gradient(90deg, transparent, #8b0000, transparent);
}
}
}
+38
View File
@@ -440,3 +440,41 @@
align-items: center;
}
}
/* Enchantement de la Loi */
.enchantement-section {
margin-top: 0.6rem;
padding: 0.5rem 0.6rem;
background: rgba(255, 215, 0, 0.05);
border: 1px solid rgba(255, 215, 0, 0.3);
border-radius: 4px;
.section-title-small {
font-size: 0.9rem;
margin: 0 0 0.4rem 0;
color: #a07800;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.3rem;
border-bottom: 1px solid rgba(255, 215, 0, 0.2);
padding-bottom: 0.2rem;
}
.enchant-badge {
display: inline-block;
background: gold;
color: #333;
font-size: 0.7rem;
padding: 0.1rem 0.4rem;
border-radius: 8px;
margin-left: 0.5rem;
}
.enchant-none {
color: #888;
font-size: 0.85rem;
margin: 0;
padding: 0.2rem 0;
}
}
+232
View File
@@ -2418,6 +2418,25 @@
&:last-child {
margin-bottom: 0;
}
&.next-steps {
background: rgba(200, 220, 255, 0.5);
border-left-color: #2255aa;
ol {
margin: 6px 0 0 0;
padding-left: 18px;
li {
margin-bottom: 4px;
font-size: 0.82rem;
}
}
i {
color: #2255aa;
}
}
}
.damage-buttons {
@@ -2916,6 +2935,219 @@
accent-color: #c0392b;
}
}
// ---- Invocation démon specific ----
.actor-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
padding: 6px 10px;
background: linear-gradient(135deg, #1a0030 0%, #3a0060 100%);
border: 1px solid #6a008a;
border-radius: 4px;
.actor-portrait {
width: 44px;
height: 44px;
border-radius: 4px;
border: 2px solid #9900cc;
object-fit: cover;
flex-shrink: 0;
}
h3 {
font-weight: bold;
font-size: 1.05rem;
color: #ffffff;
text-shadow: 0 1px 3px rgba(0,0,0,0.9);
margin: 0;
}
.dialog-subtitle {
font-size: 0.85rem;
color: #ddb0ff;
i { margin-right: 4px; }
}
.actor-info { display: flex; flex-direction: column; gap: 2px; }
}
.invoc-warning-box {
background: rgba(180, 20, 20, 0.15);
border: 1px solid #8b0000;
border-left: 4px solid #c0392b;
border-radius: 4px;
padding: 7px 10px;
margin-bottom: 8px;
font-size: 0.85rem;
color: #6a0000;
i { margin-right: 6px; color: #c0392b; }
strong { color: #8b0000; }
div { margin-top: 3px; }
}
.invoc-comp-summary {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 10px;
.comp-badge {
background: rgba(60, 20, 80, 0.12);
border: 1px solid rgba(120, 40, 160, 0.4);
border-radius: 12px;
padding: 3px 10px;
font-size: 0.82rem;
color: #3a0060;
&.comp-missing {
background: rgba(120, 0, 0, 0.08);
border-color: rgba(180, 0, 0, 0.3);
color: #800000;
em { font-style: italic; opacity: 0.7; }
}
strong { color: #2a004a; font-weight: bold; }
}
}
.invoc-seuil-calculator {
background: rgba(60, 10, 80, 0.07);
border: 1px solid rgba(120, 40, 160, 0.35);
border-radius: 5px;
padding: 10px 12px;
margin-bottom: 10px;
.invoc-section-title {
font-size: 0.88rem;
font-weight: bold;
color: #3a0060;
margin: 0 0 8px 0;
text-transform: uppercase;
letter-spacing: 0.5px;
i { margin-right: 5px; }
}
.invoc-criteria-grid {
display: grid;
grid-template-columns: 1fr auto;
gap: 5px 10px;
align-items: center;
margin-bottom: 10px;
label {
font-size: 0.83rem;
color: #3a1a0a;
font-weight: bold;
padding: 1px 0;
}
select, input[type="number"] {
background: #3a1a08;
border: 1px solid #8b4513;
border-radius: 3px;
padding: 4px 24px 4px 8px;
color: #f0e8d8;
font-family: CentaurMT, serif;
font-size: 0.83rem;
cursor: pointer;
min-width: 200px;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath fill='%23ffd070' d='M5 7L0 0h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 7px center;
background-size: 9px 6px;
option { background: #1a0e06; color: #f0e8d8; }
}
input[type="number"] {
min-width: 80px;
max-width: 80px;
text-align: center;
background-image: none;
padding: 4px 8px;
}
}
.invoc-seuil-total-row {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 8px;
background: linear-gradient(135deg, #2a005a 0%, #4a0080 100%);
border: 1px solid #9900cc;
border-radius: 4px;
.invoc-seuil-label {
flex: 1;
font-weight: bold;
font-size: 0.9rem;
color: #ddb0ff;
}
.invoc-seuil-total {
font-size: 1.3rem;
font-weight: bold;
color: #ffd700;
text-shadow: 0 0 8px rgba(255, 200, 0, 0.6);
min-width: 32px;
text-align: center;
}
}
}
.invoc-form-grid {
display: flex;
flex-direction: column;
gap: 5px;
.invoc-field {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 6px;
border-bottom: 1px solid rgba(139, 69, 19, 0.2);
label {
flex: 1;
font-weight: bold;
font-size: 0.87rem;
color: #3a1a0a;
}
.invoc-value-highlight {
font-size: 1.1rem;
font-weight: bold;
color: #6a0080;
min-width: 30px;
text-align: center;
}
.invoc-hint {
font-size: 0.78rem;
color: #5a3a0a;
font-style: italic;
strong { color: #8b0000; font-weight: bold; }
}
select {
background: #3a1a08;
border: 1px solid #8b4513;
border-radius: 3px;
padding: 4px 24px 4px 8px;
color: #f0e8d8;
font-family: CentaurMT, serif;
font-size: 0.85rem;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath fill='%23ffd070' d='M5 7L0 0h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 7px center;
background-size: 9px 6px;
option { background: #1a0e06; color: #f0e8d8; }
}
}
}
}
// =====================================================
@@ -0,0 +1,157 @@
import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeEnchantementDialog {
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe")
.replace(/æ/g, "ae")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ")
.trim()
}
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeEnchantementDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeEnchantementDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
static async create(actor, item) {
const normalize = MournbladeEnchantementDialog._normalize.bind(MournbladeEnchantementDialog)
const findComp = (...kw) => MournbladeEnchantementDialog._findCompetence(actor, ...kw)
const ameDisponible = Math.max(0, (actor.system.ame.currentmax - actor.system.ame.value))
const aspect = actor.system.balance.aspect ?? 0
// Skill lookups
const savoirRunesComp = findComp("rune")
const hautParlerComp = findComp("haut", "parler")
const artisanatComp = findComp("savoir", "artisanat")
const claAttr = actor.system.attributs?.clairvoyance
// Prerequisite: Rune de la Loi in inventory
const hasRuneLoi = actor.items.some(i => {
if (i.type !== "rune") return false
return normalize(i.name).includes("loi")
})
const savoirRunesNiveau = savoirRunesComp ? (savoirRunesComp.system.niveau ?? 0) : null
const hautParlerNiveau = hautParlerComp ? (hautParlerComp.system.niveau ?? 0) : null
const artisanatNiveau = artisanatComp ? (artisanatComp.system.niveau ?? 0) : null
const claValeur = claAttr ? (claAttr.value ?? 0) : 0
// Limit: CLA + Savoir:Runes is capped by min(Haut-Parler, Artisanat) if those skills exist
const limiteur = (hautParlerNiveau !== null && artisanatNiveau !== null)
? Math.min(hautParlerNiveau, artisanatNiveau)
: (hautParlerNiveau ?? artisanatNiveau ?? null)
const context = {
actorImg: actor.img,
actorName: actor.name,
itemImg: item.img,
itemName: item.name,
itemType: item.type,
itemId: item.id,
ameDisponible,
aspect,
hasRuneLoi,
savoirRunesNiveau,
hautParlerNiveau,
artisanatNiveau,
claValeur,
limiteur,
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
enchantementActif: item.system.enchantementLoi?.actif ?? false,
enchantementBonus: item.system.enchantementLoi?.bonus ?? 0,
enchantementAntiChaos: item.system.enchantementLoi?.antiChaos ?? false,
}
const prerequisOk = hasRuneLoi
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-enchantement.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeEnchantementDialog._attachListeners(form, ameDisponible, claValeur, savoirRunesNiveau, limiteur, prerequisOk)
})
return foundry.applications.api.DialogV2.wait({
window: { title: `Enchanter : ${item.name}`, icon: "fa-solid fa-star" },
classes: ["mournblade-roll-dialog"],
position: { width: 520 },
modal: false,
content,
buttons: [
{
action: "enchanter",
label: "Enchanter",
icon: "fa-solid fa-star",
default: true,
callback: (event, button, dialog) => {
const elems = button.form.elements
const ptsAme = parseInt(elems["ptsAme"]?.value ?? 5) || 5
const antiChaos = elems["antiChaos"]?.value === "true"
const modificateur = parseInt(elems["modificateur"]?.value ?? 0) || 0
MournbladeUtility.rollEnchantement({
actor,
item,
ptsAme,
antiChaos,
modificateur,
savoirRunesComp,
hautParlerComp,
artisanatComp,
claValeur,
limiteur,
})
}
},
],
rejectClose: false,
})
}
static _attachListeners(html, ameDisponible, claValeur, savoirRunesNiveau, limiteur, prerequisOk = true) {
const enchanterBtn = html.querySelector('button[data-action="enchanter"]')
if (enchanterBtn) enchanterBtn.disabled = !prerequisOk
const diffEl = html.querySelector('#enchant-difficulte')
const bonusEl = html.querySelector('#enchant-bonus-preview')
const warnAmeEl = html.querySelector('#enchant-ame-warn')
const warnLimitEl = html.querySelector('#enchant-limit-warn')
const totalEl = html.querySelector('#enchant-total-dice')
const recalculate = () => {
const ptsAme = parseInt(html.querySelector('[name="ptsAme"]')?.value ?? 5) || 0
const difficulte = ptsAme
const bonus = Math.floor(ptsAme / 5)
const savoir = savoirRunesNiveau ?? 0
const basePool = claValeur + savoir
const effectivePool = limiteur !== null ? Math.min(basePool, limiteur) : basePool
if (diffEl) diffEl.textContent = difficulte
if (bonusEl) bonusEl.textContent = `+${bonus}`
if (totalEl) totalEl.textContent = effectivePool
if (warnAmeEl) warnAmeEl.style.display = ptsAme > ameDisponible ? "" : "none"
if (warnLimitEl && limiteur !== null)
warnLimitEl.style.display = basePool > limiteur ? "" : "none"
}
const ptsAmeEl = html.querySelector('[name="ptsAme"]')
if (ptsAmeEl) {
ptsAmeEl.addEventListener('input', recalculate)
ptsAmeEl.addEventListener('change', recalculate)
}
recalculate()
}
}
@@ -0,0 +1,174 @@
import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeInvocationDemonDialog {
/**
* Normalize a string for fuzzy matching: lowercase, remove diacritics,
* replace œ/æ ligatures, collapse whitespace and punctuation.
*/
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe")
.replace(/æ/g, "ae")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "") // strip combining diacritics
.replace(/[^a-z0-9]+/g, " ") // replace any non-alnum with space
.trim()
}
/**
* Returns the first competence item whose name contains all of the given keywords.
* Keywords are normalized before comparison.
*/
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationDemonDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeInvocationDemonDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
/**
* Returns true if the actor has a capacite or don whose name contains all given keywords.
*/
static _hasCapacite(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationDemonDialog._normalize(k))
return actor.items.some(item => {
if (item.type !== "capacite" && item.type !== "don") return false
const norm = MournbladeInvocationDemonDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
})
}
static async create(actor, rollData) {
const ameDisponible = Math.max(0, (actor.system.ame.currentmax - actor.system.ame.value))
// Robust skill detection: partial keyword matching, diacritic-insensitive
const coercitionComp = MournbladeInvocationDemonDialog._findCompetence(actor, "coercition")
const hautParlerComp = MournbladeInvocationDemonDialog._findCompetence(actor, "haut", "parler")
const loiChaosComp = MournbladeInvocationDemonDialog._findCompetence(actor, "loi", "chaos")
// Check prerequisites — robust capacite/rune detection
const isChaotique = actor.system.balance.chaos > actor.system.balance.loi
const hasOeilSorcier = MournbladeInvocationDemonDialog._hasCapacite(actor, "oeil", "sorcier")
const hasRuneChaos = actor.items.some(i => {
if (i.type !== "rune") return false
return MournbladeInvocationDemonDialog._normalize(i.name).includes("chaos")
})
const prerequisOk = isChaotique && hasOeilSorcier && hasRuneChaos
// Auto-detect chaos link bonuses
const aspectGe8 = (actor.system.balance.aspect ?? 0) >= 8
// hasPacte: true if actor has a Pacte item whose associated deity matches a demon
// (simplified: just expose it as a selectable option — can't auto-detect)
const hasPacte = false
const context = {
...rollData,
img: actor.img,
name: actor.name,
ameDisponible,
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
coercitionNiveau: coercitionComp ? coercitionComp.system.niveau : null,
hautParlerNiveau: hautParlerComp ? hautParlerComp.system.niveau : null,
loiChaosNiveau: loiChaosComp ? loiChaosComp.system.niveau : null,
isChaotique,
hasOeilSorcier,
hasRuneChaos,
prerequisOk,
aspectGe8,
hasPacte,
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-invocation-demon.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeInvocationDemonDialog._attachListeners(form, ameDisponible, prerequisOk)
})
return foundry.applications.api.DialogV2.wait({
window: { title: "Invoquer un Démon", icon: "fa-solid fa-skull" },
classes: ["mournblade-roll-dialog"],
position: { width: 560 },
modal: false,
content,
buttons: [
{
action: "invoquer",
label: "Invoquer",
icon: "fa-solid fa-skull",
default: true,
callback: (event, button, dialog) => {
MournbladeInvocationDemonDialog._updateRollData(rollData, button.form.elements, actor, {
coercitionComp, hautParlerComp, loiChaosComp
})
MournbladeUtility.rollInvocationDemon(rollData)
}
},
],
rejectClose: false,
})
}
static _calculateSeuil(html) {
const get = (name) => parseInt(html.querySelector(`[name="${name}"]`)?.value ?? 0) || 0
const seuil = get("seuil_nature")
+ get("seuil_traits")
+ get("seuil_augmentation")
+ get("seuil_service")
+ get("seuil_duree")
+ get("seuil_marche")
+ get("seuil_chaos")
+ get("seuil_sacrifice")
return Math.max(1, seuil)
}
static _attachListeners(html, ameDisponible, prerequisOk = true) {
const invoquerBtn = html.querySelector('button[data-action="invoquer"]')
if (invoquerBtn) invoquerBtn.disabled = !prerequisOk
const coutEl = html.querySelector('#invoc-demon-cout')
const totalEl = html.querySelector('#invoc-demon-seuil-total')
const hiddenEl = html.querySelector('#invoc-demon-seuil-hidden')
const warnEl = html.querySelector('#invoc-demon-ame-warn')
const criteriaNames = [
"seuil_nature", "seuil_traits", "seuil_augmentation",
"seuil_service", "seuil_duree", "seuil_marche",
"seuil_chaos", "seuil_sacrifice"
]
const recalculate = () => {
const seuil = MournbladeInvocationDemonDialog._calculateSeuil(html)
if (totalEl) totalEl.textContent = seuil
if (hiddenEl) hiddenEl.value = seuil
if (coutEl) coutEl.textContent = seuil
if (warnEl) warnEl.style.display = seuil > ameDisponible ? "" : "none"
}
for (const name of criteriaNames) {
const el = html.querySelector(`[name="${name}"]`)
if (el) el.addEventListener('change', recalculate)
if (el && el.type === 'number') el.addEventListener('input', recalculate)
}
recalculate()
}
static _updateRollData(rollData, formElements, actor, { coercitionComp }) {
const seuil = parseInt(formElements['seuil']?.value ?? 5)
const modificateur = parseInt(formElements['modificateur']?.value ?? 0)
rollData.invocationSeuil = seuil
rollData.invocationSoulCost = seuil
rollData.difficulte = seuil
rollData.modificateur = modificateur
rollData.competence = coercitionComp ?? null
}
}
@@ -2,6 +2,33 @@ import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeInvocationDialog {
/**
* Normalize a string for fuzzy matching: lowercase, remove diacritics,
* replace œ/æ ligatures, collapse whitespace and punctuation.
*/
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe")
.replace(/æ/g, "ae")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ")
.trim()
}
/**
* Returns the first competence item whose name contains all of the given keywords.
*/
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeInvocationDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
static async create(actor, rollData) {
const ameDisponible = Math.max(0, (actor.system.ame.currentmax - actor.system.ame.value))
const maxExtra = Math.max(0, ameDisponible - 15)
@@ -14,8 +41,9 @@ export default class MournbladeInvocationDialog {
allegeance: (p.system.allegeance || "").toLowerCase()
}))
const hautParlerComp = actor.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : haut-parler")
const seigneursElemComp = actor.items.find(c => c.type == "competence" && c.name.toLowerCase() == "savoir : seigneurs élémentaires")
// Robust skill detection: partial keyword matching, diacritic-insensitive
const hautParlerComp = MournbladeInvocationDialog._findCompetence(actor, "haut", "parler")
const seigneursElemComp = MournbladeInvocationDialog._findCompetence(actor, "seigneurs", "elementaires")
const context = {
...rollData,
@@ -0,0 +1,138 @@
import { MournbladeUtility } from "../mournblade-utility.js"
export default class MournbladeInvocationEspritDialog {
static _normalize(str) {
return (str ?? "")
.toLowerCase()
.replace(/œ/g, "oe").replace(/æ/g, "ae")
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ").trim()
}
static _findCompetence(actor, ...keywords) {
const normKeys = keywords.map(k => MournbladeInvocationEspritDialog._normalize(k))
return actor.items.find(item => {
if (item.type !== "competence") return false
const norm = MournbladeInvocationEspritDialog._normalize(item.name)
return normKeys.every(k => norm.includes(k))
}) ?? null
}
/** Seuil and soul cost per puissance */
static SEUILS = { mineur: 15, median: 20, majeur: 25 }
static async create(actor, rollData) {
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
const persuasionComp = MournbladeInvocationEspritDialog._findCompetence(actor, "persuasion")
const hautParlerComp = MournbladeInvocationEspritDialog._findCompetence(actor, "haut", "parler")
const loiChaosComp = MournbladeInvocationEspritDialog._findCompetence(actor, "loi", "chaos")
const isLoyal = actor.system.balance.loi > actor.system.balance.chaos
const hasRuneLoi = actor.items.some(i =>
i.type === "rune" && MournbladeInvocationEspritDialog._normalize(i.name).includes("loi")
)
const prerequisOk = isLoyal && hasRuneLoi
const automatonTypes = {
combat: "Combat",
voyage: "Voyage",
perception: "Perception",
restauration:"Restauration",
reparateur: "Réparateur",
}
const context = {
...rollData,
img: actor.img,
name: actor.name,
ameDisponible,
treValeur: actor.system.attributs?.tre?.value ?? 0,
persuasionNiveau: persuasionComp?.system?.niveau ?? null,
hautParlerNiveau: hautParlerComp?.system?.niveau ?? null,
loiChaosNiveau: loiChaosComp?.system?.niveau ?? null,
isLoyal,
hasRuneLoi,
prerequisOk,
automatonTypes,
seuils: MournbladeInvocationEspritDialog.SEUILS,
modOptions: Array.from({ length: 21 }, (_, i) => i - 10),
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-mournblade/templates/dialog-invocation-esprit.hbs",
context
)
Hooks.once("renderDialogV2", (_app, html) => {
const form = html.querySelector ? html : html[0]
MournbladeInvocationEspritDialog._attachListeners(form, ameDisponible, prerequisOk)
})
return foundry.applications.api.DialogV2.wait({
window: { title: "Invoquer un Esprit de la Loi", icon: "fa-solid fa-star" },
classes: ["mournblade-roll-dialog"],
position: { width: 520 },
modal: false,
content,
buttons: [
{
action: "invoquer",
label: "Invoquer",
icon: "fa-solid fa-star",
default: true,
callback: (event, button, dialog) => {
MournbladeInvocationEspritDialog._updateRollData(rollData, button.form.elements, actor, {
persuasionComp, hautParlerComp, loiChaosComp
})
MournbladeUtility.rollInvocationEsprit(rollData)
}
},
],
rejectClose: false,
})
}
static _attachListeners(html, ameDisponible, prerequisOk = true) {
const invoquerBtn = html.querySelector('button[data-action="invoquer"]')
if (invoquerBtn) invoquerBtn.disabled = !prerequisOk
const puissanceEl = html.querySelector('[name="puissance"]')
const seuilEl = html.querySelector('#esprit-seuil-total')
const coutEl = html.querySelector('#esprit-cout-ame')
const hiddenEl = html.querySelector('#esprit-seuil-hidden')
const warnEl = html.querySelector('#esprit-ame-warn')
const dureeEl = html.querySelector('#esprit-duree')
const DUREE = { mineur: "1 heure", median: "1 jour", majeur: "1 semaine" }
const recalculate = () => {
const puissance = puissanceEl?.value ?? "mineur"
const seuil = MournbladeInvocationEspritDialog.SEUILS[puissance] ?? 15
if (seuilEl) seuilEl.textContent = seuil
if (coutEl) coutEl.textContent = seuil
if (hiddenEl) hiddenEl.value = seuil
if (dureeEl) dureeEl.textContent = DUREE[puissance] ?? "1 heure"
if (warnEl) warnEl.style.display = seuil > ameDisponible ? "" : "none"
}
if (puissanceEl) puissanceEl.addEventListener("change", recalculate)
recalculate()
}
static _updateRollData(rollData, formElements, actor, { persuasionComp }) {
const seuil = parseInt(formElements["seuil"]?.value ?? 15)
const modificateur = parseInt(formElements["modificateur"]?.value ?? 0)
const automatonType = formElements["automatonType"]?.value ?? "combat"
const puissance = formElements["puissance"]?.value ?? "mineur"
rollData.invocationSeuil = seuil
rollData.invocationSoulCost = seuil
rollData.difficulte = seuil
rollData.modificateur = modificateur
rollData.competence = persuasionComp ?? null
rollData.automatonType = automatonType
rollData.automatonPuissance = puissance
}
}
@@ -57,6 +57,10 @@ export default class MournbladeActorSheet extends HandlebarsApplicationMixin(fou
preparePotion: MournbladeActorSheet.#onPreparePotion,
invoquerElementaire: MournbladeActorSheet.#onInvoquerElementaire,
bannirElementaire: MournbladeActorSheet.#onBannirElementaire,
invoquerDemon: MournbladeActorSheet.#onInvoquerDemon,
libererDemon: MournbladeActorSheet.#onLibererDemon,
invoquerEsprit: MournbladeActorSheet.#onInvoquerEsprit,
enchanter: MournbladeActorSheet.#onEnchanter,
rollArmeOffensif: MournbladeActorSheet.#onRollArmeOffensif,
rollArmeSpecial: MournbladeActorSheet.#onRollArmeSpecial,
rollArmeDegats: MournbladeActorSheet.#onRollArmeDegats,
@@ -110,6 +114,21 @@ export default class MournbladeActorSheet extends HandlebarsApplicationMixin(fou
return context
}
/** @override */
_preSyncPartState(partId, newElement, priorElement, state) {
super._preSyncPartState(partId, newElement, priorElement, state)
// Save scrollable tab positions for deferred restoration in _onRender.
// Tabs are hidden (display:none) at _syncPartState time, so scrollTop
// assignments have no effect. We re-apply them after making tabs visible.
const part = this.constructor.PARTS?.[partId]
if (part?.scrollable) {
this._pendingScrollRestores = part.scrollable.map(selector => {
const el = selector ? priorElement.querySelector(selector) : priorElement
return el ? { selector, scrollTop: el.scrollTop } : null
}).filter(Boolean)
}
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
@@ -136,22 +155,37 @@ export default class MournbladeActorSheet extends HandlebarsApplicationMixin(fou
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
const activeTab = this.tabGroups[group] || "stats"
const switchTab = (tab) => {
this.tabGroups[group] = tab
nav.querySelectorAll('[data-tab]').forEach(link => {
link.classList.toggle('active', link.dataset.tab === tab)
})
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === tab)
})
}
// Set initial state (makes active tab visible)
switchTab(activeTab)
// Restore scroll positions now that the active tab is visible
if (this._pendingScrollRestores?.length) {
for (const { selector, scrollTop } of this._pendingScrollRestores) {
const el = selector ? this.element.querySelector(selector) : this.element
if (el) el.scrollTop = scrollTop
}
this._pendingScrollRestores = null
}
// Tab clicks: DOM-only, no re-render (preserves scroll positions)
nav.querySelectorAll('[data-tab]').forEach(link => {
const tab = link.dataset.tab
link.classList.toggle('active', tab === activeTab)
link.addEventListener('click', (event) => {
event.preventDefault()
this.tabGroups[group] = tab
this.render()
switchTab(link.dataset.tab)
})
})
// Show/hide tab content
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
}
@@ -383,6 +417,40 @@ export default class MournbladeActorSheet extends HandlebarsApplicationMixin(fou
await this.document.bannirElementaire(invocIndex)
}
/**
* Handle invoking a demon
*/
static async #onInvoquerDemon(event, target) {
event.preventDefault()
await this.document.invoquerDemon()
}
/**
* Handle releasing a demon invocation
*/
static async #onLibererDemon(event, target) {
event.preventDefault()
const invocIndex = parseInt(target.dataset.invocIndex ?? "0")
await this.document.libererDemon(invocIndex)
}
/**
* Handle invoking a Law Spirit into an Automaton
*/
static async #onInvoquerEsprit(event, target) {
event.preventDefault()
await this.document.invoquerEspritLoi()
}
/**
* Handle enchanting an item with Loi power
*/
static async #onEnchanter(event, target) {
event.preventDefault()
const itemId = target.dataset.itemId
await this.document.enchanter(itemId)
}
/**
* Handle rolling an arme offensif
* @param {Event} event - The triggering event
@@ -32,6 +32,7 @@ export default class MournbladeItemSheet extends HandlebarsApplicationMixin(foun
actions: {
editImage: MournbladeItemSheet.#onEditImage,
postItem: MournbladeItemSheet.#onPostItem,
enchanter: MournbladeItemSheet.#onEnchanter,
},
}
@@ -43,17 +44,24 @@ export default class MournbladeItemSheet extends HandlebarsApplicationMixin(foun
/** @override */
async _prepareContext() {
const item = this.document
const enchantableTypes = ["arme", "equipement", "protection", "bouclier"]
const actorIsLoyal = item.actor?.getAlignement?.() === "loyal"
const alreadyEnchanted = enchantableTypes.includes(item.type) && (item.system.enchantementLoi?.actif ?? false)
const canEnchant = enchantableTypes.includes(item.type) && !!item.actor && actorIsLoyal && !alreadyEnchanted
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
fields: item.schema.fields,
systemFields: item.system.schema.fields,
item,
system: item.system,
source: item.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.system.description, { async: true }),
isEditMode: true,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.mournblade.config,
canEnchant,
enchantementActif: alreadyEnchanted,
}
return context
}
@@ -96,6 +104,19 @@ export default class MournbladeItemSheet extends HandlebarsApplicationMixin(foun
// #region Actions
/**
* Handle enchanting this item with Loi power
*/
static async #onEnchanter(event) {
event.preventDefault()
const item = this.document
if (!item.actor) {
ui.notifications.warn("Cet objet doit être sur un personnage pour être enchanté.")
return
}
await item.actor.enchanter(item.id)
}
/**
* Handle editing the item image
* @param {Event} event - The triggering event
@@ -137,7 +158,8 @@ export default class MournbladeItemSheet extends HandlebarsApplicationMixin(foun
protection: "Protection", equipement: "Équipement", heritage: "Héritage",
metier: "Métier", capacite: "Capacité", tendance: "Tendance",
traitchaotique: "Trait Chaotique", traitespece: "Trait d'Espèce",
origine: "Origine", modifier: "Modificateur", monnaie: "Monnaie"
origine: "Origine", modifier: "Modificateur", monnaie: "Monnaie",
potion: "Potion"
}
chatData.typeLabel = typeLabels[chatData.type] ?? chatData.type
@@ -148,10 +170,30 @@ export default class MournbladeItemSheet extends HandlebarsApplicationMixin(foun
pacte: "fa-scroll", protection: "fa-shield", equipement: "fa-box",
heritage: "fa-dna", metier: "fa-hammer", capacite: "fa-bolt",
tendance: "fa-yin-yang", traitchaotique: "fa-skull", traitespece: "fa-paw",
origine: "fa-compass", modifier: "fa-sliders", monnaie: "fa-coins"
origine: "fa-compass", modifier: "fa-sliders", monnaie: "fa-coins",
potion: "fa-flask"
}
chatData.typeIcon = typeIcons[chatData.type] ?? "fa-cube"
// Potion: add localized labels for statut and forme
if (chatData.type === "potion") {
const statutLabels = {
inconnue: game.i18n.localize("MNBL.potionInconnue"),
efficace: game.i18n.localize("MNBL.potionEfficace"),
heroique: game.i18n.localize("MNBL.potionHeroique"),
inefficace: game.i18n.localize("MNBL.potionInefficace"),
poison: game.i18n.localize("MNBL.potionPoison"),
}
const formeLabels = {
liquide: game.i18n.localize("MNBL.potionLiquide"),
onguent: game.i18n.localize("MNBL.potionOnguent"),
cachets: game.i18n.localize("MNBL.potionCachets"),
pilules: game.i18n.localize("MNBL.potionPilules"),
}
chatData.system.statutLabel = statutLabels[chatData.system.statut] ?? chatData.system.statut
chatData.system.formeLabel = formeLabels[chatData.system.forme] ?? chatData.system.forme
}
const html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-mournblade/templates/post-item.hbs', chatData)
ChatMessage.create({ user: game.user.id, content: html })
}
@@ -15,6 +15,7 @@ export default class MournbladeCreatureSheet extends MournbladeActorSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-mournblade/templates/creature-sheet.hbs",
scrollable: [".tab.competences", ".tab.equipement", ".tab.biodata"],
},
}
@@ -15,6 +15,7 @@ export default class MournbladePersonnageSheet extends MournbladeActorSheet {
static PARTS = {
sheet: {
template: "systems/fvtt-mournblade/templates/actor-sheet.hbs",
scrollable: [".tab.principal", ".tab.competences", ".tab.dons", ".tab.equipement"],
},
}
@@ -35,6 +36,8 @@ export default class MournbladePersonnageSheet extends MournbladeActorSheet {
context.dons = foundry.utils.duplicate(actor.getDons())
context.pactes = foundry.utils.duplicate(actor.getPactes())
context.alignement = actor.getAlignement()
context.isChaotique = context.alignement === "chaotique"
context.isLoyal = context.alignement === "loyal"
context.aspect = actor.getAspect()
context.marge = actor.getMarge()
context.tendances = foundry.utils.duplicate(actor.getTendances())
@@ -46,6 +49,7 @@ export default class MournbladePersonnageSheet extends MournbladeActorSheet {
context.metier = foundry.utils.duplicate(actor.getMetier() || {})
context.combat = actor.getCombatValues()
context.equipements = foundry.utils.duplicate(actor.getEquipments())
context.potions = foundry.utils.duplicate(actor.getPotions())
context.modifiers = foundry.utils.duplicate(actor.getModifiers())
context.monnaies = foundry.utils.duplicate(actor.getMonnaies())
context.runeEffects = foundry.utils.duplicate(actor.getRuneEffects())
+6 -1
View File
@@ -21,7 +21,12 @@ export default class ArmeDataModel extends foundry.abstract.TypeDataModel {
tr: new fields.NumberField({ initial: 0, integer: true }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
equipped: new fields.BooleanField({ initial: false }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+6 -1
View File
@@ -11,7 +11,12 @@ export default class BouclierDataModel extends foundry.abstract.TypeDataModel {
nonletaux: new fields.BooleanField({ initial: false }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
equipped: new fields.BooleanField({ initial: false }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+5
View File
@@ -12,6 +12,11 @@ export default class CreatureDataModel extends foundry.abstract.TypeDataModel {
alignement: new fields.StringField({ initial: "" }),
creatureType: new fields.StringField({ initial: "creature" }),
elementType: new fields.StringField({ initial: "" }),
demonType: new fields.StringField({ initial: "" }),
demonPuissance: new fields.StringField({ initial: "" }),
automatonType: new fields.StringField({ initial: "" }),
automatonPuissance: new fields.StringField({ initial: "" }),
automatonVoyageType: new fields.StringField({ initial: "" }),
poids: new fields.StringField({ initial: "" }),
taille: new fields.StringField({ initial: "" }),
cheveux: new fields.StringField({ initial: "" }),
+6 -1
View File
@@ -7,7 +7,12 @@ export default class EquipementDataModel extends foundry.abstract.TypeDataModel
return {
description: new fields.HTMLField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true })
prix: new fields.NumberField({ initial: 0, integer: true }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+1
View File
@@ -81,6 +81,7 @@ export default class PersonnageDataModel extends foundry.abstract.TypeDataModel
traumatismes: new fields.StringField({ initial: "" })
}),
invocationsElementaires: new fields.ArrayField(new fields.ObjectField(), { initial: [] }),
invocationsDemons: new fields.ArrayField(new fields.ObjectField(), { initial: [] }),
combat: new fields.SchemaField({
initbonus: new fields.NumberField({ initial: 0, integer: true }),
vitessebonus: new fields.NumberField({ initial: 0, integer: true }),
+6 -1
View File
@@ -11,7 +11,12 @@ export default class ProtectionDataModel extends foundry.abstract.TypeDataModel
degats: new fields.StringField({ initial: "" }),
rarete: new fields.NumberField({ initial: 0, integer: true }),
prix: new fields.NumberField({ initial: 0, integer: true }),
equipped: new fields.BooleanField({ initial: false })
equipped: new fields.BooleanField({ initial: false }),
enchantementLoi: new fields.SchemaField({
actif: new fields.BooleanField({ initial: false }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
antiChaos: new fields.BooleanField({ initial: false }),
}),
};
}
}
+97 -3
View File
@@ -2,6 +2,9 @@
import { MournbladeUtility } from "./mournblade-utility.js";
import { MournbladeRollDialog } from "./applications/mournblade-roll-dialog.mjs";
import MournbladeInvocationDialog from "./applications/mournblade-invocation-dialog.mjs";
import MournbladeInvocationDemonDialog from "./applications/mournblade-invocation-demon-dialog.mjs";
import MournbladeInvocationEspritDialog from "./applications/mournblade-invocation-esprit-dialog.mjs";
import MournbladeEnchantementDialog from "./applications/mournblade-enchantement-dialog.mjs";
/* -------------------------------------------- */
const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10]
@@ -91,6 +94,8 @@ export class MournbladeActor extends Actor {
prepareArme(arme) {
arme = foundry.utils.duplicate(arme)
let combat = this.getCombatValues()
const enchBonus = (arme.system.enchantementLoi?.actif && arme.system.enchantementLoi?.bonus > 0)
? arme.system.enchantementLoi.bonus : 0
if (arme.system.typearme == "contact" || arme.system.typearme == "contactjet") {
arme.system.isMelee = true
let competence = this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")
@@ -98,7 +103,7 @@ export class MournbladeActor extends Actor {
arme.system.competence = foundry.utils.duplicate(competence)
arme.system.attrKey = "pui"
arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal
arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff + combat.attaqueModifier
arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff + combat.attaqueModifier + enchBonus
if (arme.system.isdefense) {
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.bonusmaniementdef
}
@@ -115,7 +120,7 @@ export class MournbladeActor extends Actor {
if (competence) {
arme.system.competence = foundry.utils.duplicate(competence)
arme.system.attrKey = "adr"
arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff + combat.attaqueModifier
arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff + combat.attaqueModifier + enchBonus
arme.system.totalDegats = arme.system.degats
if (arme.system.isdefense) {
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.bonusmaniementdef
@@ -133,12 +138,14 @@ export class MournbladeActor extends Actor {
prepareBouclier(bouclier) {
bouclier = foundry.utils.duplicate(bouclier)
let combat = this.getCombatValues()
const enchBonus = (bouclier.system.enchantementLoi?.actif && bouclier.system.enchantementLoi?.bonus > 0)
? bouclier.system.enchantementLoi.bonus : 0
let competence = this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée")
if (competence) {
bouclier.system.competence = foundry.utils.duplicate(competence)
bouclier.system.attrKey = "pui"
bouclier.system.totalDegats = bouclier.system.degats + "+" + combat.bonusDegatsTotal
bouclier.system.totalOffensif = this.system.attributs.pui.value + bouclier.system.competence.system.niveau
bouclier.system.totalOffensif = this.system.attributs.pui.value + bouclier.system.competence.system.niveau + enchBonus
bouclier.system.isdefense = true
bouclier.system.bonusmaniementoff = 0
bouclier.system.totalDefensif = combat.defenseTotal + bouclier.system.competence.system.niveau + bouclier.system.bonusdefense
@@ -213,6 +220,9 @@ export class MournbladeActor extends Actor {
getMonnaies() {
return this.getItemSorted(["monnaie"])
}
getPotions() {
return this.getItemSorted(["potion"])
}
getArmors() {
return this.getItemSorted(["protection"])
}
@@ -739,6 +749,90 @@ export class MournbladeActor extends Actor {
ui.notifications.info(`L'Élémentaire ${invoc.actorName} a été banni. ${invoc.soulCost} points d'Âme libérés.`)
}
/* -------------------------------------------- */
async invoquerDemon() {
const normalize = str => str.toLowerCase()
.replace(/œ/g, "oe").replace(/æ/g, "ae")
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ").trim()
const hasKeywords = (item, ...words) => {
const n = normalize(item.name)
return words.every(w => n.includes(normalize(w)))
}
const coercitionComp = this.items.find(c => c.type === "competence" && hasKeywords(c, "coercition"))
if (!coercitionComp) {
ui.notifications.warn("La compétence Coercition est requise pour invoquer un Démon.")
return
}
const isChaotique = this.system.balance.chaos > this.system.balance.loi
const hasOeilSorcier = this.items.some(c => (c.type === "capacite" || c.type === "don") && hasKeywords(c, "oeil", "sorcier"))
const hasRuneChaos = this.items.some(i => i.type === "rune" && hasKeywords(i, "chaos"))
if (!isChaotique || !hasOeilSorcier || !hasRuneChaos) {
const missing = []
if (!isChaotique) missing.push("alignement chaotique (Chaos > Loi)")
if (!hasOeilSorcier) missing.push("Capacité/Don Œil du Sorcier")
if (!hasRuneChaos) missing.push("Rune du Chaos")
ui.notifications.warn(`Prérequis manquants pour l'invocation démoniaque : ${missing.join(", ")}.`)
}
let rollData = this.getCommonRollData("tre", coercitionComp._id)
rollData.isInvocationDemon = true
rollData.mainDice = "1d10"
await MournbladeInvocationDemonDialog.create(this, rollData)
}
/* -------------------------------------------- */
async libererDemon(invocIndex) {
const invocations = foundry.utils.duplicate(this.system.invocationsDemons || [])
const invoc = invocations[invocIndex]
if (!invoc) return
invocations.splice(invocIndex, 1)
await this.update({ "system.invocationsDemons": invocations })
ui.notifications.info(`Le Démon ${invoc.demonName ?? "invoqué"} a été libéré.`)
}
/* -------------------------------------------- */
async invoquerEspritLoi() {
const normalize = str => str.toLowerCase()
.replace(/œ/g, "oe").replace(/æ/g, "ae")
.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, " ").trim()
const hasKeywords = (item, ...words) => {
const n = normalize(item.name)
return words.every(w => n.includes(normalize(w)))
}
const persuasionComp = this.items.find(c => c.type === "competence" && hasKeywords(c, "persuasion"))
const isLoyal = this.system.balance.loi > this.system.balance.chaos
const hasRuneLoi = this.items.some(i => i.type === "rune" && hasKeywords(i, "loi"))
if (!isLoyal || !hasRuneLoi) {
const missing = []
if (!isLoyal) missing.push("alignement loyal (Loi > Chaos)")
if (!hasRuneLoi) missing.push("Rune de la Loi")
ui.notifications.warn(`Prérequis manquants : ${missing.join(", ")}.`)
}
const rollData = this.getCommonRollData("tre", persuasionComp?._id ?? null)
rollData.isInvocationEsprit = true
rollData.mainDice = "1d10"
await MournbladeInvocationEspritDialog.create(this, rollData)
}
/* -------------------------------------------- */
async enchanter(itemId) {
const item = this.items.get(itemId)
if (!item) return
if (!["arme", "equipement", "protection", "bouclier"].includes(item.type)) {
ui.notifications.warn("Seules les armes, équipements, protections et boucliers peuvent être enchantés.")
return
}
await MournbladeEnchantementDialog.create(this, item)
}
/* -------------------------------------------- */
async rollArmeOffensif(armeId) {
let arme = this.items.get(armeId)
+37
View File
@@ -90,18 +90,55 @@ export class MournbladeConfig {
elue: game.i18n.localize("MNBL.typeCapaciteElue"),
elementaire: game.i18n.localize("MNBL.typeCapaciteElementaire"),
demoniaque: game.i18n.localize("MNBL.typeCapaciteDemoniaque"),
automaton: game.i18n.localize("MNBL.typeCapaciteAutomaton"),
creature: game.i18n.localize("MNBL.typeCapaciteCreature"),
},
creatureTypeOptions: {
creature: game.i18n.localize("MNBL.creatureTypeCreature"),
demon: game.i18n.localize("MNBL.creatureTypeDemon"),
elementaire: game.i18n.localize("MNBL.creatureTypeElementaire"),
automaton: game.i18n.localize("MNBL.creatureTypeAutomaton"),
},
demonTypeOptions: {
"": game.i18n.localize("MNBL.demonTypeNone"),
combat: game.i18n.localize("MNBL.demonTypeCombat"),
desir: game.i18n.localize("MNBL.demonTypeDesir"),
savoir: game.i18n.localize("MNBL.demonTypeSavoir"),
protection: game.i18n.localize("MNBL.demonTypeProtection"),
voyage: game.i18n.localize("MNBL.demonTypeVoyage"),
},
demonPuissanceOptions: {
"": game.i18n.localize("MNBL.demonPuissanceNone"),
mineur: game.i18n.localize("MNBL.demonPuissanceMineur"),
median: game.i18n.localize("MNBL.demonPuissanceMedian"),
majeur: game.i18n.localize("MNBL.demonPuissanceMajeur"),
},
elementTypeOptions: {
air: game.i18n.localize("MNBL.elementTypeAir"),
terre: game.i18n.localize("MNBL.elementTypeTerre"),
feu: game.i18n.localize("MNBL.elementTypeFeu"),
eau: game.i18n.localize("MNBL.elementTypeEau"),
},
automatonTypeOptions: {
"": game.i18n.localize("MNBL.automatonTypeNone"),
combat: game.i18n.localize("MNBL.automatonTypeCombat"),
voyage: game.i18n.localize("MNBL.automatonTypeVoyage"),
perception: game.i18n.localize("MNBL.automatonTypePerception"),
restauration: game.i18n.localize("MNBL.automatonTypeRestauration"),
reparateur: game.i18n.localize("MNBL.automatonTypeReparateur"),
},
automatonPuissanceOptions: {
"": game.i18n.localize("MNBL.automatonPuissanceNone"),
mineur: game.i18n.localize("MNBL.automatonPuissanceMineur"),
median: game.i18n.localize("MNBL.automatonPuissanceMedian"),
majeur: game.i18n.localize("MNBL.automatonPuissanceMajeur"),
},
automatonVoyageTypeOptions: {
"": game.i18n.localize("MNBL.automatonVoyageTypeNone"),
terrestre: game.i18n.localize("MNBL.automatonVoyageTypeTerrestre"),
aquatique: game.i18n.localize("MNBL.automatonVoyageTypeAquatique"),
aerien: game.i18n.localize("MNBL.automatonVoyageTypeAerien"),
extradimensionnel: game.i18n.localize("MNBL.automatonVoyageTypeExtradimensionnel"),
}
}
+241 -3
View File
@@ -219,8 +219,15 @@ export class MournbladeUtility {
'systems/fvtt-mournblade/templates/partial-item-description.hbs',
'systems/fvtt-mournblade/templates/partial-item-header.hbs',
'systems/fvtt-mournblade/templates/partial-item-nav.hbs',
'systems/fvtt-mournblade/templates/partial-item-enchantement.hbs',
'systems/fvtt-mournblade/templates/dialog-invocation-elementaire.hbs',
'systems/fvtt-mournblade/templates/chat-invocation-result.hbs',
'systems/fvtt-mournblade/templates/dialog-invocation-demon.hbs',
'systems/fvtt-mournblade/templates/chat-invocation-demon-result.hbs',
'systems/fvtt-mournblade/templates/dialog-enchantement.hbs',
'systems/fvtt-mournblade/templates/chat-enchantement-result.hbs',
'systems/fvtt-mournblade/templates/dialog-invocation-esprit.hbs',
'systems/fvtt-mournblade/templates/chat-invocation-esprit-result.hbs',
]
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
@@ -1079,9 +1086,7 @@ export class MournbladeUtility {
return
}
const actor = rollData.tokenId
? game.canvas.tokens.get(rollData.tokenId)?.actor
: game.actors.get(rollData.actorId)
const actor = game.actors.get(rollData.actorId)
const pa = rollData.pointsAme ?? 1
const seuil = rollData.runeSeuil ?? 0
@@ -1269,6 +1274,8 @@ export class MournbladeUtility {
rollData.createdActorName = createdActorName
rollData.bonusPacte = bonusPacte
rollData.isGM = game.user.isGM
const powersByTier = { mineur: 2, median: 3, majeur: 4 }
rollData.invocationPowerCount = powersByTier[rollData.invocationTier] ?? 2
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(
@@ -1276,4 +1283,235 @@ export class MournbladeUtility {
}, { ...rollData, rollMode: "blindroll" })
}
/* -------------------------------------------- */
static async rollInvocationDemon(rollData) {
const actor = rollData.tokenId
? game.canvas.tokens.get(rollData.tokenId)?.actor
: game.actors.get(rollData.actorId)
if (!actor) {
ui.notifications.error("Acteur introuvable pour l'invocation démoniaque.")
return
}
const soulCost = rollData.invocationSoulCost ?? rollData.invocationSeuil ?? 20
const compNiveau = rollData.competence?.system?.niveau ?? 0
const compMod = compNiveau === 0 ? -3 : 0
const modificateur = rollData.modificateur ?? 0
// Validate soul
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
if (ameDisponible < soulCost) {
ui.notifications.warn(`Âme insuffisante pour cette invocation (requis : ${soulCost}, disponible : ${ameDisponible}).`)
return
}
rollData.difficulte = rollData.invocationSeuil
rollData.diceFormula = `${rollData.mainDice ?? "1d10"}+${rollData.attr.value}+${compNiveau}+${modificateur}+${compMod}+${rollData.malusSante}+${rollData.malusAme}`
const myRoll = await new Roll(rollData.diceFormula).evaluate()
await this.showDiceSoNice(myRoll, "blindroll")
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.terms[0].results[0].result
rollData.finalResult = myRoll.total
this.computeResult(rollData)
// Soul cost handling
let ameDeduct = soulCost
let d20Result = null
let isDemonAttaque = false
let isDisastreDramatique = false
let isTraitChaotique = false
if (rollData.isSuccess || rollData.isHeroique) {
// Soul spent immediately (not blocked)
await actor.subPointsAme("prononcer", soulCost)
// Track active invocation
const invocations = foundry.utils.duplicate(actor.system.invocationsDemons || [])
invocations.push({ demonName: "Démon invoqué", soulCost, date: Date.now() })
await actor.update({ "system.invocationsDemons": invocations })
} else if (rollData.isDramatique) {
// All soul lost
await actor.subPointsAme("prononcer", soulCost)
// Roll d20 for dramatic failure consequences
const d20Roll = await new Roll("1d20").evaluate()
await this.showDiceSoNice(d20Roll, "blindroll")
d20Result = d20Roll.total
if (d20Result === 1 || d20Result === 11) {
isDisastreDramatique = true
} else if (d20Result % 2 !== 0) {
// Odd (not 1 or 11) → demon attacks
isDemonAttaque = true
} else {
// Even → chaotic trait
isTraitChaotique = true
}
} else {
// Simple failure: half soul lost (round up)
ameDeduct = Math.ceil(soulCost / 2)
await actor.subPointsAme("prononcer", ameDeduct)
}
rollData.invocationSoulDeducted = (rollData.isSuccess || rollData.isHeroique) ? soulCost : ameDeduct
rollData.d20Result = d20Result
rollData.isDemonAttaque = isDemonAttaque
rollData.isDisastreDramatique = isDisastreDramatique
rollData.isTraitChaotique = isTraitChaotique
rollData.isGM = game.user.isGM
rollData.claValue = actor.system.attributs?.cla?.value ?? 0
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(
`systems/fvtt-mournblade/templates/chat-invocation-demon-result.hbs`, rollData)
}, { ...rollData, rollMode: "blindroll" })
}
/* -------------------------------------------- */
static async rollInvocationEsprit(rollData) {
const actor = rollData.tokenId
? game.canvas.tokens.get(rollData.tokenId)?.actor
: game.actors.get(rollData.actorId)
if (!actor) {
ui.notifications.error("Acteur introuvable pour l'invocation d'un Esprit de la Loi.")
return
}
const soulCost = rollData.invocationSoulCost ?? 15
const compNiveau = rollData.competence?.system?.niveau ?? 0
const compMod = compNiveau === 0 ? -3 : 0
const modificateur = rollData.modificateur ?? 0
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
if (ameDisponible < soulCost) {
ui.notifications.warn(`Âme insuffisante (requis : ${soulCost}, disponible : ${ameDisponible}).`)
return
}
rollData.difficulte = soulCost
rollData.diceFormula = `${rollData.mainDice ?? "1d10"}+${rollData.attr.value}+${compNiveau}+${modificateur}+${compMod}+${rollData.malusSante}+${rollData.malusAme}`
const myRoll = await new Roll(rollData.diceFormula).evaluate()
await this.showDiceSoNice(myRoll, "roll")
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.terms[0].results[0].result
rollData.finalResult = myRoll.total
this.computeResult(rollData)
let ameDeduct = soulCost
if (rollData.isSuccess || rollData.isHeroique) {
await actor.subPointsAme("prononcer", soulCost)
} else if (rollData.isDramatique) {
// All soul lost, Réceptacle destroyed
await actor.subPointsAme("prononcer", soulCost)
} else {
// Simple failure: half soul lost (round up)
ameDeduct = Math.ceil(soulCost / 2)
await actor.subPointsAme("prononcer", ameDeduct)
}
rollData.invocationSoulDeducted = (rollData.isSuccess || rollData.isHeroique) ? soulCost : ameDeduct
rollData.isGM = game.user.isGM
const typeLabels = {
combat: "Combat", voyage: "Voyage", perception: "Perception",
restauration: "Restauration", reparateur: "Réparateur",
}
const puissanceLabels = { mineur: "Mineur", median: "Médian", majeur: "Majeur" }
rollData.automatonTypeLabel = typeLabels[rollData.automatonType] ?? rollData.automatonType
rollData.automatonPuissanceLabel = puissanceLabels[rollData.automatonPuissance] ?? rollData.automatonPuissance
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(
`systems/fvtt-mournblade/templates/chat-invocation-esprit-result.hbs`, rollData)
}, { ...rollData, rollMode: "roll" })
}
/* -------------------------------------------- */
static async rollEnchantement({ actor, item, ptsAme, antiChaos, modificateur,
savoirRunesComp, hautParlerComp, artisanatComp, claValeur, limiteur }) {
// Validate soul
const ameDisponible = Math.max(0, actor.system.ame.currentmax - actor.system.ame.value)
if (ameDisponible < ptsAme) {
ui.notifications.warn(`Âme insuffisante (requis : ${ptsAme}, disponible : ${ameDisponible}).`)
return
}
const savoirNiveau = savoirRunesComp?.system?.niveau ?? 0
const compMod = savoirNiveau === 0 ? -3 : 0
const basePool = claValeur + savoirNiveau + compMod + (modificateur ?? 0)
const effectivePool = limiteur !== null ? Math.min(basePool, limiteur) : basePool
const difficulte = ptsAme
const formula = `1d10+${effectivePool}`
const myRoll = await new Roll(formula).evaluate()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
const rollData = this.getBasicRollData()
rollData.alias = actor.name
rollData.actorImg = actor.img
rollData.diceResult = myRoll.terms[0].results[0].result
rollData.finalResult = myRoll.total
rollData.difficulte = difficulte
rollData.roll = foundry.utils.duplicate(myRoll)
this.computeResult(rollData)
// Compute bonus
let bonusBase = Math.floor(ptsAme / 5)
let bonusFinal = bonusBase
let ameDeduct = ptsAme
let itemDestroyed = false
let message = ""
if (rollData.isHeroique) {
bonusFinal = bonusBase + 1
await actor.subPointsAme("prononcer", ptsAme)
await item.update({
"system.enchantementLoi.actif": true,
"system.enchantementLoi.bonus": bonusFinal,
"system.enchantementLoi.antiChaos": antiChaos,
})
message = `Réussite héroïque ! L'objet est enchanté avec un bonus de +${bonusFinal}.`
} else if (rollData.isSuccess) {
await actor.subPointsAme("prononcer", ptsAme)
await item.update({
"system.enchantementLoi.actif": true,
"system.enchantementLoi.bonus": bonusFinal,
"system.enchantementLoi.antiChaos": antiChaos,
})
message = `Succès ! L'objet est enchanté avec un bonus de +${bonusFinal}.`
} else if (rollData.isDramatique) {
ameDeduct = ptsAme
await actor.subPointsAme("prononcer", ameDeduct)
await item.delete()
itemDestroyed = true
message = `Échec dramatique ! Tous les points d'Âme sont perdus et l'objet est détruit !`
} else {
// Failure: half soul lost
ameDeduct = Math.ceil(ptsAme / 2)
await actor.subPointsAme("prononcer", ameDeduct)
message = `Échec ! ${ameDeduct} points d'Âme perdus. L'objet n'est pas enchanté.`
}
rollData.itemName = item.name
rollData.itemImg = item.img
rollData.ptsAme = ptsAme
rollData.antiChaos = antiChaos
rollData.bonusFinal = bonusFinal
rollData.ameDeduct = ameDeduct
rollData.itemDestroyed = itemDestroyed
rollData.enchantMessage = message
rollData.effectivePool = effectivePool
rollData.savoirNiveau = savoirNiveau
this.createChatWithRollMode(actor.name, {
content: await foundry.applications.handlebars.renderTemplate(
`systems/fvtt-mournblade/templates/chat-enchantement-result.hbs`, rollData)
})
}
}
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000368
MANIFEST-000396
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:23.718556 7fd754fed6c0 Recovering log #366
2026/05/01-23:51:23.773168 7fd754fed6c0 Delete type=3 #364
2026/05/01-23:51:23.773253 7fd754fed6c0 Delete type=0 #366
2026/05/02-08:26:03.206863 7fd7477fe6c0 Level-0 table #371: started
2026/05/02-08:26:03.206928 7fd7477fe6c0 Level-0 table #371: 0 bytes OK
2026/05/02-08:26:03.241219 7fd7477fe6c0 Delete type=0 #369
2026/05/02-08:26:03.317629 7fd7477fe6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.618357 7fd747fff6c0 Recovering log #394
2026/05/02-22:17:12.628023 7fd747fff6c0 Delete type=3 #392
2026/05/02-22:17:12.628086 7fd747fff6c0 Delete type=0 #394
2026/05/02-23:14:30.830178 7fd7477fe6c0 Level-0 table #399: started
2026/05/02-23:14:30.830206 7fd7477fe6c0 Level-0 table #399: 0 bytes OK
2026/05/02-23:14:30.842898 7fd7477fe6c0 Delete type=0 #397
2026/05/02-23:14:30.854693 7fd7477fe6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.849265 7fd7557ee6c0 Recovering log #362
2026/05/01-23:45:25.858985 7fd7557ee6c0 Delete type=3 #360
2026/05/01-23:45:25.859069 7fd7557ee6c0 Delete type=0 #362
2026/05/01-23:51:17.894067 7fd7477fe6c0 Level-0 table #367: started
2026/05/01-23:51:17.894105 7fd7477fe6c0 Level-0 table #367: 0 bytes OK
2026/05/01-23:51:17.920657 7fd7477fe6c0 Delete type=0 #365
2026/05/01-23:51:18.003271 7fd7477fe6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
2026/05/02-10:42:39.976018 7fd755fef6c0 Recovering log #390
2026/05/02-10:42:39.986262 7fd755fef6c0 Delete type=3 #388
2026/05/02-10:42:39.986345 7fd755fef6c0 Delete type=0 #390
2026/05/02-10:55:36.095495 7fd7477fe6c0 Level-0 table #395: started
2026/05/02-10:55:36.095552 7fd7477fe6c0 Level-0 table #395: 0 bytes OK
2026/05/02-10:55:36.101888 7fd7477fe6c0 Delete type=0 #393
2026/05/02-10:55:36.113150 7fd7477fe6c0 Manual compaction at level-0 from '!items!0swiE8k5zfUIqmXu' @ 72057594037927935 : 1 .. '!items!wv5EiePmPTpqFutt' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
+5
View File
@@ -0,0 +1,5 @@
2026/05/02-23:15:37.006124 7fcf117ed6c0 Delete type=3 #1
2026/05/02-23:15:37.009476 7fcef3fff6c0 Level-0 table #5: started
2026/05/02-23:15:37.013256 7fcef3fff6c0 Level-0 table #5: 30382 bytes OK
2026/05/02-23:15:37.019148 7fcef3fff6c0 Delete type=0 #3
2026/05/02-23:15:37.019361 7fcef3fff6c0 Manual compaction at level-0 from '!actors!AutomCombatMaj001' @ 72057594037927935 : 1 .. '!actors.items!OeilDeVerite0001.OeilVeritProt001' @ 0 : 0; will stop at (end)
Binary file not shown.
@@ -0,0 +1 @@
MANIFEST-000002
@@ -0,0 +1,5 @@
2026/05/02-22:42:38.620324 7fe3377fe6c0 Delete type=3 #1
2026/05/02-22:42:38.623484 7fe335ffb6c0 Level-0 table #5: started
2026/05/02-22:42:38.627227 7fe335ffb6c0 Level-0 table #5: 3865 bytes OK
2026/05/02-22:42:38.633343 7fe335ffb6c0 Delete type=0 #3
2026/05/02-22:42:38.633487 7fe335ffb6c0 Manual compaction at level-0 from '!items!CapAutoComAbs001' @ 72057594037927935 : 1 .. '!items!CapAutoVoyVit001' @ 0 : 0; will stop at (end)
@@ -1 +1 @@
MANIFEST-000012
MANIFEST-000047
@@ -1,12 +1,14 @@
2026/05/01-23:51:23.539516 7fd754fed6c0 Delete type=3 #1
2026/05/02-08:26:02.910133 7fd7477fe6c0 Level-0 table #15: started
2026/05/02-08:26:02.928665 7fd7477fe6c0 Level-0 table #15: 17652 bytes OK
2026/05/02-08:26:02.971253 7fd7477fe6c0 Delete type=0 #13
2026/05/02-08:26:03.098794 7fd7477fe6c0 Manual compaction at level-0 from '!actors!ElemAirMaj0000003' @ 72057594037927935 : 1 .. '!actors.items!zYGQJ8FibxCCWynl' @ 0 : 0; will stop at '!actors.items!zYGQJ8FibxCCWynl' @ 118 : 1
2026/05/02-08:26:03.098818 7fd7477fe6c0 Compacting 2@0 + 0@1 files
2026/05/02-08:26:03.114677 7fd7477fe6c0 Generated table #16@0: 61 keys, 12773 bytes
2026/05/02-08:26:03.114714 7fd7477fe6c0 Compacted 2@0 + 0@1 files => 12773 bytes
2026/05/02-08:26:03.143492 7fd7477fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/02-08:26:03.143650 7fd7477fe6c0 Delete type=2 #10
2026/05/02-08:26:03.143790 7fd7477fe6c0 Delete type=2 #15
2026/05/02-08:26:03.143908 7fd7477fe6c0 Manual compaction at level-0 from '!actors.items!zYGQJ8FibxCCWynl' @ 118 : 1 .. '!actors.items!zYGQJ8FibxCCWynl' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.533680 7fd754fed6c0 Recovering log #44
2026/05/02-22:17:12.543680 7fd754fed6c0 Delete type=3 #42
2026/05/02-22:17:12.543746 7fd754fed6c0 Delete type=0 #44
2026/05/02-23:14:30.692810 7fd7477fe6c0 Level-0 table #50: started
2026/05/02-23:14:30.701352 7fd7477fe6c0 Level-0 table #50: 17662 bytes OK
2026/05/02-23:14:30.711206 7fd7477fe6c0 Delete type=0 #48
2026/05/02-23:14:30.731427 7fd7477fe6c0 Manual compaction at level-0 from '!actors!ElemAirMaj0000003' @ 72057594037927935 : 1 .. '!actors.items!xgdbv9heAyLXGkCu' @ 0 : 0; will stop at '!actors.items!xgdbv9heAyLXGkCu' @ 547 : 1
2026/05/02-23:14:30.731439 7fd7477fe6c0 Compacting 1@0 + 1@1 files
2026/05/02-23:14:30.738933 7fd7477fe6c0 Generated table #51@0: 61 keys, 12842 bytes
2026/05/02-23:14:30.739005 7fd7477fe6c0 Compacted 1@0 + 1@1 files => 12842 bytes
2026/05/02-23:14:30.747174 7fd7477fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/02-23:14:30.747353 7fd7477fe6c0 Delete type=2 #46
2026/05/02-23:14:30.747504 7fd7477fe6c0 Delete type=2 #50
2026/05/02-23:14:30.779059 7fd7477fe6c0 Manual compaction at level-0 from '!actors.items!xgdbv9heAyLXGkCu' @ 547 : 1 .. '!actors.items!xgdbv9heAyLXGkCu' @ 0 : 0; will stop at (end)
@@ -1,4 +1,14 @@
2026/05/01-23:51:23.458933 7fd754fed6c0 Log #8: 0 ops saved to Table #11 OK
2026/05/01-23:51:23.459064 7fd754fed6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-mournblade/packs/creatures-elementaires/creatures-elementaires/000008.log: OK
2026/05/01-23:51:23.459141 7fd754fed6c0 Table #10: 61 entries OK
2026/05/01-23:51:23.477072 7fd754fed6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-mournblade/packs/creatures-elementaires/creatures-elementaires; recovered 1 files; 12818 bytes. Some data may have been lost. ****
2026/05/02-10:42:39.892083 7fd754fed6c0 Recovering log #39
2026/05/02-10:42:39.902219 7fd754fed6c0 Delete type=3 #37
2026/05/02-10:42:39.902273 7fd754fed6c0 Delete type=0 #39
2026/05/02-10:55:36.036221 7fd7477fe6c0 Level-0 table #45: started
2026/05/02-10:55:36.039553 7fd7477fe6c0 Level-0 table #45: 17295 bytes OK
2026/05/02-10:55:36.045711 7fd7477fe6c0 Delete type=0 #43
2026/05/02-10:55:36.063221 7fd7477fe6c0 Manual compaction at level-0 from '!actors!ElemAirMaj0000003' @ 72057594037927935 : 1 .. '!actors.items!vZXf6vfOj986NEYr' @ 0 : 0; will stop at '!actors.items!zCAO1NzNTX1SCdQ8' @ 456 : 0
2026/05/02-10:55:36.063230 7fd7477fe6c0 Compacting 1@0 + 1@1 files
2026/05/02-10:55:36.066937 7fd7477fe6c0 Generated table #46@0: 61 keys, 12728 bytes
2026/05/02-10:55:36.066956 7fd7477fe6c0 Compacted 1@0 + 1@1 files => 12728 bytes
2026/05/02-10:55:36.073653 7fd7477fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/02-10:55:36.073784 7fd7477fe6c0 Delete type=2 #41
2026/05/02-10:55:36.073906 7fd7477fe6c0 Delete type=2 #45
2026/05/02-10:55:36.083653 7fd7477fe6c0 Manual compaction at level-0 from '!actors.items!zCAO1NzNTX1SCdQ8' @ 456 : 0 .. '!actors.items!vZXf6vfOj986NEYr' @ 0 : 0; will stop at (end)
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MANIFEST-000026
+15
View File
@@ -0,0 +1,15 @@
2026/05/02-22:17:12.519161 7fd755fef6c0 Recovering log #23
2026/05/02-22:17:12.529405 7fd755fef6c0 Delete type=3 #21
2026/05/02-22:17:12.529482 7fd755fef6c0 Delete type=0 #23
2026/05/02-23:14:30.629959 7fd7477fe6c0 Level-0 table #29: started
2026/05/02-23:14:30.639557 7fd7477fe6c0 Level-0 table #29: 24275 bytes OK
2026/05/02-23:14:30.650756 7fd7477fe6c0 Delete type=0 #27
2026/05/02-23:14:30.662871 7fd7477fe6c0 Manual compaction at level-0 from '!actors!DemonCombatMaj001' @ 72057594037927935 : 1 .. '!actors.items!ip5rWxXRlXaEyQD6' @ 0 : 0; will stop at (end)
2026/05/02-23:14:30.662925 7fd7477fe6c0 Manual compaction at level-1 from '!actors!DemonCombatMaj001' @ 72057594037927935 : 1 .. '!actors.items!ip5rWxXRlXaEyQD6' @ 0 : 0; will stop at '!actors.items!ip5rWxXRlXaEyQD6' @ 366 : 1
2026/05/02-23:14:30.662935 7fd7477fe6c0 Compacting 1@1 + 1@2 files
2026/05/02-23:14:30.671179 7fd7477fe6c0 Generated table #30@1: 99 keys, 34643 bytes
2026/05/02-23:14:30.671218 7fd7477fe6c0 Compacted 1@1 + 1@2 files => 34643 bytes
2026/05/02-23:14:30.682390 7fd7477fe6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2026/05/02-23:14:30.682554 7fd7477fe6c0 Delete type=2 #25
2026/05/02-23:14:30.682716 7fd7477fe6c0 Delete type=2 #29
2026/05/02-23:14:30.731391 7fd7477fe6c0 Manual compaction at level-1 from '!actors.items!ip5rWxXRlXaEyQD6' @ 366 : 1 .. '!actors.items!ip5rWxXRlXaEyQD6' @ 0 : 0; will stop at (end)
+15
View File
@@ -0,0 +1,15 @@
2026/05/02-10:42:39.878619 7fd7557ee6c0 Recovering log #18
2026/05/02-10:42:39.888389 7fd7557ee6c0 Delete type=3 #16
2026/05/02-10:42:39.888487 7fd7557ee6c0 Delete type=0 #18
2026/05/02-10:55:36.007797 7fd7477fe6c0 Level-0 table #24: started
2026/05/02-10:55:36.011680 7fd7477fe6c0 Level-0 table #24: 24272 bytes OK
2026/05/02-10:55:36.018592 7fd7477fe6c0 Delete type=0 #22
2026/05/02-10:55:36.025623 7fd7477fe6c0 Manual compaction at level-0 from '!actors!DemonCombatMaj001' @ 72057594037927935 : 1 .. '!actors.items!GqtMRrjUTucZj9cj' @ 0 : 0; will stop at (end)
2026/05/02-10:55:36.025666 7fd7477fe6c0 Manual compaction at level-1 from '!actors!DemonCombatMaj001' @ 72057594037927935 : 1 .. '!actors.items!GqtMRrjUTucZj9cj' @ 0 : 0; will stop at '!actors.items!gPJSnL0lXZ0x3Uvt' @ 300 : 0
2026/05/02-10:55:36.025679 7fd7477fe6c0 Compacting 1@1 + 1@2 files
2026/05/02-10:55:36.030173 7fd7477fe6c0 Generated table #25@1: 99 keys, 34641 bytes
2026/05/02-10:55:36.030197 7fd7477fe6c0 Compacted 1@1 + 1@2 files => 34641 bytes
2026/05/02-10:55:36.035952 7fd7477fe6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2026/05/02-10:55:36.036040 7fd7477fe6c0 Delete type=2 #20
2026/05/02-10:55:36.036148 7fd7477fe6c0 Delete type=2 #24
2026/05/02-10:55:36.063204 7fd7477fe6c0 Manual compaction at level-1 from '!actors.items!gPJSnL0lXZ0x3Uvt' @ 300 : 0 .. '!actors.items!GqtMRrjUTucZj9cj' @ 0 : 0; will stop at (end)
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000367
MANIFEST-000395
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:23.899083 7fd755fef6c0 Recovering log #365
2026/05/01-23:51:23.963281 7fd755fef6c0 Delete type=3 #363
2026/05/01-23:51:23.963367 7fd755fef6c0 Delete type=0 #365
2026/05/02-08:26:03.439764 7fd7477fe6c0 Level-0 table #370: started
2026/05/02-08:26:03.439860 7fd7477fe6c0 Level-0 table #370: 0 bytes OK
2026/05/02-08:26:03.476944 7fd7477fe6c0 Delete type=0 #368
2026/05/02-08:26:03.477143 7fd7477fe6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.656044 7fd754fed6c0 Recovering log #393
2026/05/02-22:17:12.666131 7fd754fed6c0 Delete type=3 #391
2026/05/02-22:17:12.666231 7fd754fed6c0 Delete type=0 #393
2026/05/02-23:14:30.872981 7fd7477fe6c0 Level-0 table #398: started
2026/05/02-23:14:30.873018 7fd7477fe6c0 Level-0 table #398: 0 bytes OK
2026/05/02-23:14:30.884443 7fd7477fe6c0 Delete type=0 #396
2026/05/02-23:14:30.905834 7fd7477fe6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.887738 7fd755fef6c0 Recovering log #361
2026/05/01-23:45:25.897606 7fd755fef6c0 Delete type=3 #359
2026/05/01-23:45:25.897664 7fd755fef6c0 Delete type=0 #361
2026/05/01-23:51:18.003483 7fd7477fe6c0 Level-0 table #366: started
2026/05/01-23:51:18.003518 7fd7477fe6c0 Level-0 table #366: 0 bytes OK
2026/05/01-23:51:18.037812 7fd7477fe6c0 Delete type=0 #364
2026/05/01-23:51:18.167176 7fd7477fe6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
2026/05/02-10:42:40.016911 7fd7557ee6c0 Recovering log #389
2026/05/02-10:42:40.026916 7fd7557ee6c0 Delete type=3 #387
2026/05/02-10:42:40.026980 7fd7557ee6c0 Delete type=0 #389
2026/05/02-10:55:36.154029 7fd7477fe6c0 Level-0 table #394: started
2026/05/02-10:55:36.154054 7fd7477fe6c0 Level-0 table #394: 0 bytes OK
2026/05/02-10:55:36.161757 7fd7477fe6c0 Delete type=0 #392
2026/05/02-10:55:36.175161 7fd7477fe6c0 Manual compaction at level-0 from '!items!5dGXNiL3WN4cAk7X' @ 72057594037927935 : 1 .. '!items!zzz9JrtWjELdoAfK' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000367
MANIFEST-000395
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:23.839110 7fd754fed6c0 Recovering log #365
2026/05/01-23:51:23.896151 7fd754fed6c0 Delete type=3 #363
2026/05/01-23:51:23.896228 7fd754fed6c0 Delete type=0 #365
2026/05/02-08:26:03.403387 7fd7477fe6c0 Level-0 table #370: started
2026/05/02-08:26:03.403427 7fd7477fe6c0 Level-0 table #370: 0 bytes OK
2026/05/02-08:26:03.439575 7fd7477fe6c0 Delete type=0 #368
2026/05/02-08:26:03.477128 7fd7477fe6c0 Manual compaction at level-0 from '!items!1cZd2hlTV9tykDED' @ 72057594037927935 : 1 .. '!items!y47dBO3Mf5Pn7tOd' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.642987 7fd747fff6c0 Recovering log #393
2026/05/02-22:17:12.653769 7fd747fff6c0 Delete type=3 #391
2026/05/02-22:17:12.653830 7fd747fff6c0 Delete type=0 #393
2026/05/02-23:14:30.884592 7fd7477fe6c0 Level-0 table #398: started
2026/05/02-23:14:30.884621 7fd7477fe6c0 Level-0 table #398: 0 bytes OK
2026/05/02-23:14:30.895025 7fd7477fe6c0 Delete type=0 #396
2026/05/02-23:14:30.905852 7fd7477fe6c0 Manual compaction at level-0 from '!items!1cZd2hlTV9tykDED' @ 72057594037927935 : 1 .. '!items!y47dBO3Mf5Pn7tOd' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.874605 7fd754fed6c0 Recovering log #361
2026/05/01-23:45:25.884831 7fd754fed6c0 Delete type=3 #359
2026/05/01-23:45:25.884885 7fd754fed6c0 Delete type=0 #361
2026/05/01-23:51:17.956955 7fd7477fe6c0 Level-0 table #366: started
2026/05/01-23:51:17.956995 7fd7477fe6c0 Level-0 table #366: 0 bytes OK
2026/05/01-23:51:18.002857 7fd7477fe6c0 Delete type=0 #364
2026/05/01-23:51:18.003348 7fd7477fe6c0 Manual compaction at level-0 from '!items!1cZd2hlTV9tykDED' @ 72057594037927935 : 1 .. '!items!y47dBO3Mf5Pn7tOd' @ 0 : 0; will stop at (end)
2026/05/02-10:42:40.003066 7fd755fef6c0 Recovering log #389
2026/05/02-10:42:40.013617 7fd755fef6c0 Delete type=3 #387
2026/05/02-10:42:40.013702 7fd755fef6c0 Delete type=0 #389
2026/05/02-10:55:36.147664 7fd7477fe6c0 Level-0 table #394: started
2026/05/02-10:55:36.147700 7fd7477fe6c0 Level-0 table #394: 0 bytes OK
2026/05/02-10:55:36.153904 7fd7477fe6c0 Delete type=0 #392
2026/05/02-10:55:36.168246 7fd7477fe6c0 Manual compaction at level-0 from '!items!1cZd2hlTV9tykDED' @ 72057594037927935 : 1 .. '!items!y47dBO3Mf5Pn7tOd' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000367
MANIFEST-000395
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:24.071860 7fd755fef6c0 Recovering log #365
2026/05/01-23:51:24.119019 7fd755fef6c0 Delete type=3 #363
2026/05/01-23:51:24.119097 7fd755fef6c0 Delete type=0 #365
2026/05/02-08:26:03.577938 7fd7477fe6c0 Level-0 table #370: started
2026/05/02-08:26:03.577975 7fd7477fe6c0 Level-0 table #370: 0 bytes OK
2026/05/02-08:26:03.614658 7fd7477fe6c0 Delete type=0 #368
2026/05/02-08:26:03.684819 7fd7477fe6c0 Manual compaction at level-0 from '!items!2GaJZsqr2c2mcDRv' @ 72057594037927935 : 1 .. '!items!ui4JGsGwHNlSXVK3' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.696255 7fd747fff6c0 Recovering log #393
2026/05/02-22:17:12.706814 7fd747fff6c0 Delete type=3 #391
2026/05/02-22:17:12.706879 7fd747fff6c0 Delete type=0 #393
2026/05/02-23:14:30.921060 7fd7477fe6c0 Level-0 table #398: started
2026/05/02-23:14:30.921121 7fd7477fe6c0 Level-0 table #398: 0 bytes OK
2026/05/02-23:14:30.931783 7fd7477fe6c0 Delete type=0 #396
2026/05/02-23:14:30.954777 7fd7477fe6c0 Manual compaction at level-0 from '!items!2GaJZsqr2c2mcDRv' @ 72057594037927935 : 1 .. '!items!ui4JGsGwHNlSXVK3' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.928160 7fd7557ee6c0 Recovering log #361
2026/05/01-23:45:25.938724 7fd7557ee6c0 Delete type=3 #359
2026/05/01-23:45:25.938783 7fd7557ee6c0 Delete type=0 #361
2026/05/01-23:51:18.037971 7fd7477fe6c0 Level-0 table #366: started
2026/05/01-23:51:18.038026 7fd7477fe6c0 Level-0 table #366: 0 bytes OK
2026/05/01-23:51:18.080237 7fd7477fe6c0 Delete type=0 #364
2026/05/01-23:51:18.167195 7fd7477fe6c0 Manual compaction at level-0 from '!items!2GaJZsqr2c2mcDRv' @ 72057594037927935 : 1 .. '!items!ui4JGsGwHNlSXVK3' @ 0 : 0; will stop at (end)
2026/05/02-10:42:40.055555 7fd747fff6c0 Recovering log #389
2026/05/02-10:42:40.065759 7fd747fff6c0 Delete type=3 #387
2026/05/02-10:42:40.065822 7fd747fff6c0 Delete type=0 #389
2026/05/02-10:55:36.175186 7fd7477fe6c0 Level-0 table #394: started
2026/05/02-10:55:36.175225 7fd7477fe6c0 Level-0 table #394: 0 bytes OK
2026/05/02-10:55:36.182810 7fd7477fe6c0 Delete type=0 #392
2026/05/02-10:55:36.196140 7fd7477fe6c0 Manual compaction at level-0 from '!items!2GaJZsqr2c2mcDRv' @ 72057594037927935 : 1 .. '!items!ui4JGsGwHNlSXVK3' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000027
MANIFEST-000055
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/01-23:51:24.450092 7fd754fed6c0 Recovering log #25
2026/05/01-23:51:24.512021 7fd754fed6c0 Delete type=3 #23
2026/05/01-23:51:24.512123 7fd754fed6c0 Delete type=0 #25
2026/05/02-08:26:03.753438 7fd7477fe6c0 Level-0 table #30: started
2026/05/02-08:26:03.753526 7fd7477fe6c0 Level-0 table #30: 0 bytes OK
2026/05/02-08:26:03.797061 7fd7477fe6c0 Delete type=0 #28
2026/05/02-08:26:03.865053 7fd7477fe6c0 Manual compaction at level-0 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
2026/05/02-08:26:03.911157 7fd7477fe6c0 Manual compaction at level-1 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.787407 7fd754fed6c0 Recovering log #53
2026/05/02-22:17:12.798064 7fd754fed6c0 Delete type=3 #51
2026/05/02-22:17:12.798132 7fd754fed6c0 Delete type=0 #53
2026/05/02-23:14:30.999818 7fd7477fe6c0 Level-0 table #58: started
2026/05/02-23:14:30.999850 7fd7477fe6c0 Level-0 table #58: 0 bytes OK
2026/05/02-23:14:31.012433 7fd7477fe6c0 Delete type=0 #56
2026/05/02-23:14:31.041770 7fd7477fe6c0 Manual compaction at level-0 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
2026/05/02-23:14:31.054104 7fd7477fe6c0 Manual compaction at level-1 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/01-23:45:26.025091 7fd747fff6c0 Recovering log #21
2026/05/01-23:45:26.035419 7fd747fff6c0 Delete type=3 #19
2026/05/01-23:45:26.035499 7fd747fff6c0 Delete type=0 #21
2026/05/01-23:51:18.423732 7fd7477fe6c0 Level-0 table #26: started
2026/05/01-23:51:18.423792 7fd7477fe6c0 Level-0 table #26: 0 bytes OK
2026/05/01-23:51:18.458393 7fd7477fe6c0 Delete type=0 #24
2026/05/01-23:51:18.501042 7fd7477fe6c0 Manual compaction at level-0 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
2026/05/01-23:51:18.535320 7fd7477fe6c0 Manual compaction at level-1 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
2026/05/02-10:42:40.145436 7fd754fed6c0 Recovering log #49
2026/05/02-10:42:40.155668 7fd754fed6c0 Delete type=3 #47
2026/05/02-10:42:40.155737 7fd754fed6c0 Delete type=0 #49
2026/05/02-10:55:36.221587 7fd7477fe6c0 Level-0 table #54: started
2026/05/02-10:55:36.221615 7fd7477fe6c0 Level-0 table #54: 0 bytes OK
2026/05/02-10:55:36.228497 7fd7477fe6c0 Delete type=0 #52
2026/05/02-10:55:36.234981 7fd7477fe6c0 Manual compaction at level-0 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
2026/05/02-10:55:36.253890 7fd7477fe6c0 Manual compaction at level-1 from '!journal!JurnlHelpGuide01' @ 72057594037927935 : 1 .. '!journal.pages!JurnlHelpGuide01.JHelpPage0000008' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000367
MANIFEST-000395
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:24.121693 7fd747fff6c0 Recovering log #365
2026/05/01-23:51:24.175522 7fd747fff6c0 Delete type=3 #363
2026/05/01-23:51:24.175606 7fd747fff6c0 Delete type=0 #365
2026/05/02-08:26:03.543492 7fd7477fe6c0 Level-0 table #370: started
2026/05/02-08:26:03.543537 7fd7477fe6c0 Level-0 table #370: 0 bytes OK
2026/05/02-08:26:03.577774 7fd7477fe6c0 Delete type=0 #368
2026/05/02-08:26:03.614845 7fd7477fe6c0 Manual compaction at level-0 from '!items!09s33sFuju8zjPqI' @ 72057594037927935 : 1 .. '!items!xlyFCQClBZ1N3O1B' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.708790 7fd7557ee6c0 Recovering log #393
2026/05/02-22:17:12.719560 7fd7557ee6c0 Delete type=3 #391
2026/05/02-22:17:12.719616 7fd7557ee6c0 Delete type=0 #393
2026/05/02-23:14:30.931940 7fd7477fe6c0 Level-0 table #398: started
2026/05/02-23:14:30.931974 7fd7477fe6c0 Level-0 table #398: 0 bytes OK
2026/05/02-23:14:30.943441 7fd7477fe6c0 Delete type=0 #396
2026/05/02-23:14:30.954807 7fd7477fe6c0 Manual compaction at level-0 from '!items!09s33sFuju8zjPqI' @ 72057594037927935 : 1 .. '!items!xlyFCQClBZ1N3O1B' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.941264 7fd755fef6c0 Recovering log #361
2026/05/01-23:45:25.951481 7fd755fef6c0 Delete type=3 #359
2026/05/01-23:45:25.951534 7fd755fef6c0 Delete type=0 #361
2026/05/01-23:51:18.167311 7fd7477fe6c0 Level-0 table #366: started
2026/05/01-23:51:18.167365 7fd7477fe6c0 Level-0 table #366: 0 bytes OK
2026/05/01-23:51:18.213184 7fd7477fe6c0 Delete type=0 #364
2026/05/01-23:51:18.334580 7fd7477fe6c0 Manual compaction at level-0 from '!items!09s33sFuju8zjPqI' @ 72057594037927935 : 1 .. '!items!xlyFCQClBZ1N3O1B' @ 0 : 0; will stop at (end)
2026/05/02-10:42:40.067527 7fd7557ee6c0 Recovering log #389
2026/05/02-10:42:40.078178 7fd7557ee6c0 Delete type=3 #387
2026/05/02-10:42:40.078243 7fd7557ee6c0 Delete type=0 #389
2026/05/02-10:55:36.182940 7fd7477fe6c0 Level-0 table #394: started
2026/05/02-10:55:36.182962 7fd7477fe6c0 Level-0 table #394: 0 bytes OK
2026/05/02-10:55:36.189078 7fd7477fe6c0 Delete type=0 #392
2026/05/02-10:55:36.202390 7fd7477fe6c0 Manual compaction at level-0 from '!items!09s33sFuju8zjPqI' @ 72057594037927935 : 1 .. '!items!xlyFCQClBZ1N3O1B' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000367
MANIFEST-000395
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:24.015685 7fd754fed6c0 Recovering log #365
2026/05/01-23:51:24.068740 7fd754fed6c0 Delete type=3 #363
2026/05/01-23:51:24.068836 7fd754fed6c0 Delete type=0 #365
2026/05/02-08:26:03.477243 7fd7477fe6c0 Level-0 table #370: started
2026/05/02-08:26:03.477285 7fd7477fe6c0 Level-0 table #370: 0 bytes OK
2026/05/02-08:26:03.516015 7fd7477fe6c0 Delete type=0 #368
2026/05/02-08:26:03.614814 7fd7477fe6c0 Manual compaction at level-0 from '!items!2t1KmBeQNuKK5qlN' @ 72057594037927935 : 1 .. '!items!yBvkQb9S64s908sR' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.682739 7fd754fed6c0 Recovering log #393
2026/05/02-22:17:12.693951 7fd754fed6c0 Delete type=3 #391
2026/05/02-22:17:12.694015 7fd754fed6c0 Delete type=0 #393
2026/05/02-23:14:30.905870 7fd7477fe6c0 Level-0 table #398: started
2026/05/02-23:14:30.905915 7fd7477fe6c0 Level-0 table #398: 0 bytes OK
2026/05/02-23:14:30.920801 7fd7477fe6c0 Delete type=0 #396
2026/05/02-23:14:30.943625 7fd7477fe6c0 Manual compaction at level-0 from '!items!2t1KmBeQNuKK5qlN' @ 72057594037927935 : 1 .. '!items!yBvkQb9S64s908sR' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.914723 7fd755fef6c0 Recovering log #361
2026/05/01-23:45:25.925030 7fd755fef6c0 Delete type=3 #359
2026/05/01-23:45:25.925095 7fd755fef6c0 Delete type=0 #361
2026/05/01-23:51:18.080386 7fd7477fe6c0 Level-0 table #366: started
2026/05/01-23:51:18.080421 7fd7477fe6c0 Level-0 table #366: 0 bytes OK
2026/05/01-23:51:18.124527 7fd7477fe6c0 Delete type=0 #364
2026/05/01-23:51:18.167208 7fd7477fe6c0 Manual compaction at level-0 from '!items!2t1KmBeQNuKK5qlN' @ 72057594037927935 : 1 .. '!items!yBvkQb9S64s908sR' @ 0 : 0; will stop at (end)
2026/05/02-10:42:40.043275 7fd7557ee6c0 Recovering log #389
2026/05/02-10:42:40.053566 7fd7557ee6c0 Delete type=3 #387
2026/05/02-10:42:40.053647 7fd7557ee6c0 Delete type=0 #389
2026/05/02-10:55:36.161945 7fd7477fe6c0 Level-0 table #394: started
2026/05/02-10:55:36.161989 7fd7477fe6c0 Level-0 table #394: 0 bytes OK
2026/05/02-10:55:36.168112 7fd7477fe6c0 Delete type=0 #392
2026/05/02-10:55:36.175175 7fd7477fe6c0 Manual compaction at level-0 from '!items!2t1KmBeQNuKK5qlN' @ 72057594037927935 : 1 .. '!items!yBvkQb9S64s908sR' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000045
MANIFEST-000073
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:23.965723 7fd7557ee6c0 Recovering log #43
2026/05/01-23:51:24.013270 7fd7557ee6c0 Delete type=3 #41
2026/05/01-23:51:24.013359 7fd7557ee6c0 Delete type=0 #43
2026/05/02-08:26:03.516163 7fd7477fe6c0 Level-0 table #48: started
2026/05/02-08:26:03.516201 7fd7477fe6c0 Level-0 table #48: 0 bytes OK
2026/05/02-08:26:03.543330 7fd7477fe6c0 Delete type=0 #46
2026/05/02-08:26:03.614833 7fd7477fe6c0 Manual compaction at level-0 from '!items!7KKX5anO8rJX492Y' @ 72057594037927935 : 1 .. '!items!veZcaW70wCVk99R7' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.669289 7fd755fef6c0 Recovering log #71
2026/05/02-22:17:12.680179 7fd755fef6c0 Delete type=3 #69
2026/05/02-22:17:12.680245 7fd755fef6c0 Delete type=0 #71
2026/05/02-23:14:30.895178 7fd7477fe6c0 Level-0 table #76: started
2026/05/02-23:14:30.895205 7fd7477fe6c0 Level-0 table #76: 0 bytes OK
2026/05/02-23:14:30.905579 7fd7477fe6c0 Delete type=0 #74
2026/05/02-23:14:30.921033 7fd7477fe6c0 Manual compaction at level-0 from '!items!7KKX5anO8rJX492Y' @ 72057594037927935 : 1 .. '!items!veZcaW70wCVk99R7' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.901174 7fd754fed6c0 Recovering log #39
2026/05/01-23:45:25.912205 7fd754fed6c0 Delete type=3 #37
2026/05/01-23:45:25.912263 7fd754fed6c0 Delete type=0 #39
2026/05/01-23:51:18.124721 7fd7477fe6c0 Level-0 table #44: started
2026/05/01-23:51:18.124762 7fd7477fe6c0 Level-0 table #44: 0 bytes OK
2026/05/01-23:51:18.167031 7fd7477fe6c0 Delete type=0 #42
2026/05/01-23:51:18.167219 7fd7477fe6c0 Manual compaction at level-0 from '!items!7KKX5anO8rJX492Y' @ 72057594037927935 : 1 .. '!items!veZcaW70wCVk99R7' @ 0 : 0; will stop at (end)
2026/05/02-10:42:40.029411 7fd755fef6c0 Recovering log #67
2026/05/02-10:42:40.040864 7fd755fef6c0 Delete type=3 #65
2026/05/02-10:42:40.040925 7fd755fef6c0 Delete type=0 #67
2026/05/02-10:55:36.168263 7fd7477fe6c0 Level-0 table #72: started
2026/05/02-10:55:36.168296 7fd7477fe6c0 Level-0 table #72: 0 bytes OK
2026/05/02-10:55:36.175025 7fd7477fe6c0 Delete type=0 #70
2026/05/02-10:55:36.182928 7fd7477fe6c0 Manual compaction at level-0 from '!items!7KKX5anO8rJX492Y' @ 72057594037927935 : 1 .. '!items!veZcaW70wCVk99R7' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000263
MANIFEST-000292
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:51:23.352194 7fd7557ee6c0 Recovering log #261
2026/05/01-23:51:23.408632 7fd7557ee6c0 Delete type=3 #259
2026/05/01-23:51:23.408711 7fd7557ee6c0 Delete type=0 #261
2026/05/02-08:26:02.971512 7fd7477fe6c0 Level-0 table #266: started
2026/05/02-08:26:02.975617 7fd7477fe6c0 Level-0 table #266: 0 bytes OK
2026/05/02-08:26:03.013303 7fd7477fe6c0 Delete type=0 #264
2026/05/02-08:26:03.143866 7fd7477fe6c0 Manual compaction at level-0 from '!actors!00CKDCqVh5fLZbYo' @ 72057594037927935 : 1 .. '!folders!dwT9WnH0ZnpuZh92' @ 0 : 0; will stop at (end)
2026/05/02-22:17:12.499898 7fd7557ee6c0 Recovering log #290
2026/05/02-22:17:12.510769 7fd7557ee6c0 Delete type=3 #288
2026/05/02-22:17:12.510837 7fd7557ee6c0 Delete type=0 #290
2026/05/02-23:14:30.650934 7fd7477fe6c0 Level-0 table #295: started
2026/05/02-23:14:30.650972 7fd7477fe6c0 Level-0 table #295: 0 bytes OK
2026/05/02-23:14:30.662662 7fd7477fe6c0 Delete type=0 #293
2026/05/02-23:14:30.662885 7fd7477fe6c0 Manual compaction at level-0 from '!actors!00CKDCqVh5fLZbYo' @ 72057594037927935 : 1 .. '!folders!dwT9WnH0ZnpuZh92' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/01-23:45:25.784183 7fd754fed6c0 Recovering log #257
2026/05/01-23:45:25.793965 7fd754fed6c0 Delete type=3 #255
2026/05/01-23:45:25.794022 7fd754fed6c0 Delete type=0 #257
2026/05/01-23:51:17.545136 7fd7477fe6c0 Level-0 table #262: started
2026/05/01-23:51:17.545243 7fd7477fe6c0 Level-0 table #262: 0 bytes OK
2026/05/01-23:51:17.585589 7fd7477fe6c0 Delete type=0 #260
2026/05/01-23:51:17.709862 7fd7477fe6c0 Manual compaction at level-0 from '!actors!00CKDCqVh5fLZbYo' @ 72057594037927935 : 1 .. '!folders!dwT9WnH0ZnpuZh92' @ 0 : 0; will stop at (end)
2026/05/02-10:42:39.863251 7fd754fed6c0 Recovering log #286
2026/05/02-10:42:39.874170 7fd754fed6c0 Delete type=3 #284
2026/05/02-10:42:39.874230 7fd754fed6c0 Delete type=0 #286
2026/05/02-10:55:36.018781 7fd7477fe6c0 Level-0 table #291: started
2026/05/02-10:55:36.018828 7fd7477fe6c0 Level-0 table #291: 0 bytes OK
2026/05/02-10:55:36.025349 7fd7477fe6c0 Delete type=0 #289
2026/05/02-10:55:36.025635 7fd7477fe6c0 Manual compaction at level-0 from '!actors!00CKDCqVh5fLZbYo' @ 72057594037927935 : 1 .. '!folders!dwT9WnH0ZnpuZh92' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.

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