Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a152ad11ba | |||
| befb8e97c2 | |||
| 6f3996d216 | |||
| 48660c9430 | |||
| 0b88e53d77 | |||
| 2c73108f63 | |||
| 8718cfff05 | |||
| 6742830f40 | |||
| 09f2349bab | |||
| d8eb23289e | |||
| 820a7d61cf | |||
| 755c349078 | |||
| 8fb27c2e82 | |||
| 98cf0bf8fd |
@@ -27,6 +27,17 @@ jobs:
|
||||
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
|
||||
|
||||
- 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: |
|
||||
apt update -y
|
||||
apt install -y zip
|
||||
|
||||
+113
-8
@@ -12,6 +12,7 @@
|
||||
"TYPES.Item.entrainement": "Entrainement",
|
||||
"DNC.Roll.Characteristic": "Jet de caracteristique",
|
||||
"DNC.Roll.Attack": "Jet d'attaque",
|
||||
"DNC.Roll.Ammunition": "Munitions",
|
||||
"DNC.Roll.Damage": "Jet de degats",
|
||||
"DNC.Roll.Favor": "Faveur",
|
||||
"DNC.Roll.HitDice": "Jet de de de vie",
|
||||
@@ -37,6 +38,8 @@
|
||||
"DNC.Chat.Kept": "Garde",
|
||||
"DNC.Chat.Rolls": "Lancers",
|
||||
"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.AutoDisadvantage": "Desavantage automatique",
|
||||
"DNC.Chat.RiskChaos": "Risquer le Chaos",
|
||||
@@ -56,11 +59,16 @@
|
||||
"DNC.Chat.HpRemaining": "PV restants",
|
||||
"DNC.Chat.CasterRank": "Rang du lanceur",
|
||||
"DNC.Chat.Difficulty": "Difficulte",
|
||||
"DNC.Chat.MartialDv": "DV martial",
|
||||
"DNC.Chat.EffectiveDamage": "Degats retenus",
|
||||
"DNC.Chat.Effect": "Effet",
|
||||
"DNC.Chat.StoredProtection": "Protection stockee pour ce combat",
|
||||
"DNC.Chat.ResourceWearsDown": "La ressource s'amenuise.",
|
||||
"DNC.Chat.ResourceStable": "La ressource tient bon.",
|
||||
"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.AutoDisadvantageApplies": "le cout du sort depasse le rang du lanceur.",
|
||||
"DNC.Chat.FocusFrom": "depuis",
|
||||
@@ -89,20 +97,26 @@
|
||||
"DNC.UI.Focus": "Focus",
|
||||
"DNC.UI.Chaos": "Chaos",
|
||||
"DNC.UI.Weapon": "Arme",
|
||||
"DNC.UI.Ammunition": "Munitions",
|
||||
"DNC.UI.Damage": "Degats",
|
||||
"DNC.UI.Range": "Portee",
|
||||
"DNC.UI.Spell": "Sortilege",
|
||||
"DNC.Dialog.InitiativeIntro": "<strong>{actorName}</strong> lance l'initiative.",
|
||||
"DNC.Dialog.InitiativeCurrent": "DEX actuelle : <strong>{dex}</strong>, bonus de fiche : <strong>{initiativeBonus}</strong>",
|
||||
"DNC.Dialog.CharacteristicUsed": "Caracteristique utilisee",
|
||||
"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.UseUsageDie": "Utiliser <strong>{itemName}</strong> et lancer son de d'usage actuel.",
|
||||
"DNC.Dialog.CurrentValue": "Valeur actuelle",
|
||||
"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.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.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.ItemsAdded": "Objets ajoutes",
|
||||
"DNC.Macro.MissionPack.ItemsMissing": "Objets manquants",
|
||||
@@ -121,13 +135,46 @@
|
||||
"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.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.DialogAction": "Generer le paquetage",
|
||||
"DNC.Macro.MissionPack.SidebarButton": "Paquetage",
|
||||
"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.DialogAction": "Generer le paquetage",
|
||||
"DNC.Macro.MissionPack.SidebarButton": "Paquetage",
|
||||
"DNC.Macro.MissionPack.CampaignDialogLead": "En mode campagne, le joueur proprietaire choisira l affectation de ses des dans un dialogue dedie.",
|
||||
"DNC.Macro.MissionPack.CampaignDialogTitle": "Paquetage de campagne",
|
||||
"DNC.Macro.MissionPack.CampaignDialogSubtitle": "Repartition logistique",
|
||||
"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.Title": "Bienvenue dans Donjon & Cie",
|
||||
"DNC.Welcome.Subtitle": "Systeme FoundryVTT · version {version}",
|
||||
@@ -150,5 +197,63 @@
|
||||
"DNC.Sheet.Create": "Creer",
|
||||
"DNC.Sheet.Delete": "Supprimer",
|
||||
"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.",
|
||||
|
||||
"DNC.Dialog.EmployeesTitle": "Employés",
|
||||
|
||||
"DNC.Dialog.EmployeesTabEmployes": "Employés",
|
||||
"DNC.Dialog.EmployeesTabCombat": "Combat",
|
||||
"DNC.Dialog.EmployeesTabCharacteristics": "Caractéristiques",
|
||||
"DNC.Dialog.EmployeesTabMagie": "Magie",
|
||||
"DNC.Dialog.EmployeesTabClients": "Clients",
|
||||
|
||||
"DNC.Empty.NoEmployees": "Aucun employé trouvé.",
|
||||
|
||||
"DNC.UI.Weapons": "Armes",
|
||||
"DNC.UI.Armors": "Armures",
|
||||
"DNC.UI.Spells": "Sortilèges",
|
||||
"DNC.UI.Capacities": "Capacités",
|
||||
|
||||
"DNC.UI.Employee": "Employé",
|
||||
"DNC.UI.Dv": "DV",
|
||||
"DNC.UI.MeleeAttacks": "Attaques CàC",
|
||||
"DNC.UI.RangedAttacks": "Attaques Dist.",
|
||||
|
||||
"DNC.UI.Damage": "Dégâts",
|
||||
"DNC.UI.Range": "Portée",
|
||||
"DNC.UI.Ammunition": "Munitions",
|
||||
"DNC.UI.ProtectionDie": "Protection Δ",
|
||||
"DNC.UI.Encumbrance": "Encombrement",
|
||||
"DNC.UI.RemainingProtection": "Protection restante",
|
||||
"DNC.UI.Notes": "Notes",
|
||||
"DNC.UI.Description": "Description",
|
||||
|
||||
"DNC.UI.Rank": "Rang",
|
||||
"DNC.UI.Focus": "Focus",
|
||||
"DNC.UI.Chaos": "Chaos",
|
||||
|
||||
"DNC.WeaponCategory.Melee": "Corps à corps",
|
||||
"DNC.WeaponCategory.Ranged": "Distance",
|
||||
"DNC.WeaponHands.Label": "Mains",
|
||||
"DNC.WeaponHands.One": "1 main",
|
||||
"DNC.WeaponHands.Two": "2 mains",
|
||||
"DNC.WeaponRange.Contact": "Contact",
|
||||
"DNC.Ammunition.Exhausted": "Épuisées",
|
||||
|
||||
"DNC.Empty.NoWeapons": "Aucune arme répertoriée.",
|
||||
"DNC.Empty.NoArmors": "Aucune armure répertoriée.",
|
||||
"DNC.Empty.NoSpells": "Aucun sortilège répertorié.",
|
||||
"DNC.Empty.NoCapacities": "Aucune capacité répertoriée.",
|
||||
"DNC.Empty.NoClients": "Aucun client répertorié.",
|
||||
|
||||
"DNC.Client.Species": "Espèce",
|
||||
"DNC.Client.Category": "Catégorie",
|
||||
"DNC.Client.Role": "Rôle"
|
||||
}
|
||||
|
||||
@@ -21,6 +21,16 @@
|
||||
gap: @spacing-md;
|
||||
}
|
||||
|
||||
// Réduction de la taille des champs PV
|
||||
.dnc-actor-sheet .hp-field .counter-field input[type="number"] {
|
||||
max-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dnc-actor-sheet .hp-field .counter-field {
|
||||
gap: @spacing-xs;
|
||||
}
|
||||
|
||||
.dnc-actor-sheet .profile-card-wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
+16
-5
@@ -135,12 +135,18 @@
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.dnc-employe-sheet .sheet-header.compact .hp-field {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.dnc-employe-sheet .sheet-header.compact input[type="number"] {
|
||||
max-width: 4.75rem;
|
||||
}
|
||||
|
||||
.dnc-employe-sheet .sheet-header.compact .counter-field input[type="number"] {
|
||||
max-width: 4rem;
|
||||
.dnc-employe-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||
.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 {
|
||||
@@ -152,14 +158,19 @@
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.dnc-pnj-sheet .sheet-header.compact .hp-field {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.dnc-pnj-sheet .sheet-header.compact input[type="number"] {
|
||||
width: 4.5rem;
|
||||
max-width: 4.5rem;
|
||||
}
|
||||
|
||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[type="number"] {
|
||||
width: 3.75rem;
|
||||
max-width: 3.75rem;
|
||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.max"] {
|
||||
width: 5.25rem;
|
||||
max-width: 5.25rem;
|
||||
}
|
||||
|
||||
.dnc-sheet label {
|
||||
|
||||
+32
-5
@@ -44,7 +44,7 @@
|
||||
}
|
||||
|
||||
.chat-card-kicker {
|
||||
margin: 0 0 0.25rem;
|
||||
margin: 0 0 @spacing-xs;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
@@ -189,7 +189,7 @@
|
||||
}
|
||||
|
||||
.chat-details li + li {
|
||||
margin-top: 0.25rem;
|
||||
margin-top: @spacing-xs;
|
||||
}
|
||||
|
||||
.chat-actions {
|
||||
@@ -225,9 +225,36 @@
|
||||
.chat-targeting {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
display: flex;
|
||||
gap: @spacing-sm;
|
||||
margin-top: @spacing-sm;
|
||||
flex-wrap: wrap;
|
||||
align-items: end;
|
||||
|
||||
.chat-action-button {
|
||||
padding: 0.35rem 0.65rem;
|
||||
min-height: 1.75rem;
|
||||
font-size: 0.82rem;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Boutons Appliquer et Avec Armure sur une seule ligne
|
||||
.dnc-chat-card-damage .chat-actions-inline {
|
||||
flex-wrap: nowrap;
|
||||
gap: @spacing-xs;
|
||||
}
|
||||
|
||||
.dnc-chat-card-damage .chat-actions-inline .chat-action-button {
|
||||
padding: 0.3rem 0.55rem;
|
||||
min-height: 1.65rem;
|
||||
font-size: 0.78rem;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.dnc-chat-card-damage .chat-actions-inline .chat-action-button i {
|
||||
font-size: 0.85rem;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
|
||||
.chat-control {
|
||||
@@ -255,7 +282,7 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 2rem;
|
||||
padding: 0.25rem 0.7rem;
|
||||
padding: @spacing-xs 0.7rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid fade(@color-border, 38%);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
@@ -417,7 +444,7 @@
|
||||
}
|
||||
|
||||
.chat-chaos-result-title {
|
||||
margin: 0 0 0.25rem;
|
||||
margin: 0 0 @spacing-xs;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,3 +34,149 @@
|
||||
.dnc-roll-dialog .window-content {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
@import "item-sheet";
|
||||
@import "dialogs";
|
||||
@import "chat";
|
||||
@import "employes-dialog";
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
// ============================================
|
||||
// Dialog Employés - Styles
|
||||
// Structure 2 niveaux : Employés/Clients → sous-onglets par PC/client
|
||||
// ============================================
|
||||
|
||||
// Conteneur principal
|
||||
.dnc-employes-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: @spacing-md;
|
||||
padding: @spacing-lg;
|
||||
min-width: 640px;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.sheet-header {
|
||||
display: block;
|
||||
grid-template-columns: none;
|
||||
gap: normal;
|
||||
|
||||
h1 {
|
||||
font-family: @font-display;
|
||||
font-size: 1.4rem;
|
||||
color: @color-accent;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper pour DialogV2
|
||||
.dnc-employes-dialog-wrapper {
|
||||
.dialog-content,
|
||||
.window-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Cacher le footer (bouton requis par l'API mais inutile visuellement)
|
||||
.dialog-footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Onglets racine (niveau 1)
|
||||
// ============================================
|
||||
.dnc-root-tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: @spacing-md;
|
||||
border-bottom: 2px solid @color-border;
|
||||
padding-bottom: @spacing-sm;
|
||||
}
|
||||
|
||||
.dnc-root-tab {
|
||||
border: 1px solid fade(@color-border, 55%);
|
||||
border-radius: @radius-md @radius-md 0 0;
|
||||
background: @color-panel-strong;
|
||||
color: @color-ink;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
padding: @spacing-sm @spacing-lg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: @spacing-xs;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
i { font-size: 0.9rem; }
|
||||
|
||||
&.active {
|
||||
background: @color-accent;
|
||||
border-color: darken(@color-accent, 12%);
|
||||
color: #fff;
|
||||
box-shadow: 0 -3px 8px fade(@color-shadow, 20%);
|
||||
position: relative;
|
||||
bottom: -2px;
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
background: fade(@color-panel-strong, 120%);
|
||||
border-color: fade(@color-border, 75%);
|
||||
}
|
||||
}
|
||||
|
||||
// Panneaux racine
|
||||
.dnc-root-panels {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dnc-root-panel {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Sous-onglets (niveau 2 — par PC ou client)
|
||||
// ============================================
|
||||
.dnc-pc-tabs {
|
||||
display: flex;
|
||||
gap: @spacing-xs;
|
||||
flex-wrap: wrap;
|
||||
padding: @spacing-sm 0;
|
||||
border-bottom: 1px solid fade(@color-border, 40%);
|
||||
margin-bottom: @spacing-md;
|
||||
}
|
||||
|
||||
.dnc-pc-tab {
|
||||
border: 1px solid fade(@color-border, 45%);
|
||||
border-radius: @radius-sm;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
color: @color-muted;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
padding: @spacing-xs @spacing-md;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&.active {
|
||||
background: @color-accent;
|
||||
border-color: darken(@color-accent, 10%);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
background: fade(@color-panel-strong, 110%);
|
||||
color: @color-ink;
|
||||
}
|
||||
}
|
||||
|
||||
// Panneaux par PC / client
|
||||
.dnc-pc-panels {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dnc-pc-panel,
|
||||
.dnc-client-panel {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Fiche compacte par employé (3 sections)
|
||||
// ============================================
|
||||
.dnc-compact-section {
|
||||
padding: @spacing-md;
|
||||
margin-bottom: @spacing-sm;
|
||||
border: 1px solid fade(@color-border, 40%);
|
||||
border-radius: @radius-md;
|
||||
background: @color-panel;
|
||||
|
||||
& + .dnc-compact-section {
|
||||
margin-top: @spacing-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.dnc-compact-section-title {
|
||||
font-family: @font-display;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: @color-accent;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin: 0 0 @spacing-sm;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: @spacing-xs;
|
||||
border-bottom: 1px solid fade(@color-border, 35%);
|
||||
padding-bottom: @spacing-xs;
|
||||
|
||||
i { font-size: 0.85rem; }
|
||||
}
|
||||
|
||||
.dnc-compact-concept {
|
||||
font-size: 0.8rem;
|
||||
color: @color-muted;
|
||||
font-style: italic;
|
||||
margin: @spacing-xs 0 0;
|
||||
}
|
||||
|
||||
// Grille de caractéristiques compacte
|
||||
.dnc-compact-carac-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
.dnc-carac-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 48px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: 1px solid fade(@color-border, 40%);
|
||||
border-radius: @radius-sm;
|
||||
padding: @spacing-xs @spacing-sm;
|
||||
}
|
||||
|
||||
.dnc-carac-label {
|
||||
font-size: 0.68rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: @color-muted;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.dnc-carac-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: @color-ink;
|
||||
|
||||
&.warning {
|
||||
color: @color-failure;
|
||||
}
|
||||
}
|
||||
|
||||
// Combat stats badges
|
||||
.dnc-compact-combat-stats {
|
||||
display: flex;
|
||||
gap: @spacing-sm;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: @spacing-sm;
|
||||
}
|
||||
|
||||
.dnc-combat-badge {
|
||||
font-size: 0.82rem;
|
||||
padding: @spacing-xs @spacing-sm;
|
||||
background: @color-panel-strong;
|
||||
border: 1px solid fade(@color-border, 40%);
|
||||
border-radius: @radius-sm;
|
||||
color: @color-ink;
|
||||
}
|
||||
|
||||
// Listes d'items compactes
|
||||
.dnc-compact-item-list {
|
||||
margin-top: @spacing-sm;
|
||||
}
|
||||
|
||||
.dnc-compact-subhead {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: @color-muted;
|
||||
margin: 0 0 @spacing-xs;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: @spacing-xs;
|
||||
|
||||
i { color: @color-accent; }
|
||||
}
|
||||
|
||||
.dnc-compact-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: @spacing-xs;
|
||||
padding: @spacing-xs 0;
|
||||
border-top: 1px dashed fade(@color-border, 35%);
|
||||
font-size: 0.85rem;
|
||||
|
||||
&:first-of-type {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dnc-compact-item-name {
|
||||
font-weight: 600;
|
||||
color: @color-ink;
|
||||
}
|
||||
|
||||
.dnc-compact-item-detail {
|
||||
font-size: 0.78rem;
|
||||
color: @color-muted;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: @radius-sm;
|
||||
padding: 0.05rem 0.3rem;
|
||||
}
|
||||
|
||||
// Stats magie compactes
|
||||
.dnc-compact-magic-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: @spacing-sm;
|
||||
margin-bottom: @spacing-sm;
|
||||
}
|
||||
|
||||
// Réutilisation de .item-meta pour les badges de sortilège/capacité
|
||||
.item-meta {
|
||||
border-radius: 999px;
|
||||
background: @color-panel-strong;
|
||||
color: @color-ink;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Fiche client dans sous-panneau
|
||||
// ============================================
|
||||
.client-summary {
|
||||
margin: @spacing-sm 0 0;
|
||||
color: @color-ink;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.client-description {
|
||||
margin-top: @spacing-md;
|
||||
padding-top: @spacing-md;
|
||||
border-top: 1px solid fade(@color-border, 45%);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: @color-muted;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================
|
||||
// Roll buttons dans vue clients
|
||||
// ============================================
|
||||
.dnc-roll-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
font-size: 0.78rem;
|
||||
font-family: @font-body;
|
||||
color: @color-ink;
|
||||
background: @color-panel-strong;
|
||||
border: 1px solid fade(@color-border, 60%);
|
||||
border-radius: @radius-sm;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
|
||||
i { font-size: 0.7rem; color: @color-accent; }
|
||||
|
||||
&:hover {
|
||||
background: @color-accent;
|
||||
color: #fff;
|
||||
border-color: @color-accent;
|
||||
i { color: #fff; }
|
||||
}
|
||||
}
|
||||
|
||||
.dnc-carac-rollable {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
border-radius: @radius-sm;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: fade(@color-accent, 18%);
|
||||
.dnc-carac-label { color: @color-accent; }
|
||||
}
|
||||
}
|
||||
|
||||
// Bouton ouvrir fiche dans les titres de section
|
||||
.dnc-compact-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
|
||||
.dnc-open-sheet-btn {
|
||||
margin-left: auto;
|
||||
background: none;
|
||||
border: 1px solid fade(@color-border, 50%);
|
||||
border-radius: @radius-sm;
|
||||
color: @color-muted;
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.35rem;
|
||||
line-height: 1;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: @color-accent;
|
||||
border-color: @color-accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
@color-failure: #842c2c;
|
||||
@color-shadow: rgba(0, 0, 0, 0.22);
|
||||
|
||||
@spacing-xs: 0.25rem;
|
||||
@spacing-sm: 0.4rem;
|
||||
@spacing-md: 0.75rem;
|
||||
@spacing-lg: 1rem;
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* Donjon & Cie - Systeme FoundryVTT
|
||||
*
|
||||
* Fenêtre de dialogue pour afficher les employés (PJ)
|
||||
* Structure : 2 onglets racine (Employés / Clients),
|
||||
* avec sous-onglets par PC ou par client.
|
||||
*
|
||||
* @author LeRatierBretonnien
|
||||
* @copyright 2025–2026 LeRatierBretonnien
|
||||
* @license CC BY-NC-SA 4.0
|
||||
*/
|
||||
|
||||
import { DonjonEtCieUtility } from "../donjon-et-cie-utility.mjs";
|
||||
import { DONJON_ET_CIE } from "../donjon-et-cie-config.mjs";
|
||||
|
||||
export class DonjonEtCieEmployesDialog {
|
||||
/**
|
||||
* Ouvre la fenêtre des employés
|
||||
*/
|
||||
static async open() {
|
||||
const pcs = this.#getPlayerCharacters();
|
||||
const clientTokens = this.#getClients();
|
||||
const characteristicKeys = this.#getCharacteristicKeys();
|
||||
|
||||
const pcsData = await Promise.all(
|
||||
pcs.map(async (pc) => this.#preparePcData(pc, characteristicKeys))
|
||||
);
|
||||
|
||||
const templateContext = {
|
||||
pcs: pcsData,
|
||||
clients: clientTokens.map(t => this.#prepareClientData(t)),
|
||||
characteristicKeys
|
||||
};
|
||||
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/employes-dialog.hbs",
|
||||
templateContext
|
||||
);
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: {
|
||||
title: "Employés",
|
||||
icon: "fa-solid fa-hard-hat"
|
||||
},
|
||||
classes: ["fvtt-donjon-et-cie", "dnc-employes-dialog-wrapper"],
|
||||
content,
|
||||
modal: false,
|
||||
buttons: [
|
||||
{
|
||||
action: "close",
|
||||
label: "Fermer",
|
||||
icon: "fa-solid fa-xmark",
|
||||
callback: () => true
|
||||
}
|
||||
],
|
||||
rejectClose: false,
|
||||
render: (event, dialog) => this.#setupTabs(dialog)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les personnages joueurs (employés)
|
||||
*/
|
||||
static #getPlayerCharacters() {
|
||||
return game.actors.filter(a => a.type === "character" || a.hasPlayerOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les PNJ clients de la scène courante (retourne les TokenDocuments)
|
||||
*/
|
||||
static #getClients() {
|
||||
const scene = canvas?.scene ?? game.scenes?.current;
|
||||
if (!scene) return [];
|
||||
|
||||
return scene.tokens.filter(token => {
|
||||
const actor = token.actor;
|
||||
return actor &&
|
||||
!actor.hasPlayerOwner &&
|
||||
actor.type === "pnj" &&
|
||||
actor.system.categorie === "Client";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les clés des caractéristiques
|
||||
*/
|
||||
static #getCharacteristicKeys() {
|
||||
return Object.entries(DONJON_ET_CIE.characteristics).map(([key, metadata]) => ({
|
||||
key,
|
||||
label: metadata.label,
|
||||
short: metadata.short
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prépare les données d'un PJ pour l'affichage
|
||||
*/
|
||||
static async #preparePcData(pc, characteristicKeys) {
|
||||
const sys = pc.system || {};
|
||||
const items = pc.items?.contents || [];
|
||||
|
||||
const pvValue = sys.sante?.pv?.value ?? 0;
|
||||
const pvMax = sys.sante?.pv?.max ?? 0;
|
||||
const dv = sys.sante?.dv ?? "1d6";
|
||||
const meleeAttacks = sys.combat?.attaquesCorpsACorps ?? 1;
|
||||
const rangedAttacks = sys.combat?.attaquesDistance ?? 1;
|
||||
|
||||
const magicResources = DonjonEtCieUtility.getMagicResourceContext(pc);
|
||||
|
||||
const weapons = [];
|
||||
const armors = [];
|
||||
const spells = [];
|
||||
const capacities = [];
|
||||
|
||||
for (const item of items) {
|
||||
const itemSys = item.system || {};
|
||||
|
||||
if (item.type === "arme") {
|
||||
const categoryLabel = itemSys.categorie === "distance" ? "Distance" : "Corps à corps";
|
||||
const handsLabel = (itemSys.mains ?? 1) > 1 ? "2 mains" : "1 main";
|
||||
|
||||
weapons.push({
|
||||
name: item.name,
|
||||
categoryLabel,
|
||||
handsLabel,
|
||||
damage: itemSys.degatsEstUsageDe
|
||||
? `${itemSys.degats}(Δ)`
|
||||
: (itemSys.degats || "—"),
|
||||
range: itemSys.portee || "Contact",
|
||||
ammunition: itemSys.munitionsDelta != null
|
||||
? (itemSys.munitionsDelta === 0 ? "Épuisées" : `Δ${itemSys.munitionsDelta}`)
|
||||
: ""
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === "armure") {
|
||||
armors.push({
|
||||
name: item.name,
|
||||
protectionDie: `Δ${itemSys.delta || 0}`,
|
||||
encumbrance: itemSys.encombrement || "—",
|
||||
remainingProtection: itemSys.resultatProtection || "—"
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === "sortilege") {
|
||||
spells.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === "capacite") {
|
||||
capacities.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Spread des valeurs de caractéristiques directement sur l'objet pc
|
||||
const characteristics = {};
|
||||
characteristicKeys.forEach(({ key }) => {
|
||||
characteristics[key] = sys.caracteristiques?.[key]?.value ?? 0;
|
||||
});
|
||||
|
||||
return {
|
||||
actorId: pc.id,
|
||||
name: pc.name,
|
||||
concept: pc.system.concept || "",
|
||||
pvValue,
|
||||
pvMax,
|
||||
dv,
|
||||
meleeAttacks,
|
||||
rangedAttacks,
|
||||
weapons,
|
||||
armors,
|
||||
spells,
|
||||
capacities,
|
||||
magicRank: magicResources.rank,
|
||||
focusDisplay: magicResources.focusDisplay,
|
||||
chaosDisplay: magicResources.chaosLabel,
|
||||
...characteristics
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prépare les données d'un client (PNJ) depuis son TokenDocument
|
||||
*/
|
||||
static #prepareClientData(token) {
|
||||
const client = token.actor;
|
||||
const sys = client.system || {};
|
||||
const items = client.items?.contents || [];
|
||||
|
||||
// Attaques système du modèle PNJ
|
||||
const attaques = (sys.attaques || []).filter(a => a.nom || a.degats);
|
||||
|
||||
// Items du PNJ
|
||||
const weapons = [];
|
||||
const spells = [];
|
||||
const capacities = [];
|
||||
|
||||
for (const item of items) {
|
||||
const itemSys = item.system || {};
|
||||
if (item.type === "arme") {
|
||||
weapons.push({
|
||||
name: item.name,
|
||||
damage: itemSys.degatsEstUsageDe
|
||||
? `${itemSys.degats}(Δ)`
|
||||
: (itemSys.degats || "—"),
|
||||
categoryLabel: itemSys.categorie === "distance" ? "Distance" : "Corps à corps"
|
||||
});
|
||||
}
|
||||
if (item.type === "sortilege") {
|
||||
spells.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
if (item.type === "capacite") {
|
||||
capacities.push({
|
||||
name: item.name,
|
||||
usageLabel: itemSys.delta > 0 ? DonjonEtCieUtility.formatUsageDie(itemSys.delta) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: client.id,
|
||||
tokenUuid: token.uuid,
|
||||
name: client.name,
|
||||
species: sys.espece || "",
|
||||
category: sys.categorie || "",
|
||||
role: sys.role || "",
|
||||
summary: sys.resume || "",
|
||||
pvValue: sys.sante?.pv?.value ?? 0,
|
||||
pvMax: sys.sante?.pv?.max ?? 0,
|
||||
dv: sys.sante?.dv || "1d8",
|
||||
armureDelta: sys.defense?.armure?.delta ?? 0,
|
||||
armureProtection: sys.defense?.armure?.resultatProtection ?? 0,
|
||||
courageDelta: sys.defense?.courage?.delta ?? 0,
|
||||
attaques,
|
||||
weapons,
|
||||
spells,
|
||||
capacities,
|
||||
pouvoirsSpeciaux: sys.pouvoirsSpeciaux || "",
|
||||
hasMagie: spells.length > 0 || capacities.length > 0 || !!(sys.pouvoirsSpeciaux || "").trim()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration des onglets à 2 niveaux
|
||||
* Niveau 1 : Employés / Clients (root tabs)
|
||||
* Niveau 2 : un onglet par PC ou par client (sub-tabs)
|
||||
*/
|
||||
static #setupTabs(dialog) {
|
||||
const root = dialog.element;
|
||||
|
||||
// ---- Onglets racine ----
|
||||
const rootTabs = root.querySelectorAll('.dnc-root-tab');
|
||||
const rootPanels = root.querySelectorAll('.dnc-root-panel');
|
||||
|
||||
const activateRoot = (tabName) => {
|
||||
rootTabs.forEach(btn => btn.classList.toggle('active', btn.dataset.rootTab === tabName));
|
||||
rootPanels.forEach(panel => panel.classList.toggle('active', panel.dataset.rootPanel === tabName));
|
||||
};
|
||||
|
||||
rootTabs.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
activateRoot(btn.dataset.rootTab);
|
||||
});
|
||||
});
|
||||
|
||||
// Activer premier onglet racine par défaut
|
||||
if (rootTabs.length > 0) activateRoot(rootTabs[0].dataset.rootTab);
|
||||
|
||||
// ---- Sous-onglets PC ----
|
||||
const pcTabs = root.querySelectorAll('.dnc-pc-tab[data-pc-tab]');
|
||||
const pcPanels = root.querySelectorAll('.dnc-pc-panel[data-pc-panel]');
|
||||
|
||||
const activatePc = (actorId) => {
|
||||
pcTabs.forEach(btn => btn.classList.toggle('active', btn.dataset.pcTab === actorId));
|
||||
pcPanels.forEach(panel => panel.classList.toggle('active', panel.dataset.pcPanel === actorId));
|
||||
};
|
||||
|
||||
pcTabs.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
activatePc(btn.dataset.pcTab);
|
||||
});
|
||||
});
|
||||
|
||||
if (pcTabs.length > 0) activatePc(pcTabs[0].dataset.pcTab);
|
||||
|
||||
// ---- Sous-onglets Clients ----
|
||||
const clientTabs = root.querySelectorAll('.dnc-pc-tab[data-client-tab]');
|
||||
const clientPanels = root.querySelectorAll('.dnc-client-panel[data-client-panel]');
|
||||
|
||||
const activateClient = (clientId) => {
|
||||
clientTabs.forEach(btn => btn.classList.toggle('active', btn.dataset.clientTab === clientId));
|
||||
clientPanels.forEach(panel => panel.classList.toggle('active', panel.dataset.clientPanel === clientId));
|
||||
};
|
||||
|
||||
clientTabs.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
activateClient(btn.dataset.clientTab);
|
||||
});
|
||||
});
|
||||
|
||||
if (clientTabs.length > 0) activateClient(clientTabs[0].dataset.clientTab);
|
||||
|
||||
// ---- Ouverture des fiches ----
|
||||
root.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.dnc-open-sheet-btn');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const type = btn.dataset.openSheet;
|
||||
if (type === "pc") {
|
||||
const actor = game.actors.get(btn.dataset.actorId);
|
||||
actor?.sheet.render(true);
|
||||
} else if (type === "client") {
|
||||
const tokenDoc = fromUuidSync(btn.dataset.tokenUuid);
|
||||
const actor = tokenDoc?.actor ?? game.actors.get(btn.dataset.actorId);
|
||||
actor?.sheet.render(true);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Jets de dés depuis la vue clients (même dialogs que la fiche PNJ) ----
|
||||
root.addEventListener('click', async (e) => {
|
||||
const btn = e.target.closest('.dnc-roll-btn, .dnc-carac-rollable');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Résolution de l'actor : token UUID pour gérer les tokens non-liés
|
||||
const tokenUuid = btn.dataset.tokenUuid;
|
||||
const actorId = btn.dataset.actorId;
|
||||
let actor = null;
|
||||
if (tokenUuid) {
|
||||
const tokenDoc = fromUuidSync(tokenUuid);
|
||||
actor = tokenDoc?.actor ?? null;
|
||||
}
|
||||
if (!actor && actorId) {
|
||||
actor = game.actors.get(actorId);
|
||||
}
|
||||
if (!actor) return;
|
||||
|
||||
const action = btn.dataset.pnjAction;
|
||||
switch (action) {
|
||||
case "rollArmure": return actor.rollPnjArmor();
|
||||
case "rollCourage": return actor.rollPnjCourage();
|
||||
case "rollAttaque": return actor.rollPnjAttackDamage(btn.dataset.attackIndex ?? 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,12 +201,19 @@ export class DonjonEtCieRollDialog {
|
||||
}
|
||||
|
||||
static async createDamage(actor, item) {
|
||||
const damageContext = DonjonEtCieUtility.getMartialDamageContext(actor, item);
|
||||
const isMeleeTwoHanded = item.type === "arme" && item.system?.categorie === "melee" && Number(item.system?.mains ?? 1) > 1;
|
||||
const defaultMode = isMeleeTwoHanded ? "avantage" : "normal";
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/damage-roll.hbs",
|
||||
{
|
||||
actorName: actor?.name ?? item.actor?.name ?? "",
|
||||
item,
|
||||
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,
|
||||
defaultMode
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ export default class DonjonEtCieItemSheet extends HandlebarsApplicationMixin(fou
|
||||
isCapacity: item.type === "capacite",
|
||||
isLanguage: item.type === "langue",
|
||||
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),
|
||||
armorProtectionDisplay: Number(item.system.resultatProtection ?? 0) > 0 ? item.system.resultatProtection : "—",
|
||||
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() {
|
||||
return DonjonEtCieUtility.getCharacteristicEntries(this.system);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ export class DonjonEtCieItem extends Item {
|
||||
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() {
|
||||
if (this.type === "arme") return DonjonEtCieRollDialog.createWeapon(this.actor, this);
|
||||
if (this.type === "sortilege") return DonjonEtCieRollDialog.createSpell(this.actor, this);
|
||||
@@ -43,10 +51,27 @@ export class DonjonEtCieItem extends Item {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/chat/item-card.hbs",
|
||||
|
||||
+685
-100
@@ -13,6 +13,18 @@
|
||||
import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
||||
|
||||
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 = [
|
||||
{ key: "melee", name: "Armes de corps a corps", 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 }
|
||||
];
|
||||
|
||||
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) {
|
||||
return String(value ?? "")
|
||||
.normalize("NFD")
|
||||
@@ -34,6 +172,50 @@ export class DonjonEtCieMacros {
|
||||
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) {
|
||||
const controlledActor = canvas?.tokens?.controlled?.[0]?.actor ?? null;
|
||||
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) {
|
||||
if (target?.documentName === "Actor") return target;
|
||||
if (target?.actor?.documentName === "Actor") return target.actor;
|
||||
@@ -121,6 +313,127 @@ export class DonjonEtCieMacros {
|
||||
.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 } = {}) {
|
||||
if (!result) {
|
||||
return {
|
||||
@@ -129,19 +442,27 @@ export class DonjonEtCieMacros {
|
||||
};
|
||||
}
|
||||
|
||||
if (result.type === "document" && result.documentCollection && result.documentId) {
|
||||
const uuid = result.documentCollection.includes(".")
|
||||
? `Compendium.${result.documentCollection}.Item.${result.documentId}`
|
||||
: `Item.${result.documentId}`;
|
||||
const document = await fromUuid(uuid);
|
||||
const label = document?.name ?? result.text ?? "";
|
||||
const source = result._source ?? {};
|
||||
const resultName = String(result.name ?? source.name ?? "").trim();
|
||||
const resultDescription = String(result.description ?? source.description ?? "").trim();
|
||||
const resultText = String(source.text ?? "").trim();
|
||||
const resultLabel = resultName || resultDescription || resultText;
|
||||
|
||||
const documentUuid = result.type === "document"
|
||||
? result.documentUuid ?? source.documentUuid ?? null
|
||||
: null;
|
||||
|
||||
if (documentUuid) {
|
||||
const document = await fromUuid(documentUuid);
|
||||
const label = document?.name ?? resultLabel;
|
||||
return {
|
||||
display: label,
|
||||
entries: label ? [{ name: label, document }] : []
|
||||
};
|
||||
}
|
||||
|
||||
const uuidTargets = this.#extractUuidTargets(result.text);
|
||||
const sourceText = resultDescription || resultText || resultLabel;
|
||||
const uuidTargets = this.#extractUuidTargets(sourceText);
|
||||
if (uuidTargets.length) {
|
||||
const entries = [];
|
||||
for (const target of uuidTargets) {
|
||||
@@ -158,8 +479,8 @@ export class DonjonEtCieMacros {
|
||||
}
|
||||
|
||||
const names = multiple
|
||||
? this.#extractPlainTextEntries(result.text)
|
||||
: [result.text].map((entry) => String(entry ?? "").trim()).filter(Boolean);
|
||||
? this.#extractPlainTextEntries(sourceText || resultLabel)
|
||||
: [resultLabel].filter(Boolean);
|
||||
|
||||
return {
|
||||
display: names.join(", "),
|
||||
@@ -175,6 +496,353 @@ export class DonjonEtCieMacros {
|
||||
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.
|
||||
* @returns {Promise<object|null>}
|
||||
@@ -191,12 +859,16 @@ export class DonjonEtCieMacros {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mode = this.#getMissionPackMode();
|
||||
const selectedActorId = this.#getDefaultMissionPackActorId(actorOptions.map((option) => game.actors.get(option.value)).filter(Boolean));
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-dialog.hbs",
|
||||
{
|
||||
actorOptions,
|
||||
selectedActorId
|
||||
selectedActorId,
|
||||
modeLabel: this.#getMissionPackModeLabel(mode),
|
||||
modeDescription: this.#getMissionPackModeDescription(mode),
|
||||
isCampaign: mode === "campaign"
|
||||
}
|
||||
);
|
||||
|
||||
@@ -246,95 +918,8 @@ export class DonjonEtCieMacros {
|
||||
return null;
|
||||
}
|
||||
|
||||
const draws = [];
|
||||
const embeddedItems = [];
|
||||
let missingCount = 0;
|
||||
|
||||
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
|
||||
};
|
||||
return this.#getMissionPackMode() === "campaign"
|
||||
? this.#grantCampaignMissionPack(actor)
|
||||
: this.#grantClassicMissionPack(actor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { DonjonEtCieItem } from "./donjon-et-cie-item.mjs";
|
||||
import * as models from "./models/index.mjs";
|
||||
import * as sheets from "./applications/sheets/_module.mjs";
|
||||
import { DonjonEtCieRollDialog } from "./applications/donjon-et-cie-roll-dialog.mjs";
|
||||
import { DonjonEtCieEmployesDialog } from "./applications/donjon-et-cie-employes-dialog.mjs";
|
||||
import { DonjonEtCieRolls } from "./donjon-et-cie-rolls.mjs";
|
||||
import { DonjonEtCieMacros } from "./donjon-et-cie-macros.mjs";
|
||||
|
||||
@@ -43,10 +44,22 @@ function injectActorDirectoryMissionPackButton(app, element) {
|
||||
void game.system.donjonEtCie.macros.openMissionPackDialog();
|
||||
});
|
||||
headerActions.append(button);
|
||||
|
||||
// Bouton "Employés / Clients"
|
||||
const empButton = document.createElement("button");
|
||||
empButton.type = "button";
|
||||
empButton.className = "dnc-employes-button";
|
||||
empButton.title = game.i18n.localize("DNC.Dialog.EmployeesTitle");
|
||||
empButton.setAttribute("aria-label", game.i18n.localize("DNC.Dialog.EmployeesTitle"));
|
||||
empButton.innerHTML = `<i class="fa-solid fa-hard-hat" inert></i><span>${game.i18n.localize("DNC.Dialog.EmployeesTitle")}</span>`;
|
||||
empButton.addEventListener("click", () => {
|
||||
void DonjonEtCieEmployesDialog.open();
|
||||
});
|
||||
headerActions.append(empButton);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
event.preventDefault();
|
||||
@@ -85,6 +98,12 @@ function onChatActionClick(event) {
|
||||
const itemUuid = button.dataset.itemUuid;
|
||||
if (!itemUuid) return;
|
||||
const item = await fromUuid(itemUuid);
|
||||
if (button.dataset.action === "rollAmmoUsage") {
|
||||
return item?.rollAmmoUsage?.();
|
||||
}
|
||||
if (button.dataset.action === "rollDamageUsage") {
|
||||
return item?.rollDamageUsage?.();
|
||||
}
|
||||
return item?.rollDamage?.();
|
||||
})();
|
||||
}
|
||||
@@ -98,6 +117,19 @@ function registerSystemSettings() {
|
||||
type: String,
|
||||
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() {
|
||||
@@ -214,9 +246,62 @@ Hooks.once("init", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion de la commande /employes
|
||||
// Enregistrement officiel via ChatLogV2.CHAT_COMMANDS (comme mgt2-compendium-amiral-denisov)
|
||||
function registerEmployesCommand() {
|
||||
const ChatLogV2 = foundry.applications.sidebar.tabs.ChatLog;
|
||||
|
||||
if (ChatLogV2?.CHAT_COMMANDS) {
|
||||
ChatLogV2.CHAT_COMMANDS.employes = {
|
||||
rgx: /^\/employes(?:\s+(.*))?$/i,
|
||||
fn: () => {
|
||||
DonjonEtCieEmployesDialog.open();
|
||||
return false;
|
||||
},
|
||||
};
|
||||
console.log("DNC | Commande /employes enregistrée via ChatLog.CHAT_COMMANDS");
|
||||
} else {
|
||||
console.warn("DNC | ChatLog.CHAT_COMMANDS indisponible, utilisation des hooks de fallback");
|
||||
}
|
||||
}
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerEmployesCommand();
|
||||
});
|
||||
|
||||
Hooks.once("ready", () => {
|
||||
DonjonEtCieMacros.registerSocketListeners();
|
||||
document.addEventListener("click", onChatActionClick);
|
||||
void maybeCreateWelcomeMessage();
|
||||
|
||||
// Hooks de fallback pour compatibilité
|
||||
Hooks.on("preCreateChatMessage", (message, data, options, userId) => {
|
||||
const content = data.content?.trim()?.toLowerCase();
|
||||
if (content === "/employes" || content?.startsWith("/employes ")) {
|
||||
DonjonEtCieEmployesDialog.open();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Hooks.on("chatMessage", (...args) => {
|
||||
// Gestion compatibilité v13/v14
|
||||
let message;
|
||||
if (args[0]?.content !== undefined) {
|
||||
message = args[0].content; // v14
|
||||
} else if (typeof args[1] === "string") {
|
||||
message = args[1]; // v13
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
const trimmed = message?.trim()?.toLowerCase();
|
||||
if (trimmed === "/employes" || trimmed?.startsWith("/employes ")) {
|
||||
DonjonEtCieEmployesDialog.open();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.on("renderActorDirectory", (app, element) => {
|
||||
|
||||
+135
-24
@@ -14,12 +14,14 @@ import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";
|
||||
import { DONJON_ET_CIE } from "./donjon-et-cie-config.mjs";
|
||||
|
||||
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 validRolls = rolls.filter((roll) => roll instanceof Roll);
|
||||
await ChatMessage.create({
|
||||
speaker: ChatMessage.getSpeaker({ actor }),
|
||||
user: game.user.id,
|
||||
content
|
||||
content,
|
||||
rolls: validRolls
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,7 +70,7 @@ export class DonjonEtCieRolls {
|
||||
const kept = this.#selectKeptValue(values, mode, "low");
|
||||
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") {
|
||||
@@ -101,6 +103,7 @@ export class DonjonEtCieRolls {
|
||||
effectiveMode: this.#applyFavorMode(mode),
|
||||
modeBefore: mode,
|
||||
modeAfter: this.#applyFavorMode(mode),
|
||||
rolls: resolved.rolls,
|
||||
note: degraded
|
||||
? "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."
|
||||
@@ -129,7 +132,7 @@ export class DonjonEtCieRolls {
|
||||
after: DonjonEtCieUtility.formatUsageDie(after),
|
||||
autoSpent: true,
|
||||
note: "La faveur est brulee pour obtenir directement l'aide souhaitee, a la discretion du MJ."
|
||||
});
|
||||
}, { rolls: [] });
|
||||
|
||||
return { key: favorKey, label, before, after };
|
||||
}
|
||||
@@ -172,6 +175,7 @@ export class DonjonEtCieRolls {
|
||||
before: focusDelta,
|
||||
after,
|
||||
degraded,
|
||||
rolls: resolved.rolls,
|
||||
values: resolved.values
|
||||
};
|
||||
}
|
||||
@@ -208,7 +212,7 @@ export class DonjonEtCieRolls {
|
||||
{ label: game.i18n.localize("DNC.Chat.After"), value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
||||
] : [])
|
||||
]
|
||||
});
|
||||
}, { rolls: [...(favor?.rolls ?? []), result.roll] });
|
||||
|
||||
return { ...result, favor, mode: effectiveMode };
|
||||
}
|
||||
@@ -246,7 +250,7 @@ export class DonjonEtCieRolls {
|
||||
mode: result.mode,
|
||||
modeLabel: this.#getModeLabel(result.mode),
|
||||
syncedCombat
|
||||
});
|
||||
}, { rolls: result.rolls });
|
||||
|
||||
return { total: result.kept, die, dieValues, dex, bonus: sheetBonus, mode: result.mode, syncedCombat };
|
||||
}
|
||||
@@ -271,7 +275,7 @@ export class DonjonEtCieRolls {
|
||||
formula: roll.formula,
|
||||
total: roll.total,
|
||||
dieValues
|
||||
});
|
||||
}, { rolls: [roll] });
|
||||
|
||||
return { formula: roll.formula, total: roll.total, dieValues };
|
||||
}
|
||||
@@ -304,12 +308,14 @@ export class DonjonEtCieRolls {
|
||||
favorLabel: favor?.label ?? null,
|
||||
favorNote: favor?.note ?? null,
|
||||
showDamageButton: result.success && Boolean(item.system.degats),
|
||||
showAmmoButton: Number(item.system.munitionsDelta ?? 0) > 0,
|
||||
itemUuid: item.uuid,
|
||||
details: [
|
||||
{ label: game.i18n.localize("DNC.UI.Weapon"), value: item.name },
|
||||
{ label: game.i18n.localize("DNC.UI.Characteristic"), value: characteristicLabel },
|
||||
{ label: `Valeur de ${characteristicLabel}`, value: result.target },
|
||||
{ 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 || "—" },
|
||||
...(favor ? [
|
||||
{ label: game.i18n.localize("DNC.Chat.Favor"), value: favor.label },
|
||||
@@ -318,16 +324,63 @@ export class DonjonEtCieRolls {
|
||||
{ label: game.i18n.localize("DNC.Chat.After"), value: DonjonEtCieUtility.formatUsageDie(favor.after) }
|
||||
] : [])
|
||||
]
|
||||
});
|
||||
}, { rolls: [...(favor?.rolls ?? []), result.roll] });
|
||||
|
||||
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" } = {}) {
|
||||
if (!item.system.degats) return null;
|
||||
const actorBonus = Number(actor?.system?.combat?.degatsBonus ?? 0);
|
||||
const totalBonus = actorBonus;
|
||||
const formula = totalBonus ? `${item.system.degats} + ${totalBonus}` : item.system.degats;
|
||||
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;
|
||||
|
||||
// Arme à 2 mains de corps à corps : avantage automatique
|
||||
const isMeleeTwoHanded = item.type === "arme" && item.system?.categorie === "melee" && Number(item.system?.mains ?? 1) > 1;
|
||||
if (isMeleeTwoHanded && mode === "normal") {
|
||||
mode = "avantage";
|
||||
}
|
||||
|
||||
const damageContext = DonjonEtCieUtility.getMartialDamageContext(actor, item);
|
||||
const effectiveDamage = damageContext.effectiveFormula || (isUsageDie ? `1d${degatsDelta}` : item.system.degats);
|
||||
const formula = effectiveDamage;
|
||||
const result = await this.#resolveFormulaRoll(formula, {}, { mode, favorable: "high" });
|
||||
const targets = DonjonEtCieUtility.getSceneDamageTargets();
|
||||
const rollDieLabels = result.rolls.map((roll) => {
|
||||
@@ -335,6 +388,7 @@ export class DonjonEtCieRolls {
|
||||
return dieValues.length ? dieValues.join(" + ") : String(roll.total ?? "—");
|
||||
});
|
||||
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", {
|
||||
title: `${game.i18n.localize("DNC.Roll.Damage")} : ${item.name}`,
|
||||
@@ -346,14 +400,65 @@ export class DonjonEtCieRolls {
|
||||
keptDieLabel,
|
||||
values: result.values,
|
||||
total: result.kept,
|
||||
bonus: totalBonus,
|
||||
baseDamage: item.system.degats,
|
||||
baseDamage: baseDamageDisplay,
|
||||
effectiveDamage,
|
||||
damageCapped: damageContext.capped,
|
||||
martialDvLabel: damageContext.martialDvSides ? `d${damageContext.martialDvSides}` : damageContext.martialDvFormula,
|
||||
sourceLabel: item.name,
|
||||
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,
|
||||
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;
|
||||
}
|
||||
|
||||
// Arme à 2 mains de corps à corps : avantage automatique
|
||||
const isMeleeTwoHanded = item.type === "arme" && item.system?.categorie === "melee" && Number(item.system?.mains ?? 1) > 1;
|
||||
if (isMeleeTwoHanded && mode === "normal") {
|
||||
mode = "avantage";
|
||||
}
|
||||
|
||||
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 = "" } = {}) {
|
||||
@@ -389,6 +494,7 @@ export class DonjonEtCieRolls {
|
||||
static async rollSpell(actor, item, { mode = "normal", favorKey = "" } = {}) {
|
||||
const characteristicKey = item.system.caracteristique || "intelligence";
|
||||
const focus = await this.#ensureFocus(actor);
|
||||
const currentSceneId = DonjonEtCieUtility.getCurrentSceneId();
|
||||
const rank = Number(actor.system.anciennete?.rang ?? actor.system.sante?.dv ?? 0);
|
||||
const cost = Number(item.system.coutPv ?? 0);
|
||||
const autoDisadvantage = cost > rank;
|
||||
@@ -399,8 +505,13 @@ export class DonjonEtCieRolls {
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
const currentPv = Number(actor.system.sante?.pv?.value ?? 0);
|
||||
const availableMagicHp = currentPv + focus.activeValue;
|
||||
const liveActor = game.actors.get(actor.id) ?? actor;
|
||||
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) {
|
||||
ui.notifications.warn(game.i18n.localize("DNC.Warn.SpellInsufficientResources"));
|
||||
@@ -409,8 +520,8 @@ export class DonjonEtCieRolls {
|
||||
|
||||
const characteristicShort = DONJON_ET_CIE.characteristics[characteristicKey]?.short ?? characteristicKey;
|
||||
const success = result.isNaturalTwenty ? false : result.success;
|
||||
const focusSpent = result.isNaturalOne ? 0 : Math.min(cost, focus.activeValue);
|
||||
const focusRemaining = Math.max(focus.activeValue - focusSpent, 0);
|
||||
const focusSpent = result.isNaturalOne ? 0 : Math.min(cost, currentFocusValue);
|
||||
const focusRemaining = Math.max(currentFocusValue - focusSpent, 0);
|
||||
const spentPv = result.isNaturalOne ? 0 : Math.max(cost - focusSpent, 0);
|
||||
const remainingPv = Math.max(currentPv - spentPv, 0);
|
||||
const updateData = {};
|
||||
@@ -483,7 +594,7 @@ export class DonjonEtCieRolls {
|
||||
focusDegraded: focus.degraded,
|
||||
spentPv,
|
||||
remainingPv
|
||||
});
|
||||
}, { rolls: [...(favor?.rolls ?? []), ...(focus.rolls ?? []), result.roll] });
|
||||
|
||||
return { ...result, success, spentPv, remainingPv, cost, focus, focusSpent, focusRemaining, favor, mode: effectiveMode };
|
||||
}
|
||||
@@ -514,7 +625,7 @@ export class DonjonEtCieRolls {
|
||||
degraded,
|
||||
exhausted: after < 4,
|
||||
itemName: item.name
|
||||
});
|
||||
}, { rolls: resolved.rolls });
|
||||
|
||||
return { result, before, after, degraded, chaosEntry };
|
||||
}
|
||||
@@ -552,7 +663,7 @@ export class DonjonEtCieRolls {
|
||||
protectionStored: item.type === "armure" ? result : null,
|
||||
degraded,
|
||||
exhausted: after === 0
|
||||
});
|
||||
}, { rolls: resolved.rolls });
|
||||
|
||||
return { result, values: resolved.values, mode: resolved.mode, before, after, degraded };
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ export class DonjonEtCieUtility {
|
||||
"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/mission-pack-dialog.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-campaign-dialog.hbs",
|
||||
"systems/fvtt-donjon-et-cie/templates/dialogs/employes-dialog.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/chaos-card.hbs",
|
||||
@@ -74,6 +76,9 @@ export class DonjonEtCieUtility {
|
||||
static getSceneDamageTargets() {
|
||||
const scene = canvas?.scene ?? game.scenes?.current;
|
||||
const tokens = scene?.tokens?.contents ?? [];
|
||||
|
||||
const targetedTokens = game.user?.targets ?? new Set();
|
||||
const targetedTokenUuid = targetedTokens?.first()?.document.uuid ?? null;
|
||||
|
||||
return tokens
|
||||
.map((token) => {
|
||||
@@ -88,7 +93,8 @@ export class DonjonEtCieUtility {
|
||||
tokenId: token.id,
|
||||
tokenUuid: token.uuid,
|
||||
actorUuid: actor.uuid,
|
||||
label
|
||||
label,
|
||||
isSelected: token.uuid === targetedTokenUuid
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
@@ -118,6 +124,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) {
|
||||
return DONJON_ET_CIE.favorDepartments[key] ?? key;
|
||||
}
|
||||
@@ -172,12 +259,20 @@ export class DonjonEtCieUtility {
|
||||
const system = item.system;
|
||||
const delta = Number(system.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
|
||||
? `${this.formatUsageDie(delta)} / ${this.formatUsageDie(deltaMax)}`
|
||||
: delta > 0
|
||||
? this.formatUsageDie(delta)
|
||||
: null;
|
||||
|
||||
const damageUsageLabel = isUsageDie
|
||||
? (degatsDelta > 0 ? this.formatUsageDie(degatsDelta) : game.i18n.localize("DNC.UI.DamageExhausted"))
|
||||
: null;
|
||||
const damageLabel = isUsageDie ? damageUsageLabel : (system.degats || null);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
@@ -186,11 +281,15 @@ export class DonjonEtCieUtility {
|
||||
system,
|
||||
uuid: item.uuid,
|
||||
usageLabel,
|
||||
ammunitionUsageLabel: item.type === "arme" && ammunitionDelta > 0 ? this.formatUsageDie(ammunitionDelta) : null,
|
||||
protectionLabel: item.type === "armure" && Number(system.resultatProtection ?? 0) > 0 ? `Protection ${system.resultatProtection}` : null,
|
||||
weaponCharacteristicLabel: item.type === "arme" ? this.getWeaponCharacteristicLabel(system.categorie) : null,
|
||||
canRoll: ["arme", "sortilege"].includes(item.type),
|
||||
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",
|
||||
damageAction: "rollDamage",
|
||||
isEquipped: Boolean(system.equipee),
|
||||
|
||||
@@ -20,6 +20,9 @@ export default class ArmeDataModel extends BaseItemDataModel {
|
||||
categorie: new fields.StringField({ initial: "melee" }),
|
||||
caracteristique: new fields.StringField({ initial: "force" }),
|
||||
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: "" }),
|
||||
mains: new fields.NumberField({ initial: 1, integer: true }),
|
||||
equipee: new fields.BooleanField({ initial: false })
|
||||
|
||||
@@ -50,7 +50,6 @@ export default class EmployeDataModel extends foundry.abstract.TypeDataModel {
|
||||
}),
|
||||
combat: new fields.SchemaField({
|
||||
initiativeBonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
degatsBonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
attaquesCorpsACorps: new fields.NumberField({ initial: 1, integer: true }),
|
||||
attaquesDistance: new fields.NumberField({ initial: 1, integer: true })
|
||||
}),
|
||||
|
||||
@@ -1 +1 @@
|
||||
MANIFEST-000053
|
||||
MANIFEST-000189
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
2026/05/01-00:26:25.801692 7f3f34bfd6c0 Recovering log #50
|
||||
2026/05/01-00:26:25.812434 7f3f34bfd6c0 Delete type=3 #48
|
||||
2026/05/01-00:26:25.812516 7f3f34bfd6c0 Delete type=0 #50
|
||||
2026/05/01-00:36:27.689762 7f3ee77fe6c0 Level-0 table #56: started
|
||||
2026/05/01-00:36:27.689836 7f3ee77fe6c0 Level-0 table #56: 0 bytes OK
|
||||
2026/05/01-00:36:27.700959 7f3ee77fe6c0 Delete type=0 #54
|
||||
2026/05/01-00:36:27.721510 7f3ee77fe6c0 Manual compaction at level-0 from '!folders!K9aiFu0dE6UYiXBd' @ 72057594037927935 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at (end)
|
||||
2026/06/03-20:02:36.286349 7f589d3fe6c0 Recovering log #187
|
||||
2026/06/03-20:02:36.295607 7f589d3fe6c0 Delete type=3 #185
|
||||
2026/06/03-20:02:36.295653 7f589d3fe6c0 Delete type=0 #187
|
||||
2026/06/03-20:14:21.989739 7f584f7fe6c0 Level-0 table #192: started
|
||||
2026/06/03-20:14:21.989753 7f584f7fe6c0 Level-0 table #192: 0 bytes OK
|
||||
2026/06/03-20:14:21.995792 7f584f7fe6c0 Delete type=0 #190
|
||||
2026/06/03-20:14:22.012809 7f584f7fe6c0 Manual compaction at level-0 from '!folders!K9aiFu0dE6UYiXBd' @ 72057594037927935 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at (end)
|
||||
|
||||
+7
-11
@@ -1,11 +1,7 @@
|
||||
2026/05/01-00:23:37.118222 7f3ee7fff6c0 Delete type=3 #1
|
||||
2026/05/01-00:24:45.280116 7f3ee77fe6c0 Level-0 table #51: started
|
||||
2026/05/01-00:24:45.280188 7f3ee77fe6c0 Level-0 table #51: 0 bytes OK
|
||||
2026/05/01-00:24:45.287008 7f3ee77fe6c0 Delete type=0 #49
|
||||
2026/05/01-00:24:45.305825 7f3ee77fe6c0 Manual compaction at level-0 from '!folders!K9aiFu0dE6UYiXBd' @ 72057594037927935 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at '!items!zyqLzmpbHxK3jt5q' @ 188 : 1
|
||||
2026/05/01-00:24:45.305838 7f3ee77fe6c0 Compacting 1@0 + 0@1 files
|
||||
2026/05/01-00:24:45.310327 7f3ee77fe6c0 Generated table #52@0: 189 keys, 41244 bytes
|
||||
2026/05/01-00:24:45.310351 7f3ee77fe6c0 Compacted 1@0 + 0@1 files => 41244 bytes
|
||||
2026/05/01-00:24:45.316310 7f3ee77fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
|
||||
2026/05/01-00:24:45.316584 7f3ee77fe6c0 Delete type=2 #39
|
||||
2026/05/01-00:24:45.327635 7f3ee77fe6c0 Manual compaction at level-0 from '!items!zyqLzmpbHxK3jt5q' @ 188 : 1 .. '!items!zyqLzmpbHxK3jt5q' @ 0 : 0; will stop at (end)
|
||||
2026/06/03-17:24:13.769178 7f589dbff6c0 Recovering log #183
|
||||
2026/06/03-17:24:13.812146 7f589dbff6c0 Delete type=3 #181
|
||||
2026/06/03-17:24:13.812199 7f589dbff6c0 Delete type=0 #183
|
||||
2026/06/03-19:19:52.344655 7f584f7fe6c0 Level-0 table #188: started
|
||||
2026/06/03-19:19:52.344671 7f584f7fe6c0 Level-0 table #188: 0 bytes OK
|
||||
2026/06/03-19:19:52.350896 7f584f7fe6c0 Delete type=0 #186
|
||||
2026/06/03-19:19:52.367561 7f584f7fe6c0 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-000040
|
||||
MANIFEST-000176
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/05/01-00:26:25.815892 7f3f353fe6c0 Recovering log #37
|
||||
2026/05/01-00:26:25.826814 7f3f353fe6c0 Delete type=3 #35
|
||||
2026/05/01-00:26:25.826879 7f3f353fe6c0 Delete type=0 #37
|
||||
2026/05/01-00:36:27.711630 7f3ee77fe6c0 Level-0 table #43: started
|
||||
2026/05/01-00:36:27.711673 7f3ee77fe6c0 Level-0 table #43: 0 bytes OK
|
||||
2026/05/01-00:36:27.721272 7f3ee77fe6c0 Delete type=0 #41
|
||||
2026/05/01-00:36:27.721541 7f3ee77fe6c0 Manual compaction at level-0 from '!tables!PPsxQgHwLCQ2gjSW' @ 72057594037927935 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at (end)
|
||||
2026/06/03-20:02:36.298130 7f584ffff6c0 Recovering log #174
|
||||
2026/06/03-20:02:36.307600 7f584ffff6c0 Delete type=3 #172
|
||||
2026/06/03-20:02:36.307643 7f584ffff6c0 Delete type=0 #174
|
||||
2026/06/03-20:14:22.006386 7f584f7fe6c0 Level-0 table #179: started
|
||||
2026/06/03-20:14:22.006405 7f584f7fe6c0 Level-0 table #179: 0 bytes OK
|
||||
2026/06/03-20:14:22.012715 7f584f7fe6c0 Delete type=0 #177
|
||||
2026/06/03-20:14:22.012822 7f584f7fe6c0 Manual compaction at level-0 from '!tables!PPsxQgHwLCQ2gjSW' @ 72057594037927935 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
2026/05/01-00:23:37.197888 7f3f353fe6c0 Delete type=3 #1
|
||||
2026/05/01-00:24:45.299410 7f3ee77fe6c0 Level-0 table #38: started
|
||||
2026/05/01-00:24:45.299452 7f3ee77fe6c0 Level-0 table #38: 0 bytes OK
|
||||
2026/05/01-00:24:45.305679 7f3ee77fe6c0 Delete type=0 #36
|
||||
2026/05/01-00:24:45.317087 7f3ee77fe6c0 Manual compaction at level-0 from '!tables!PPsxQgHwLCQ2gjSW' @ 72057594037927935 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 208 : 1
|
||||
2026/05/01-00:24:45.317098 7f3ee77fe6c0 Compacting 1@0 + 0@1 files
|
||||
2026/05/01-00:24:45.321477 7f3ee77fe6c0 Generated table #39@0: 120 keys, 28120 bytes
|
||||
2026/05/01-00:24:45.321513 7f3ee77fe6c0 Compacted 1@0 + 0@1 files => 28120 bytes
|
||||
2026/05/01-00:24:45.327369 7f3ee77fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
|
||||
2026/05/01-00:24:45.327452 7f3ee77fe6c0 Delete type=2 #26
|
||||
2026/05/01-00:24:45.338220 7f3ee77fe6c0 Manual compaction at level-0 from '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 208 : 1 .. '!tables.results!wJZXUo4q5b5vE3Dy.zFTPLMc9zOl5hISV' @ 0 : 0; will stop at (end)
|
||||
2026/06/03-17:24:13.815984 7f589cbfd6c0 Recovering log #170
|
||||
2026/06/03-17:24:13.870566 7f589cbfd6c0 Delete type=3 #168
|
||||
2026/06/03-17:24:13.870643 7f589cbfd6c0 Delete type=0 #170
|
||||
2026/06/03-19:19:52.350980 7f584f7fe6c0 Level-0 table #175: started
|
||||
2026/06/03-19:19:52.351000 7f584f7fe6c0 Level-0 table #175: 0 bytes OK
|
||||
2026/06/03-19:19:52.357007 7f584f7fe6c0 Delete type=0 #173
|
||||
2026/06/03-19:19:52.367567 7f584f7fe6c0 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,5 +1,5 @@
|
||||
2026/05/01-00:35:09.714392 7fb5bbfff6c0 Delete type=3 #1
|
||||
2026/05/01-00:35:09.717944 7fb5baffd6c0 Level-0 table #5: started
|
||||
2026/05/01-00:35:09.721749 7fb5baffd6c0 Level-0 table #5: 3056 bytes OK
|
||||
2026/05/01-00:35:09.728545 7fb5baffd6c0 Delete type=0 #3
|
||||
2026/05/01-00:35:09.728707 7fb5baffd6c0 Manual compaction at level-0 from '!journal!69Da9YvF9BfOV7oK' @ 72057594037927935 : 1 .. '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 0 : 0; will stop at (end)
|
||||
2026/06/03-20:12:45.450958 7f07bbfff6c0 Delete type=3 #1
|
||||
2026/06/03-20:12:45.455909 7f07baffd6c0 Level-0 table #5: started
|
||||
2026/06/03-20:12:45.474772 7f07baffd6c0 Level-0 table #5: 3056 bytes OK
|
||||
2026/06/03-20:12:45.523202 7f07baffd6c0 Delete type=0 #3
|
||||
2026/06/03-20:12:45.523559 7f07baffd6c0 Manual compaction at level-0 from '!journal!69Da9YvF9BfOV7oK' @ 72057594037927935 : 1 .. '!journal.pages!69Da9YvF9BfOV7oK.XM0eLkgKXPyskV65' @ 0 : 0; will stop at (end)
|
||||
|
||||
+487
-6
@@ -121,11 +121,16 @@
|
||||
.dnc-employe-sheet .sheet-header.compact label {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.dnc-employe-sheet .sheet-header.compact .hp-field {
|
||||
grid-column: span 2;
|
||||
}
|
||||
.dnc-employe-sheet .sheet-header.compact input[type="number"] {
|
||||
max-width: 4.75rem;
|
||||
}
|
||||
.dnc-employe-sheet .sheet-header.compact .counter-field input[type="number"] {
|
||||
max-width: 4rem;
|
||||
.dnc-employe-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||
.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 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@@ -134,13 +139,17 @@
|
||||
.dnc-pnj-sheet .sheet-header.compact label {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.dnc-pnj-sheet .sheet-header.compact .hp-field {
|
||||
grid-column: span 2;
|
||||
}
|
||||
.dnc-pnj-sheet .sheet-header.compact input[type="number"] {
|
||||
width: 4.5rem;
|
||||
max-width: 4.5rem;
|
||||
}
|
||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[type="number"] {
|
||||
width: 3.75rem;
|
||||
max-width: 3.75rem;
|
||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.value"],
|
||||
.dnc-pnj-sheet .sheet-header.compact .counter-field input[name="system.sante.pv.max"] {
|
||||
width: 5.25rem;
|
||||
max-width: 5.25rem;
|
||||
}
|
||||
.dnc-sheet label {
|
||||
display: flex;
|
||||
@@ -262,6 +271,13 @@
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.dnc-actor-sheet .hp-field .counter-field input[type="number"] {
|
||||
max-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
.dnc-actor-sheet .hp-field .counter-field {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.dnc-actor-sheet .profile-card-wide {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -556,6 +572,126 @@
|
||||
.dnc-roll-dialog .window-content {
|
||||
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 {
|
||||
position: relative;
|
||||
border: 2px solid #5b4634;
|
||||
@@ -750,9 +886,31 @@
|
||||
.chat-targeting {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
margin-top: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: end;
|
||||
}
|
||||
.chat-targeting .chat-action-button {
|
||||
padding: 0.35rem 0.65rem;
|
||||
min-height: 1.75rem;
|
||||
font-size: 0.82rem;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.dnc-chat-card-damage .chat-actions-inline {
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.dnc-chat-card-damage .chat-actions-inline .chat-action-button {
|
||||
padding: 0.3rem 0.55rem;
|
||||
min-height: 1.65rem;
|
||||
font-size: 0.78rem;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.dnc-chat-card-damage .chat-actions-inline .chat-action-button i {
|
||||
font-size: 0.85rem;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
.chat-control {
|
||||
display: grid;
|
||||
@@ -954,5 +1112,328 @@
|
||||
.initiative-sync p {
|
||||
margin: 0.4rem 0 0;
|
||||
}
|
||||
.dnc-employes-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem;
|
||||
min-width: 640px;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.dnc-employes-dialog .sheet-header {
|
||||
display: block;
|
||||
grid-template-columns: none;
|
||||
gap: normal;
|
||||
}
|
||||
.dnc-employes-dialog .sheet-header h1 {
|
||||
font-family: "IM Fell English SC", "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||
font-size: 1.4rem;
|
||||
color: #8b2e17;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.dnc-employes-dialog-wrapper .dialog-content,
|
||||
.dnc-employes-dialog-wrapper .window-content {
|
||||
padding: 0;
|
||||
}
|
||||
.dnc-employes-dialog-wrapper .dialog-footer {
|
||||
display: none;
|
||||
}
|
||||
.dnc-root-tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
border-bottom: 2px solid #5b4634;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
.dnc-root-tab {
|
||||
border: 1px solid rgba(91, 70, 52, 0.55);
|
||||
border-radius: 10px 10px 0 0;
|
||||
background: #e2d0b1;
|
||||
color: #221b18;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
padding: 0.4rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.dnc-root-tab i {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.dnc-root-tab.active {
|
||||
background: #8b2e17;
|
||||
border-color: #561d0e;
|
||||
color: #fff;
|
||||
box-shadow: 0 -3px 8px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
bottom: -2px;
|
||||
}
|
||||
.dnc-root-tab:hover:not(.active) {
|
||||
background: #e2d0b1;
|
||||
border-color: rgba(91, 70, 52, 0.75);
|
||||
}
|
||||
.dnc-root-panels {
|
||||
position: relative;
|
||||
}
|
||||
.dnc-root-panel {
|
||||
display: none;
|
||||
}
|
||||
.dnc-root-panel.active {
|
||||
display: block;
|
||||
}
|
||||
.dnc-pc-tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.4rem 0;
|
||||
border-bottom: 1px solid rgba(91, 70, 52, 0.4);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.dnc-pc-tab {
|
||||
border: 1px solid rgba(91, 70, 52, 0.45);
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
color: #6d5a4f;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
padding: 0.25rem 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dnc-pc-tab.active {
|
||||
background: #8b2e17;
|
||||
border-color: #5f2010;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
.dnc-pc-tab:hover:not(.active) {
|
||||
background: #e2d0b1;
|
||||
color: #221b18;
|
||||
}
|
||||
.dnc-pc-panels {
|
||||
position: relative;
|
||||
}
|
||||
.dnc-pc-panel,
|
||||
.dnc-client-panel {
|
||||
display: none;
|
||||
}
|
||||
.dnc-pc-panel.active,
|
||||
.dnc-client-panel.active {
|
||||
display: block;
|
||||
}
|
||||
.dnc-compact-section {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.4rem;
|
||||
border: 1px solid rgba(91, 70, 52, 0.4);
|
||||
border-radius: 10px;
|
||||
background: #f1e5d0;
|
||||
}
|
||||
.dnc-compact-section + .dnc-compact-section {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
.dnc-compact-section-title {
|
||||
font-family: "IM Fell English SC", "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: #8b2e17;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin: 0 0 0.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
border-bottom: 1px solid rgba(91, 70, 52, 0.35);
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
.dnc-compact-section-title i {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.dnc-compact-concept {
|
||||
font-size: 0.8rem;
|
||||
color: #6d5a4f;
|
||||
font-style: italic;
|
||||
margin: 0.25rem 0 0;
|
||||
}
|
||||
.dnc-compact-carac-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.dnc-carac-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 48px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: 1px solid rgba(91, 70, 52, 0.4);
|
||||
border-radius: 6px;
|
||||
padding: 0.25rem 0.4rem;
|
||||
}
|
||||
.dnc-carac-label {
|
||||
font-size: 0.68rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #6d5a4f;
|
||||
font-weight: 700;
|
||||
}
|
||||
.dnc-carac-value {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #221b18;
|
||||
}
|
||||
.dnc-carac-value.warning {
|
||||
color: #842c2c;
|
||||
}
|
||||
.dnc-compact-combat-stats {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.dnc-combat-badge {
|
||||
font-size: 0.82rem;
|
||||
padding: 0.25rem 0.4rem;
|
||||
background: #e2d0b1;
|
||||
border: 1px solid rgba(91, 70, 52, 0.4);
|
||||
border-radius: 6px;
|
||||
color: #221b18;
|
||||
}
|
||||
.dnc-compact-item-list {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
.dnc-compact-subhead {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #6d5a4f;
|
||||
margin: 0 0 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.dnc-compact-subhead i {
|
||||
color: #8b2e17;
|
||||
}
|
||||
.dnc-compact-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0;
|
||||
border-top: 1px dashed rgba(91, 70, 52, 0.35);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.dnc-compact-item:first-of-type {
|
||||
border-top: 0;
|
||||
}
|
||||
.dnc-compact-item-name {
|
||||
font-weight: 600;
|
||||
color: #221b18;
|
||||
}
|
||||
.dnc-compact-item-detail {
|
||||
font-size: 0.78rem;
|
||||
color: #6d5a4f;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 6px;
|
||||
padding: 0.05rem 0.3rem;
|
||||
}
|
||||
.dnc-compact-magic-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.item-meta {
|
||||
border-radius: 999px;
|
||||
background: #e2d0b1;
|
||||
color: #221b18;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
}
|
||||
.client-summary {
|
||||
margin: 0.4rem 0 0;
|
||||
color: #221b18;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.client-description {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid rgba(91, 70, 52, 0.45);
|
||||
}
|
||||
.client-description p {
|
||||
margin: 0;
|
||||
color: #6d5a4f;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.dnc-roll-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
font-size: 0.78rem;
|
||||
font-family: "Signika", sans-serif;
|
||||
color: #221b18;
|
||||
background: #e2d0b1;
|
||||
border: 1px solid rgba(91, 70, 52, 0.6);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.dnc-roll-btn i {
|
||||
font-size: 0.7rem;
|
||||
color: #8b2e17;
|
||||
}
|
||||
.dnc-roll-btn:hover {
|
||||
background: #8b2e17;
|
||||
color: #fff;
|
||||
border-color: #8b2e17;
|
||||
}
|
||||
.dnc-roll-btn:hover i {
|
||||
color: #fff;
|
||||
}
|
||||
.dnc-carac-rollable {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
}
|
||||
.dnc-carac-rollable:hover {
|
||||
background: rgba(139, 46, 23, 0.18);
|
||||
}
|
||||
.dnc-carac-rollable:hover .dnc-carac-label {
|
||||
color: #8b2e17;
|
||||
}
|
||||
.dnc-compact-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.dnc-compact-section-title .dnc-open-sheet-btn {
|
||||
margin-left: auto;
|
||||
background: none;
|
||||
border: 1px solid rgba(91, 70, 52, 0.5);
|
||||
border-radius: 6px;
|
||||
color: #6d5a4f;
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.35rem;
|
||||
line-height: 1;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.dnc-compact-section-title .dnc-open-sheet-btn:hover {
|
||||
color: #8b2e17;
|
||||
border-color: #8b2e17;
|
||||
}
|
||||
/*# sourceMappingURL=donjon-et-cie.css.map */
|
||||
/*# sourceMappingURL=donjon-et-cie.css.map */
|
||||
|
||||
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>
|
||||
</div>
|
||||
</label>
|
||||
<label class="with-controls">
|
||||
<label class="with-controls hp-field" style="grid-column: span 2;">
|
||||
<span>PV</span>
|
||||
<div class="counter-field">
|
||||
<button type="button" data-action="adjustCounter" data-path="system.sante.pv.value" data-delta="-1">-</button>
|
||||
@@ -46,10 +46,6 @@
|
||||
<span>Attaques distance</span>
|
||||
<input type="number" name="system.combat.attaquesDistance" value="{{system.combat.attaquesDistance}}">
|
||||
</label>
|
||||
<label>
|
||||
<span>Bonus degats</span>
|
||||
<input type="number" name="system.combat.degatsBonus" value="{{system.combat.degatsBonus}}">
|
||||
</label>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -93,7 +89,8 @@
|
||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{#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 class="item-actions">
|
||||
{{#if this.canRoll}}
|
||||
@@ -138,7 +135,8 @@
|
||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{#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 class="item-actions">
|
||||
{{#if this.canRoll}}
|
||||
@@ -234,7 +232,8 @@
|
||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{#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 class="item-actions">
|
||||
{{#if this.canRoll}}
|
||||
@@ -278,7 +277,8 @@
|
||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{#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 class="item-actions">
|
||||
{{#if this.canRoll}}
|
||||
@@ -395,7 +395,8 @@
|
||||
{{#if this.protectionLabel}}<span class="item-meta">{{this.protectionLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{#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 class="item-actions">
|
||||
{{#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>
|
||||
</div>
|
||||
</label>
|
||||
<label class="with-controls">
|
||||
<label class="with-controls hp-field" style="grid-column: span 2;">
|
||||
<span>PV</span>
|
||||
<div class="counter-field">
|
||||
<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}}
|
||||
</div>
|
||||
{{#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 class="item-actions">
|
||||
{{#if this.canRoll}}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="chat-pill-row">
|
||||
{{#if modeLabel}}<span class="chat-pill">{{modeLabel}}</span>{{/if}}
|
||||
<span class="chat-pill success">Dé {{keptDieLabel}}</span>
|
||||
{{#if bonus}}<span class="chat-pill">Bonus +{{bonus}}</span>{{/if}}
|
||||
{{#if damageCapped}}<span class="chat-pill">{{localize "DNC.Chat.DamageCapped" damage=effectiveDamage dv=martialDvLabel}}</span>{{/if}}
|
||||
</div>
|
||||
<p class="chat-formula">{{formula}}</p>
|
||||
{{#if rollDieLabels.[1]}}
|
||||
@@ -22,14 +22,14 @@
|
||||
<p class="roll-values">{{#each rollDieLabels}}<span>{{this}}</span>{{/each}}</p>
|
||||
</div>
|
||||
{{/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}}</p>
|
||||
<div class="chat-targeting">
|
||||
<label class="chat-control">
|
||||
<span class="chat-keyline-label">Cible</span>
|
||||
<select class="chat-select" data-role="damage-target" {{#unless hasTargets}}disabled{{/unless}}>
|
||||
{{#if hasTargets}}
|
||||
{{#each targets}}
|
||||
<option value="{{this.tokenUuid}}">{{this.label}}</option>
|
||||
<option value="{{this.tokenUuid}}"{{#if this.isSelected}} selected{{/if}}>{{this.label}}</option>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<option value="">Aucune cible sur la scène</option>
|
||||
@@ -45,4 +45,11 @@
|
||||
</button>
|
||||
</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>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
</header>
|
||||
|
||||
<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>
|
||||
{{#if missingCount}}<span class="chat-pill failure">{{localize "DNC.Macro.MissionPack.ItemsMissing"}} {{missingCount}}</span>{{/if}}
|
||||
</div>
|
||||
@@ -27,8 +30,33 @@
|
||||
{{#each draws}}
|
||||
<li>
|
||||
<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}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</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>
|
||||
|
||||
@@ -28,7 +28,20 @@
|
||||
<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"}}
|
||||
</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>
|
||||
{{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}}
|
||||
<ul class="chat-details chat-details-ornate">
|
||||
{{#each details}}
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
</div>
|
||||
{{/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 exhausted}}<p class="warning">{{localize "DNC.Chat.ResourceExhausted"}}</p>{{/if}}
|
||||
{{#if isAmmunition}}
|
||||
{{#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>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<div class="dnc-dialog-form">
|
||||
<p><strong>{{item.name}}</strong> inflige <strong>{{item.system.degats}}</strong>.</p>
|
||||
<p>{{localize "DNC.Dialog.ActorDamageBonus"}} : <strong>{{actorBonus}}</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}}
|
||||
<label>
|
||||
<span>{{localize "DNC.UI.Mode"}}</span>
|
||||
<select name="mode">
|
||||
<option value="normal">{{localize "DNC.UI.ModeNormal"}}</option>
|
||||
<option value="avantage">{{localize "DNC.UI.ModeAdvantage"}}</option>
|
||||
<option value="desavantage">{{localize "DNC.UI.ModeDisadvantage"}}</option>
|
||||
<option value="normal" {{#if (eq defaultMode "normal")}}selected{{/if}}>{{localize "DNC.UI.ModeNormal"}}</option>
|
||||
<option value="avantage" {{#if (eq defaultMode "avantage")}}selected{{/if}}>{{localize "DNC.UI.ModeAdvantage"}}</option>
|
||||
<option value="desavantage" {{#if (eq defaultMode "desavantage")}}selected{{/if}}>{{localize "DNC.UI.ModeDisadvantage"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
<div class="dnc-employes-dialog" role="region" aria-labelledby="employes-title">
|
||||
<header class="sheet-header">
|
||||
<h1 id="employes-title">{{localize "DNC.Dialog.EmployeesTitle"}}</h1>
|
||||
</header>
|
||||
|
||||
<!-- ===== ONGLETS RACINE ===== -->
|
||||
<nav class="dnc-root-tabs" role="tablist" aria-label="{{localize 'DNC.Dialog.EmployeesTitle'}}">
|
||||
<button type="button"
|
||||
class="dnc-root-tab active"
|
||||
data-root-tab="employes"
|
||||
role="tab"
|
||||
aria-selected="true"
|
||||
aria-controls="root-panel-employes"
|
||||
id="root-tab-employes"
|
||||
>
|
||||
<i class="fa-solid fa-hard-hat" aria-hidden="true"></i> {{localize "DNC.Dialog.EmployeesTabEmployes"}}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="dnc-root-tab"
|
||||
data-root-tab="clients"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
aria-controls="root-panel-clients"
|
||||
id="root-tab-clients"
|
||||
>
|
||||
<i class="fa-solid fa-users" aria-hidden="true"></i> {{localize "DNC.Dialog.EmployeesTabClients"}}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="dnc-root-panels">
|
||||
|
||||
<!-- ===== ROOT PANEL : EMPLOYÉS ===== -->
|
||||
<div class="dnc-root-panel active"
|
||||
data-root-panel="employes"
|
||||
role="tabpanel"
|
||||
aria-labelledby="root-tab-employes"
|
||||
id="root-panel-employes"
|
||||
>
|
||||
{{#if pcs.length}}
|
||||
<!-- Sous-onglets par employé -->
|
||||
<nav class="dnc-pc-tabs" role="tablist" aria-label="{{localize 'DNC.Dialog.EmployeesTabEmployes'}}">
|
||||
{{#each pcs}}
|
||||
<button type="button"
|
||||
class="dnc-pc-tab{{#if @first}} active{{/if}}"
|
||||
data-pc-tab="{{this.actorId}}"
|
||||
role="tab"
|
||||
aria-selected="{{#if @first}}true{{else}}false{{/if}}"
|
||||
aria-controls="pc-panel-{{this.actorId}}"
|
||||
id="pc-tab-{{this.actorId}}"
|
||||
>
|
||||
{{this.name}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</nav>
|
||||
|
||||
<!-- Sous-panneaux par employé -->
|
||||
<div class="dnc-pc-panels">
|
||||
{{#each pcs}}
|
||||
<div class="dnc-pc-panel{{#if @first}} active{{/if}}"
|
||||
data-pc-panel="{{this.actorId}}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="pc-tab-{{this.actorId}}"
|
||||
id="pc-panel-{{this.actorId}}"
|
||||
>
|
||||
<!-- SECTION : CARACTÉRISTIQUES -->
|
||||
<section class="dnc-compact-section dnc-compact-carac">
|
||||
<h3 class="dnc-compact-section-title">
|
||||
<i class="fa-solid fa-chart-line" aria-hidden="true"></i>
|
||||
{{localize "DNC.Dialog.EmployeesTabCharacteristics"}}
|
||||
<button type="button" class="dnc-open-sheet-btn" data-open-sheet="pc" data-actor-id="{{this.actorId}}" title="Ouvrir la fiche">
|
||||
<i class="fa-solid fa-external-link-alt"></i>
|
||||
</button>
|
||||
</h3>
|
||||
<div class="dnc-compact-carac-grid">
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{localize "DNC.UI.Pv"}}</span>
|
||||
<span class="dnc-carac-value{{#if (lt this.pvValue 3)}} warning{{/if}}">{{this.pvValue}}/{{this.pvMax}}</span>
|
||||
</div>
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{localize "DNC.UI.Dv"}}</span>
|
||||
<span class="dnc-carac-value">{{this.dv}}</span>
|
||||
</div>
|
||||
{{#each ../characteristicKeys}}
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{this.short}}</span>
|
||||
<span class="dnc-carac-value">{{lookup .. this.key}}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if this.concept}}
|
||||
<p class="dnc-compact-concept">{{this.concept}}</p>
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
<!-- SECTION : COMBAT -->
|
||||
<section class="dnc-compact-section dnc-compact-combat">
|
||||
<h3 class="dnc-compact-section-title">
|
||||
<i class="fa-solid fa-sword" aria-hidden="true"></i>
|
||||
{{localize "DNC.Dialog.EmployeesTabCombat"}}
|
||||
</h3>
|
||||
<div class="dnc-compact-combat-stats">
|
||||
<span class="dnc-combat-badge">⚔️ ×{{this.meleeAttacks}} {{localize "DNC.UI.MeleeAttacks"}}</span>
|
||||
<span class="dnc-combat-badge">🏹 ×{{this.rangedAttacks}} {{localize "DNC.UI.RangedAttacks"}}</span>
|
||||
</div>
|
||||
|
||||
{{#if this.weapons.length}}
|
||||
<div class="dnc-compact-item-list">
|
||||
<h4 class="dnc-compact-subhead"><i class="fa-solid fa-sword"></i> {{localize "DNC.UI.Weapons"}}</h4>
|
||||
{{#each this.weapons}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
<span class="item-meta">{{this.categoryLabel}}</span>
|
||||
<span class="item-meta">{{this.handsLabel}}</span>
|
||||
<span class="dnc-compact-item-detail">{{localize "DNC.UI.Damage"}} {{this.damage}}</span>
|
||||
{{#if this.ammunition}}<span class="dnc-compact-item-detail">{{localize "DNC.UI.Ammunition"}} {{this.ammunition}}</span>{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.armors.length}}
|
||||
<div class="dnc-compact-item-list">
|
||||
<h4 class="dnc-compact-subhead"><i class="fa-solid fa-shield-halved"></i> {{localize "DNC.UI.Armors"}}</h4>
|
||||
{{#each this.armors}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
<span class="dnc-compact-item-detail">{{this.protectionDie}}</span>
|
||||
{{#if this.remainingProtection}}<span class="dnc-compact-item-detail">ARM {{this.remainingProtection}}</span>{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
<!-- SECTION : MAGIE -->
|
||||
<section class="dnc-compact-section dnc-compact-magie">
|
||||
<h3 class="dnc-compact-section-title">
|
||||
<i class="fa-solid fa-book-sparkles" aria-hidden="true"></i>
|
||||
{{localize "DNC.Dialog.EmployeesTabMagie"}}
|
||||
</h3>
|
||||
<div class="dnc-compact-magic-stats">
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{localize "DNC.UI.Rank"}}</span>
|
||||
<span class="dnc-carac-value">{{this.magicRank}}</span>
|
||||
</div>
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{localize "DNC.UI.Focus"}}</span>
|
||||
<span class="dnc-carac-value">{{this.focusDisplay}}</span>
|
||||
</div>
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{localize "DNC.UI.Chaos"}}</span>
|
||||
<span class="dnc-carac-value">{{this.chaosDisplay}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.spells.length}}
|
||||
<div class="dnc-compact-item-list">
|
||||
<h4 class="dnc-compact-subhead"><i class="fa-solid fa-book-sparkles"></i> Sortilèges</h4>
|
||||
{{#each this.spells}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
{{#if this.usageLabel}}<span class="item-meta">{{this.usageLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.capacities.length}}
|
||||
<div class="dnc-compact-item-list">
|
||||
<h4 class="dnc-compact-subhead"><i class="fa-solid fa-brain"></i> Capacités</h4>
|
||||
{{#each this.capacities}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
{{#if this.usageLabel}}<span class="item-meta">{{this.usageLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
<p class="empty-state">{{localize "DNC.Empty.NoEmployees"}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<!-- ===== ROOT PANEL : CLIENTS ===== -->
|
||||
<div class="dnc-root-panel"
|
||||
data-root-panel="clients"
|
||||
role="tabpanel"
|
||||
aria-labelledby="root-tab-clients"
|
||||
id="root-panel-clients"
|
||||
>
|
||||
{{#if clients.length}}
|
||||
<!-- Sous-onglets par client -->
|
||||
<nav class="dnc-pc-tabs dnc-client-tabs" role="tablist" aria-label="{{localize 'DNC.Dialog.EmployeesTabClients'}}">
|
||||
{{#each clients}}
|
||||
<button type="button"
|
||||
class="dnc-pc-tab{{#if @first}} active{{/if}}"
|
||||
data-client-tab="{{this.id}}"
|
||||
role="tab"
|
||||
aria-selected="{{#if @first}}true{{else}}false{{/if}}"
|
||||
aria-controls="client-panel-{{this.id}}"
|
||||
id="client-tab-{{this.id}}"
|
||||
>
|
||||
{{this.name}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</nav>
|
||||
|
||||
<!-- Sous-panneaux par client -->
|
||||
<div class="dnc-pc-panels">
|
||||
{{#each clients}}
|
||||
<div class="dnc-client-panel{{#if @first}} active{{/if}}"
|
||||
data-client-panel="{{this.id}}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="client-tab-{{this.id}}"
|
||||
id="client-panel-{{this.id}}"
|
||||
>
|
||||
<!-- SECTION : IDENTITÉ + SANTÉ -->
|
||||
<section class="dnc-compact-section">
|
||||
<h3 class="dnc-compact-section-title">
|
||||
<i class="fa-solid fa-id-card" aria-hidden="true"></i>
|
||||
{{this.name}}
|
||||
<button type="button" class="dnc-open-sheet-btn" data-open-sheet="client" data-token-uuid="{{this.tokenUuid}}" data-actor-id="{{this.id}}" title="Ouvrir la fiche">
|
||||
<i class="fa-solid fa-external-link-alt"></i>
|
||||
</button>
|
||||
</h3>
|
||||
<div class="dnc-compact-carac-grid">
|
||||
{{#if this.species}}<div class="dnc-carac-stat"><span class="dnc-carac-label">Espèce</span><span class="dnc-carac-value">{{this.species}}</span></div>{{/if}}
|
||||
{{#if this.category}}<div class="dnc-carac-stat"><span class="dnc-carac-label">Catégorie</span><span class="dnc-carac-value">{{this.category}}</span></div>{{/if}}
|
||||
{{#if this.role}}<div class="dnc-carac-stat"><span class="dnc-carac-label">Rôle</span><span class="dnc-carac-value">{{this.role}}</span></div>{{/if}}
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{localize "DNC.UI.Pv"}}</span>
|
||||
<span class="dnc-carac-value{{#if (lt this.pvValue 3)}} warning{{/if}}">{{this.pvValue}}/{{this.pvMax}}</span>
|
||||
</div>
|
||||
<div class="dnc-carac-stat">
|
||||
<span class="dnc-carac-label">{{localize "DNC.UI.Dv"}}</span>
|
||||
<span class="dnc-carac-value">{{this.dv}}</span>
|
||||
</div>
|
||||
{{#if this.armureDelta}}
|
||||
<div class="dnc-carac-stat dnc-carac-rollable"
|
||||
data-pnj-action="rollArmure"
|
||||
data-token-uuid="{{this.tokenUuid}}"
|
||||
data-actor-id="{{this.id}}"
|
||||
title="Lancer armure Δ{{this.armureDelta}}"
|
||||
>
|
||||
<span class="dnc-carac-label">Armure <i class="fa-solid fa-dice" style="font-size:0.6rem"></i></span>
|
||||
<span class="dnc-carac-value">Δ{{this.armureDelta}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.courageDelta}}
|
||||
<div class="dnc-carac-stat dnc-carac-rollable"
|
||||
data-pnj-action="rollCourage"
|
||||
data-token-uuid="{{this.tokenUuid}}"
|
||||
data-actor-id="{{this.id}}"
|
||||
title="Lancer courage Δ{{this.courageDelta}}"
|
||||
>
|
||||
<span class="dnc-carac-label">Courage <i class="fa-solid fa-dice" style="font-size:0.6rem"></i></span>
|
||||
<span class="dnc-carac-value">Δ{{this.courageDelta}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if this.summary}}<p class="client-summary">{{this.summary}}</p>{{/if}}
|
||||
</section>
|
||||
|
||||
<!-- SECTION : ATTAQUES -->
|
||||
{{#if this.attaques.length}}
|
||||
<section class="dnc-compact-section dnc-compact-combat">
|
||||
<h3 class="dnc-compact-section-title">
|
||||
<i class="fa-solid fa-sword" aria-hidden="true"></i>
|
||||
{{localize "DNC.Dialog.EmployeesTabCombat"}}
|
||||
</h3>
|
||||
<div class="dnc-compact-item-list">
|
||||
{{#each this.attaques}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.nom}}</span>
|
||||
<button type="button"
|
||||
class="dnc-roll-btn"
|
||||
data-pnj-action="rollAttaque"
|
||||
data-attack-index="{{@index}}"
|
||||
data-token-uuid="{{../tokenUuid}}"
|
||||
data-actor-id="{{../id}}"
|
||||
><i class="fa-solid fa-dice"></i> {{this.degats}}</button>
|
||||
{{#if this.notes}}<span class="dnc-compact-item-detail">{{this.notes}}</span>{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if this.weapons.length}}
|
||||
<div class="dnc-compact-item-list" style="margin-top:0.5rem">
|
||||
<h4 class="dnc-compact-subhead"><i class="fa-solid fa-sword"></i> {{localize "DNC.UI.Weapons"}}</h4>
|
||||
{{#each this.weapons}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
<span class="item-meta">{{this.categoryLabel}}</span>
|
||||
<span class="dnc-compact-item-detail">{{this.damage}}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
{{else if this.weapons.length}}
|
||||
<section class="dnc-compact-section dnc-compact-combat">
|
||||
<h3 class="dnc-compact-section-title">
|
||||
<i class="fa-solid fa-sword" aria-hidden="true"></i>
|
||||
{{localize "DNC.Dialog.EmployeesTabCombat"}}
|
||||
</h3>
|
||||
<div class="dnc-compact-item-list">
|
||||
{{#each this.weapons}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
<span class="item-meta">{{this.categoryLabel}}</span>
|
||||
<span class="dnc-compact-item-detail">{{this.damage}}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<!-- SECTION : MAGIE / CAPACITÉS -->
|
||||
{{#if this.hasMagie}}
|
||||
<section class="dnc-compact-section dnc-compact-magie">
|
||||
<h3 class="dnc-compact-section-title">
|
||||
<i class="fa-solid fa-book-sparkles" aria-hidden="true"></i>
|
||||
{{localize "DNC.Dialog.EmployeesTabMagie"}}
|
||||
</h3>
|
||||
{{#if this.spells.length}}
|
||||
<div class="dnc-compact-item-list">
|
||||
<h4 class="dnc-compact-subhead"><i class="fa-solid fa-book-sparkles"></i> Sortilèges</h4>
|
||||
{{#each this.spells}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
{{#if this.usageLabel}}<span class="item-meta">{{this.usageLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.capacities.length}}
|
||||
<div class="dnc-compact-item-list">
|
||||
<h4 class="dnc-compact-subhead"><i class="fa-solid fa-brain"></i> Capacités</h4>
|
||||
{{#each this.capacities}}
|
||||
<div class="dnc-compact-item">
|
||||
<span class="dnc-compact-item-name">{{this.name}}</span>
|
||||
{{#if this.usageLabel}}<span class="item-meta">{{this.usageLabel}}</span>{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.pouvoirsSpeciaux}}
|
||||
<div class="client-description">{{{this.pouvoirsSpeciaux}}}</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
<p class="empty-state">{{localize "DNC.Empty.NoClients"}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -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">
|
||||
<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>
|
||||
<span>{{localize "DNC.Macro.MissionPack.DialogActor"}}</span>
|
||||
<select name="actorId">
|
||||
{{selectOptions actorOptions selected=selectedActorId labelAttr="label" valueAttr="value" localize=false}}
|
||||
</select>
|
||||
</label>
|
||||
{{#if isCampaign}}
|
||||
<p class="dnc-mission-pack-note">{{localize "DNC.Macro.MissionPack.CampaignDialogLead"}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -19,8 +19,23 @@
|
||||
<p>{{weaponCharacteristicLabel}}</p>
|
||||
</div>
|
||||
<label>
|
||||
<span>Degats</span>
|
||||
<input type="text" name="system.degats" value="{{system.degats}}">
|
||||
<span>Degats usage die (Δ)</span>
|
||||
<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>
|
||||
<span>Portee</span>
|
||||
|
||||
Reference in New Issue
Block a user