Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6742830f40 | |||
| 09f2349bab | |||
| d8eb23289e | |||
| 820a7d61cf | |||
| 755c349078 | |||
| 8fb27c2e82 |
@@ -27,6 +27,17 @@ jobs:
|
|||||||
manifest: https://www.uberwald.me/gitea/uberwald/fvtt-donjon-et-cie/releases/download/latest/system.json
|
manifest: https://www.uberwald.me/gitea/uberwald/fvtt-donjon-et-cie/releases/download/latest/system.json
|
||||||
download: https://www.uberwald.me/gitea/${{ gitea.repository }}/releases/download/${{ github.event.release.tag_name }}/fvtt-donjon-et-cie.zip
|
download: https://www.uberwald.me/gitea/${{ gitea.repository }}/releases/download/${{ github.event.release.tag_name }}/fvtt-donjon-et-cie.zip
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build system and pack compendiums
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
- run: |
|
- run: |
|
||||||
apt update -y
|
apt update -y
|
||||||
apt install -y zip
|
apt install -y zip
|
||||||
|
|||||||
+62
-7
@@ -12,6 +12,7 @@
|
|||||||
"TYPES.Item.entrainement": "Entrainement",
|
"TYPES.Item.entrainement": "Entrainement",
|
||||||
"DNC.Roll.Characteristic": "Jet de caracteristique",
|
"DNC.Roll.Characteristic": "Jet de caracteristique",
|
||||||
"DNC.Roll.Attack": "Jet d'attaque",
|
"DNC.Roll.Attack": "Jet d'attaque",
|
||||||
|
"DNC.Roll.Ammunition": "Munitions",
|
||||||
"DNC.Roll.Damage": "Jet de degats",
|
"DNC.Roll.Damage": "Jet de degats",
|
||||||
"DNC.Roll.Favor": "Faveur",
|
"DNC.Roll.Favor": "Faveur",
|
||||||
"DNC.Roll.HitDice": "Jet de de de vie",
|
"DNC.Roll.HitDice": "Jet de de de vie",
|
||||||
@@ -37,6 +38,8 @@
|
|||||||
"DNC.Chat.Kept": "Garde",
|
"DNC.Chat.Kept": "Garde",
|
||||||
"DNC.Chat.Rolls": "Lancers",
|
"DNC.Chat.Rolls": "Lancers",
|
||||||
"DNC.Chat.RollDamage": "Lancer les degats",
|
"DNC.Chat.RollDamage": "Lancer les degats",
|
||||||
|
"DNC.Chat.RollAmmunition": "Munitions",
|
||||||
|
"DNC.Chat.DamageCapped": "Degats plafonnes a {damage} par {dv}",
|
||||||
"DNC.Chat.RollValue": "Jet",
|
"DNC.Chat.RollValue": "Jet",
|
||||||
"DNC.Chat.AutoDisadvantage": "Desavantage automatique",
|
"DNC.Chat.AutoDisadvantage": "Desavantage automatique",
|
||||||
"DNC.Chat.RiskChaos": "Risquer le Chaos",
|
"DNC.Chat.RiskChaos": "Risquer le Chaos",
|
||||||
@@ -56,11 +59,16 @@
|
|||||||
"DNC.Chat.HpRemaining": "PV restants",
|
"DNC.Chat.HpRemaining": "PV restants",
|
||||||
"DNC.Chat.CasterRank": "Rang du lanceur",
|
"DNC.Chat.CasterRank": "Rang du lanceur",
|
||||||
"DNC.Chat.Difficulty": "Difficulte",
|
"DNC.Chat.Difficulty": "Difficulte",
|
||||||
|
"DNC.Chat.MartialDv": "DV martial",
|
||||||
|
"DNC.Chat.EffectiveDamage": "Degats retenus",
|
||||||
"DNC.Chat.Effect": "Effet",
|
"DNC.Chat.Effect": "Effet",
|
||||||
"DNC.Chat.StoredProtection": "Protection stockee pour ce combat",
|
"DNC.Chat.StoredProtection": "Protection stockee pour ce combat",
|
||||||
"DNC.Chat.ResourceWearsDown": "La ressource s'amenuise.",
|
"DNC.Chat.ResourceWearsDown": "La ressource s'amenuise.",
|
||||||
"DNC.Chat.ResourceStable": "La ressource tient bon.",
|
"DNC.Chat.ResourceStable": "La ressource tient bon.",
|
||||||
"DNC.Chat.ResourceExhausted": "La ressource est epuisee.",
|
"DNC.Chat.ResourceExhausted": "La ressource est epuisee.",
|
||||||
|
"DNC.Chat.AmmunitionWearsDown": "Les munitions diminuent d'un cran.",
|
||||||
|
"DNC.Chat.AmmunitionStable": "Le stock de munitions tient bon.",
|
||||||
|
"DNC.Chat.AmmunitionExhausted": "Il n'y a plus de munitions disponibles pour cette arme.",
|
||||||
"DNC.Chat.AutoDisadvantageCanceled": "le cout du sort depasse le rang du lanceur, mais une faveur l'a annule.",
|
"DNC.Chat.AutoDisadvantageCanceled": "le cout du sort depasse le rang du lanceur, mais une faveur l'a annule.",
|
||||||
"DNC.Chat.AutoDisadvantageApplies": "le cout du sort depasse le rang du lanceur.",
|
"DNC.Chat.AutoDisadvantageApplies": "le cout du sort depasse le rang du lanceur.",
|
||||||
"DNC.Chat.FocusFrom": "depuis",
|
"DNC.Chat.FocusFrom": "depuis",
|
||||||
@@ -89,6 +97,7 @@
|
|||||||
"DNC.UI.Focus": "Focus",
|
"DNC.UI.Focus": "Focus",
|
||||||
"DNC.UI.Chaos": "Chaos",
|
"DNC.UI.Chaos": "Chaos",
|
||||||
"DNC.UI.Weapon": "Arme",
|
"DNC.UI.Weapon": "Arme",
|
||||||
|
"DNC.UI.Ammunition": "Munitions",
|
||||||
"DNC.UI.Damage": "Degats",
|
"DNC.UI.Damage": "Degats",
|
||||||
"DNC.UI.Range": "Portee",
|
"DNC.UI.Range": "Portee",
|
||||||
"DNC.UI.Spell": "Sortilege",
|
"DNC.UI.Spell": "Sortilege",
|
||||||
@@ -96,13 +105,19 @@
|
|||||||
"DNC.Dialog.InitiativeCurrent": "DEX actuelle : <strong>{dex}</strong>, bonus de fiche : <strong>{initiativeBonus}</strong>",
|
"DNC.Dialog.InitiativeCurrent": "DEX actuelle : <strong>{dex}</strong>, bonus de fiche : <strong>{initiativeBonus}</strong>",
|
||||||
"DNC.Dialog.CharacteristicUsed": "Caracteristique utilisee",
|
"DNC.Dialog.CharacteristicUsed": "Caracteristique utilisee",
|
||||||
"DNC.Dialog.ActorDamageBonus": "Bonus de degats de l'acteur",
|
"DNC.Dialog.ActorDamageBonus": "Bonus de degats de l'acteur",
|
||||||
|
"DNC.Dialog.DamageCappedByDv": "Le DV martial actuel ({dv}) plafonne cette arme : {base} devient {damage}.",
|
||||||
"DNC.Dialog.SpellAutoDisadvantage": "Le cout depasse le rang du lanceur : le jet se fera automatiquement avec desavantage.",
|
"DNC.Dialog.SpellAutoDisadvantage": "Le cout depasse le rang du lanceur : le jet se fera automatiquement avec desavantage.",
|
||||||
"DNC.Dialog.UseUsageDie": "Utiliser <strong>{itemName}</strong> et lancer son de d'usage actuel.",
|
"DNC.Dialog.UseUsageDie": "Utiliser <strong>{itemName}</strong> et lancer son de d'usage actuel.",
|
||||||
"DNC.Dialog.CurrentValue": "Valeur actuelle",
|
"DNC.Dialog.CurrentValue": "Valeur actuelle",
|
||||||
"DNC.Warn.NoFavorAvailable": "Aucune faveur disponible pour {label}.",
|
"DNC.Warn.NoFavorAvailable": "Aucune faveur disponible pour {label}.",
|
||||||
|
"DNC.Warn.NoAmmunitionAvailable": "Il n'y a plus de munitions disponibles pour cette arme.",
|
||||||
"DNC.Warn.SpellInsufficientResources": "Le lanceur ne dispose pas d'assez de PV et de focus pour payer ce sort.",
|
"DNC.Warn.SpellInsufficientResources": "Le lanceur ne dispose pas d'assez de PV et de focus pour payer ce sort.",
|
||||||
"DNC.Warn.ChaosUnavailable": "Le Chaos n'est pas disponible pour ce sort.",
|
"DNC.Warn.ChaosUnavailable": "Le Chaos n'est pas disponible pour ce sort.",
|
||||||
"DNC.Warn.TrainingExhausted": "Cet entrainement est epuise pour aujourd'hui. Reinitialisez son delta quotidien pour le lendemain.",
|
"DNC.Warn.TrainingExhausted": "Cet entrainement est epuise pour aujourd'hui. Reinitialisez son delta quotidien pour le lendemain.",
|
||||||
|
"DNC.Settings.MissionPackMode.Name": "Mode de paquetage de mission",
|
||||||
|
"DNC.Settings.MissionPackMode.Hint": "Choisit entre le tirage classique rapide et la regle optionnelle de campagne avec allocation des des par les joueurs.",
|
||||||
|
"DNC.Settings.MissionPackMode.Classic": "Classique",
|
||||||
|
"DNC.Settings.MissionPackMode.Campaign": "Campagne",
|
||||||
"DNC.Macro.MissionPack.Title": "Paquetage de debut de mission",
|
"DNC.Macro.MissionPack.Title": "Paquetage de debut de mission",
|
||||||
"DNC.Macro.MissionPack.ItemsAdded": "Objets ajoutes",
|
"DNC.Macro.MissionPack.ItemsAdded": "Objets ajoutes",
|
||||||
"DNC.Macro.MissionPack.ItemsMissing": "Objets manquants",
|
"DNC.Macro.MissionPack.ItemsMissing": "Objets manquants",
|
||||||
@@ -121,13 +136,46 @@
|
|||||||
"DNC.Macro.MissionPack.Partial": "{actor} recoit {count} objet(s), mais {missing} entree(s) du paquetage sont introuvables.",
|
"DNC.Macro.MissionPack.Partial": "{actor} recoit {count} objet(s), mais {missing} entree(s) du paquetage sont introuvables.",
|
||||||
"DNC.Macro.MissionPack.DialogTitle": "Paquetage de mission",
|
"DNC.Macro.MissionPack.DialogTitle": "Paquetage de mission",
|
||||||
"DNC.Macro.MissionPack.DialogIntro": "Selectionnez l'employe qui recevra le paquetage de debut de mission, puis lancez le tirage.",
|
"DNC.Macro.MissionPack.DialogIntro": "Selectionnez l'employe qui recevra le paquetage de debut de mission, puis lancez le tirage.",
|
||||||
|
"DNC.Macro.MissionPack.ActiveMode": "Mode :",
|
||||||
|
"DNC.Macro.MissionPack.ModeClassic": "Classique",
|
||||||
|
"DNC.Macro.MissionPack.ModeClassicHint": "Le systeme tire directement les 4 tables d equipement, comme dans les one-shots.",
|
||||||
|
"DNC.Macro.MissionPack.ModeCampaign": "Campagne",
|
||||||
|
"DNC.Macro.MissionPack.ModeCampaignHint": "Le joueur alloue 1d20, 1d12, 1d10 et 1d8 aux 4 categories, puis l anciennete est ajoutee a chaque resultat.",
|
||||||
"DNC.Macro.MissionPack.DialogActor": "Employe",
|
"DNC.Macro.MissionPack.DialogActor": "Employe",
|
||||||
"DNC.Macro.MissionPack.DialogAction": "Generer le paquetage",
|
"DNC.Macro.MissionPack.DialogAction": "Generer le paquetage",
|
||||||
"DNC.Macro.MissionPack.SidebarButton": "Paquetage",
|
"DNC.Macro.MissionPack.SidebarButton": "Paquetage",
|
||||||
"DNC.Macro.MissionPack.melee": "Arme de corps a corps",
|
"DNC.Macro.MissionPack.CampaignDialogLead": "En mode campagne, le joueur proprietaire choisira l affectation de ses des dans un dialogue dedie.",
|
||||||
"DNC.Macro.MissionPack.ranged": "Arme a distance",
|
"DNC.Macro.MissionPack.CampaignDialogTitle": "Paquetage de campagne",
|
||||||
"DNC.Macro.MissionPack.armor": "Armure",
|
"DNC.Macro.MissionPack.CampaignDialogSubtitle": "Repartition logistique",
|
||||||
"DNC.Macro.MissionPack.misc": "Encas et equipement divers",
|
"DNC.Macro.MissionPack.CampaignDialogIntro": "{actor} prepare son paquetage de campagne. Repartissez les des entre les categories, puis indiquez la relation a l econome.",
|
||||||
|
"DNC.Macro.MissionPack.CampaignDialogAction": "Valider l allocation",
|
||||||
|
"DNC.Macro.MissionPack.CampaignDialogPlayer": "Joueur",
|
||||||
|
"DNC.Macro.MissionPack.CampaignDialogRequester": "Demande de",
|
||||||
|
"DNC.Macro.MissionPack.CampaignDialogRank": "Anciennete",
|
||||||
|
"DNC.Macro.MissionPack.CampaignDialogRelation": "Relation a l econome",
|
||||||
|
"DNC.Macro.MissionPack.CampaignDialogAssignHelp": "Attribuez un de different a chaque categorie de paquetage.",
|
||||||
|
"DNC.Macro.MissionPack.CampaignDialogHelp": "Le d20 alloue servira aussi pour verifier l equipement unique. L anciennete est ajoutee a chaque jet apres avantage ou desavantage.",
|
||||||
|
"DNC.Macro.MissionPack.CampaignController": "Choix joueur :",
|
||||||
|
"DNC.Macro.MissionPack.CampaignRelation": "Relation :",
|
||||||
|
"DNC.Macro.MissionPack.CampaignRequestCanceled": "Le paquetage de campagne pour {actor} n a pas ete valide par {player}.",
|
||||||
|
"DNC.Macro.MissionPack.WarnDiceRequired": "Attribuez un de a chacune des 4 categories du paquetage.",
|
||||||
|
"DNC.Macro.MissionPack.WarnDiceUnique": "Chaque de ne peut etre utilise qu une seule fois.",
|
||||||
|
"DNC.Macro.MissionPack.Relation.positive": "Positive",
|
||||||
|
"DNC.Macro.MissionPack.Relation.neutral": "Neutre",
|
||||||
|
"DNC.Macro.MissionPack.Relation.negative": "Negative",
|
||||||
|
"DNC.Macro.MissionPack.RollDetail": "{die} · {mode} · lancers {values} · garde {kept} · + anciennete {rank} = {total}",
|
||||||
|
"DNC.Macro.MissionPack.TotalClamped": "Total retenu sur la table : {clamped} (au lieu de {total}).",
|
||||||
|
"DNC.Macro.MissionPack.melee": "Arme de corps a corps",
|
||||||
|
"DNC.Macro.MissionPack.ranged": "Arme a distance",
|
||||||
|
"DNC.Macro.MissionPack.armor": "Armure",
|
||||||
|
"DNC.Macro.MissionPack.misc": "Encas et equipement divers",
|
||||||
|
"DNC.Macro.MissionPack.UniqueReference": "d20 de reference",
|
||||||
|
"DNC.Macro.MissionPack.UniqueActorRoll": "d20 du joueur",
|
||||||
|
"DNC.Macro.MissionPack.UniqueMatch": "Objet unique",
|
||||||
|
"DNC.Macro.MissionPack.UniqueMiss": "Pas d objet unique",
|
||||||
|
"DNC.Macro.MissionPack.UniqueGranted": "Objet unique ajoute :",
|
||||||
|
"DNC.Macro.MissionPack.UniqueTableRoll": "tirage d objet unique",
|
||||||
|
"DNC.Macro.MissionPack.UniqueRuleReminder": "Aucun objet unique supplementaire cette fois ci.",
|
||||||
"DNC.Welcome.Kicker": "Accueil",
|
"DNC.Welcome.Kicker": "Accueil",
|
||||||
"DNC.Welcome.Title": "Bienvenue dans Donjon & Cie",
|
"DNC.Welcome.Title": "Bienvenue dans Donjon & Cie",
|
||||||
"DNC.Welcome.Subtitle": "Systeme FoundryVTT · version {version}",
|
"DNC.Welcome.Subtitle": "Systeme FoundryVTT · version {version}",
|
||||||
@@ -150,5 +198,12 @@
|
|||||||
"DNC.Sheet.Create": "Creer",
|
"DNC.Sheet.Create": "Creer",
|
||||||
"DNC.Sheet.Delete": "Supprimer",
|
"DNC.Sheet.Delete": "Supprimer",
|
||||||
"DNC.Sheet.Edit": "Editer",
|
"DNC.Sheet.Edit": "Editer",
|
||||||
"DNC.Sheet.Post": "Poster dans le chat"
|
"DNC.Sheet.Post": "Poster dans le chat",
|
||||||
|
"DNC.Roll.DamageUsage": "Usage des degats",
|
||||||
|
"DNC.Chat.RollDamageUsage": "Usage degats",
|
||||||
|
"DNC.Chat.DamageUsageWearsDown": "Les degats de l'arme diminuent d'un cran.",
|
||||||
|
"DNC.Chat.DamageUsageStable": "L'arme tient bon, ses degats restent inchanges.",
|
||||||
|
"DNC.Chat.DamageUsageExhausted": "L'arme est epuisee, elle ne peut plus causer de degats.",
|
||||||
|
"DNC.UI.DamageExhausted": "Epuise",
|
||||||
|
"DNC.Warn.DamageExhausted": "Cette arme est epuisee et ne peut plus causer de degats."
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-5
@@ -135,12 +135,18 @@
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dnc-employe-sheet .sheet-header.compact .hp-field {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
.dnc-employe-sheet .sheet-header.compact input[type="number"] {
|
.dnc-employe-sheet .sheet-header.compact input[type="number"] {
|
||||||
max-width: 4.75rem;
|
max-width: 4.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnc-employe-sheet .sheet-header.compact .counter-field input[type="number"] {
|
.dnc-employe-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||||
max-width: 4rem;
|
.dnc-employe-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.max"] {
|
||||||
|
width: 5.25rem;
|
||||||
|
max-width: 5.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnc-pnj-sheet .sheet-header.compact .identity-grid {
|
.dnc-pnj-sheet .sheet-header.compact .identity-grid {
|
||||||
@@ -152,14 +158,19 @@
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dnc-pnj-sheet .sheet-header.compact .hp-field {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
.dnc-pnj-sheet .sheet-header.compact input[type="number"] {
|
.dnc-pnj-sheet .sheet-header.compact input[type="number"] {
|
||||||
width: 4.5rem;
|
width: 4.5rem;
|
||||||
max-width: 4.5rem;
|
max-width: 4.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[type="number"] {
|
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||||
width: 3.75rem;
|
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.max"] {
|
||||||
max-width: 3.75rem;
|
width: 5.25rem;
|
||||||
|
max-width: 5.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dnc-sheet label {
|
.dnc-sheet label {
|
||||||
|
|||||||
@@ -34,3 +34,149 @@
|
|||||||
.dnc-roll-dialog .window-content {
|
.dnc-roll-dialog .window-content {
|
||||||
background: linear-gradient(180deg, #f7efe0 0%, #e3d0b1 100%);
|
background: linear-gradient(180deg, #f7efe0 0%, #e3d0b1 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-mode,
|
||||||
|
.dnc-mission-pack-note {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-campaign {
|
||||||
|
gap: @spacing-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-hero {
|
||||||
|
padding: @spacing-lg;
|
||||||
|
border: 1px solid fade(@color-border, 35%);
|
||||||
|
border-radius: @radius-md;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, fade(#ffffff, 65%) 0%, fade(@color-panel, 68%) 100%),
|
||||||
|
linear-gradient(135deg, fade(@color-accent, 8%) 0%, fade(@color-accent, 0%) 100%);
|
||||||
|
box-shadow: 0 8px 18px fade(@color-shadow, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-kicker {
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: fade(@color-accent, 82%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-hero h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: @font-display;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
color: @color-accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-subtitle {
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: fade(@color-border, 78%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-intro {
|
||||||
|
margin-top: @spacing-sm;
|
||||||
|
color: @color-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-meta-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: @spacing-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-meta-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.2rem;
|
||||||
|
padding: @spacing-md;
|
||||||
|
border: 1px solid fade(@color-border, 30%);
|
||||||
|
border-radius: @radius-md;
|
||||||
|
background: fade(#ffffff, 42%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-meta-card span {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: @color-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-meta-card strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-section {
|
||||||
|
display: grid;
|
||||||
|
gap: @spacing-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-select {
|
||||||
|
padding: @spacing-md;
|
||||||
|
border: 1px solid fade(@color-border, 32%);
|
||||||
|
border-radius: @radius-md;
|
||||||
|
background: fade(#ffffff, 36%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-assignments {
|
||||||
|
display: grid;
|
||||||
|
gap: @spacing-md;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-assignment {
|
||||||
|
display: grid;
|
||||||
|
gap: @spacing-sm;
|
||||||
|
padding: @spacing-md;
|
||||||
|
border: 1px solid fade(@color-border, 35%);
|
||||||
|
border-radius: @radius-md;
|
||||||
|
background: fade(#ffffff, 38%);
|
||||||
|
box-shadow: inset 0 1px 0 fade(#ffffff, 55%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-assignment span {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-note {
|
||||||
|
color: @color-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-note-foot {
|
||||||
|
padding: @spacing-md;
|
||||||
|
border-top: 1px solid fade(@color-border, 24%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-dialog .window-header {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, fade(#fdf6e7, 96%) 0%, fade(#ead4aa, 96%) 100%),
|
||||||
|
linear-gradient(90deg, fade(@color-accent, 12%) 0%, fade(@color-accent, 0%) 100%);
|
||||||
|
border-bottom: 1px solid fade(@color-border, 35%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-dialog .window-title {
|
||||||
|
color: @color-accent;
|
||||||
|
font-family: @font-display;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dnc-mission-pack-dialog .window-header button {
|
||||||
|
color: @color-ink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.dnc-mission-pack-meta-grid,
|
||||||
|
.dnc-mission-pack-assignments {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -201,12 +201,17 @@ export class DonjonEtCieRollDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async createDamage(actor, item) {
|
static async createDamage(actor, item) {
|
||||||
|
const damageContext = DonjonEtCieUtility.getMartialDamageContext(actor, item);
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
"systems/fvtt-donjon-et-cie/templates/dialogs/damage-roll.hbs",
|
"systems/fvtt-donjon-et-cie/templates/dialogs/damage-roll.hbs",
|
||||||
{
|
{
|
||||||
actorName: actor?.name ?? item.actor?.name ?? "",
|
actorName: actor?.name ?? item.actor?.name ?? "",
|
||||||
item,
|
item,
|
||||||
actorBonus: actor?.system?.combat?.degatsBonus ?? 0
|
actorBonus: actor?.system?.combat?.degatsBonus ?? 0,
|
||||||
|
damageFormula: damageContext.effectiveFormula || item.system.degats,
|
||||||
|
damageBase: damageContext.baseFormula || item.system.degats,
|
||||||
|
damageCapped: damageContext.capped,
|
||||||
|
martialDvLabel: damageContext.martialDvSides ? `d${damageContext.martialDvSides}` : damageContext.martialDvFormula
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ export default class DonjonEtCieItemSheet extends HandlebarsApplicationMixin(fou
|
|||||||
isCapacity: item.type === "capacite",
|
isCapacity: item.type === "capacite",
|
||||||
isLanguage: item.type === "langue",
|
isLanguage: item.type === "langue",
|
||||||
isTrait: item.type === "trait",
|
isTrait: item.type === "trait",
|
||||||
|
ammunitionUsageLabel: item.type === "arme" && Number(item.system.munitionsDelta ?? 0) > 0
|
||||||
|
? DonjonEtCieUtility.formatUsageDie(item.system.munitionsDelta)
|
||||||
|
: "—",
|
||||||
canResetUsage: item.type === "entrainement" && Number(item.system.deltaMax ?? 0) > 0 && Number(item.system.delta ?? 0) !== Number(item.system.deltaMax ?? 0),
|
canResetUsage: item.type === "entrainement" && Number(item.system.deltaMax ?? 0) > 0 && Number(item.system.delta ?? 0) !== Number(item.system.deltaMax ?? 0),
|
||||||
armorProtectionDisplay: Number(item.system.resultatProtection ?? 0) > 0 ? item.system.resultatProtection : "—",
|
armorProtectionDisplay: Number(item.system.resultatProtection ?? 0) > 0 ? item.system.resultatProtection : "—",
|
||||||
weaponCharacteristicLabel: item.type === "arme" ? DonjonEtCieUtility.getWeaponCharacteristicLabel(item.system.categorie) : null,
|
weaponCharacteristicLabel: item.type === "arme" ? DonjonEtCieUtility.getWeaponCharacteristicLabel(item.system.categorie) : null,
|
||||||
|
|||||||
@@ -29,6 +29,19 @@ export class DonjonEtCieActor extends Actor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _preUpdate(changed, options, user) {
|
||||||
|
if (
|
||||||
|
foundry.utils.hasProperty(changed, "system.magie.focus.delta")
|
||||||
|
&& !foundry.utils.hasProperty(changed, "system.magie.focus.resultat")
|
||||||
|
&& !foundry.utils.hasProperty(changed, "system.magie.focus.sceneId")
|
||||||
|
) {
|
||||||
|
foundry.utils.setProperty(changed, "system.magie.focus.resultat", 0);
|
||||||
|
foundry.utils.setProperty(changed, "system.magie.focus.sceneId", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._preUpdate(changed, options, user);
|
||||||
|
}
|
||||||
|
|
||||||
getCharacteristicEntries() {
|
getCharacteristicEntries() {
|
||||||
return DonjonEtCieUtility.getCharacteristicEntries(this.system);
|
return DonjonEtCieUtility.getCharacteristicEntries(this.system);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ export class DonjonEtCieItem extends Item {
|
|||||||
return Number(this.system.deltaMax ?? this.system.delta ?? 0);
|
return Number(this.system.deltaMax ?? this.system.delta ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get ammunitionUsageDie() {
|
||||||
|
return Number(this.system.munitionsDelta ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get damageUsageDie() {
|
||||||
|
return Number(this.system.degatsDelta ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
async roll() {
|
async roll() {
|
||||||
if (this.type === "arme") return DonjonEtCieRollDialog.createWeapon(this.actor, this);
|
if (this.type === "arme") return DonjonEtCieRollDialog.createWeapon(this.actor, this);
|
||||||
if (this.type === "sortilege") return DonjonEtCieRollDialog.createSpell(this.actor, this);
|
if (this.type === "sortilege") return DonjonEtCieRollDialog.createSpell(this.actor, this);
|
||||||
@@ -43,10 +51,27 @@ export class DonjonEtCieItem extends Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async rollDamage() {
|
async rollDamage() {
|
||||||
if (!this.system.degats) return null;
|
if (this.system.degatsEstUsageDe) {
|
||||||
|
if (!Number(this.system.degatsDelta ?? 0)) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("DNC.Warn.DamageExhausted"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (!this.system.degats) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return DonjonEtCieRollDialog.createDamage(this.actor, this);
|
return DonjonEtCieRollDialog.createDamage(this.actor, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async rollAmmoUsage() {
|
||||||
|
if (this.type !== "arme") return null;
|
||||||
|
return game.system.donjonEtCie.rolls.rollWeaponAmmoUsage(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollDamageUsage() {
|
||||||
|
if (this.type !== "arme") return null;
|
||||||
|
return game.system.donjonEtCie.rolls.rollWeaponDamageUsage(this);
|
||||||
|
}
|
||||||
|
|
||||||
async postToChat() {
|
async postToChat() {
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
"systems/fvtt-donjon-et-cie/templates/chat/item-card.hbs",
|
"systems/fvtt-donjon-et-cie/templates/chat/item-card.hbs",
|
||||||
|
|||||||
+685
-100
@@ -13,6 +13,18 @@
|
|||||||
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
||||||
|
|
||||||
export class DonjonEtCieMacros {
|
export class DonjonEtCieMacros {
|
||||||
|
static MISSION_PACK_MODE_SETTING = "missionPackMode";
|
||||||
|
static MISSION_PACK_SOCKET_SCOPE = "missionPackCampaign";
|
||||||
|
static MISSION_PACK_REQUEST_TIMEOUT = 300000;
|
||||||
|
static MISSION_PACK_CAMPAIGN_DICE = [20, 12, 10, 8];
|
||||||
|
static MISSION_PACK_STEWARD_MODES = {
|
||||||
|
positive: "avantage",
|
||||||
|
neutral: "normal",
|
||||||
|
negative: "desavantage"
|
||||||
|
};
|
||||||
|
static #campaignRequests = new Map();
|
||||||
|
static #socketRegistered = false;
|
||||||
|
|
||||||
static MISSION_PACK_TABLES = [
|
static MISSION_PACK_TABLES = [
|
||||||
{ key: "melee", name: "Armes de corps a corps", multiple: false },
|
{ key: "melee", name: "Armes de corps a corps", multiple: false },
|
||||||
{ key: "ranged", name: "Armes a distance", multiple: false },
|
{ key: "ranged", name: "Armes a distance", multiple: false },
|
||||||
@@ -20,6 +32,132 @@ export class DonjonEtCieMacros {
|
|||||||
{ key: "misc", name: "Encas et equipement divers", multiple: true }
|
{ key: "misc", name: "Encas et equipement divers", multiple: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static MISSION_PACK_UNIQUE_ITEMS = [
|
||||||
|
{
|
||||||
|
name: "L epee de Monsieur Noir",
|
||||||
|
type: "arme",
|
||||||
|
system: {
|
||||||
|
categorie: "melee",
|
||||||
|
caracteristique: "force",
|
||||||
|
degats: "1d6",
|
||||||
|
portee: "",
|
||||||
|
mains: 1,
|
||||||
|
equipee: false,
|
||||||
|
description: "<p>Elle n a rien de particulier mais confere un prestige important.</p><p>Avantage a tous les jets d interaction avec les employes de Donjon & Cie.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Fil a plomb d Arnezon",
|
||||||
|
type: "equipement",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
equipee: false,
|
||||||
|
emplacement: "",
|
||||||
|
description: "<p>Il oscille de maniere clairement etrange en presence d un passage secret.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Boussole de Drize Durban",
|
||||||
|
type: "equipement",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
equipee: false,
|
||||||
|
emplacement: "",
|
||||||
|
description: "<p>Trois reglages : pointe vers le client, le resident ou l employe le plus proche.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Boule de cristal de la supervision",
|
||||||
|
type: "equipement",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
equipee: false,
|
||||||
|
emplacement: "",
|
||||||
|
description: "<p>Permet de voir toute personne ou lieu qu on connait deja.</p><p>Jet de SAG pour la controler. La plupart des huiles sentent quand on les observe.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dent du dragon Leogradonardicus III",
|
||||||
|
type: "equipement",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
equipee: false,
|
||||||
|
emplacement: "",
|
||||||
|
description: "<p>Un jet de CHA reussi permet de controler les reptiles inintelligents et de charmer les humanoides reptiliens.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Doigt d Aarcarcerax",
|
||||||
|
type: "consommable",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
delta: 4,
|
||||||
|
effet: "Tue la creature vers qui on pointe le doigt.",
|
||||||
|
description: "<p>Tue la creature vers qui on pointe le doigt.</p><p>La liche sait instantanement qu on a retrouve son doigt.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cape de Vlad von Drakovitch",
|
||||||
|
type: "consommable",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
delta: 6,
|
||||||
|
effet: "Permet de se transformer en 1-3 rats, 4-5 chauve-souris, 6 forme gazeuse.",
|
||||||
|
description: "<p>Delta 6 charges.</p><p>Permet de se transformer en 1-3 rats, 4-5 chauve-souris, 6 forme gazeuse.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vieux carnet de notes d Affalella",
|
||||||
|
type: "consommable",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
delta: 6,
|
||||||
|
effet: "Fonctionne comme des faveurs de la Mercatique utilisables uniquement dans les aires client.",
|
||||||
|
description: "<p>Vieux carnet de notes d Affalella, directrice de la Mercatique.</p><p>Fonctionne comme Delta 6 faveurs de la Mercatique utilisables uniquement dans les aires client.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ancienne cle universelle de Paiji",
|
||||||
|
type: "consommable",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
delta: 8,
|
||||||
|
effet: "Ouvre tous les coffres, portes, armoires et autres serrures du Donjon.",
|
||||||
|
description: "<p>Ancienne cle universelle de Paiji, directeur de la Maintenance.</p><p>Delta 8 usages. Ouvre tous les coffres, portes, armoires et autres serrures du Donjon.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Baguette d urgence",
|
||||||
|
type: "equipement",
|
||||||
|
system: {
|
||||||
|
quantite: 1,
|
||||||
|
equipee: false,
|
||||||
|
emplacement: "",
|
||||||
|
description: "<p>Teleporte l utilisateur au palier des huiles lorsque la baguette est brisee.</p>",
|
||||||
|
notes: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
static registerSocketListeners() {
|
||||||
|
if (this.#socketRegistered || !game.socket) return;
|
||||||
|
|
||||||
|
game.socket.on(`system.${game.system.id}`, (payload) => {
|
||||||
|
void this.#handleSocketMessage(payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#socketRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
static #normalizeName(value) {
|
static #normalizeName(value) {
|
||||||
return String(value ?? "")
|
return String(value ?? "")
|
||||||
.normalize("NFD")
|
.normalize("NFD")
|
||||||
@@ -34,6 +172,50 @@ export class DonjonEtCieMacros {
|
|||||||
return game.i18n.localize(`DNC.Macro.MissionPack.${key}`);
|
return game.i18n.localize(`DNC.Macro.MissionPack.${key}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static #getModeLabel(mode) {
|
||||||
|
if (mode === "avantage") return game.i18n.localize("DNC.UI.ModeAdvantage");
|
||||||
|
if (mode === "desavantage") return game.i18n.localize("DNC.UI.ModeDisadvantage");
|
||||||
|
return game.i18n.localize("DNC.UI.ModeNormal");
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getMissionPackMode() {
|
||||||
|
return String(game.settings.get("fvtt-donjon-et-cie", this.MISSION_PACK_MODE_SETTING) ?? "classic");
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getMissionPackModeLabel(mode) {
|
||||||
|
return game.i18n.localize(mode === "campaign"
|
||||||
|
? "DNC.Macro.MissionPack.ModeCampaign"
|
||||||
|
: "DNC.Macro.MissionPack.ModeClassic");
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getMissionPackModeDescription(mode) {
|
||||||
|
return game.i18n.localize(mode === "campaign"
|
||||||
|
? "DNC.Macro.MissionPack.ModeCampaignHint"
|
||||||
|
: "DNC.Macro.MissionPack.ModeClassicHint");
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getStewardRelationLabel(relation) {
|
||||||
|
return game.i18n.localize(`DNC.Macro.MissionPack.Relation.${relation ?? "neutral"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getStewardRelationOptions() {
|
||||||
|
return ["positive", "neutral", "negative"].map((relation) => ({
|
||||||
|
value: relation,
|
||||||
|
label: this.#getStewardRelationLabel(relation)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getCampaignDiceOptions() {
|
||||||
|
return this.MISSION_PACK_CAMPAIGN_DICE.map((sides) => ({
|
||||||
|
value: String(sides),
|
||||||
|
label: `1d${sides}`
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getDefaultCampaignAssignments() {
|
||||||
|
return Object.fromEntries(this.MISSION_PACK_TABLES.map((spec, index) => [spec.key, String(this.MISSION_PACK_CAMPAIGN_DICE[index])]));
|
||||||
|
}
|
||||||
|
|
||||||
static #getDefaultMissionPackActorId(actors) {
|
static #getDefaultMissionPackActorId(actors) {
|
||||||
const controlledActor = canvas?.tokens?.controlled?.[0]?.actor ?? null;
|
const controlledActor = canvas?.tokens?.controlled?.[0]?.actor ?? null;
|
||||||
if (controlledActor?.type === "employe") return controlledActor.id;
|
if (controlledActor?.type === "employe") return controlledActor.id;
|
||||||
@@ -50,6 +232,16 @@ export class DonjonEtCieMacros {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static #getMissionPackOwnerUsers(actor) {
|
||||||
|
return game.users
|
||||||
|
.filter((user) => !user.isGM && actor.testUserPermission(user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER))
|
||||||
|
.sort((a, b) => Number(b.active) - Number(a.active) || a.name.localeCompare(b.name, "fr", { sensitivity: "base" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getCampaignTargetUser(actor) {
|
||||||
|
return this.#getMissionPackOwnerUsers(actor).find((user) => user.active) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
static async #resolveMissionPackActor(target = null) {
|
static async #resolveMissionPackActor(target = null) {
|
||||||
if (target?.documentName === "Actor") return target;
|
if (target?.documentName === "Actor") return target;
|
||||||
if (target?.actor?.documentName === "Actor") return target.actor;
|
if (target?.actor?.documentName === "Actor") return target.actor;
|
||||||
@@ -121,6 +313,127 @@ export class DonjonEtCieMacros {
|
|||||||
.filter((entry) => !/^dotation\s+\d+$/i.test(entry));
|
.filter((entry) => !/^dotation\s+\d+$/i.test(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #evaluateRoll(formula) {
|
||||||
|
const roll = await (new Roll(formula)).evaluate();
|
||||||
|
const values = roll.dice
|
||||||
|
.flatMap((die) => die.results.map((result) => Number(result.result ?? result.count ?? 0)))
|
||||||
|
.filter((value) => Number.isFinite(value));
|
||||||
|
|
||||||
|
return {
|
||||||
|
roll,
|
||||||
|
total: Number(roll.total ?? 0),
|
||||||
|
values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static #formatRollValues(values, kept) {
|
||||||
|
if (!values.length) return String(kept ?? 0);
|
||||||
|
if (values.length === 1) return String(values[0]);
|
||||||
|
return `${values.join(" / ")} -> ${kept}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #rollPackDie(sides, { mode = "normal" } = {}) {
|
||||||
|
const formula = mode === "avantage"
|
||||||
|
? `2d${sides}kh`
|
||||||
|
: mode === "desavantage"
|
||||||
|
? `2d${sides}kl`
|
||||||
|
: `1d${sides}`;
|
||||||
|
const evaluated = await this.#evaluateRoll(formula);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sides: Number(sides),
|
||||||
|
dieLabel: `1d${sides}`,
|
||||||
|
roll: evaluated.roll,
|
||||||
|
values: evaluated.values,
|
||||||
|
valuesLabel: this.#formatRollValues(evaluated.values, evaluated.total),
|
||||||
|
kept: evaluated.total,
|
||||||
|
mode,
|
||||||
|
modeLabel: this.#getModeLabel(mode)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static #createInlineItemData(spec) {
|
||||||
|
return {
|
||||||
|
name: spec.name,
|
||||||
|
type: spec.type,
|
||||||
|
img: DonjonEtCieUtility.getDefaultItemIcon(spec.type),
|
||||||
|
system: foundry.utils.deepClone(spec.system)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getTableResults(table) {
|
||||||
|
return table?.results?.contents ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static #getTableRange(result) {
|
||||||
|
const rawRange = Array.isArray(result?.range)
|
||||||
|
? result.range
|
||||||
|
: result?._source?.range ?? [];
|
||||||
|
return [Number(rawRange[0] ?? 0), Number(rawRange[1] ?? 0)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static #findTableResultByTotal(table, total) {
|
||||||
|
const entries = this.#getTableResults(table)
|
||||||
|
.map((result) => ({
|
||||||
|
result,
|
||||||
|
range: this.#getTableRange(result)
|
||||||
|
}))
|
||||||
|
.filter(({ range }) => Number.isFinite(range[0]) && Number.isFinite(range[1]) && range[1] >= range[0])
|
||||||
|
.sort((a, b) => a.range[0] - b.range[0]);
|
||||||
|
|
||||||
|
if (!entries.length) {
|
||||||
|
return {
|
||||||
|
result: null,
|
||||||
|
clampedTotal: total,
|
||||||
|
range: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimum = entries[0].range[0];
|
||||||
|
const maximum = entries.at(-1).range[1];
|
||||||
|
const clampedTotal = Math.max(minimum, Math.min(maximum, total));
|
||||||
|
const match = entries.find(({ range }) => clampedTotal >= range[0] && clampedTotal <= range[1]) ?? entries.at(-1);
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: match.result,
|
||||||
|
clampedTotal,
|
||||||
|
range: match.range
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #resolveUniqueMissionPackEntry({ comparisonRoll = null } = {}) {
|
||||||
|
const reference = await this.#rollPackDie(20);
|
||||||
|
const actorEvaluation = comparisonRoll == null ? await this.#rollPackDie(20) : null;
|
||||||
|
const actorRoll = Number(comparisonRoll ?? actorEvaluation?.kept ?? 0);
|
||||||
|
|
||||||
|
if (reference.kept !== actorRoll) {
|
||||||
|
return {
|
||||||
|
matched: false,
|
||||||
|
referenceRoll: reference.kept,
|
||||||
|
actorRoll,
|
||||||
|
uniqueRoll: null,
|
||||||
|
itemName: "",
|
||||||
|
itemData: null,
|
||||||
|
rolls: [reference.roll, ...(actorEvaluation ? [actorEvaluation.roll] : [])]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueRoll = await this.#rollPackDie(this.MISSION_PACK_UNIQUE_ITEMS.length);
|
||||||
|
const spec = this.MISSION_PACK_UNIQUE_ITEMS[Math.max(0, uniqueRoll.kept - 1)] ?? this.MISSION_PACK_UNIQUE_ITEMS[0];
|
||||||
|
const document = await this.#findItemByName(spec.name);
|
||||||
|
const itemData = document ? this.#toEmbeddedItemData(document) : this.#createInlineItemData(spec);
|
||||||
|
|
||||||
|
return {
|
||||||
|
matched: true,
|
||||||
|
referenceRoll: reference.kept,
|
||||||
|
actorRoll,
|
||||||
|
uniqueRoll: uniqueRoll.kept,
|
||||||
|
itemName: document?.name ?? spec.name,
|
||||||
|
itemData,
|
||||||
|
rolls: [reference.roll, ...(actorEvaluation ? [actorEvaluation.roll] : []), uniqueRoll.roll]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static async #resolveTableResultEntries(result, { multiple = false } = {}) {
|
static async #resolveTableResultEntries(result, { multiple = false } = {}) {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return {
|
return {
|
||||||
@@ -129,19 +442,27 @@ export class DonjonEtCieMacros {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.type === "document" && result.documentCollection && result.documentId) {
|
const source = result._source ?? {};
|
||||||
const uuid = result.documentCollection.includes(".")
|
const resultName = String(result.name ?? source.name ?? "").trim();
|
||||||
? `Compendium.${result.documentCollection}.Item.${result.documentId}`
|
const resultDescription = String(result.description ?? source.description ?? "").trim();
|
||||||
: `Item.${result.documentId}`;
|
const resultText = String(source.text ?? "").trim();
|
||||||
const document = await fromUuid(uuid);
|
const resultLabel = resultName || resultDescription || resultText;
|
||||||
const label = document?.name ?? result.text ?? "";
|
|
||||||
|
const documentUuid = result.type === "document"
|
||||||
|
? result.documentUuid ?? source.documentUuid ?? null
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (documentUuid) {
|
||||||
|
const document = await fromUuid(documentUuid);
|
||||||
|
const label = document?.name ?? resultLabel;
|
||||||
return {
|
return {
|
||||||
display: label,
|
display: label,
|
||||||
entries: label ? [{ name: label, document }] : []
|
entries: label ? [{ name: label, document }] : []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuidTargets = this.#extractUuidTargets(result.text);
|
const sourceText = resultDescription || resultText || resultLabel;
|
||||||
|
const uuidTargets = this.#extractUuidTargets(sourceText);
|
||||||
if (uuidTargets.length) {
|
if (uuidTargets.length) {
|
||||||
const entries = [];
|
const entries = [];
|
||||||
for (const target of uuidTargets) {
|
for (const target of uuidTargets) {
|
||||||
@@ -158,8 +479,8 @@ export class DonjonEtCieMacros {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const names = multiple
|
const names = multiple
|
||||||
? this.#extractPlainTextEntries(result.text)
|
? this.#extractPlainTextEntries(sourceText || resultLabel)
|
||||||
: [result.text].map((entry) => String(entry ?? "").trim()).filter(Boolean);
|
: [resultLabel].filter(Boolean);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
display: names.join(", "),
|
display: names.join(", "),
|
||||||
@@ -175,6 +496,353 @@ export class DonjonEtCieMacros {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #materializeMissionPackEntries(entries) {
|
||||||
|
const embeddedItems = [];
|
||||||
|
const addedNames = [];
|
||||||
|
const missingNames = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const item = entry.document ?? await this.#findItemByName(entry.name);
|
||||||
|
if (!item) {
|
||||||
|
missingNames.push(entry.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
embeddedItems.push(this.#toEmbeddedItemData(item));
|
||||||
|
addedNames.push(item.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
embeddedItems,
|
||||||
|
addedNames,
|
||||||
|
missingNames
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #openCampaignAllocationDialog(actor, { playerName = "", requesterName = "" } = {}) {
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-campaign-dialog.hbs",
|
||||||
|
{
|
||||||
|
actorName: actor.name,
|
||||||
|
playerName,
|
||||||
|
requesterName,
|
||||||
|
rank: Number(actor.system.anciennete?.rang ?? actor.system.sante?.dv ?? 0),
|
||||||
|
assignments: this.MISSION_PACK_TABLES.map((spec) => ({
|
||||||
|
key: spec.key,
|
||||||
|
label: this.#getMissionPackLabel(spec.key),
|
||||||
|
fieldName: `${spec.key}Die`,
|
||||||
|
selectedDie: this.#getDefaultCampaignAssignments()[spec.key]
|
||||||
|
})),
|
||||||
|
diceOptions: this.#getCampaignDiceOptions(),
|
||||||
|
relationOptions: this.#getStewardRelationOptions(),
|
||||||
|
selectedRelation: "neutral"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return foundry.applications.api.DialogV2.wait({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize("DNC.Macro.MissionPack.CampaignDialogTitle"),
|
||||||
|
icon: "fa-solid fa-dice"
|
||||||
|
},
|
||||||
|
classes: ["dnc-roll-dialog", "dnc-mission-pack-dialog"],
|
||||||
|
content,
|
||||||
|
modal: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
action: "confirm",
|
||||||
|
label: game.i18n.localize("DNC.Macro.MissionPack.CampaignDialogAction"),
|
||||||
|
icon: "fa-solid fa-check",
|
||||||
|
default: true,
|
||||||
|
callback: async (event, button) => {
|
||||||
|
const assignments = Object.fromEntries(this.MISSION_PACK_TABLES.map((spec) => [
|
||||||
|
spec.key,
|
||||||
|
Number(button.form.elements[`${spec.key}Die`]?.value ?? 0)
|
||||||
|
]));
|
||||||
|
const selectedDice = Object.values(assignments).filter((value) => value > 0);
|
||||||
|
|
||||||
|
if (selectedDice.length !== this.MISSION_PACK_TABLES.length) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnDiceRequired"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new Set(selectedDice).size !== selectedDice.length) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnDiceUnique"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
assignments,
|
||||||
|
stewardRelation: button.form.elements.stewardRelation?.value ?? "neutral"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
rejectClose: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #handleSocketMessage(payload) {
|
||||||
|
if (payload?.scope !== this.MISSION_PACK_SOCKET_SCOPE) return;
|
||||||
|
|
||||||
|
if (payload.type === "campaign-response") {
|
||||||
|
const pending = this.#campaignRequests.get(payload.requestId);
|
||||||
|
if (!pending) return;
|
||||||
|
clearTimeout(pending.timeoutId);
|
||||||
|
this.#campaignRequests.delete(payload.requestId);
|
||||||
|
pending.resolve(payload.result ?? null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type !== "campaign-request" || payload.targetUserId !== game.user.id) return;
|
||||||
|
|
||||||
|
const actor = await fromUuid(payload.actorUuid);
|
||||||
|
const allocation = actor
|
||||||
|
? await this.#openCampaignAllocationDialog(actor, {
|
||||||
|
playerName: game.user.name,
|
||||||
|
requesterName: payload.requesterName ?? ""
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
game.socket.emit(`system.${game.system.id}`, {
|
||||||
|
scope: this.MISSION_PACK_SOCKET_SCOPE,
|
||||||
|
type: "campaign-response",
|
||||||
|
requestId: payload.requestId,
|
||||||
|
result: allocation
|
||||||
|
? {
|
||||||
|
allocation,
|
||||||
|
responderUserId: game.user.id,
|
||||||
|
responderName: game.user.name
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
allocation: null,
|
||||||
|
responderUserId: game.user.id,
|
||||||
|
responderName: game.user.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #requestCampaignAllocation(actor, targetUser) {
|
||||||
|
if (!targetUser || targetUser.id === game.user.id) {
|
||||||
|
const allocation = await this.#openCampaignAllocationDialog(actor, {
|
||||||
|
playerName: game.user.name,
|
||||||
|
requesterName: game.user.name
|
||||||
|
});
|
||||||
|
return allocation ? { allocation, responderName: game.user.name } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = foundry.utils.randomID();
|
||||||
|
const responsePromise = new Promise((resolve) => {
|
||||||
|
const timeoutId = globalThis.setTimeout(() => {
|
||||||
|
this.#campaignRequests.delete(requestId);
|
||||||
|
resolve(null);
|
||||||
|
}, this.MISSION_PACK_REQUEST_TIMEOUT);
|
||||||
|
this.#campaignRequests.set(requestId, { resolve, timeoutId });
|
||||||
|
});
|
||||||
|
|
||||||
|
game.socket.emit(`system.${game.system.id}`, {
|
||||||
|
scope: this.MISSION_PACK_SOCKET_SCOPE,
|
||||||
|
type: "campaign-request",
|
||||||
|
requestId,
|
||||||
|
targetUserId: targetUser.id,
|
||||||
|
actorUuid: actor.uuid,
|
||||||
|
requesterName: game.user.name
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await responsePromise;
|
||||||
|
if (!response?.allocation) {
|
||||||
|
ui.notifications.warn(game.i18n.format("DNC.Macro.MissionPack.CampaignRequestCanceled", {
|
||||||
|
actor: actor.name,
|
||||||
|
player: targetUser.name
|
||||||
|
}));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
allocation: response.allocation,
|
||||||
|
responderName: response.responderName ?? targetUser.name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #finalizeMissionPack(actor, drawPlans, {
|
||||||
|
generationMode = "classic",
|
||||||
|
controllerName = "",
|
||||||
|
stewardRelation = "neutral",
|
||||||
|
uniqueComparisonRoll = null
|
||||||
|
} = {}) {
|
||||||
|
const draws = [];
|
||||||
|
const embeddedItems = [];
|
||||||
|
const rolls = [];
|
||||||
|
let missingCount = 0;
|
||||||
|
|
||||||
|
for (const plan of drawPlans) {
|
||||||
|
rolls.push(...(plan.rolls ?? []).filter((roll) => roll instanceof Roll));
|
||||||
|
|
||||||
|
if (plan.failed || !plan.resolved) {
|
||||||
|
draws.push({
|
||||||
|
label: this.#getMissionPackLabel(plan.spec.key),
|
||||||
|
display: game.i18n.format("DNC.Macro.MissionPack.TableMissing", { table: plan.spec.name }),
|
||||||
|
addedNames: [],
|
||||||
|
addedSummary: "",
|
||||||
|
missingNames: [],
|
||||||
|
missingSummary: "",
|
||||||
|
failed: true
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const materialized = await this.#materializeMissionPackEntries(plan.resolved.entries);
|
||||||
|
embeddedItems.push(...materialized.embeddedItems);
|
||||||
|
missingCount += materialized.missingNames.length;
|
||||||
|
|
||||||
|
draws.push({
|
||||||
|
label: this.#getMissionPackLabel(plan.spec.key),
|
||||||
|
display: plan.resolved.display || game.i18n.localize("DNC.Macro.MissionPack.NoResult"),
|
||||||
|
addedNames: materialized.addedNames,
|
||||||
|
addedSummary: materialized.addedNames.join(", "),
|
||||||
|
missingNames: materialized.missingNames,
|
||||||
|
missingSummary: materialized.missingNames.join(", "),
|
||||||
|
failed: false,
|
||||||
|
...plan.detail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueEntry = await this.#resolveUniqueMissionPackEntry({ comparisonRoll: uniqueComparisonRoll });
|
||||||
|
rolls.push(...(uniqueEntry.rolls ?? []).filter((roll) => roll instanceof Roll));
|
||||||
|
if (uniqueEntry.itemData) {
|
||||||
|
embeddedItems.push(uniqueEntry.itemData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdItems = embeddedItems.length
|
||||||
|
? await actor.createEmbeddedDocuments("Item", embeddedItems, { renderSheet: false })
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-donjon-et-cie/templates/chat/mission-pack-card.hbs",
|
||||||
|
{
|
||||||
|
title: game.i18n.localize("DNC.Macro.MissionPack.Title"),
|
||||||
|
actorName: actor.name,
|
||||||
|
createdCount: createdItems.length,
|
||||||
|
missingCount,
|
||||||
|
draws,
|
||||||
|
uniqueEntry,
|
||||||
|
isCampaign: generationMode === "campaign",
|
||||||
|
generationModeLabel: this.#getMissionPackModeLabel(generationMode),
|
||||||
|
controllerName,
|
||||||
|
stewardRelationLabel: generationMode === "campaign" ? this.#getStewardRelationLabel(stewardRelation) : ""
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await ChatMessage.create({
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor }),
|
||||||
|
user: game.user.id,
|
||||||
|
content,
|
||||||
|
rolls
|
||||||
|
});
|
||||||
|
|
||||||
|
if (createdItems.length && !missingCount) {
|
||||||
|
ui.notifications.info(game.i18n.format("DNC.Macro.MissionPack.Success", {
|
||||||
|
actor: actor.name,
|
||||||
|
count: createdItems.length
|
||||||
|
}));
|
||||||
|
} else if (createdItems.length) {
|
||||||
|
ui.notifications.warn(game.i18n.format("DNC.Macro.MissionPack.Partial", {
|
||||||
|
actor: actor.name,
|
||||||
|
count: createdItems.length,
|
||||||
|
missing: missingCount
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnNothingAdded"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
actor,
|
||||||
|
createdItems,
|
||||||
|
missingCount,
|
||||||
|
draws,
|
||||||
|
uniqueEntry,
|
||||||
|
generationMode,
|
||||||
|
controllerName,
|
||||||
|
stewardRelation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #grantClassicMissionPack(actor) {
|
||||||
|
const drawPlans = [];
|
||||||
|
|
||||||
|
for (const spec of this.MISSION_PACK_TABLES) {
|
||||||
|
const table = await this.#findRollTableByName(spec.name);
|
||||||
|
if (!table) {
|
||||||
|
drawPlans.push({ spec, failed: true, resolved: null, rolls: [] });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const draw = await table.draw({ displayChat: false });
|
||||||
|
const result = draw.results?.[0] ?? null;
|
||||||
|
drawPlans.push({
|
||||||
|
spec,
|
||||||
|
failed: false,
|
||||||
|
resolved: await this.#resolveTableResultEntries(result, { multiple: spec.multiple }),
|
||||||
|
detail: null,
|
||||||
|
rolls: draw.roll instanceof Roll ? [draw.roll] : []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#finalizeMissionPack(actor, drawPlans, {
|
||||||
|
generationMode: "classic"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #grantCampaignMissionPack(actor) {
|
||||||
|
const targetUser = this.#getCampaignTargetUser(actor);
|
||||||
|
const allocationResult = await this.#requestCampaignAllocation(actor, targetUser);
|
||||||
|
if (!allocationResult?.allocation) return null;
|
||||||
|
|
||||||
|
const rankBonus = Number(actor.system.anciennete?.rang ?? actor.system.sante?.dv ?? 0);
|
||||||
|
const stewardRelation = allocationResult.allocation.stewardRelation ?? "neutral";
|
||||||
|
const mode = this.MISSION_PACK_STEWARD_MODES[stewardRelation] ?? "normal";
|
||||||
|
const drawPlans = [];
|
||||||
|
let uniqueComparisonRoll = null;
|
||||||
|
|
||||||
|
for (const spec of this.MISSION_PACK_TABLES) {
|
||||||
|
const table = await this.#findRollTableByName(spec.name);
|
||||||
|
if (!table) {
|
||||||
|
drawPlans.push({ spec, failed: true, resolved: null, rolls: [] });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sides = Number(allocationResult.allocation.assignments?.[spec.key] ?? 0);
|
||||||
|
const rollData = await this.#rollPackDie(sides, { mode });
|
||||||
|
if (sides === 20) uniqueComparisonRoll = rollData.kept;
|
||||||
|
|
||||||
|
const total = rollData.kept + rankBonus;
|
||||||
|
const tableResult = this.#findTableResultByTotal(table, total);
|
||||||
|
|
||||||
|
drawPlans.push({
|
||||||
|
spec,
|
||||||
|
failed: false,
|
||||||
|
resolved: await this.#resolveTableResultEntries(tableResult.result, { multiple: spec.multiple }),
|
||||||
|
detail: {
|
||||||
|
dieLabel: rollData.dieLabel,
|
||||||
|
modeLabel: rollData.modeLabel,
|
||||||
|
rollValuesLabel: rollData.valuesLabel,
|
||||||
|
kept: rollData.kept,
|
||||||
|
rankBonus,
|
||||||
|
total,
|
||||||
|
resolvedTotal: tableResult.clampedTotal,
|
||||||
|
clamped: tableResult.clampedTotal !== total
|
||||||
|
},
|
||||||
|
rolls: [rollData.roll]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#finalizeMissionPack(actor, drawPlans, {
|
||||||
|
generationMode: "campaign",
|
||||||
|
controllerName: allocationResult.responderName ?? targetUser?.name ?? game.user.name,
|
||||||
|
stewardRelation,
|
||||||
|
uniqueComparisonRoll
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the GM-only mission pack dialog.
|
* Open the GM-only mission pack dialog.
|
||||||
* @returns {Promise<object|null>}
|
* @returns {Promise<object|null>}
|
||||||
@@ -191,12 +859,16 @@ export class DonjonEtCieMacros {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mode = this.#getMissionPackMode();
|
||||||
const selectedActorId = this.#getDefaultMissionPackActorId(actorOptions.map((option) => game.actors.get(option.value)).filter(Boolean));
|
const selectedActorId = this.#getDefaultMissionPackActorId(actorOptions.map((option) => game.actors.get(option.value)).filter(Boolean));
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-dialog.hbs",
|
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-dialog.hbs",
|
||||||
{
|
{
|
||||||
actorOptions,
|
actorOptions,
|
||||||
selectedActorId
|
selectedActorId,
|
||||||
|
modeLabel: this.#getMissionPackModeLabel(mode),
|
||||||
|
modeDescription: this.#getMissionPackModeDescription(mode),
|
||||||
|
isCampaign: mode === "campaign"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -246,95 +918,8 @@ export class DonjonEtCieMacros {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const draws = [];
|
return this.#getMissionPackMode() === "campaign"
|
||||||
const embeddedItems = [];
|
? this.#grantCampaignMissionPack(actor)
|
||||||
let missingCount = 0;
|
: this.#grantClassicMissionPack(actor);
|
||||||
|
|
||||||
for (const spec of this.MISSION_PACK_TABLES) {
|
|
||||||
const table = await this.#findRollTableByName(spec.name);
|
|
||||||
if (!table) {
|
|
||||||
draws.push({
|
|
||||||
label: this.#getMissionPackLabel(spec.key),
|
|
||||||
display: game.i18n.format("DNC.Macro.MissionPack.TableMissing", { table: spec.name }),
|
|
||||||
addedNames: [],
|
|
||||||
addedSummary: "",
|
|
||||||
missingNames: [],
|
|
||||||
missingSummary: "",
|
|
||||||
failed: true
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const draw = await table.draw({ displayChat: false });
|
|
||||||
const result = draw.results?.[0] ?? null;
|
|
||||||
const resolved = await this.#resolveTableResultEntries(result, { multiple: spec.multiple });
|
|
||||||
const addedNames = [];
|
|
||||||
const missingNames = [];
|
|
||||||
|
|
||||||
for (const entry of resolved.entries) {
|
|
||||||
const item = entry.document ?? await this.#findItemByName(entry.name);
|
|
||||||
if (!item) {
|
|
||||||
missingNames.push(entry.name);
|
|
||||||
missingCount += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
embeddedItems.push(this.#toEmbeddedItemData(item));
|
|
||||||
addedNames.push(item.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
draws.push({
|
|
||||||
label: this.#getMissionPackLabel(spec.key),
|
|
||||||
display: resolved.display || game.i18n.localize("DNC.Macro.MissionPack.NoResult"),
|
|
||||||
addedNames,
|
|
||||||
addedSummary: addedNames.join(", "),
|
|
||||||
missingNames,
|
|
||||||
missingSummary: missingNames.join(", "),
|
|
||||||
failed: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdItems = embeddedItems.length
|
|
||||||
? await actor.createEmbeddedDocuments("Item", embeddedItems, { renderSheet: false })
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
"systems/fvtt-donjon-et-cie/templates/chat/mission-pack-card.hbs",
|
|
||||||
{
|
|
||||||
title: game.i18n.localize("DNC.Macro.MissionPack.Title"),
|
|
||||||
actorName: actor.name,
|
|
||||||
createdCount: createdItems.length,
|
|
||||||
missingCount,
|
|
||||||
draws
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await ChatMessage.create({
|
|
||||||
speaker: ChatMessage.getSpeaker({ actor }),
|
|
||||||
user: game.user.id,
|
|
||||||
content
|
|
||||||
});
|
|
||||||
|
|
||||||
if (createdItems.length && !missingCount) {
|
|
||||||
ui.notifications.info(game.i18n.format("DNC.Macro.MissionPack.Success", {
|
|
||||||
actor: actor.name,
|
|
||||||
count: createdItems.length
|
|
||||||
}));
|
|
||||||
} else if (createdItems.length) {
|
|
||||||
ui.notifications.warn(game.i18n.format("DNC.Macro.MissionPack.Partial", {
|
|
||||||
actor: actor.name,
|
|
||||||
count: createdItems.length,
|
|
||||||
missing: missingCount
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnNothingAdded"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
actor,
|
|
||||||
createdItems,
|
|
||||||
missingCount,
|
|
||||||
draws
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ function injectActorDirectoryMissionPackButton(app, element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onChatActionClick(event) {
|
function onChatActionClick(event) {
|
||||||
const button = event.target.closest("[data-action='rollChatDamage'], [data-action='rollSpellChaos'], [data-action='applyDamage']");
|
const button = event.target.closest("[data-action='rollChatDamage'], [data-action='rollSpellChaos'], [data-action='applyDamage'], [data-action='rollAmmoUsage'], [data-action='rollDamageUsage']");
|
||||||
if (!(button instanceof HTMLElement)) return;
|
if (!(button instanceof HTMLElement)) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -85,6 +85,12 @@ function onChatActionClick(event) {
|
|||||||
const itemUuid = button.dataset.itemUuid;
|
const itemUuid = button.dataset.itemUuid;
|
||||||
if (!itemUuid) return;
|
if (!itemUuid) return;
|
||||||
const item = await fromUuid(itemUuid);
|
const item = await fromUuid(itemUuid);
|
||||||
|
if (button.dataset.action === "rollAmmoUsage") {
|
||||||
|
return item?.rollAmmoUsage?.();
|
||||||
|
}
|
||||||
|
if (button.dataset.action === "rollDamageUsage") {
|
||||||
|
return item?.rollDamageUsage?.();
|
||||||
|
}
|
||||||
return item?.rollDamage?.();
|
return item?.rollDamage?.();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@@ -98,6 +104,19 @@ function registerSystemSettings() {
|
|||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register("fvtt-donjon-et-cie", DonjonEtCieMacros.MISSION_PACK_MODE_SETTING, {
|
||||||
|
name: "Mode de paquetage de mission",
|
||||||
|
hint: "Choisit entre le tirage classique rapide et la regle optionnelle de campagne avec allocation des des par les joueurs.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: "classic",
|
||||||
|
choices: {
|
||||||
|
classic: "Classique",
|
||||||
|
campaign: "Campagne"
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getHelpJournalLink() {
|
async function getHelpJournalLink() {
|
||||||
@@ -215,6 +234,7 @@ Hooks.once("init", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Hooks.once("ready", () => {
|
Hooks.once("ready", () => {
|
||||||
|
DonjonEtCieMacros.registerSocketListeners();
|
||||||
document.addEventListener("click", onChatActionClick);
|
document.addEventListener("click", onChatActionClick);
|
||||||
void maybeCreateWelcomeMessage();
|
void maybeCreateWelcomeMessage();
|
||||||
});
|
});
|
||||||
|
|||||||
+124
-21
@@ -14,12 +14,14 @@ import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
|||||||
import { DONJON_ET_CIE } from "./donjon-et-cie-config.mjs";
|
import { DONJON_ET_CIE } from "./donjon-et-cie-config.mjs";
|
||||||
|
|
||||||
export class DonjonEtCieRolls {
|
export class DonjonEtCieRolls {
|
||||||
static async #createChatCard(actor, template, context) {
|
static async #createChatCard(actor, template, context, { rolls = [] } = {}) {
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(template, context);
|
const content = await foundry.applications.handlebars.renderTemplate(template, context);
|
||||||
|
const validRolls = rolls.filter((roll) => roll instanceof Roll);
|
||||||
await ChatMessage.create({
|
await ChatMessage.create({
|
||||||
speaker: ChatMessage.getSpeaker({ actor }),
|
speaker: ChatMessage.getSpeaker({ actor }),
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
content
|
content,
|
||||||
|
rolls: validRolls
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ export class DonjonEtCieRolls {
|
|||||||
const kept = this.#selectKeptValue(values, mode, "low");
|
const kept = this.#selectKeptValue(values, mode, "low");
|
||||||
const success = kept <= target;
|
const success = kept <= target;
|
||||||
|
|
||||||
return { characteristic, characteristicKey, target, values, kept, success, mode, isNaturalOne: kept === 1, isNaturalTwenty: kept === 20 };
|
return { characteristic, characteristicKey, target, values, kept, success, mode, roll, isNaturalOne: kept === 1, isNaturalTwenty: kept === 20 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #resolveFavorBoost(actor, favorKey, mode = "normal") {
|
static async #resolveFavorBoost(actor, favorKey, mode = "normal") {
|
||||||
@@ -101,6 +103,7 @@ export class DonjonEtCieRolls {
|
|||||||
effectiveMode: this.#applyFavorMode(mode),
|
effectiveMode: this.#applyFavorMode(mode),
|
||||||
modeBefore: mode,
|
modeBefore: mode,
|
||||||
modeAfter: this.#applyFavorMode(mode),
|
modeAfter: this.#applyFavorMode(mode),
|
||||||
|
rolls: resolved.rolls,
|
||||||
note: degraded
|
note: degraded
|
||||||
? "Le coup de pouce reste anonyme : un collegue du departement a donne l'info utile."
|
? "Le coup de pouce reste anonyme : un collegue du departement a donne l'info utile."
|
||||||
: "Le coup de pouce tient bon : nommez le collegue, ses trois traits et la relation pour le trombinoscope."
|
: "Le coup de pouce tient bon : nommez le collegue, ses trois traits et la relation pour le trombinoscope."
|
||||||
@@ -129,7 +132,7 @@ export class DonjonEtCieRolls {
|
|||||||
after: DonjonEtCieUtility.formatUsageDie(after),
|
after: DonjonEtCieUtility.formatUsageDie(after),
|
||||||
autoSpent: true,
|
autoSpent: true,
|
||||||
note: "La faveur est brulee pour obtenir directement l'aide souhaitee, a la discretion du MJ."
|
note: "La faveur est brulee pour obtenir directement l'aide souhaitee, a la discretion du MJ."
|
||||||
});
|
}, { rolls: [] });
|
||||||
|
|
||||||
return { key: favorKey, label, before, after };
|
return { key: favorKey, label, before, after };
|
||||||
}
|
}
|
||||||
@@ -172,6 +175,7 @@ export class DonjonEtCieRolls {
|
|||||||
before: focusDelta,
|
before: focusDelta,
|
||||||
after,
|
after,
|
||||||
degraded,
|
degraded,
|
||||||
|
rolls: resolved.rolls,
|
||||||
values: resolved.values
|
values: resolved.values
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -208,7 +212,7 @@ export class DonjonEtCieRolls {
|
|||||||
{ label: game.i18n.localize("DNC.Chat.After"), value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
{ label: game.i18n.localize("DNC.Chat.After"), value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
||||||
] : [])
|
] : [])
|
||||||
]
|
]
|
||||||
});
|
}, { rolls: [...(favor?.rolls ?? []), result.roll] });
|
||||||
|
|
||||||
return { ...result, favor, mode: effectiveMode };
|
return { ...result, favor, mode: effectiveMode };
|
||||||
}
|
}
|
||||||
@@ -246,7 +250,7 @@ export class DonjonEtCieRolls {
|
|||||||
mode: result.mode,
|
mode: result.mode,
|
||||||
modeLabel: this.#getModeLabel(result.mode),
|
modeLabel: this.#getModeLabel(result.mode),
|
||||||
syncedCombat
|
syncedCombat
|
||||||
});
|
}, { rolls: result.rolls });
|
||||||
|
|
||||||
return { total: result.kept, die, dieValues, dex, bonus: sheetBonus, mode: result.mode, syncedCombat };
|
return { total: result.kept, die, dieValues, dex, bonus: sheetBonus, mode: result.mode, syncedCombat };
|
||||||
}
|
}
|
||||||
@@ -271,7 +275,7 @@ export class DonjonEtCieRolls {
|
|||||||
formula: roll.formula,
|
formula: roll.formula,
|
||||||
total: roll.total,
|
total: roll.total,
|
||||||
dieValues
|
dieValues
|
||||||
});
|
}, { rolls: [roll] });
|
||||||
|
|
||||||
return { formula: roll.formula, total: roll.total, dieValues };
|
return { formula: roll.formula, total: roll.total, dieValues };
|
||||||
}
|
}
|
||||||
@@ -304,12 +308,14 @@ export class DonjonEtCieRolls {
|
|||||||
favorLabel: favor?.label ?? null,
|
favorLabel: favor?.label ?? null,
|
||||||
favorNote: favor?.note ?? null,
|
favorNote: favor?.note ?? null,
|
||||||
showDamageButton: result.success && Boolean(item.system.degats),
|
showDamageButton: result.success && Boolean(item.system.degats),
|
||||||
|
showAmmoButton: Number(item.system.munitionsDelta ?? 0) > 0,
|
||||||
itemUuid: item.uuid,
|
itemUuid: item.uuid,
|
||||||
details: [
|
details: [
|
||||||
{ label: game.i18n.localize("DNC.UI.Weapon"), value: item.name },
|
{ label: game.i18n.localize("DNC.UI.Weapon"), value: item.name },
|
||||||
{ label: game.i18n.localize("DNC.UI.Characteristic"), value: characteristicLabel },
|
{ label: game.i18n.localize("DNC.UI.Characteristic"), value: characteristicLabel },
|
||||||
{ label: `Valeur de ${characteristicLabel}`, value: result.target },
|
{ label: `Valeur de ${characteristicLabel}`, value: result.target },
|
||||||
{ label: game.i18n.localize("DNC.UI.Damage"), value: item.system.degats || "—" },
|
{ label: game.i18n.localize("DNC.UI.Damage"), value: item.system.degats || "—" },
|
||||||
|
{ label: game.i18n.localize("DNC.UI.Ammunition"), value: Number(item.system.munitionsDelta ?? 0) > 0 ? DonjonEtCieUtility.formatUsageDie(item.system.munitionsDelta) : "—" },
|
||||||
{ label: game.i18n.localize("DNC.UI.Range"), value: item.system.portee || "—" },
|
{ label: game.i18n.localize("DNC.UI.Range"), value: item.system.portee || "—" },
|
||||||
...(favor ? [
|
...(favor ? [
|
||||||
{ label: game.i18n.localize("DNC.Chat.Favor"), value: favor.label },
|
{ label: game.i18n.localize("DNC.Chat.Favor"), value: favor.label },
|
||||||
@@ -318,16 +324,59 @@ export class DonjonEtCieRolls {
|
|||||||
{ label: game.i18n.localize("DNC.Chat.After"), value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
{ label: game.i18n.localize("DNC.Chat.After"), value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
||||||
] : [])
|
] : [])
|
||||||
]
|
]
|
||||||
});
|
}, { rolls: [...(favor?.rolls ?? []), result.roll] });
|
||||||
|
|
||||||
return { ...result, favor, mode: effectiveMode };
|
return { ...result, favor, mode: effectiveMode };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async rollWeaponAmmoUsage(item, { mode = "normal" } = {}) {
|
||||||
|
const before = Number(item?.system?.munitionsDelta ?? 0);
|
||||||
|
if (!before) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("DNC.Warn.NoAmmunitionAvailable"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = await this.#resolveFormulaRoll(`1d${before}`, {}, { mode, favorable: "high" });
|
||||||
|
const result = resolved.kept;
|
||||||
|
const degraded = result <= 3;
|
||||||
|
const after = degraded ? DonjonEtCieUtility.degradeUsageDie(before) : before;
|
||||||
|
|
||||||
|
if (after !== before) {
|
||||||
|
await item.update({ "system.munitionsDelta": after });
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#createChatCard(item.actor, "systems/fvtt-donjon-et-cie/templates/chat/usage-card.hbs", {
|
||||||
|
title: `${game.i18n.localize("DNC.Roll.Ammunition")} : ${item.name}`,
|
||||||
|
value: result,
|
||||||
|
values: resolved.values,
|
||||||
|
mode: resolved.mode,
|
||||||
|
modeLabel: this.#getModeLabel(resolved.mode),
|
||||||
|
before: DonjonEtCieUtility.formatUsageDie(before),
|
||||||
|
after: DonjonEtCieUtility.formatUsageDie(after),
|
||||||
|
protectionStored: null,
|
||||||
|
degraded,
|
||||||
|
exhausted: after === 0,
|
||||||
|
isAmmunition: true
|
||||||
|
}, { rolls: resolved.rolls });
|
||||||
|
|
||||||
|
return { result, values: resolved.values, mode: resolved.mode, before, after, degraded };
|
||||||
|
}
|
||||||
|
|
||||||
static async rollDamage(actor, item, { mode = "normal" } = {}) {
|
static async rollDamage(actor, item, { mode = "normal" } = {}) {
|
||||||
if (!item.system.degats) return null;
|
const isUsageDie = Boolean(item.system.degatsEstUsageDe);
|
||||||
|
const degatsDelta = Number(item.system.degatsDelta ?? 0);
|
||||||
|
|
||||||
|
if (isUsageDie && !degatsDelta) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("DNC.Warn.DamageExhausted"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isUsageDie && !item.system.degats) return null;
|
||||||
|
|
||||||
|
const damageContext = DonjonEtCieUtility.getMartialDamageContext(actor, item);
|
||||||
const actorBonus = Number(actor?.system?.combat?.degatsBonus ?? 0);
|
const actorBonus = Number(actor?.system?.combat?.degatsBonus ?? 0);
|
||||||
const totalBonus = actorBonus;
|
const totalBonus = actorBonus;
|
||||||
const formula = totalBonus ? `${item.system.degats} + ${totalBonus}` : item.system.degats;
|
const effectiveDamage = damageContext.effectiveFormula || (isUsageDie ? `1d${degatsDelta}` : item.system.degats);
|
||||||
|
const formula = totalBonus ? `${effectiveDamage} + ${totalBonus}` : effectiveDamage;
|
||||||
const result = await this.#resolveFormulaRoll(formula, {}, { mode, favorable: "high" });
|
const result = await this.#resolveFormulaRoll(formula, {}, { mode, favorable: "high" });
|
||||||
const targets = DonjonEtCieUtility.getSceneDamageTargets();
|
const targets = DonjonEtCieUtility.getSceneDamageTargets();
|
||||||
const rollDieLabels = result.rolls.map((roll) => {
|
const rollDieLabels = result.rolls.map((roll) => {
|
||||||
@@ -335,6 +384,7 @@ export class DonjonEtCieRolls {
|
|||||||
return dieValues.length ? dieValues.join(" + ") : String(roll.total ?? "—");
|
return dieValues.length ? dieValues.join(" + ") : String(roll.total ?? "—");
|
||||||
});
|
});
|
||||||
const keptDieLabel = rollDieLabels[result.keptIndex] ?? rollDieLabels[0] ?? String(result.kept);
|
const keptDieLabel = rollDieLabels[result.keptIndex] ?? rollDieLabels[0] ?? String(result.kept);
|
||||||
|
const baseDamageDisplay = isUsageDie ? DonjonEtCieUtility.formatUsageDie(degatsDelta) : item.system.degats;
|
||||||
|
|
||||||
await this.#createChatCard(actor ?? item.actor, "systems/fvtt-donjon-et-cie/templates/chat/damage-card.hbs", {
|
await this.#createChatCard(actor ?? item.actor, "systems/fvtt-donjon-et-cie/templates/chat/damage-card.hbs", {
|
||||||
title: `${game.i18n.localize("DNC.Roll.Damage")} : ${item.name}`,
|
title: `${game.i18n.localize("DNC.Roll.Damage")} : ${item.name}`,
|
||||||
@@ -347,13 +397,60 @@ export class DonjonEtCieRolls {
|
|||||||
values: result.values,
|
values: result.values,
|
||||||
total: result.kept,
|
total: result.kept,
|
||||||
bonus: totalBonus,
|
bonus: totalBonus,
|
||||||
baseDamage: item.system.degats,
|
baseDamage: baseDamageDisplay,
|
||||||
|
effectiveDamage,
|
||||||
|
damageCapped: damageContext.capped,
|
||||||
|
martialDvLabel: damageContext.martialDvSides ? `d${damageContext.martialDvSides}` : damageContext.martialDvFormula,
|
||||||
sourceLabel: item.name,
|
sourceLabel: item.name,
|
||||||
targets,
|
targets,
|
||||||
hasTargets: targets.length > 0
|
hasTargets: targets.length > 0,
|
||||||
});
|
showDamageUsageButton: isUsageDie && degatsDelta > 0,
|
||||||
|
itemUuid: item.uuid
|
||||||
|
}, { rolls: result.rolls });
|
||||||
|
|
||||||
return { total: result.kept, formula: result.formula, bonus: totalBonus, values: result.values, mode: result.mode };
|
return {
|
||||||
|
total: result.kept,
|
||||||
|
formula: result.formula,
|
||||||
|
baseDamage: baseDamageDisplay,
|
||||||
|
effectiveDamage,
|
||||||
|
damageCapped: damageContext.capped,
|
||||||
|
bonus: totalBonus,
|
||||||
|
values: result.values,
|
||||||
|
mode: result.mode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async rollWeaponDamageUsage(item, { mode = "normal" } = {}) {
|
||||||
|
const before = Number(item?.system?.degatsDelta ?? 0);
|
||||||
|
if (!before) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("DNC.Warn.DamageExhausted"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = await this.#resolveFormulaRoll(`1d${before}`, {}, { mode, favorable: "high" });
|
||||||
|
const result = resolved.kept;
|
||||||
|
const degraded = result <= 3;
|
||||||
|
const after = degraded ? DonjonEtCieUtility.degradeUsageDie(before) : before;
|
||||||
|
|
||||||
|
if (after !== before) {
|
||||||
|
await item.update({ "system.degatsDelta": after });
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#createChatCard(item.actor, "systems/fvtt-donjon-et-cie/templates/chat/usage-card.hbs", {
|
||||||
|
title: `${game.i18n.localize("DNC.Roll.DamageUsage")} : ${item.name}`,
|
||||||
|
value: result,
|
||||||
|
values: resolved.values,
|
||||||
|
mode: resolved.mode,
|
||||||
|
modeLabel: this.#getModeLabel(resolved.mode),
|
||||||
|
before: DonjonEtCieUtility.formatUsageDie(before),
|
||||||
|
after: DonjonEtCieUtility.formatUsageDie(after),
|
||||||
|
protectionStored: null,
|
||||||
|
degraded,
|
||||||
|
exhausted: after === 0,
|
||||||
|
isDamageUsage: true
|
||||||
|
}, { rolls: resolved.rolls });
|
||||||
|
|
||||||
|
return { result, values: resolved.values, mode: resolved.mode, before, after, degraded };
|
||||||
}
|
}
|
||||||
|
|
||||||
static async applyDamage(target, { damage = 0, useArmor = false, sourceLabel = "" } = {}) {
|
static async applyDamage(target, { damage = 0, useArmor = false, sourceLabel = "" } = {}) {
|
||||||
@@ -389,6 +486,7 @@ export class DonjonEtCieRolls {
|
|||||||
static async rollSpell(actor, item, { mode = "normal", favorKey = "" } = {}) {
|
static async rollSpell(actor, item, { mode = "normal", favorKey = "" } = {}) {
|
||||||
const characteristicKey = item.system.caracteristique || "intelligence";
|
const characteristicKey = item.system.caracteristique || "intelligence";
|
||||||
const focus = await this.#ensureFocus(actor);
|
const focus = await this.#ensureFocus(actor);
|
||||||
|
const currentSceneId = DonjonEtCieUtility.getCurrentSceneId();
|
||||||
const rank = Number(actor.system.anciennete?.rang ?? actor.system.sante?.dv ?? 0);
|
const rank = Number(actor.system.anciennete?.rang ?? actor.system.sante?.dv ?? 0);
|
||||||
const cost = Number(item.system.coutPv ?? 0);
|
const cost = Number(item.system.coutPv ?? 0);
|
||||||
const autoDisadvantage = cost > rank;
|
const autoDisadvantage = cost > rank;
|
||||||
@@ -399,8 +497,13 @@ export class DonjonEtCieRolls {
|
|||||||
|
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
|
|
||||||
const currentPv = Number(actor.system.sante?.pv?.value ?? 0);
|
const liveActor = game.actors.get(actor.id) ?? actor;
|
||||||
const availableMagicHp = currentPv + focus.activeValue;
|
const currentPv = Number(liveActor.system.sante?.pv?.value ?? 0);
|
||||||
|
const currentFocusSceneId = liveActor.system.magie?.focus?.sceneId ?? "";
|
||||||
|
const currentFocusValue = currentFocusSceneId === currentSceneId
|
||||||
|
? Number(liveActor.system.magie?.focus?.resultat ?? 0)
|
||||||
|
: 0;
|
||||||
|
const availableMagicHp = currentPv + currentFocusValue;
|
||||||
|
|
||||||
if (cost > availableMagicHp) {
|
if (cost > availableMagicHp) {
|
||||||
ui.notifications.warn(game.i18n.localize("DNC.Warn.SpellInsufficientResources"));
|
ui.notifications.warn(game.i18n.localize("DNC.Warn.SpellInsufficientResources"));
|
||||||
@@ -409,8 +512,8 @@ export class DonjonEtCieRolls {
|
|||||||
|
|
||||||
const characteristicShort = DONJON_ET_CIE.characteristics[characteristicKey]?.short ?? characteristicKey;
|
const characteristicShort = DONJON_ET_CIE.characteristics[characteristicKey]?.short ?? characteristicKey;
|
||||||
const success = result.isNaturalTwenty ? false : result.success;
|
const success = result.isNaturalTwenty ? false : result.success;
|
||||||
const focusSpent = result.isNaturalOne ? 0 : Math.min(cost, focus.activeValue);
|
const focusSpent = result.isNaturalOne ? 0 : Math.min(cost, currentFocusValue);
|
||||||
const focusRemaining = Math.max(focus.activeValue - focusSpent, 0);
|
const focusRemaining = Math.max(currentFocusValue - focusSpent, 0);
|
||||||
const spentPv = result.isNaturalOne ? 0 : Math.max(cost - focusSpent, 0);
|
const spentPv = result.isNaturalOne ? 0 : Math.max(cost - focusSpent, 0);
|
||||||
const remainingPv = Math.max(currentPv - spentPv, 0);
|
const remainingPv = Math.max(currentPv - spentPv, 0);
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
@@ -483,7 +586,7 @@ export class DonjonEtCieRolls {
|
|||||||
focusDegraded: focus.degraded,
|
focusDegraded: focus.degraded,
|
||||||
spentPv,
|
spentPv,
|
||||||
remainingPv
|
remainingPv
|
||||||
});
|
}, { rolls: [...(favor?.rolls ?? []), ...(focus.rolls ?? []), result.roll] });
|
||||||
|
|
||||||
return { ...result, success, spentPv, remainingPv, cost, focus, focusSpent, focusRemaining, favor, mode: effectiveMode };
|
return { ...result, success, spentPv, remainingPv, cost, focus, focusSpent, focusRemaining, favor, mode: effectiveMode };
|
||||||
}
|
}
|
||||||
@@ -514,7 +617,7 @@ export class DonjonEtCieRolls {
|
|||||||
degraded,
|
degraded,
|
||||||
exhausted: after < 4,
|
exhausted: after < 4,
|
||||||
itemName: item.name
|
itemName: item.name
|
||||||
});
|
}, { rolls: resolved.rolls });
|
||||||
|
|
||||||
return { result, before, after, degraded, chaosEntry };
|
return { result, before, after, degraded, chaosEntry };
|
||||||
}
|
}
|
||||||
@@ -552,7 +655,7 @@ export class DonjonEtCieRolls {
|
|||||||
protectionStored: item.type === "armure" ? result : null,
|
protectionStored: item.type === "armure" ? result : null,
|
||||||
degraded,
|
degraded,
|
||||||
exhausted: after === 0
|
exhausted: after === 0
|
||||||
});
|
}, { rolls: resolved.rolls });
|
||||||
|
|
||||||
return { result, values: resolved.values, mode: resolved.mode, before, after, degraded };
|
return { result, values: resolved.values, mode: resolved.mode, before, after, degraded };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export class DonjonEtCieUtility {
|
|||||||
"systems/fvtt-donjon-et-cie/templates/dialogs/spell-roll.hbs",
|
"systems/fvtt-donjon-et-cie/templates/dialogs/spell-roll.hbs",
|
||||||
"systems/fvtt-donjon-et-cie/templates/dialogs/usage-roll.hbs",
|
"systems/fvtt-donjon-et-cie/templates/dialogs/usage-roll.hbs",
|
||||||
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-dialog.hbs",
|
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-dialog.hbs",
|
||||||
|
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-campaign-dialog.hbs",
|
||||||
"systems/fvtt-donjon-et-cie/templates/chat/roll-card.hbs",
|
"systems/fvtt-donjon-et-cie/templates/chat/roll-card.hbs",
|
||||||
"systems/fvtt-donjon-et-cie/templates/chat/spell-card.hbs",
|
"systems/fvtt-donjon-et-cie/templates/chat/spell-card.hbs",
|
||||||
"systems/fvtt-donjon-et-cie/templates/chat/chaos-card.hbs",
|
"systems/fvtt-donjon-et-cie/templates/chat/chaos-card.hbs",
|
||||||
@@ -118,6 +119,87 @@ export class DonjonEtCieUtility {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static parseDieFormula(formula) {
|
||||||
|
const normalized = String(formula ?? "").trim();
|
||||||
|
if (!normalized) return null;
|
||||||
|
|
||||||
|
const dieMatch = normalized.match(/(?<count>\d*)d(?<sides>\d+)/i);
|
||||||
|
if (dieMatch?.groups?.sides) {
|
||||||
|
return {
|
||||||
|
formula: normalized,
|
||||||
|
count: Number(dieMatch.groups.count || 1),
|
||||||
|
countRaw: dieMatch.groups.count ?? "",
|
||||||
|
sides: Number(dieMatch.groups.sides),
|
||||||
|
match: dieMatch[0],
|
||||||
|
index: dieMatch.index ?? 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d+$/.test(normalized)) {
|
||||||
|
const sides = Number(normalized);
|
||||||
|
if ([4, 6, 8, 10, 12, 20].includes(sides)) {
|
||||||
|
return {
|
||||||
|
formula: normalized,
|
||||||
|
count: 1,
|
||||||
|
countRaw: "",
|
||||||
|
sides,
|
||||||
|
match: normalized,
|
||||||
|
index: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMartialDamageContext(actor, item) {
|
||||||
|
const isUsageDie = Boolean(item?.system?.degatsEstUsageDe);
|
||||||
|
const degatsDelta = Number(item?.system?.degatsDelta ?? 0);
|
||||||
|
const baseFormula = isUsageDie
|
||||||
|
? (degatsDelta > 0 ? `1d${degatsDelta}` : "")
|
||||||
|
: String(item?.system?.degats ?? "").trim();
|
||||||
|
|
||||||
|
const baseContext = {
|
||||||
|
baseFormula,
|
||||||
|
effectiveFormula: baseFormula,
|
||||||
|
capped: false,
|
||||||
|
martialDvFormula: String(actor?.system?.sante?.dv ?? "").trim(),
|
||||||
|
martialDvSides: 0,
|
||||||
|
weaponSides: 0,
|
||||||
|
isUsageDie
|
||||||
|
};
|
||||||
|
|
||||||
|
if (actor?.type !== "employe" || item?.type !== "arme" || !baseFormula) {
|
||||||
|
return baseContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const damageDie = this.parseDieFormula(baseFormula);
|
||||||
|
const martialDie = this.parseDieFormula(actor.system.sante?.dv);
|
||||||
|
if (!damageDie || !martialDie?.sides) return baseContext;
|
||||||
|
|
||||||
|
const cappedSides = Math.min(damageDie.sides, martialDie.sides);
|
||||||
|
if (cappedSides >= damageDie.sides) {
|
||||||
|
return {
|
||||||
|
...baseContext,
|
||||||
|
martialDvSides: martialDie.sides,
|
||||||
|
weaponSides: damageDie.sides
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const replacement = `${damageDie.countRaw || ""}d${cappedSides}`;
|
||||||
|
const effectiveFormula = damageDie.match === damageDie.formula
|
||||||
|
? replacement
|
||||||
|
: `${damageDie.formula.slice(0, damageDie.index)}${replacement}${damageDie.formula.slice(damageDie.index + damageDie.match.length)}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseContext,
|
||||||
|
effectiveFormula,
|
||||||
|
capped: true,
|
||||||
|
martialDvSides: martialDie.sides,
|
||||||
|
weaponSides: damageDie.sides
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static getFavorLabel(key) {
|
static getFavorLabel(key) {
|
||||||
return DONJON_ET_CIE.favorDepartments[key] ?? key;
|
return DONJON_ET_CIE.favorDepartments[key] ?? key;
|
||||||
}
|
}
|
||||||
@@ -172,12 +254,20 @@ export class DonjonEtCieUtility {
|
|||||||
const system = item.system;
|
const system = item.system;
|
||||||
const delta = Number(system.delta ?? 0);
|
const delta = Number(system.delta ?? 0);
|
||||||
const deltaMax = Number(system.deltaMax ?? delta ?? 0);
|
const deltaMax = Number(system.deltaMax ?? delta ?? 0);
|
||||||
|
const ammunitionDelta = Number(system.munitionsDelta ?? 0);
|
||||||
|
const isUsageDie = Boolean(system.degatsEstUsageDe);
|
||||||
|
const degatsDelta = Number(system.degatsDelta ?? 0);
|
||||||
const usageLabel = item.type === "entrainement" && deltaMax > 0
|
const usageLabel = item.type === "entrainement" && deltaMax > 0
|
||||||
? `${this.formatUsageDie(delta)} / ${this.formatUsageDie(deltaMax)}`
|
? `${this.formatUsageDie(delta)} / ${this.formatUsageDie(deltaMax)}`
|
||||||
: delta > 0
|
: delta > 0
|
||||||
? this.formatUsageDie(delta)
|
? this.formatUsageDie(delta)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const damageUsageLabel = isUsageDie
|
||||||
|
? (degatsDelta > 0 ? this.formatUsageDie(degatsDelta) : game.i18n.localize("DNC.UI.DamageExhausted"))
|
||||||
|
: null;
|
||||||
|
const damageLabel = isUsageDie ? damageUsageLabel : (system.degats || null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
@@ -186,11 +276,15 @@ export class DonjonEtCieUtility {
|
|||||||
system,
|
system,
|
||||||
uuid: item.uuid,
|
uuid: item.uuid,
|
||||||
usageLabel,
|
usageLabel,
|
||||||
|
ammunitionUsageLabel: item.type === "arme" && ammunitionDelta > 0 ? this.formatUsageDie(ammunitionDelta) : null,
|
||||||
protectionLabel: item.type === "armure" && Number(system.resultatProtection ?? 0) > 0 ? `Protection ${system.resultatProtection}` : null,
|
protectionLabel: item.type === "armure" && Number(system.resultatProtection ?? 0) > 0 ? `Protection ${system.resultatProtection}` : null,
|
||||||
weaponCharacteristicLabel: item.type === "arme" ? this.getWeaponCharacteristicLabel(system.categorie) : null,
|
weaponCharacteristicLabel: item.type === "arme" ? this.getWeaponCharacteristicLabel(system.categorie) : null,
|
||||||
canRoll: ["arme", "sortilege"].includes(item.type),
|
canRoll: ["arme", "sortilege"].includes(item.type),
|
||||||
canUse: delta > 0,
|
canUse: delta > 0,
|
||||||
canRollDamage: Boolean(system.degats),
|
hasTrackedAmmunition: item.type === "arme" && ammunitionDelta > 0,
|
||||||
|
damageUsageLabel,
|
||||||
|
damageLabel,
|
||||||
|
canRollDamage: item.type === "arme" && (isUsageDie ? degatsDelta > 0 : Boolean(system.degats)),
|
||||||
rollAction: item.type === "sortilege" ? "rollSpell" : "rollWeapon",
|
rollAction: item.type === "sortilege" ? "rollSpell" : "rollWeapon",
|
||||||
damageAction: "rollDamage",
|
damageAction: "rollDamage",
|
||||||
isEquipped: Boolean(system.equipee),
|
isEquipped: Boolean(system.equipee),
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export default class ArmeDataModel extends BaseItemDataModel {
|
|||||||
categorie: new fields.StringField({ initial: "melee" }),
|
categorie: new fields.StringField({ initial: "melee" }),
|
||||||
caracteristique: new fields.StringField({ initial: "force" }),
|
caracteristique: new fields.StringField({ initial: "force" }),
|
||||||
degats: new fields.StringField({ initial: "1d6" }),
|
degats: new fields.StringField({ initial: "1d6" }),
|
||||||
|
degatsEstUsageDe: new fields.BooleanField({ initial: false }),
|
||||||
|
degatsDelta: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
|
||||||
|
munitionsDelta: new fields.NumberField({ initial: 0, integer: true, min: 0 }),
|
||||||
portee: new fields.StringField({ initial: "" }),
|
portee: new fields.StringField({ initial: "" }),
|
||||||
mains: new fields.NumberField({ initial: 1, integer: true }),
|
mains: new fields.NumberField({ initial: 1, integer: true }),
|
||||||
equipee: new fields.BooleanField({ initial: false })
|
equipee: new fields.BooleanField({ initial: false })
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
MANIFEST-000061
|
MANIFEST-000085
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
2026/05/01-09:12:26.343249 7f3f34bfd6c0 Recovering log #59
|
2026/05/22-09:28:26.005874 7fb57dfee6c0 Recovering log #83
|
||||||
2026/05/01-09:12:26.359113 7f3f34bfd6c0 Delete type=3 #57
|
2026/05/22-09:28:26.015537 7fb57dfee6c0 Delete type=3 #81
|
||||||
2026/05/01-09:12:26.359184 7f3f34bfd6c0 Delete type=0 #59
|
2026/05/22-09:28:26.015651 7fb57dfee6c0 Delete type=0 #83
|
||||||
2026/05/01-09:12:52.776973 7f3ee77fe6c0 Level-0 table #64: started
|
2026/05/22-09:49:57.296086 7fb567fff6c0 Level-0 table #88: started
|
||||||
2026/05/01-09:12:52.777005 7f3ee77fe6c0 Level-0 table #64: 0 bytes OK
|
2026/05/22-09:49:57.296112 7fb567fff6c0 Level-0 table #88: 0 bytes OK
|
||||||
2026/05/01-09:12:52.786696 7f3ee77fe6c0 Delete type=0 #62
|
2026/05/22-09:49:57.302010 7fb567fff6c0 Delete type=0 #86
|
||||||
2026/05/01-09:12:52.798282 7f3ee77fe6c0 Manual compaction at level-0 from '!folders!K9aiFu0dE6UYiXBd' @ 72057594037927935 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at (end)
|
2026/05/22-09:49:57.319148 7fb567fff6c0 Manual compaction at level-0 from '!folders!K9aiFu0dE6UYiXBd' @ 72057594037927935 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2026/05/01-00:45:47.927508 7f3f34bfd6c0 Recovering log #55
|
2026/05/09-23:56:26.228807 7fe7211fe6c0 Recovering log #79
|
||||||
2026/05/01-00:45:47.937371 7f3f34bfd6c0 Delete type=3 #53
|
2026/05/09-23:56:26.239813 7fe7211fe6c0 Delete type=3 #77
|
||||||
2026/05/01-00:45:47.937437 7f3f34bfd6c0 Delete type=0 #55
|
2026/05/09-23:56:26.239859 7fe7211fe6c0 Delete type=0 #79
|
||||||
2026/05/01-00:49:08.816625 7f3ee77fe6c0 Level-0 table #60: started
|
2026/05/09-23:58:32.093072 7fe6d37fe6c0 Level-0 table #84: started
|
||||||
2026/05/01-00:49:08.816656 7f3ee77fe6c0 Level-0 table #60: 0 bytes OK
|
2026/05/09-23:58:32.093102 7fe6d37fe6c0 Level-0 table #84: 0 bytes OK
|
||||||
2026/05/01-00:49:08.823102 7f3ee77fe6c0 Delete type=0 #58
|
2026/05/09-23:58:32.100189 7fe6d37fe6c0 Delete type=0 #82
|
||||||
2026/05/01-00:49:08.830613 7f3ee77fe6c0 Manual compaction at level-0 from '!folders!K9aiFu0dE6UYiXBd' @ 72057594037927935 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at (end)
|
2026/05/09-23:58:32.106644 7fe6d37fe6c0 Manual compaction at level-0 from '!folders!K9aiFu0dE6UYiXBd' @ 72057594037927935 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000048
|
MANIFEST-000072
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2026/05/01-09:12:26.362764 7f3ee7fff6c0 Recovering log #46
|
2026/05/22-09:28:26.020644 7fb57d7ed6c0 Recovering log #70
|
||||||
2026/05/01-09:12:26.377202 7f3ee7fff6c0 Delete type=3 #44
|
2026/05/22-09:28:26.031504 7fb57d7ed6c0 Delete type=3 #68
|
||||||
2026/05/01-09:12:26.377270 7f3ee7fff6c0 Delete type=0 #46
|
2026/05/22-09:28:26.031624 7fb57d7ed6c0 Delete type=0 #70
|
||||||
2026/05/01-09:12:52.767020 7f3ee77fe6c0 Level-0 table #51: started
|
2026/05/22-09:49:57.312571 7fb567fff6c0 Level-0 table #75: started
|
||||||
2026/05/01-09:12:52.767054 7f3ee77fe6c0 Level-0 table #51: 0 bytes OK
|
2026/05/22-09:49:57.312612 7fb567fff6c0 Level-0 table #75: 0 bytes OK
|
||||||
2026/05/01-09:12:52.776818 7f3ee77fe6c0 Delete type=0 #49
|
2026/05/22-09:49:57.318950 7fb567fff6c0 Delete type=0 #73
|
||||||
2026/05/01-09:12:52.798270 7f3ee77fe6c0 Manual compaction at level-0 from '!tables!PPsxQgHwLCQ2gjSW' @ 72057594037927935 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at (end)
|
2026/05/22-09:49:57.319188 7fb567fff6c0 Manual compaction at level-0 from '!tables!PPsxQgHwLCQ2gjSW' @ 72057594037927935 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2026/05/01-00:45:47.940542 7f3f35bff6c0 Recovering log #42
|
2026/05/09-23:56:26.242643 7fe7219ff6c0 Recovering log #66
|
||||||
2026/05/01-00:45:47.951337 7f3f35bff6c0 Delete type=3 #40
|
2026/05/09-23:56:26.252924 7fe7219ff6c0 Delete type=3 #64
|
||||||
2026/05/01-00:45:47.951402 7f3f35bff6c0 Delete type=0 #42
|
2026/05/09-23:56:26.252971 7fe7219ff6c0 Delete type=0 #66
|
||||||
2026/05/01-00:49:08.810118 7f3ee77fe6c0 Level-0 table #47: started
|
2026/05/09-23:58:32.087004 7fe6d37fe6c0 Level-0 table #71: started
|
||||||
2026/05/01-00:49:08.810197 7f3ee77fe6c0 Level-0 table #47: 0 bytes OK
|
2026/05/09-23:58:32.087037 7fe6d37fe6c0 Level-0 table #71: 0 bytes OK
|
||||||
2026/05/01-00:49:08.816466 7f3ee77fe6c0 Delete type=0 #45
|
2026/05/09-23:58:32.092951 7fe6d37fe6c0 Delete type=0 #69
|
||||||
2026/05/01-00:49:08.830594 7f3ee77fe6c0 Manual compaction at level-0 from '!tables!PPsxQgHwLCQ2gjSW' @ 72057594037927935 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at (end)
|
2026/05/09-23:58:32.106635 7fe6d37fe6c0 Manual compaction at level-0 from '!tables!PPsxQgHwLCQ2gjSW' @ 72057594037927935 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000030
|
MANIFEST-000006
|
||||||
|
|||||||
+15
-7
@@ -1,7 +1,15 @@
|
|||||||
2026/05/01-09:12:26.379797 7f3f353fe6c0 Recovering log #28
|
2026/05/22-09:28:26.037049 7fb57dfee6c0 Recovering log #4
|
||||||
2026/05/01-09:12:26.395236 7f3f353fe6c0 Delete type=3 #26
|
2026/05/22-09:28:26.047411 7fb57dfee6c0 Delete type=3 #2
|
||||||
2026/05/01-09:12:26.395290 7f3f353fe6c0 Delete type=0 #28
|
2026/05/22-09:28:26.047498 7fb57dfee6c0 Delete type=0 #4
|
||||||
2026/05/01-09:12:52.786838 7f3ee77fe6c0 Level-0 table #33: started
|
2026/05/22-09:49:57.302133 7fb567fff6c0 Level-0 table #9: started
|
||||||
2026/05/01-09:12:52.786863 7f3ee77fe6c0 Level-0 table #33: 0 bytes OK
|
2026/05/22-09:49:57.305585 7fb567fff6c0 Level-0 table #9: 3183 bytes OK
|
||||||
2026/05/01-09:12:52.798150 7f3ee77fe6c0 Delete type=0 #31
|
2026/05/22-09:49:57.312423 7fb567fff6c0 Delete type=0 #7
|
||||||
2026/05/01-09:12:52.798293 7f3ee77fe6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
2026/05/22-09:49:57.319164 7fb567fff6c0 Manual compaction at level-0 from '!journal!69Da9YvF9BfOV7oK' @ 72057594037927935 : 1 .. '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 0 : 0; will stop at (end)
|
||||||
|
2026/05/22-09:49:57.319233 7fb567fff6c0 Manual compaction at level-1 from '!journal!69Da9YvF9BfOV7oK' @ 72057594037927935 : 1 .. '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 0 : 0; will stop at '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 3 : 1
|
||||||
|
2026/05/22-09:49:57.319246 7fb567fff6c0 Compacting 1@1 + 1@2 files
|
||||||
|
2026/05/22-09:49:57.322612 7fb567fff6c0 Generated table #10@1: 2 keys, 3183 bytes
|
||||||
|
2026/05/22-09:49:57.322655 7fb567fff6c0 Compacted 1@1 + 1@2 files => 3183 bytes
|
||||||
|
2026/05/22-09:49:57.328541 7fb567fff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
|
2026/05/22-09:49:57.328673 7fb567fff6c0 Delete type=2 #5
|
||||||
|
2026/05/22-09:49:57.328793 7fb567fff6c0 Delete type=2 #9
|
||||||
|
2026/05/22-09:49:57.351951 7fb567fff6c0 Manual compaction at level-1 from '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 3 : 1 .. '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
2026/05/01-00:45:48.110687 7f3f35bff6c0 Delete type=3 #1
|
2026/05/22-08:07:40.563282 7f63427ef6c0 Delete type=3 #1
|
||||||
2026/05/01-00:49:08.823197 7f3ee77fe6c0 Level-0 table #29: started
|
2026/05/22-08:07:40.567990 7f6323fff6c0 Level-0 table #5: started
|
||||||
2026/05/01-00:49:08.823234 7f3ee77fe6c0 Level-0 table #29: 0 bytes OK
|
2026/05/22-08:07:40.571414 7f6323fff6c0 Level-0 table #5: 3056 bytes OK
|
||||||
2026/05/01-00:49:08.830411 7f3ee77fe6c0 Delete type=0 #27
|
2026/05/22-08:07:40.577623 7f6323fff6c0 Delete type=0 #3
|
||||||
2026/05/01-00:49:08.830628 7f3ee77fe6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
2026/05/22-08:07:40.577857 7f6323fff6c0 Manual compaction at level-0 from '!journal!69Da9YvF9BfOV7oK' @ 72057594037927935 : 1 .. '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+134
-5
@@ -121,11 +121,16 @@
|
|||||||
.dnc-employe-sheet .sheet-header.compact label {
|
.dnc-employe-sheet .sheet-header.compact label {
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.dnc-employe-sheet .sheet-header.compact .hp-field {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
.dnc-employe-sheet .sheet-header.compact input[type="number"] {
|
.dnc-employe-sheet .sheet-header.compact input[type="number"] {
|
||||||
max-width: 4.75rem;
|
max-width: 4.75rem;
|
||||||
}
|
}
|
||||||
.dnc-employe-sheet .sheet-header.compact .counter-field input[type="number"] {
|
.dnc-employe-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||||
max-width: 4rem;
|
.dnc-employe-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.max"] {
|
||||||
|
width: 5.25rem;
|
||||||
|
max-width: 5.25rem;
|
||||||
}
|
}
|
||||||
.dnc-pnj-sheet .sheet-header.compact .identity-grid {
|
.dnc-pnj-sheet .sheet-header.compact .identity-grid {
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
@@ -134,13 +139,17 @@
|
|||||||
.dnc-pnj-sheet .sheet-header.compact label {
|
.dnc-pnj-sheet .sheet-header.compact label {
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
.dnc-pnj-sheet .sheet-header.compact .hp-field {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
.dnc-pnj-sheet .sheet-header.compact input[type="number"] {
|
.dnc-pnj-sheet .sheet-header.compact input[type="number"] {
|
||||||
width: 4.5rem;
|
width: 4.5rem;
|
||||||
max-width: 4.5rem;
|
max-width: 4.5rem;
|
||||||
}
|
}
|
||||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[type="number"] {
|
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||||
width: 3.75rem;
|
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.max"] {
|
||||||
max-width: 3.75rem;
|
width: 5.25rem;
|
||||||
|
max-width: 5.25rem;
|
||||||
}
|
}
|
||||||
.dnc-sheet label {
|
.dnc-sheet label {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -556,6 +565,126 @@
|
|||||||
.dnc-roll-dialog .window-content {
|
.dnc-roll-dialog .window-content {
|
||||||
background: linear-gradient(180deg, #f7efe0 0%, #e3d0b1 100%);
|
background: linear-gradient(180deg, #f7efe0 0%, #e3d0b1 100%);
|
||||||
}
|
}
|
||||||
|
.dnc-mission-pack-mode,
|
||||||
|
.dnc-mission-pack-note {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-campaign {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-hero {
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid rgba(91, 70, 52, 0.35);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.65) 0%, rgba(241, 229, 208, 0.68) 100%), linear-gradient(135deg, rgba(139, 46, 23, 0.08) 0%, rgba(139, 46, 23, 0) 100%);
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-kicker {
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(139, 46, 23, 0.82);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-hero h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "IM Fell English SC", "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
color: #8b2e17;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-subtitle {
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(91, 70, 52, 0.78);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-intro {
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
color: #6d5a4f;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-meta-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-meta-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.2rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid rgba(91, 70, 52, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.42);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-meta-card span {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #6d5a4f;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-meta-card strong {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-section {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-select {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid rgba(91, 70, 52, 0.32);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.36);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-assignments {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-assignment {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid rgba(91, 70, 52, 0.35);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.38);
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-assignment span {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-note {
|
||||||
|
color: #6d5a4f;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-note-foot {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-top: 1px solid rgba(91, 70, 52, 0.24);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-dialog .window-header {
|
||||||
|
background: linear-gradient(180deg, rgba(253, 246, 231, 0.96) 0%, rgba(234, 212, 170, 0.96) 100%), linear-gradient(90deg, rgba(139, 46, 23, 0.12) 0%, rgba(139, 46, 23, 0) 100%);
|
||||||
|
border-bottom: 1px solid rgba(91, 70, 52, 0.35);
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-dialog .window-title {
|
||||||
|
color: #8b2e17;
|
||||||
|
font-family: "IM Fell English SC", "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.dnc-mission-pack-dialog .window-header button {
|
||||||
|
color: #221b18;
|
||||||
|
}
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.dnc-mission-pack-meta-grid,
|
||||||
|
.dnc-mission-pack-assignments {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
.dnc-chat-card {
|
.dnc-chat-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 2px solid #5b4634;
|
border: 2px solid #5b4634;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -28,7 +28,7 @@
|
|||||||
<button type="button" data-action="rollHitDice" aria-label="Lancer le de de vie" title="Lancer le de de vie"><i class="fa-solid fa-dice-d20"></i></button>
|
<button type="button" data-action="rollHitDice" aria-label="Lancer le de de vie" title="Lancer le de de vie"><i class="fa-solid fa-dice-d20"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label class="with-controls">
|
<label class="with-controls hp-field" style="grid-column: span 2;">
|
||||||
<span>PV</span>
|
<span>PV</span>
|
||||||
<div class="counter-field">
|
<div class="counter-field">
|
||||||
<button type="button" data-action="adjustCounter" data-path="system.sante.pv.value" data-delta="-1">-</button>
|
<button type="button" data-action="adjustCounter" data-path="system.sante.pv.value" data-delta="-1">-</button>
|
||||||
@@ -93,7 +93,8 @@
|
|||||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
||||||
{{#if this.system.degats}}<p>Degats : {{this.system.degats}}</p>{{/if}}
|
{{#if this.damageLabel}}<p>Degats : {{this.damageLabel}}</p>{{/if}}
|
||||||
|
{{#if this.ammunitionUsageLabel}}<p>Munitions : {{this.ammunitionUsageLabel}}</p>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
{{#if this.canRoll}}
|
{{#if this.canRoll}}
|
||||||
@@ -138,7 +139,8 @@
|
|||||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
||||||
{{#if this.system.degats}}<p>Degats : {{this.system.degats}}</p>{{/if}}
|
{{#if this.damageLabel}}<p>Degats : {{this.damageLabel}}</p>{{/if}}
|
||||||
|
{{#if this.ammunitionUsageLabel}}<p>Munitions : {{this.ammunitionUsageLabel}}</p>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
{{#if this.canRoll}}
|
{{#if this.canRoll}}
|
||||||
@@ -234,7 +236,8 @@
|
|||||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
||||||
{{#if this.system.degats}}<p>Degats : {{this.system.degats}}</p>{{/if}}
|
{{#if this.damageLabel}}<p>Degats : {{this.damageLabel}}</p>{{/if}}
|
||||||
|
{{#if this.ammunitionUsageLabel}}<p>Munitions : {{this.ammunitionUsageLabel}}</p>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
{{#if this.canRoll}}
|
{{#if this.canRoll}}
|
||||||
@@ -278,7 +281,8 @@
|
|||||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
||||||
{{#if this.system.degats}}<p>Degats : {{this.system.degats}}</p>{{/if}}
|
{{#if this.damageLabel}}<p>Degats : {{this.damageLabel}}</p>{{/if}}
|
||||||
|
{{#if this.ammunitionUsageLabel}}<p>Munitions : {{this.ammunitionUsageLabel}}</p>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
{{#if this.canRoll}}
|
{{#if this.canRoll}}
|
||||||
@@ -395,7 +399,8 @@
|
|||||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
||||||
{{#if this.system.degats}}<p>Degats : {{this.system.degats}}</p>{{/if}}
|
{{#if this.damageLabel}}<p>Degats : {{this.damageLabel}}</p>{{/if}}
|
||||||
|
{{#if this.ammunitionUsageLabel}}<p>Munitions : {{this.ammunitionUsageLabel}}</p>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
{{#if this.canRoll}}
|
{{#if this.canRoll}}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<button type="button" data-action="rollHitDice" aria-label="Lancer le de de vie" title="Lancer le de de vie"><i class="fa-solid fa-dice-d20"></i></button>
|
<button type="button" data-action="rollHitDice" aria-label="Lancer le de de vie" title="Lancer le de de vie"><i class="fa-solid fa-dice-d20"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label class="with-controls">
|
<label class="with-controls hp-field" style="grid-column: span 2;">
|
||||||
<span>PV</span>
|
<span>PV</span>
|
||||||
<div class="counter-field">
|
<div class="counter-field">
|
||||||
<button type="button" data-action="adjustCounter" data-path="system.sante.pv.value" data-delta="-1">-</button>
|
<button type="button" data-action="adjustCounter" data-path="system.sante.pv.value" data-delta="-1">-</button>
|
||||||
@@ -122,7 +122,8 @@
|
|||||||
{{#if this.usageLabel}}<span class="item-meta">{{this.usageLabel}}</span>{{/if}}
|
{{#if this.usageLabel}}<span class="item-meta">{{this.usageLabel}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
{{#if this.system.effet}}<p>{{this.system.effet}}</p>{{/if}}
|
||||||
{{#if this.system.degats}}<p>Degats : {{this.system.degats}}</p>{{/if}}
|
{{#if this.damageLabel}}<p>Degats : {{this.damageLabel}}</p>{{/if}}
|
||||||
|
{{#if this.ammunitionUsageLabel}}<p>Munitions : {{this.ammunitionUsageLabel}}</p>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
{{#if this.canRoll}}
|
{{#if this.canRoll}}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<div class="chat-pill-row">
|
<div class="chat-pill-row">
|
||||||
{{#if modeLabel}}<span class="chat-pill">{{modeLabel}}</span>{{/if}}
|
{{#if modeLabel}}<span class="chat-pill">{{modeLabel}}</span>{{/if}}
|
||||||
<span class="chat-pill success">Dé {{keptDieLabel}}</span>
|
<span class="chat-pill success">Dé {{keptDieLabel}}</span>
|
||||||
|
{{#if damageCapped}}<span class="chat-pill">{{localize "DNC.Chat.DamageCapped" damage=effectiveDamage dv=martialDvLabel}}</span>{{/if}}
|
||||||
{{#if bonus}}<span class="chat-pill">Bonus +{{bonus}}</span>{{/if}}
|
{{#if bonus}}<span class="chat-pill">Bonus +{{bonus}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<p class="chat-formula">{{formula}}</p>
|
<p class="chat-formula">{{formula}}</p>
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
<p class="roll-values">{{#each rollDieLabels}}<span>{{this}}</span>{{/each}}</p>
|
<p class="roll-values">{{#each rollDieLabels}}<span>{{this}}</span>{{/each}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<p class="chat-note"><strong>Base</strong> : {{baseDamage}}{{#if bonus}} · <strong>Bonus</strong> : +{{bonus}}{{/if}}</p>
|
<p class="chat-note"><strong>Base</strong> : {{baseDamage}}{{#if damageCapped}} · <strong>{{localize "DNC.Chat.MartialDv"}}</strong> : {{martialDvLabel}} · <strong>{{localize "DNC.Chat.EffectiveDamage"}}</strong> : {{effectiveDamage}}{{/if}}{{#if bonus}} · <strong>Bonus</strong> : +{{bonus}}{{/if}}</p>
|
||||||
<div class="chat-targeting">
|
<div class="chat-targeting">
|
||||||
<label class="chat-control">
|
<label class="chat-control">
|
||||||
<span class="chat-keyline-label">Cible</span>
|
<span class="chat-keyline-label">Cible</span>
|
||||||
@@ -45,4 +46,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{#if showDamageUsageButton}}
|
||||||
|
<div class="chat-actions">
|
||||||
|
<button type="button" class="chat-action-button" data-action="rollDamageUsage" data-item-uuid="{{itemUuid}}">
|
||||||
|
<i class="fa-solid fa-hourglass-half"></i> {{localize "DNC.Chat.RollDamageUsage"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="chat-pill-row">
|
<div class="chat-pill-row">
|
||||||
|
<span class="chat-pill">{{localize "DNC.Macro.MissionPack.ActiveMode"}} {{generationModeLabel}}</span>
|
||||||
|
{{#if isCampaign}}<span class="chat-pill">{{localize "DNC.Macro.MissionPack.CampaignController"}} {{controllerName}}</span>{{/if}}
|
||||||
|
{{#if isCampaign}}<span class="chat-pill">{{localize "DNC.Macro.MissionPack.CampaignRelation"}} {{stewardRelationLabel}}</span>{{/if}}
|
||||||
<span class="chat-pill success">{{localize "DNC.Macro.MissionPack.ItemsAdded"}} {{createdCount}}</span>
|
<span class="chat-pill success">{{localize "DNC.Macro.MissionPack.ItemsAdded"}} {{createdCount}}</span>
|
||||||
{{#if missingCount}}<span class="chat-pill failure">{{localize "DNC.Macro.MissionPack.ItemsMissing"}} {{missingCount}}</span>{{/if}}
|
{{#if missingCount}}<span class="chat-pill failure">{{localize "DNC.Macro.MissionPack.ItemsMissing"}} {{missingCount}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
@@ -27,8 +30,33 @@
|
|||||||
{{#each draws}}
|
{{#each draws}}
|
||||||
<li>
|
<li>
|
||||||
<strong>{{this.label}}</strong> : {{this.display}}
|
<strong>{{this.label}}</strong> : {{this.display}}
|
||||||
|
{{#if this.dieLabel}}
|
||||||
|
<br>{{localize "DNC.Macro.MissionPack.RollDetail" die=this.dieLabel mode=this.modeLabel values=this.rollValuesLabel kept=this.kept rank=this.rankBonus total=this.total}}
|
||||||
|
{{#if this.clamped}}<br>{{localize "DNC.Macro.MissionPack.TotalClamped" total=this.total clamped=this.resolvedTotal}}{{/if}}
|
||||||
|
{{/if}}
|
||||||
{{#if this.missingSummary}}<br>{{localize "DNC.Macro.MissionPack.Missing"}} : {{this.missingSummary}}{{/if}}
|
{{#if this.missingSummary}}<br>{{localize "DNC.Macro.MissionPack.Missing"}} : {{this.missingSummary}}{{/if}}
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="chat-pill-row">
|
||||||
|
<span class="chat-pill">{{localize "DNC.Macro.MissionPack.UniqueReference"}} {{uniqueEntry.referenceRoll}}</span>
|
||||||
|
<span class="chat-pill">{{localize "DNC.Macro.MissionPack.UniqueActorRoll"}} {{uniqueEntry.actorRoll}}</span>
|
||||||
|
<span class="chat-pill {{#if uniqueEntry.matched}}success{{else}}failure{{/if}}">
|
||||||
|
{{#if uniqueEntry.matched}}
|
||||||
|
{{localize "DNC.Macro.MissionPack.UniqueMatch"}}
|
||||||
|
{{else}}
|
||||||
|
{{localize "DNC.Macro.MissionPack.UniqueMiss"}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if uniqueEntry.matched}}
|
||||||
|
<p class="chat-note">
|
||||||
|
<strong>{{localize "DNC.Macro.MissionPack.UniqueGranted"}}</strong> {{uniqueEntry.itemName}}
|
||||||
|
{{#if uniqueEntry.uniqueRoll}}({{localize "DNC.Macro.MissionPack.UniqueTableRoll"}} {{uniqueEntry.uniqueRoll}}){{/if}}
|
||||||
|
</p>
|
||||||
|
{{else}}
|
||||||
|
<p class="chat-note">{{localize "DNC.Macro.MissionPack.UniqueRuleReminder"}}</p>
|
||||||
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -28,7 +28,20 @@
|
|||||||
<button type="button" class="chat-action-button" data-action="rollChatDamage" data-item-uuid="{{itemUuid}}">
|
<button type="button" class="chat-action-button" data-action="rollChatDamage" data-item-uuid="{{itemUuid}}">
|
||||||
<i class="fa-solid fa-burst"></i> {{localize "DNC.Chat.RollDamage"}}
|
<i class="fa-solid fa-burst"></i> {{localize "DNC.Chat.RollDamage"}}
|
||||||
</button>
|
</button>
|
||||||
|
{{#if showAmmoButton}}
|
||||||
|
<button type="button" class="chat-action-button" data-action="rollAmmoUsage" data-item-uuid="{{itemUuid}}">
|
||||||
|
<i class="fa-solid fa-bullseye"></i> {{localize "DNC.Chat.RollAmmunition"}}
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{#if showAmmoButton}}
|
||||||
|
<div class="chat-actions">
|
||||||
|
<button type="button" class="chat-action-button" data-action="rollAmmoUsage" data-item-uuid="{{itemUuid}}">
|
||||||
|
<i class="fa-solid fa-bullseye"></i> {{localize "DNC.Chat.RollAmmunition"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<ul class="chat-details chat-details-ornate">
|
<ul class="chat-details chat-details-ornate">
|
||||||
{{#each details}}
|
{{#each details}}
|
||||||
|
|||||||
@@ -21,6 +21,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if protectionStored}}<p class="chat-note">{{localize "DNC.Chat.StoredProtection"}} : <strong>{{protectionStored}}</strong>.</p>{{/if}}
|
{{#if protectionStored}}<p class="chat-note">{{localize "DNC.Chat.StoredProtection"}} : <strong>{{protectionStored}}</strong>.</p>{{/if}}
|
||||||
{{#if degraded}}<p class="chat-note">{{localize "DNC.Chat.ResourceWearsDown"}}</p>{{else}}<p class="chat-note">{{localize "DNC.Chat.ResourceStable"}}</p>{{/if}}
|
{{#if isAmmunition}}
|
||||||
{{#if exhausted}}<p class="warning">{{localize "DNC.Chat.ResourceExhausted"}}</p>{{/if}}
|
{{#if degraded}}<p class="chat-note">{{localize "DNC.Chat.AmmunitionWearsDown"}}</p>{{else}}<p class="chat-note">{{localize "DNC.Chat.AmmunitionStable"}}</p>{{/if}}
|
||||||
|
{{#if exhausted}}<p class="warning">{{localize "DNC.Chat.AmmunitionExhausted"}}</p>{{/if}}
|
||||||
|
{{else if isDamageUsage}}
|
||||||
|
{{#if degraded}}<p class="chat-note">{{localize "DNC.Chat.DamageUsageWearsDown"}}</p>{{else}}<p class="chat-note">{{localize "DNC.Chat.DamageUsageStable"}}</p>{{/if}}
|
||||||
|
{{#if exhausted}}<p class="warning">{{localize "DNC.Chat.DamageUsageExhausted"}}</p>{{/if}}
|
||||||
|
{{else}}
|
||||||
|
{{#if degraded}}<p class="chat-note">{{localize "DNC.Chat.ResourceWearsDown"}}</p>{{else}}<p class="chat-note">{{localize "DNC.Chat.ResourceStable"}}</p>{{/if}}
|
||||||
|
{{#if exhausted}}<p class="warning">{{localize "DNC.Chat.ResourceExhausted"}}</p>{{/if}}
|
||||||
|
{{/if}}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<div class="dnc-dialog-form">
|
<div class="dnc-dialog-form">
|
||||||
<p><strong>{{item.name}}</strong> inflige <strong>{{item.system.degats}}</strong>.</p>
|
<p><strong>{{item.name}}</strong> inflige <strong>{{damageFormula}}</strong>.</p>
|
||||||
|
{{#if damageCapped}}
|
||||||
|
<p>{{localize "DNC.Dialog.DamageCappedByDv" dv=martialDvLabel damage=damageFormula base=damageBase}}</p>
|
||||||
|
{{/if}}
|
||||||
<p>{{localize "DNC.Dialog.ActorDamageBonus"}} : <strong>{{actorBonus}}</strong></p>
|
<p>{{localize "DNC.Dialog.ActorDamageBonus"}} : <strong>{{actorBonus}}</strong></p>
|
||||||
<label>
|
<label>
|
||||||
<span>{{localize "DNC.UI.Mode"}}</span>
|
<span>{{localize "DNC.UI.Mode"}}</span>
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<div class="dnc-dialog-form dnc-mission-pack-campaign">
|
||||||
|
<header class="dnc-mission-pack-hero">
|
||||||
|
<p class="dnc-mission-pack-kicker">{{localize "DNC.Chat.Kicker.Logistics"}}</p>
|
||||||
|
<h2>{{localize "DNC.Macro.MissionPack.CampaignDialogTitle"}}</h2>
|
||||||
|
<p class="dnc-mission-pack-subtitle">{{localize "DNC.Macro.MissionPack.CampaignDialogSubtitle"}}</p>
|
||||||
|
<p class="dnc-mission-pack-intro">{{localize "DNC.Macro.MissionPack.CampaignDialogIntro" actor=actorName}}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="dnc-mission-pack-meta-grid">
|
||||||
|
<article class="dnc-mission-pack-meta-card">
|
||||||
|
<span>{{localize "DNC.Macro.MissionPack.DialogActor"}}</span>
|
||||||
|
<strong>{{actorName}}</strong>
|
||||||
|
</article>
|
||||||
|
<article class="dnc-mission-pack-meta-card">
|
||||||
|
<span>{{localize "DNC.Macro.MissionPack.CampaignDialogPlayer"}}</span>
|
||||||
|
<strong>{{playerName}}</strong>
|
||||||
|
</article>
|
||||||
|
{{#if requesterName}}
|
||||||
|
<article class="dnc-mission-pack-meta-card">
|
||||||
|
<span>{{localize "DNC.Macro.MissionPack.CampaignDialogRequester"}}</span>
|
||||||
|
<strong>{{requesterName}}</strong>
|
||||||
|
</article>
|
||||||
|
{{/if}}
|
||||||
|
<article class="dnc-mission-pack-meta-card">
|
||||||
|
<span>{{localize "DNC.Macro.MissionPack.CampaignDialogRank"}}</span>
|
||||||
|
<strong>+{{rank}}</strong>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="dnc-mission-pack-section">
|
||||||
|
<label class="dnc-mission-pack-select">
|
||||||
|
<span>{{localize "DNC.Macro.MissionPack.CampaignDialogRelation"}}</span>
|
||||||
|
<select name="stewardRelation">
|
||||||
|
{{selectOptions relationOptions selected=selectedRelation labelAttr="label" valueAttr="value" localize=false}}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="dnc-mission-pack-section">
|
||||||
|
<p class="dnc-mission-pack-note">{{localize "DNC.Macro.MissionPack.CampaignDialogAssignHelp"}}</p>
|
||||||
|
<div class="dnc-mission-pack-assignments">
|
||||||
|
{{#each assignments}}
|
||||||
|
<label class="dnc-mission-pack-assignment">
|
||||||
|
<span>{{this.label}}</span>
|
||||||
|
<select name="{{this.fieldName}}">
|
||||||
|
{{selectOptions ../diceOptions selected=this.selectedDie labelAttr="label" valueAttr="value" localize=false}}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p class="dnc-mission-pack-note dnc-mission-pack-note-foot">{{localize "DNC.Macro.MissionPack.CampaignDialogHelp"}}</p>
|
||||||
|
</div>
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
<div class="dnc-dialog-form">
|
<div class="dnc-dialog-form">
|
||||||
<p>{{localize "DNC.Macro.MissionPack.DialogIntro"}}</p>
|
<p>{{localize "DNC.Macro.MissionPack.DialogIntro"}}</p>
|
||||||
|
<p class="dnc-mission-pack-mode"><strong>{{localize "DNC.Macro.MissionPack.ActiveMode"}}</strong> {{modeLabel}}</p>
|
||||||
|
<p>{{modeDescription}}</p>
|
||||||
<label>
|
<label>
|
||||||
<span>{{localize "DNC.Macro.MissionPack.DialogActor"}}</span>
|
<span>{{localize "DNC.Macro.MissionPack.DialogActor"}}</span>
|
||||||
<select name="actorId">
|
<select name="actorId">
|
||||||
{{selectOptions actorOptions selected=selectedActorId labelAttr="label" valueAttr="value" localize=false}}
|
{{selectOptions actorOptions selected=selectedActorId labelAttr="label" valueAttr="value" localize=false}}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
{{#if isCampaign}}
|
||||||
|
<p class="dnc-mission-pack-note">{{localize "DNC.Macro.MissionPack.CampaignDialogLead"}}</p>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,8 +19,23 @@
|
|||||||
<p>{{weaponCharacteristicLabel}}</p>
|
<p>{{weaponCharacteristicLabel}}</p>
|
||||||
</div>
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<span>Degats</span>
|
<span>Degats usage die (Δ)</span>
|
||||||
<input type="text" name="system.degats" value="{{system.degats}}">
|
<input type="checkbox" name="system.degatsEstUsageDe" {{checked system.degatsEstUsageDe}}>
|
||||||
|
</label>
|
||||||
|
{{#if system.degatsEstUsageDe}}
|
||||||
|
<label>
|
||||||
|
<span>Δ Degats courant</span>
|
||||||
|
<select name="system.degatsDelta">{{selectOptions config.usageDieOptions selected=system.degatsDelta localize=false blank="Epuise"}}</select>
|
||||||
|
</label>
|
||||||
|
{{else}}
|
||||||
|
<label>
|
||||||
|
<span>Degats</span>
|
||||||
|
<input type="text" name="system.degats" value="{{system.degats}}">
|
||||||
|
</label>
|
||||||
|
{{/if}}
|
||||||
|
<label>
|
||||||
|
<span>Munitions</span>
|
||||||
|
<select name="system.munitionsDelta">{{selectOptions config.usageDieOptions selected=system.munitionsDelta localize=false blank="Aucune"}}</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Portee</span>
|
<span>Portee</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user