Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d499f60386 | ||
|
|
1c7e05bae6 | ||
|
|
06603ceff7 | ||
|
|
ca74ff4a88 | ||
|
|
b0a3aff648 | ||
|
|
3cb96410b9 | ||
|
|
ad4daf0b00 | ||
|
|
c96e8d69d7 | ||
|
|
282fb2794a | ||
|
|
46b9512256 | ||
|
|
8316112741 | ||
|
|
75f39551f8 | ||
|
|
ef76ae1116 |
+2
-1
@@ -34,7 +34,8 @@ module.exports = {
|
||||
Dialog: 'readonly',
|
||||
foundry: 'readonly',
|
||||
Handlebars: 'readonly',
|
||||
renderTemplate: 'readonly'
|
||||
renderTemplate: 'readonly',
|
||||
fromUuid: 'readonly'
|
||||
},
|
||||
rules: {
|
||||
// Possible Problems
|
||||
|
||||
+42
-1
@@ -304,7 +304,20 @@
|
||||
"die_successes": "successes",
|
||||
"total": "Total",
|
||||
"totem_dice": "Totem Dice",
|
||||
"totem_hint": "Totem Hint",
|
||||
"totem_hint": "Check to use totem dice (double success possible)",
|
||||
"totem_neutral": "Neutral",
|
||||
"totem_manage": "Manage Totem Dice (Instincts / Interdits)",
|
||||
"totem_serious": "Serious act (2 dice)",
|
||||
"totem_manage_hint": "Instincts: obeying the Human's Instincts gains 1 Human die; another Totem's Instincts gain 1 Adapted die. Interdit: breaking a Human Interdit loses 1 Human die (or gains 1 Adapted die if none). Max 3 dice of the same Totem and 5 dice total.",
|
||||
"totem_instinct_human": "Human Instinct (+1 Human die)",
|
||||
"totem_instinct_adapted": "Another Totem's Instinct (+1 Adapted die)",
|
||||
"totem_interdit_human": "Human Interdit (-1 Human die)",
|
||||
"totem_gained": "{totem} +1D",
|
||||
"totem_gained_adapted": "+1D Adapted",
|
||||
"totem_exchanged": "{totem} gained in exchange (1 opposite die removed)",
|
||||
"totem_replaced": "Opposite die removed instead (max 3D reached)",
|
||||
"totem_lost": "-1 Human die",
|
||||
"totem_limit_reached": "Totem dice limit reached.",
|
||||
"FightTool": "Fight Tool",
|
||||
"RollTool": "Roll Tool",
|
||||
"Roll4Fight": "Roll for Fight",
|
||||
@@ -374,8 +387,36 @@
|
||||
"trauma_reward": "Chosen trauma",
|
||||
"trauma_reward_5": "Trauma reward (+5D)",
|
||||
"evolution_dice": "Evolution dice",
|
||||
"buy_evolution": "Buy an Adaptation/Mutation",
|
||||
"evolution_name": "Name",
|
||||
"evolution_level": "Level (cost in Evolution dice)",
|
||||
"evolution_mutation": "Mutation",
|
||||
"evolution_adaptation": "Adaptation",
|
||||
"evolution_available_dice": "Accumulated Evolution dice",
|
||||
"evolution_total": "Total levels (after purchase)",
|
||||
"evolution_mode_max": "Game mode maximum",
|
||||
"game_mode": "Game mode",
|
||||
"mode_survie": "Survival",
|
||||
"mode_cauchemar": "Nightmare",
|
||||
"mode_apocalypse": "Apocalypse",
|
||||
"evolution_buy_hint": "The purchase consumes and erases all accumulated Evolution dice. Limits: Survival 5D, Nightmare 6D, Apocalypse 7D total. Level 3/4 Mutations are forbidden in Survival; only one level 4 Mutation.",
|
||||
"buy_evolution_done": "Adaptation/Mutation acquired. Evolution dice erased.",
|
||||
"error_evolution_no_name": "Provide a name for the Adaptation/Mutation.",
|
||||
"error_evolution_invalid_level": "Invalid level (1 to 4).",
|
||||
"error_evolution_not_enough_dice": "Not enough Evolution dice for this level.",
|
||||
"error_evolution_mode_limit": "Game mode maximum Evolution dice reached.",
|
||||
"error_evolution_mutation_mode": "Level 3/4 Mutations require Nightmare or Apocalypse modes.",
|
||||
"error_evolution_mutation_4d": "The character already has a level 4 Mutation.",
|
||||
"error_evolution_create": "Could not create the Adaptation/Mutation.",
|
||||
"open_experience_phase": "Experience phase",
|
||||
"open_learning": "Learning",
|
||||
"validate_phase": "Validate phase",
|
||||
"validate_phase_done": "Experience phase validated: learning limits reset.",
|
||||
"error_not_editable": "You do not have permission to modify this element.",
|
||||
"vehicle_assign": "Attach vehicle",
|
||||
"vehicle_assign_confirm": "This vehicle already belongs to \"{group}\". Attach it to this group?",
|
||||
"vehicle_assign_failed": "Could not attach this vehicle to the group.",
|
||||
"vehicle_import_failed": "Could not import this vehicle from the compendium.",
|
||||
"learning_limits": "Learning limits (per phase)",
|
||||
"learned_skill": "Skill",
|
||||
"learned_specialty": "Specialty",
|
||||
|
||||
@@ -302,6 +302,19 @@
|
||||
"totem_dice": "Dés de totem",
|
||||
"keep_totem": "Garder le totem",
|
||||
"totem_hint": "Cochez pour utiliser les dés de totem (double réussite possible)",
|
||||
"totem_neutral": "Neutre",
|
||||
"totem_manage": "Gérer les Dés de Totems (Instincts / Interdits)",
|
||||
"totem_serious": "Acte grave (2 dés)",
|
||||
"totem_manage_hint": "Instincts : obéir aux Instincts de l'Humain gagne 1D Humain ; ceux d'un autre Totem gagnent 1D Adapté. Interdit : briser un Interdit de l'Humain fait perdre 1D Humain (ou gagner 1D Adapté si aucun). Max 3 Dés du même Totem et 5 Dés au total.",
|
||||
"totem_instinct_human": "Instinct de l'Humain (+1D Humain)",
|
||||
"totem_instinct_adapted": "Instinct d'un autre Totem (+1D Adapté)",
|
||||
"totem_interdit_human": "Interdit de l'Humain (-1D Humain)",
|
||||
"totem_gained": "{totem} +1D",
|
||||
"totem_gained_adapted": "+1D Adapté",
|
||||
"totem_exchanged": "{totem} gagné en échange (1D opposé retiré)",
|
||||
"totem_replaced": "Dé opposé retiré à la place (max 3D atteint)",
|
||||
"totem_lost": "-1D Humain",
|
||||
"totem_limit_reached": "Limite de Dés de Totems atteinte.",
|
||||
"attack": "Attaque",
|
||||
"attack_contact": "Attaque au contact",
|
||||
"attack_ranged": "Attaque à distance",
|
||||
@@ -357,8 +370,36 @@
|
||||
"trauma_reward": "Traumatisme choisi",
|
||||
"trauma_reward_5": "Récompense de Traumatisme (+5D)",
|
||||
"evolution_dice": "Dés d'Évolution",
|
||||
"buy_evolution": "Acheter une Adaptation/Mutation",
|
||||
"evolution_name": "Nom",
|
||||
"evolution_level": "Niveau (coût en Dés d'Évolution)",
|
||||
"evolution_mutation": "Mutation",
|
||||
"evolution_adaptation": "Adaptation",
|
||||
"evolution_available_dice": "Dés d'Évolution accumulés",
|
||||
"evolution_total": "Total des niveaux (après achat)",
|
||||
"evolution_mode_max": "Maximum du Mode de jeu",
|
||||
"game_mode": "Mode de jeu",
|
||||
"mode_survie": "Survie",
|
||||
"mode_cauchemar": "Cauchemar",
|
||||
"mode_apocalypse": "Apocalypse",
|
||||
"evolution_buy_hint": "L'achat consomme et efface tous les Dés d'Évolution accumulés. Limites : Survie 5D, Cauchemar 6D, Apocalypse 7D au total. Mutations 3D/4D interdites en Mode Survie ; une seule Mutation à 4D.",
|
||||
"buy_evolution_done": "Adaptation/Mutation acquise. Dés d'Évolution effacés.",
|
||||
"error_evolution_no_name": "Indiquez un nom pour l'Adaptation/Mutation.",
|
||||
"error_evolution_invalid_level": "Niveau invalide (1 à 4).",
|
||||
"error_evolution_not_enough_dice": "Dés d'Évolution insuffisants pour ce niveau.",
|
||||
"error_evolution_mode_limit": "Maximum de Dés d'Évolution du Mode de jeu atteint.",
|
||||
"error_evolution_mutation_mode": "Les Mutations de Niveau 3/4 nécessitent les Modes Cauchemar ou Apocalypse.",
|
||||
"error_evolution_mutation_4d": "Le personnage possède déjà une Mutation à 4D.",
|
||||
"error_evolution_create": "Impossible de créer l'Adaptation/Mutation.",
|
||||
"open_experience_phase": "Phase d'Expérience",
|
||||
"open_learning": "Apprentissage",
|
||||
"validate_phase": "Valider la phase",
|
||||
"validate_phase_done": "Phase d'Expérience validée : limites d'apprentissage réinitialisées.",
|
||||
"error_not_editable": "Vous n'avez pas la permission de modifier cet élément.",
|
||||
"vehicle_assign": "Attacher le véhicule",
|
||||
"vehicle_assign_confirm": "Ce véhicule appartient déjà à « {group} ». L'attacher à ce Groupe ?",
|
||||
"vehicle_assign_failed": "Impossible d'attacher ce véhicule au Groupe.",
|
||||
"vehicle_import_failed": "Impossible d'importer ce véhicule depuis le compendium.",
|
||||
"learning_limits": "Limites d'apprentissage (par phase)",
|
||||
"learned_skill": "Compétence",
|
||||
"learned_specialty": "Spécialité",
|
||||
|
||||
+51
-3
@@ -21,7 +21,7 @@
|
||||
|
||||
input:not([type="radio"]):not([type="checkbox"]),
|
||||
select, textarea {
|
||||
color: @color-text-light-0;
|
||||
color: #333;
|
||||
line-height: 1.2;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
@@ -368,6 +368,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Fiche de Groupe : mêmes dés de Réserve cliquables (AppV2).
|
||||
&.group div.hexa {
|
||||
.hexa-style();
|
||||
.transition();
|
||||
margin: 0.2rem;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
.hexa-style(rgb(0, 0, 0), rgba(0, 0, 0, 0.288));
|
||||
&:hover { .hexa-style(rgb(43, 43, 43), rgba(0, 0, 0, 0.288)); }
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-header-toggle {
|
||||
text-align: right;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -669,6 +696,27 @@
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
}
|
||||
|
||||
.evolution-badge {
|
||||
margin-left: 6px;
|
||||
padding: 1px 6px;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
text-transform: uppercase;
|
||||
border-radius: 3px;
|
||||
|
||||
&.adaptation {
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-acid-green, 25%);
|
||||
border: 1px solid @color-acid-green;
|
||||
}
|
||||
|
||||
&.mutation {
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-hemolymph, 30%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── V2 tab navigation — chitin-plate style ─────────────────────────
|
||||
@@ -679,7 +727,7 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: @color-chitin-dark;
|
||||
border-bottom: 2px solid @color-amber;
|
||||
border-bottom: 2px solid @color-hemolymph;
|
||||
min-height: 36px;
|
||||
|
||||
> a {
|
||||
@@ -703,7 +751,7 @@
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(180deg, @color-amber 0%, @color-honeycomb 100%);
|
||||
background: @color-honeycomb;
|
||||
color: @color-chitin-dark;
|
||||
|
||||
&::after {
|
||||
|
||||
@@ -69,14 +69,14 @@
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
|
||||
.community-size-info {
|
||||
margin: 6px 0 0;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
margin: 0;
|
||||
font-size: @font-size-11;
|
||||
font-style: italic;
|
||||
color: @color-text-light-0;
|
||||
opacity: 0.7;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
// Jauges de Domaines
|
||||
@@ -111,7 +111,7 @@
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
@@ -123,7 +123,7 @@
|
||||
.gauge-value {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
font-size: @font-size-16;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-header;
|
||||
}
|
||||
|
||||
.gauge-state {
|
||||
@@ -134,7 +134,7 @@
|
||||
}
|
||||
|
||||
&.crisis { border-left-color: @color-hemolymph; .gauge-state { color: @color-hemolymph; } }
|
||||
&.low { border-left-color: @color-amber; .gauge-state { color: @color-amber; } }
|
||||
&.low { border-left-color: @color-honeycomb; .gauge-state { color: @color-honeycomb; } }
|
||||
&.stable { border-left-color: @color-acid-green; .gauge-state { color: @color-acid-green; } }
|
||||
&.opulent { border-left-color: @theme-color-accent; .gauge-state { color: @theme-color-accent; } }
|
||||
}
|
||||
@@ -145,8 +145,8 @@
|
||||
font-weight: 700;
|
||||
|
||||
&.high { color: @color-acid-green; }
|
||||
&.normal { color: @color-text-light-0; }
|
||||
&.low { color: @color-amber; }
|
||||
&.normal { color: @color-text-dark-secondary; }
|
||||
&.low { color: @color-honeycomb; }
|
||||
&.crisis { color: @color-hemolymph; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
.capacity-type {
|
||||
flex: 0 0 auto;
|
||||
font-size: @font-size-12;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
color: @color-amber;
|
||||
// Brun foncé lisible sur le fond beige de la fiche (le jaune était illisible).
|
||||
color: @color-chitin-dark;
|
||||
margin: 8px 0 4px;
|
||||
text-align: left;
|
||||
border-bottom: none;
|
||||
|
||||
+3
-3
@@ -71,13 +71,13 @@
|
||||
}
|
||||
|
||||
.npc-roll-btn {
|
||||
color: @color-text-light-primary;
|
||||
opacity: 0.7;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.75;
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: @color-text-light-1;
|
||||
color: @theme-color-dark;
|
||||
text-shadow: 0 0 6px @theme-color-highlight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle « Chaman » de l'onglet Totem : ne pas étirer sur toute la largeur.
|
||||
.chaman-toggle {
|
||||
width: fit-content;
|
||||
margin: 0.5rem auto;
|
||||
gap: 0.5rem;
|
||||
|
||||
label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
color: @theme-color-dark;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
div.minor-totems {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -101,5 +124,37 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.totem-influence-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
margin-top: 2px;
|
||||
font-size: 0.75rem;
|
||||
|
||||
.totem-influence-label {
|
||||
color: @color-text-dark-header;
|
||||
|
||||
strong.totem-human { color: @totem-human-color; }
|
||||
strong.totem-adapted { color: @totem-adapted-color; }
|
||||
}
|
||||
|
||||
.totem-manage-btn {
|
||||
flex: none;
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-text-light-0, 30%);
|
||||
border: 1px solid @color-text-dark-secondary;
|
||||
border-radius: 3px;
|
||||
.transition();
|
||||
|
||||
&:hover {
|
||||
color: @theme-color-dark;
|
||||
background: @color-hemolymph;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
|
||||
.unit {
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-50
@@ -13,28 +13,42 @@ body.system-vermine2047 {
|
||||
}
|
||||
|
||||
// Override Foundry Core default CSS custom properties
|
||||
// for better readability on the dark Vermine2047 theme.
|
||||
// for better readability on the light Vermine2047 theme.
|
||||
|
||||
// AppV2 windows (sheets) use .themed.theme-dark on their content
|
||||
.system-vermine2047 .themed.theme-dark {
|
||||
--color-text-primary: #888888;
|
||||
--color-text-emphatic: #999999;
|
||||
--color-text-secondary: #777777;
|
||||
--color-text-subtle: #666666;
|
||||
--color-text-light-highlight: #888888;
|
||||
--color-text-light-heading: #777777;
|
||||
--color-text-light-primary: #666666;
|
||||
// Core windows (FilePicker, dialogs, compendium, RollTable, Journal, …) and chat
|
||||
// messages sit on the light box_background texture: force dark readable text.
|
||||
// The dark sidebar keeps Foundry's default light-on-dark text colors.
|
||||
body.system-vermine2047.theme-dark {
|
||||
.window-content,
|
||||
.roll-table-sheet,
|
||||
.journal-sheet,
|
||||
.document-sheet,
|
||||
.settings-sheet {
|
||||
color: @color-text-dark-primary;
|
||||
--color-text-primary: @color-text-dark-primary;
|
||||
--color-text-emphatic: @color-text-dark-header;
|
||||
--color-text-secondary: @color-text-dark-secondary;
|
||||
--color-text-subtle: #666666;
|
||||
--color-text-light-highlight: @color-acid-green;
|
||||
--color-text-light-heading: @color-text-dark-header;
|
||||
--color-text-light-primary: @color-text-dark-secondary;
|
||||
--color-text-dark-primary: @color-text-dark-primary;
|
||||
--color-text-dark-secondary: @color-text-dark-secondary;
|
||||
--color-text-dark-header: @color-text-dark-header;
|
||||
--text-color: @color-text-dark-primary;
|
||||
}
|
||||
}
|
||||
|
||||
// Old-style Application windows (settings-config, dialogs) inherit from body.theme-dark
|
||||
body.system-vermine2047.theme-dark {
|
||||
--color-text-primary: #888888;
|
||||
--color-text-emphatic: #999999;
|
||||
--color-text-secondary: #777777;
|
||||
--color-text-subtle: #666666;
|
||||
--color-text-light-highlight: #888888;
|
||||
--color-text-light-heading: #777777;
|
||||
--color-text-light-primary: #666666;
|
||||
#chat-log .message,
|
||||
.chat-log .message {
|
||||
.message-content {
|
||||
color: @color-text-dark-primary;
|
||||
}
|
||||
|
||||
header.message-header {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 5px #000;
|
||||
}
|
||||
}
|
||||
|
||||
#settings-config button,
|
||||
@@ -44,31 +58,13 @@ body.system-vermine2047.theme-dark {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
// Core sheets the system doesn't restyle (RollTable, Journal, …) inherit the
|
||||
// gray text overrides above, which are unreadable on their dark backgrounds.
|
||||
// Restore Foundry's default light-on-dark text colors for those apps (dark theme only).
|
||||
body.system-vermine2047.theme-dark {
|
||||
.roll-table-sheet,
|
||||
.journal-sheet,
|
||||
.document-sheet,
|
||||
.settings-sheet {
|
||||
--color-text-primary: var(--color-light-2);
|
||||
--color-text-emphatic: var(--color-light-1);
|
||||
--color-text-secondary: var(--color-light-3);
|
||||
--color-text-subtle: var(--color-light-5);
|
||||
--color-text-light-highlight: #f0f0e0;
|
||||
--color-text-light-heading: #c9c7b8;
|
||||
--color-text-light-primary: #b5b3a4;
|
||||
}
|
||||
}
|
||||
|
||||
body.system-vermine2047.theme-dark .form-group > label {
|
||||
color: #777777;
|
||||
color: @color-text-dark-secondary;
|
||||
}
|
||||
|
||||
#settings-config p,
|
||||
#settings-config .hint {
|
||||
color: #777777;
|
||||
color: @color-text-dark-secondary;
|
||||
}
|
||||
|
||||
img {
|
||||
@@ -732,7 +728,7 @@ ol.chat-log {
|
||||
}
|
||||
|
||||
#required {
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,6 +833,30 @@ ol.chat-log {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.weapon-traits {
|
||||
gap: 4px 6px;
|
||||
margin-top: 4px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px dashed fade(@color-hemolymph, 35%);
|
||||
align-items: center;
|
||||
|
||||
.weapon-traits-label {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.weapon-trait {
|
||||
flex: none;
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
font-weight: 600;
|
||||
padding: 1px 6px;
|
||||
border: 1px solid @color-border-dark-4;
|
||||
border-radius: 3px;
|
||||
background: fade(@color-acid-green, 10%);
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Échange de coups ─────────────────────────────────────────────
|
||||
@@ -894,7 +914,7 @@ ol.chat-log {
|
||||
.exchange-passive {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
opacity: 0.85;
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -907,7 +927,7 @@ ol.chat-log {
|
||||
font-size: @font-size-11;
|
||||
padding: 3px 12px;
|
||||
cursor: pointer;
|
||||
color: @color-text-light-0;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-hemolymph, 25%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
border-radius: 3px;
|
||||
@@ -923,8 +943,8 @@ ol.chat-log {
|
||||
div.exchange-resolution {
|
||||
margin: 4px 0 6px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid @color-amber;
|
||||
background: fade(@color-amber, 8%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
|
||||
.resolution-header {
|
||||
display: flex;
|
||||
@@ -937,7 +957,7 @@ ol.chat-log {
|
||||
font-weight: 900;
|
||||
color: @theme-color-dark;
|
||||
|
||||
i { color: @color-amber; }
|
||||
i { color: @color-hemolymph; }
|
||||
}
|
||||
|
||||
.resolution-line {
|
||||
@@ -962,7 +982,7 @@ ol.chat-log {
|
||||
font-size: @font-size-11;
|
||||
padding: 3px 12px;
|
||||
cursor: pointer;
|
||||
color: @color-text-light-0;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-hemolymph, 25%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
border-radius: 3px;
|
||||
@@ -1063,7 +1083,7 @@ ol.chat-log {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
.exp-value {
|
||||
@@ -1084,7 +1104,7 @@ ol.chat-log {
|
||||
font-size: @font-size-12;
|
||||
padding: 5px 14px;
|
||||
cursor: pointer;
|
||||
color: @color-text-light-0;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-hemolymph, 20%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
border-radius: 3px;
|
||||
@@ -1111,7 +1131,7 @@ ol.chat-log {
|
||||
margin: 0 0 6px;
|
||||
|
||||
.exp-total {
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1126,7 +1146,7 @@ ol.chat-log {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+117
-28
@@ -148,7 +148,7 @@ iframe {
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
color: @color-text-light-0;
|
||||
color: @theme-color-dark;
|
||||
|
||||
.dice-pool {
|
||||
flex: 1;
|
||||
@@ -188,7 +188,7 @@ iframe {
|
||||
|
||||
> label {
|
||||
font-size: @font-size-13;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ iframe {
|
||||
.ability-score {
|
||||
gap: 4px;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.85;
|
||||
|
||||
#abilityScoreValue {
|
||||
@@ -290,11 +290,11 @@ iframe {
|
||||
font-size: @font-size-12;
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @color-amber;
|
||||
color: @color-acid-green;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ iframe {
|
||||
|
||||
> label.label {
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-secondary;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ iframe {
|
||||
font-size: @font-size-12;
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
}
|
||||
|
||||
.item-list {
|
||||
@@ -343,11 +343,11 @@ iframe {
|
||||
font-size: @font-size-11;
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @color-amber;
|
||||
color: @color-acid-green;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,14 +374,14 @@ iframe {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
font-size: @font-size-12;
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @color-amber;
|
||||
color: @color-acid-green;
|
||||
}
|
||||
|
||||
small {
|
||||
@@ -392,8 +392,8 @@ iframe {
|
||||
.totem-hint {
|
||||
width: 100%;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-0;
|
||||
opacity: 0.6;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
@@ -448,7 +448,7 @@ iframe {
|
||||
.weapon-detail {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
|
||||
strong { text-transform: uppercase; }
|
||||
}
|
||||
@@ -466,7 +466,7 @@ iframe {
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
input[type="radio"] { flex: none; }
|
||||
@@ -487,14 +487,14 @@ iframe {
|
||||
|
||||
.label {
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
.combat-target-tag {
|
||||
padding: 2px 8px;
|
||||
font-size: @font-size-11;
|
||||
font-family: "Roboto", sans-serif;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-header;
|
||||
background: fade(@color-hemolymph, 20%);
|
||||
border: 1px solid fade(@color-hemolymph, 50%);
|
||||
border-radius: 3px;
|
||||
@@ -527,7 +527,7 @@ iframe {
|
||||
|
||||
.label {
|
||||
font-size: @font-size-12;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
.exp-value {
|
||||
@@ -550,12 +550,12 @@ iframe {
|
||||
font-family: "Roboto", sans-serif;
|
||||
text-transform: none;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
cursor: pointer;
|
||||
|
||||
input[type="radio"] { flex: none; }
|
||||
|
||||
&:hover { color: @color-amber; }
|
||||
&:hover { color: @color-acid-green; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,8 +571,8 @@ iframe {
|
||||
margin: 0;
|
||||
font-size: @font-size-11;
|
||||
font-style: italic;
|
||||
color: @color-text-light-0;
|
||||
opacity: 0.75;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,8 +581,8 @@ iframe {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
padding: 8px;
|
||||
border: 1px solid @color-amber;
|
||||
background: fade(@color-amber, 8%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
|
||||
.context-item {
|
||||
display: flex;
|
||||
@@ -593,14 +593,14 @@ iframe {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-11;
|
||||
color: @color-amber;
|
||||
color: @color-text-light-2;
|
||||
}
|
||||
|
||||
.context-value {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-13;
|
||||
font-weight: 700;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,7 +613,7 @@ iframe {
|
||||
|
||||
.experience-group {
|
||||
font-size: @font-size-11;
|
||||
color: @color-text-light-0;
|
||||
color: @color-text-dark-secondary;
|
||||
opacity: 0.8;
|
||||
|
||||
#exp-group {
|
||||
@@ -628,7 +628,7 @@ iframe {
|
||||
font-size: @font-size-11;
|
||||
padding: 3px 12px;
|
||||
cursor: pointer;
|
||||
color: @color-text-light-0;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-acid-green, 15%);
|
||||
border: 1px solid @color-acid-green;
|
||||
border-radius: 3px;
|
||||
@@ -721,3 +721,92 @@ iframe {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Achat d'Adaptation/Mutation ───────────────────────────────────────
|
||||
.application.buy-evolution .bonus-item {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
> label.label {
|
||||
flex: none;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
select {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
flex: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Gestion des Dés de Totems ─────────────────────────────────────────
|
||||
.application.totem-dice .totem-dice-state {
|
||||
gap: 12px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid @color-hemolymph;
|
||||
background: fade(@color-hemolymph, 8%);
|
||||
|
||||
.totem-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.label {
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
color: @color-text-light-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.totem-human .label { color: @color-text-light-2; }
|
||||
&.totem-adapted .label { color: @color-text-light-2; }
|
||||
|
||||
.value {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-size: @font-size-14;
|
||||
font-weight: 700;
|
||||
color: @color-text-dark-header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.application.totem-dice .totem-influence {
|
||||
font-size: @font-size-16;
|
||||
}
|
||||
|
||||
.application.totem-dice .totem-actions {
|
||||
gap: 8px;
|
||||
|
||||
button {
|
||||
flex: 1 1 100%;
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
text-transform: uppercase;
|
||||
font-size: @font-size-12;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
color: @theme-color-dark;
|
||||
background: fade(@color-hemolymph, 15%);
|
||||
border: 1px solid @color-hemolymph;
|
||||
border-radius: 3px;
|
||||
.transition();
|
||||
|
||||
&:hover {
|
||||
color: @theme-color-dark;
|
||||
background: @color-hemolymph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -66,11 +66,10 @@
|
||||
|
||||
select {
|
||||
.custom-select-style();
|
||||
color: @color-text-light-1;
|
||||
color: @color-text-dark-primary;
|
||||
font-family: "DistressBlack", sans-serif;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
|
||||
|
||||
option {
|
||||
background: @color-bg-option;
|
||||
|
||||
+1
-2
@@ -20,7 +20,7 @@
|
||||
// Niveaux de gris pour le texte clair
|
||||
@color-text-light-0: #fff;
|
||||
@color-text-light-1: #e0f0f0;
|
||||
@color-text-light-2: #c9e0c0;
|
||||
@color-text-light-2: #3f9b55;
|
||||
@color-text-light-3: #90c4a4;
|
||||
@color-text-light-4: #80c08b;
|
||||
@color-text-light-5: #60b06b;
|
||||
@@ -139,7 +139,6 @@
|
||||
|
||||
// Couleurs insecte / organique
|
||||
@color-acid-green: #1e7d32;
|
||||
@color-amber: #e8b84b;
|
||||
@color-chitin-dark: #2a2520;
|
||||
@color-hemolymph: #8b4513;
|
||||
@color-membrane: rgba(106, 176, 76, 0.1);
|
||||
|
||||
@@ -311,6 +311,11 @@ export default class VermineBaseActorSheet extends HandlebarsApplicationMixin(fo
|
||||
static #onClickRadioHexa(event, target) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
// Évite une erreur de permission pour un utilisateur non éditeur.
|
||||
if (!this.isEditable) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.error_not_editable"))
|
||||
return
|
||||
}
|
||||
const input = target
|
||||
const update = {}
|
||||
let current = this.document
|
||||
|
||||
@@ -9,7 +9,10 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
|
||||
actions: {
|
||||
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty,
|
||||
openExperiencePhase: VermineCharacterSheetV2.#onOpenExperiencePhase,
|
||||
openLearning: VermineCharacterSheetV2.#onOpenLearning
|
||||
openLearning: VermineCharacterSheetV2.#onOpenLearning,
|
||||
validatePhase: VermineCharacterSheetV2.#onValidatePhase,
|
||||
buyEvolution: VermineCharacterSheetV2.#onBuyEvolution,
|
||||
openTotemDice: VermineCharacterSheetV2.#onOpenTotemDice
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,4 +128,25 @@ export default class VermineCharacterSheetV2 extends VermineBaseActorSheet {
|
||||
const dialog = await LearningDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
/** Valide la phase d'Expérience courante et réinitialise les limites. */
|
||||
static async #onValidatePhase() {
|
||||
const { VermineExperience } = await import("../../system/experience.mjs")
|
||||
await VermineExperience.resetPhaseLimits(this.document)
|
||||
ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done"))
|
||||
}
|
||||
|
||||
/** Ouvre le dialogue d'achat d'une Adaptation/Mutation (Dés d'Évolution). */
|
||||
static async #onBuyEvolution(event, target) {
|
||||
const { default: BuyEvolutionDialog } = await import("../../system/dialogs/buyEvolutionDialog.mjs")
|
||||
const dialog = await BuyEvolutionDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
/** Ouvre le dialogue de gestion des Dés de Totems. */
|
||||
static async #onOpenTotemDice(event, target) {
|
||||
const { default: TotemDiceDialog } = await import("../../system/dialogs/totemDiceDialog.mjs")
|
||||
const dialog = await TotemDiceDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
|
||||
addObjective: VermineGroupSheetV2.#onAddObjective,
|
||||
createTotemAbility: VermineGroupSheetV2.#onCreateTotemAbility,
|
||||
openLearning: VermineGroupSheetV2.#onOpenLearning,
|
||||
openExperiencePhase: VermineGroupSheetV2.#onOpenExperiencePhase
|
||||
openExperiencePhase: VermineGroupSheetV2.#onOpenExperiencePhase,
|
||||
validatePhase: VermineGroupSheetV2.#onValidatePhase
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +110,50 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
|
||||
return game.actors.filter(a => a.type === "vehicle" && a.system.ownerId === this.document.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dépôt d'un acteur Véhicule sur la fiche : lie le véhicule au Groupe
|
||||
* (system.ownerId) au lieu de le dupliquer, puis affiche la liste.
|
||||
* Gère les dépôts depuis un compendium (copie importée dans le monde)
|
||||
* et demande confirmation si le véhicule appartient déjà à un autre Groupe.
|
||||
* @override
|
||||
*/
|
||||
async _onDropActor(event, data) {
|
||||
let actor = data instanceof foundry.abstract.Document ? data : await fromUuid(data.uuid)
|
||||
if (actor?.type !== "vehicle") {
|
||||
return super._onDropActor?.(event, data)
|
||||
}
|
||||
// Véhicule issu d'un compendium : importer une copie dans le monde.
|
||||
if (actor.compendium) {
|
||||
try {
|
||||
const copy = await game.actors.importFromCompendium(actor.compendium, actor._id, { renderSheet: false })
|
||||
if (!copy) return
|
||||
actor = copy
|
||||
} catch {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.vehicle_import_failed"))
|
||||
return
|
||||
}
|
||||
}
|
||||
// Véhicule déjà rattaché à un autre Groupe : confirmation avant transfert.
|
||||
const currentOwner = actor.system?.ownerId
|
||||
if (currentOwner && currentOwner !== this.document.id) {
|
||||
const other = game.actors.get(currentOwner)
|
||||
const content = game.i18n.localize("VERMINE.vehicle_assign_confirm").replace("{group}", other?.name ?? currentOwner)
|
||||
const confirmed = await Dialog.confirm({
|
||||
title: game.i18n.localize("VERMINE.vehicle_assign"),
|
||||
content,
|
||||
defaultYes: false
|
||||
})
|
||||
if (!confirmed) return
|
||||
}
|
||||
try {
|
||||
await actor.update({ "system.ownerId": this.document.id })
|
||||
} catch {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.vehicle_assign_failed"))
|
||||
return
|
||||
}
|
||||
this.render()
|
||||
}
|
||||
|
||||
// Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker
|
||||
static async #onChooseTotem(event, target) {
|
||||
const { TotemPicker } = await import("../../system/applications.mjs")
|
||||
@@ -172,4 +217,11 @@ export default class VermineGroupSheetV2 extends VermineBaseActorSheet {
|
||||
const dialog = await GroupLearningDialog.create({ actorId: this.document.id })
|
||||
if (dialog) dialog.render(true)
|
||||
}
|
||||
|
||||
/** Valide la phase d'Expérience courante du Groupe et réinitialise les limites. */
|
||||
static async #onValidatePhase() {
|
||||
const { VermineExperience } = await import("../../system/experience.mjs")
|
||||
await VermineExperience.resetPhaseLimits(this.document)
|
||||
ui.notifications.info(game.i18n.localize("VERMINE.validate_phase_done"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ export function baseItemSchema() {
|
||||
return {
|
||||
description: new fields.HTMLField({ required: true, initial: "", textSearch: true }),
|
||||
rarity: raritySchema(),
|
||||
reliability: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }),
|
||||
reliability: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 10 }),
|
||||
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
quantity: new fields.NumberField({ ...reqInt, initial: 1, min: 1 }),
|
||||
weight: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
|
||||
@@ -13,7 +13,8 @@ export default class VermineEvolutionData extends foundry.abstract.TypeDataModel
|
||||
const fields = foundry.data.fields
|
||||
return {
|
||||
...listItemSchema(),
|
||||
level: levelSchema(1, 1, 4)
|
||||
level: levelSchema(1, 1, 4),
|
||||
mutation: new fields.BooleanField({ required: true, initial: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,12 +129,14 @@ export default class VermineGroupData extends foundry.abstract.TypeDataModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule la réserve max en fonction du niveau du groupe.
|
||||
* Règle simplifiée : niveau × 2, plafonné à 10.
|
||||
* Calcule la réserve max selon le nombre de membres (personnages).
|
||||
* Règles p. 68-69 : la Réserve débute à 3D et gagne 1D par personnage,
|
||||
* avec un maximum de 10D.
|
||||
*/
|
||||
_calculateGroupReserve() {
|
||||
const level = this.level?.value || 1
|
||||
this.reserve.max = Math.min(10, level * 2)
|
||||
const members = Array.isArray(this.members) ? this.members : []
|
||||
const characterCount = members.filter((id) => game.actors?.get(id)?.type === "character").length
|
||||
this.reserve.max = Math.min(10, 3 + characterCount)
|
||||
|
||||
if (this.reserve.value > this.reserve.max) {
|
||||
this.reserve.value = this.reserve.max
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class VermineWeaponData extends foundry.abstract.TypeDataModel {
|
||||
min_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
max_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||
value: new fields.NumberField({ ...reqInt, initial: 0, min: -10 }),
|
||||
type: new fields.StringField({ required: true, initial: "" }),
|
||||
addVigor: new fields.BooleanField({ required: true, initial: false })
|
||||
}),
|
||||
|
||||
@@ -288,6 +288,23 @@ VERMINE.totems = {
|
||||
"loner": "TOTEMS.loner.name",
|
||||
"adapted": "TOTEMS.adapted.name"
|
||||
}
|
||||
/**
|
||||
* Influence des Dés de Totems (règles p. 121).
|
||||
* Selon le Totem dominant (strictement plus de Dés Humains ou Adaptés),
|
||||
* le personnage subit un Bonus/Malus de 1D sur les Domaines de Compétences.
|
||||
* Neutre : aucun modificateur.
|
||||
*/
|
||||
VERMINE.totemInfluence = {
|
||||
"human": {
|
||||
"bonus": ["man", "tool", "world"],
|
||||
"malus": ["animal", "survival", "weapon"]
|
||||
},
|
||||
"adapted": {
|
||||
"bonus": ["animal", "survival", "weapon"],
|
||||
"malus": ["man", "tool", "world"]
|
||||
}
|
||||
}
|
||||
|
||||
VERMINE.totem_opposites = {
|
||||
"human": "TOTEMS.adapted.name",
|
||||
"predator": "TOTEMS.scavenger.name",
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { VermineExperience } from "../experience.mjs";
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
/**
|
||||
* Dialogue d'achat d'une Adaptation/Mutation avec les Dés d'Évolution
|
||||
* accumulés. Vérifie le coût (niveau 1-4), la limite du Mode de jeu et les
|
||||
* restrictions sur les Mutations ; en cas de succès, crée l'item et efface
|
||||
* les Dés d'Évolution accumulés.
|
||||
*/
|
||||
export default class BuyEvolutionDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
|
||||
|
||||
#actor;
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["vermine-roll", "buy-evolution"],
|
||||
tag: "form",
|
||||
window: {
|
||||
icon: "fas fa-cart-shopping",
|
||||
resizable: false
|
||||
},
|
||||
position: {
|
||||
width: 480,
|
||||
height: 460
|
||||
},
|
||||
actions: {
|
||||
buy: BuyEvolutionDialog.#onBuy,
|
||||
cancel: BuyEvolutionDialog.#onCancel
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/vermine2047/templates/dialogs/buy-evolution-dialog.hbs", scrollable: [""] }
|
||||
};
|
||||
|
||||
static async create({ actorId }) {
|
||||
const actor = await game.actors.get(actorId);
|
||||
if (!actor) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
|
||||
return null;
|
||||
}
|
||||
return new BuyEvolutionDialog({ actor });
|
||||
}
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.#actor = options.actor;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize("VERMINE.buy_evolution");
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const actor = this.#actor;
|
||||
const mode = VermineExperience.gameMode();
|
||||
const modeLabels = {
|
||||
1: game.i18n.localize("VERMINE.mode_survie"),
|
||||
2: game.i18n.localize("VERMINE.mode_cauchemar"),
|
||||
3: game.i18n.localize("VERMINE.mode_apocalypse")
|
||||
};
|
||||
const levels = [];
|
||||
for (let value = 1; value <= 4; value++) {
|
||||
levels.push({ value, cost: value });
|
||||
}
|
||||
return {
|
||||
evolutionDice: actor.system?.experience?.evolution || 0,
|
||||
defaultName: game.i18n.localize("ITEMS.new_evolution"),
|
||||
levels,
|
||||
totalLevels: VermineExperience.evolutionTotalLevels(actor),
|
||||
modeMax: VermineExperience.evolutionMaxForMode(mode),
|
||||
modeLabel: modeLabels[mode] || ""
|
||||
};
|
||||
}
|
||||
|
||||
static async #onCancel(event, target) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
static async #onBuy(event, target) {
|
||||
const actor = this.#actor;
|
||||
const name = this.element.querySelector("#evolution-name")?.value?.trim() || "";
|
||||
const cost = parseInt(this.element.querySelector("#evolution-level")?.value, 10) || 0;
|
||||
const mutation = this.element.querySelector("#evolution-mutation")?.checked || false;
|
||||
|
||||
if (!name) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.error_evolution_no_name"));
|
||||
return;
|
||||
}
|
||||
|
||||
const error = VermineExperience.validateEvolutionPurchase(actor, cost, mutation);
|
||||
if (error) {
|
||||
ui.notifications.warn(game.i18n.localize(error));
|
||||
return;
|
||||
}
|
||||
|
||||
const created = await actor.createEmbeddedDocuments("Item", [{
|
||||
name,
|
||||
type: "evolution",
|
||||
system: {
|
||||
level: { value: cost },
|
||||
mutation
|
||||
}
|
||||
}]);
|
||||
// N'efface les Dés d'Évolution que si l'item a bien été créé.
|
||||
if (!created || created.length === 0) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.error_evolution_create"));
|
||||
return;
|
||||
}
|
||||
await actor.update({ "system.experience.evolution": 0 });
|
||||
|
||||
ui.notifications.info(game.i18n.localize("VERMINE.buy_evolution_done"));
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,9 @@ export default class LearningDialog extends HandlebarsApplicationMixin(foundry.a
|
||||
}
|
||||
|
||||
#getSpecialtyName() {
|
||||
return this.#el.querySelector("#specialty-name")?.value?.trim() || game.i18n.localize("ITEMS.new_specialty");
|
||||
// Le dialogue peut avoir été fermé pendant le jet (this.#el null).
|
||||
const el = this.#el?.querySelector("#specialty-name");
|
||||
return el?.value?.trim() || game.i18n.localize("ITEMS.new_specialty");
|
||||
}
|
||||
|
||||
#compute() {
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { VermineTotemDice } from "../totem-dice.mjs";
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
/**
|
||||
* Dialogue de gestion des Dés de Totems (Humain / Adapté).
|
||||
* Permet d'appliquer les gains et pertes liés aux Instincts et Interdits
|
||||
* (1D ou 2D selon la gravité de l'acte), en respectant les limites
|
||||
* (3 Dés du même Totem, 5 Dés au maximum) et les règles d'échange.
|
||||
*/
|
||||
export default class TotemDiceDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
|
||||
|
||||
#actor;
|
||||
#serious = false;
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["vermine-roll", "totem-dice"],
|
||||
tag: "form",
|
||||
window: {
|
||||
icon: "fas fa-dice-d10",
|
||||
resizable: false
|
||||
},
|
||||
position: {
|
||||
width: 480,
|
||||
height: 420
|
||||
},
|
||||
actions: {
|
||||
gainHuman: TotemDiceDialog.#onGainHuman,
|
||||
gainAdapted: TotemDiceDialog.#onGainAdapted,
|
||||
loseHuman: TotemDiceDialog.#onLoseHuman,
|
||||
close: TotemDiceDialog.#onClose
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/vermine2047/templates/dialogs/totem-dice-dialog.hbs", scrollable: [""] }
|
||||
};
|
||||
|
||||
static async create({ actorId }) {
|
||||
const actor = await game.actors.get(actorId);
|
||||
if (!actor) {
|
||||
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
|
||||
return null;
|
||||
}
|
||||
return new TotemDiceDialog({ actor });
|
||||
}
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.#actor = options.actor;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize("VERMINE.totem_dice");
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const actor = this.#actor;
|
||||
const influence = VermineTotemDice.getInfluence(actor);
|
||||
const influenceLabels = {
|
||||
human: game.i18n.localize("TOTEMS.human.name"),
|
||||
adapted: game.i18n.localize("TOTEMS.adapted.name"),
|
||||
neutral: game.i18n.localize("VERMINE.totem_neutral")
|
||||
};
|
||||
return {
|
||||
humanValue: actor.system?.adaptation?.totems?.human?.value ?? 0,
|
||||
humanMax: actor.system?.adaptation?.totems?.human?.max ?? 3,
|
||||
adaptedValue: actor.system?.adaptation?.totems?.adapted?.value ?? 0,
|
||||
adaptedMax: actor.system?.adaptation?.totems?.adapted?.max ?? 3,
|
||||
influenceLabel: influenceLabels[influence] || "",
|
||||
serious: this.#serious
|
||||
};
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
const checkbox = this.element.querySelector("#totem-serious");
|
||||
if (checkbox) {
|
||||
checkbox.checked = this.#serious;
|
||||
checkbox.addEventListener("change", () => { this.#serious = checkbox.checked; });
|
||||
}
|
||||
}
|
||||
|
||||
#getCount() {
|
||||
return this.element.querySelector("#totem-serious")?.checked ? 2 : 1;
|
||||
}
|
||||
|
||||
#notify(result) {
|
||||
const key = result.totem === "human" ? "TOTEMS.human.name" : "TOTEMS.adapted.name";
|
||||
const totemName = game.i18n.localize(key);
|
||||
const parts = [];
|
||||
if (result.gained) parts.push(game.i18n.localize("VERMINE.totem_gained").replace("{totem}", totemName));
|
||||
if (result.exchanged) parts.push(game.i18n.localize("VERMINE.totem_exchanged").replace("{totem}", totemName));
|
||||
if (result.replaced) parts.push(game.i18n.localize("VERMINE.totem_replaced").replace("{totem}", totemName));
|
||||
if (result.lost) parts.push(game.i18n.localize("VERMINE.totem_lost"));
|
||||
if (result.gainedAdapted) parts.push(game.i18n.localize("VERMINE.totem_gained_adapted"));
|
||||
if (result.refused) parts.push(game.i18n.localize("VERMINE.totem_limit_reached"));
|
||||
const text = parts.join(" ");
|
||||
if (result.refused && !result.gained && !result.lost && !result.exchanged && !result.replaced) {
|
||||
ui.notifications.warn(text || game.i18n.localize("VERMINE.totem_limit_reached"));
|
||||
} else if (text) {
|
||||
ui.notifications.info(text);
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #onGainHuman() {
|
||||
const result = await VermineTotemDice.gainDie(this.#actor, "human", this.#getCount());
|
||||
this.#notify(result);
|
||||
}
|
||||
|
||||
static async #onGainAdapted() {
|
||||
const result = await VermineTotemDice.gainDie(this.#actor, "adapted", this.#getCount());
|
||||
this.#notify(result);
|
||||
}
|
||||
|
||||
static async #onLoseHuman() {
|
||||
const result = await VermineTotemDice.loseDie(this.#actor, "human", this.#getCount());
|
||||
this.#notify(result);
|
||||
}
|
||||
|
||||
static #onClose() {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@@ -213,4 +213,82 @@ export class VermineExperience {
|
||||
const preferred = actor.system?.skill_categories?.preferred
|
||||
return Boolean(category && preferred && category === preferred)
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise les limites d'apprentissage (learned.*) de la phase
|
||||
* courante, pour un personnage ou un Groupe, sans toucher au compteur
|
||||
* de phase (avancé uniquement par le dialogue d'attribution du MJ).
|
||||
* @param {foundry.abstract.Document} actor personnage ou Groupe
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async resetPhaseLimits(actor) {
|
||||
const ex = actor.system?.experience
|
||||
if (!ex) return
|
||||
const update = {}
|
||||
if (ex.learned && typeof ex.learned === "object") {
|
||||
for (const key of Object.keys(ex.learned)) {
|
||||
if (key === "phase") continue
|
||||
update[`system.experience.learned.${key}`] = false
|
||||
}
|
||||
}
|
||||
if (Object.keys(update).length) await actor.update(update)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode de jeu actif (1=Survie, 2=Cauchemar, 3=Apocalypse).
|
||||
* @returns {number}
|
||||
*/
|
||||
static gameMode() {
|
||||
return parseInt(game.settings.get("vermine2047", "game-mode"), 10) || 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Total de Dés d'Évolution maximum selon le Mode de jeu.
|
||||
* @param {number} mode 1=Survie, 2=Cauchemar, 3=Apocalypse
|
||||
* @returns {number}
|
||||
*/
|
||||
static evolutionMaxForMode(mode) {
|
||||
switch (parseInt(mode, 10)) {
|
||||
case 2: return 6
|
||||
case 3: return 7
|
||||
default: return 5
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Somme des niveaux des Adaptations/Mutations déjà possédées.
|
||||
* @param {foundry.abstract.Document} actor personnage
|
||||
* @returns {number}
|
||||
*/
|
||||
static evolutionTotalLevels(actor) {
|
||||
return (actor?.itemTypes?.evolution || []).reduce((sum, item) => sum + (item.system?.level?.value || 0), 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide l'achat d'une Adaptation/Mutation contre les Dés d'Évolution
|
||||
* accumulés. Retourne une clé i18n d'erreur, ou null si l'achat est permis.
|
||||
* Règles p. 116-117 : coût = niveau ; total limité par le Mode ; Mutations
|
||||
* 3D/4D interdites en Mode Survie ; une seule Mutation à 4D.
|
||||
* @param {foundry.abstract.Document} actor personnage
|
||||
* @param {number} cost niveau de l'Adaptation/Mutation (1-4)
|
||||
* @param {boolean} mutation vrai si c'est une Mutation
|
||||
* @returns {string|null}
|
||||
*/
|
||||
static validateEvolutionPurchase(actor, cost, mutation) {
|
||||
const level = parseInt(cost, 10) || 0
|
||||
const accumulated = actor.system?.experience?.evolution || 0
|
||||
const mode = this.gameMode()
|
||||
if (accumulated < level) return "VERMINE.error_evolution_not_enough_dice"
|
||||
if (level < 1 || level > 4) return "VERMINE.error_evolution_invalid_level"
|
||||
const total = this.evolutionTotalLevels(actor) + level
|
||||
if (total > this.evolutionMaxForMode(mode)) return "VERMINE.error_evolution_mode_limit"
|
||||
if (mutation && level >= 3 && mode < 2) return "VERMINE.error_evolution_mutation_mode"
|
||||
if (mutation && level === 4) {
|
||||
const hasFourDMutation = (actor?.itemTypes?.evolution || []).some(
|
||||
(item) => item.system?.mutation && (item.system?.level?.value || 0) === 4
|
||||
)
|
||||
if (hasFourDMutation) return "VERMINE.error_evolution_mutation_4d"
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,15 @@ export const registerHooks = function () {
|
||||
}
|
||||
});
|
||||
|
||||
// Un Groupe démarre avec une Réserve à son maximum (3D + 1D par membre).
|
||||
Hooks.on("createActor", async function (actor) {
|
||||
if (actor.type !== "group") return;
|
||||
const reserve = actor.system?.reserve;
|
||||
if (reserve && reserve.value === 0 && reserve.max > 0) {
|
||||
await actor.update({ "system.reserve.value": reserve.max });
|
||||
}
|
||||
});
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Combat Hooks */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
+16
-64
@@ -1,3 +1,5 @@
|
||||
import { VermineTotemDice } from "./totem-dice.mjs";
|
||||
|
||||
export class VermineUtils {
|
||||
/**
|
||||
* Rolls dice with Vermine2047-specific rules.
|
||||
@@ -49,12 +51,12 @@ export class VermineUtils {
|
||||
// Declare variables
|
||||
let formula = "";
|
||||
let modFormula = null;
|
||||
let totemBonus = { human: 0, adapted: 0 };
|
||||
|
||||
// Calculate domain bonuses for totems
|
||||
if (skillCategory) {
|
||||
totemBonus = this._calculateTotemDomainBonuses(skillCategory, actor);
|
||||
}
|
||||
// Influence des Dés de Totems : Bonus/Malus de 1D selon le Totem dominant
|
||||
// et le Domaine de la Compétence (règles p. 121). S'applique même sans
|
||||
// utiliser un Dé de Totem.
|
||||
const influenceMod = this._calculateTotemInfluence(skillCategory, actor);
|
||||
NoD += influenceMod;
|
||||
|
||||
// Apply automatic successes from skill mastery
|
||||
let adjustedDifficulty = difficulty;
|
||||
@@ -65,11 +67,6 @@ export class VermineUtils {
|
||||
const humanDifficulty = adjustedDifficulty;
|
||||
const humanFormula = `(1D10cs>=${humanDifficulty}[human_${safeUserName}]*2)`;
|
||||
|
||||
// Apply domain bonus/malus
|
||||
if (totemBonus.human !== 0) {
|
||||
NoD += totemBonus.human;
|
||||
}
|
||||
|
||||
modFormula = humanFormula;
|
||||
}
|
||||
|
||||
@@ -79,11 +76,6 @@ export class VermineUtils {
|
||||
const adaptedDifficulty = adjustedDifficulty;
|
||||
const adaptedFormula = `(1D10cs>=${adaptedDifficulty}[adapted_${safeUserName}]*2)`;
|
||||
|
||||
// Apply domain bonus/malus
|
||||
if (totemBonus.adapted !== 0) {
|
||||
NoD += totemBonus.adapted;
|
||||
}
|
||||
|
||||
// Build combined formula
|
||||
if (modFormula !== null) {
|
||||
modFormula = `${modFormula}+${adaptedFormula}`;
|
||||
@@ -113,8 +105,8 @@ export class VermineUtils {
|
||||
formula = modFormula !== null ? `${baseFormula}+${modFormula}` : baseFormula;
|
||||
|
||||
// Reconcile the breakdown displayed in chat with the dice actually rolled.
|
||||
// Domain bonuses/malus are only applied when the corresponding totem die
|
||||
// is used, and totem dice replace (not add to) base-pool dice.
|
||||
// L'influence s'applique au pool ; les Dés de Totem remplacent (n'ajoutent
|
||||
// pas) des dés du pool de base.
|
||||
if (poolBreakdown) {
|
||||
// Count totem dice present in the final modifier formula.
|
||||
let totemDice = 0;
|
||||
@@ -122,14 +114,9 @@ export class VermineUtils {
|
||||
if (modFormula.includes("human_")) totemDice += 1;
|
||||
if (modFormula.includes("adapted_")) totemDice += 1;
|
||||
}
|
||||
// Only count the domain bonus of totems actually activated in the roll.
|
||||
const domainBonus = (totems.human ? totemBonus.human : 0)
|
||||
+ (totems.adapted ? totemBonus.adapted : 0);
|
||||
const basePool = poolBreakdown.total;
|
||||
poolBreakdown.totemDice = totemDice;
|
||||
poolBreakdown.domainBonus = domainBonus;
|
||||
poolBreakdown.domainBonus = influenceMod;
|
||||
// Total = nombre réel de dés lancés (NoD réguliers + dés de totem).
|
||||
// Équivaut à basePool + domainBonus sauf si le pool régulier clampe à 0.
|
||||
poolBreakdown.total = NoD + totemDice;
|
||||
}
|
||||
|
||||
@@ -145,7 +132,7 @@ export class VermineUtils {
|
||||
skillCategory: skillCategory,
|
||||
skillLevel: skillLevel,
|
||||
hasSpecialty: hasSpecialty,
|
||||
totemBonuses: { ...totemBonus },
|
||||
totemInfluence: influenceMod,
|
||||
baseNoD: NoD,
|
||||
rerolls: Reroll,
|
||||
selfControl: self_control,
|
||||
@@ -188,47 +175,12 @@ export class VermineUtils {
|
||||
* Calculates domain bonuses/penalties for totems.
|
||||
* @param {string} skillCategory - The skill category
|
||||
* @param {Actor} actor - The actor
|
||||
* @returns {Object} Bonuses for each totem {human: number, adapted: number}
|
||||
* @returns {number} Modificateur d'influence (-1, 0 ou +1)
|
||||
*/
|
||||
static _calculateTotemDomainBonuses(skillCategory, actor) {
|
||||
const bonuses = { human: 0, adapted: 0 };
|
||||
|
||||
// Validate inputs
|
||||
if (!CONFIG.VERMINE?.totemDomains || !actor?.system?.identity?.totem) {
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
const actorTotem = actor.system.identity.totem;
|
||||
|
||||
// Check if actor's totem exists in configuration
|
||||
if (!CONFIG.VERMINE.totemDomains[actorTotem]) {
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
const totemConfig = CONFIG.VERMINE.totemDomains[actorTotem];
|
||||
|
||||
if (!totemConfig?.domains) {
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
// Get actor's preferred skill category
|
||||
const preferredCategory = actor.system.skill_categories?.preferred;
|
||||
|
||||
// Bonus for actor's totem if preferred category is in its domains
|
||||
if (preferredCategory && totemConfig.domains.includes(preferredCategory)) {
|
||||
bonuses[actorTotem] = totemConfig.bonus || 1;
|
||||
}
|
||||
|
||||
// Penalty for opposite totem if preferred category is in its domains
|
||||
const oppositeTotem = CONFIG.VERMINE.totem_opposites?.[actorTotem];
|
||||
if (oppositeTotem && CONFIG.VERMINE.totemDomains[oppositeTotem]) {
|
||||
const oppositeConfig = CONFIG.VERMINE.totemDomains[oppositeTotem];
|
||||
if (preferredCategory && oppositeConfig?.domains?.includes(preferredCategory)) {
|
||||
bonuses[oppositeTotem] = -(oppositeConfig.bonus || 1);
|
||||
}
|
||||
}
|
||||
|
||||
return bonuses;
|
||||
static _calculateTotemInfluence(skillCategory, actor) {
|
||||
// Les Dés de Totems n'existent que sur les personnages.
|
||||
if (actor?.type !== "character" || !skillCategory) return 0;
|
||||
return VermineTotemDice.influenceModifier(actor, skillCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* VermineTotemDice - Gestion des Dés de Totems (Humain / Adapté).
|
||||
*
|
||||
* Règles p. 121 :
|
||||
* - Influence : si un joueur possède strictement plus de Dés d'un Totem que
|
||||
* de l'autre, ce Totem prend l'ascendant (+1D/-1D sur certains Domaines).
|
||||
* - Gains et pertes via les Instincts et les Interdits (1D ou 2D selon la
|
||||
* gravité), avec des limites (3 Dés du même Totem, 5 Dés au maximum).
|
||||
*/
|
||||
export class VermineTotemDice {
|
||||
|
||||
/**
|
||||
* Totem dominant d'un personnage.
|
||||
* @param {foundry.abstract.Document} actor
|
||||
* @returns {'human'|'adapted'|'neutral'}
|
||||
*/
|
||||
static getInfluence(actor) {
|
||||
const human = actor.system?.adaptation?.totems?.human?.value || 0;
|
||||
const adapted = actor.system?.adaptation?.totems?.adapted?.value || 0;
|
||||
if (human > adapted) return "human";
|
||||
if (adapted > human) return "adapted";
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
/**
|
||||
* Modificateur (Bonus/Malus) d'influence pour une catégorie de compétence.
|
||||
* @param {foundry.abstract.Document} actor
|
||||
* @param {string} skillCategory catégorie (man, animal, tool, weapon, survival, world)
|
||||
* @returns {number} -1, 0 ou +1
|
||||
*/
|
||||
static influenceModifier(actor, skillCategory) {
|
||||
const influence = this.getInfluence(actor);
|
||||
if (influence === "neutral" || !skillCategory) return 0;
|
||||
const cfg = CONFIG.VERMINE?.totemInfluence?.[influence];
|
||||
if (!cfg) return 0;
|
||||
if ((cfg.bonus || []).includes(skillCategory)) return 1;
|
||||
if ((cfg.malus || []).includes(skillCategory)) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique un gain d'un Dé de Totem selon les règles d'échange et de limites.
|
||||
* Retourne un objet décrivant ce qui s'est passé (pour notification).
|
||||
* @param {foundry.abstract.Document} actor
|
||||
* @param {'human'|'adapted'} totem
|
||||
* @param {number} [count=1] 1 ou 2 dés (acte grave)
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
static async gainDie(actor, totem, count = 1) {
|
||||
const human = actor.system.adaptation.totems.human;
|
||||
const adapted = actor.system.adaptation.totems.adapted;
|
||||
const actions = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const step = this._gainOneDie(human.value, adapted.value, totem);
|
||||
if (step.unchanged) {
|
||||
actions.push({ ok: false, reason: "max" });
|
||||
break;
|
||||
}
|
||||
human.value = step.human;
|
||||
adapted.value = step.adapted;
|
||||
actions.push({ ok: true, exchanged: step.exchanged, replaced: step.replaced });
|
||||
}
|
||||
|
||||
await actor.update({
|
||||
"system.adaptation.totems.human.value": human.value,
|
||||
"system.adaptation.totems.adapted.value": adapted.value
|
||||
});
|
||||
|
||||
return this._summarize(actions, totem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique une perte d'un Dé de Totem.
|
||||
* Règle : seul l'Humain a des Interdits. Briser un Interdit Humain fait
|
||||
* perdre 1D Humain ; si le personnage n'en possède pas, il gagne 1D Adapté.
|
||||
* @param {foundry.abstract.Document} actor
|
||||
* @param {'human'} totem
|
||||
* @param {number} [count=1] 1 ou 2 dés (acte grave)
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
static async loseDie(actor, totem, count = 1) {
|
||||
const human = actor.system.adaptation.totems.human;
|
||||
const adapted = actor.system.adaptation.totems.adapted;
|
||||
const actions = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (human.value > 0) {
|
||||
human.value -= 1;
|
||||
actions.push({ ok: true, lost: true });
|
||||
} else if (adapted.value < adapted.max && (human.value + adapted.value) < 5) {
|
||||
// Pas de Dé Humain : gagne un Dé Adapté à la place (limites appliquées).
|
||||
adapted.value += 1;
|
||||
actions.push({ ok: true, gainedAdapted: true });
|
||||
} else {
|
||||
actions.push({ ok: false, reason: "max" });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await actor.update({
|
||||
"system.adaptation.totems.human.value": human.value,
|
||||
"system.adaptation.totems.adapted.value": adapted.value
|
||||
});
|
||||
|
||||
return this._summarize(actions, totem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un Dé du Totem donné en respectant les limites et les échanges.
|
||||
* Règles :
|
||||
* - 3 Dés max du même Totem, 5 Dés max au total.
|
||||
* - Si le joueur gagne un Dé qu'il ne peut conserver, il peut supprimer un
|
||||
* Dé du Totem opposé à la place ; sinon le gain est annulé.
|
||||
* - Gagner un 4e Dé Adapté est impossible, mais supprime un Dé Humain.
|
||||
* @param {number} human
|
||||
* @param {number} adapted
|
||||
* @param {'human'|'adapted'} totem
|
||||
* @returns {Object}
|
||||
*/
|
||||
static _gainOneDie(human, adapted, totem) {
|
||||
const limitTotem = 3;
|
||||
const opposite = totem === "human" ? "adapted" : "human";
|
||||
const total = human + adapted;
|
||||
const current = totem === "human" ? human : adapted;
|
||||
const oppositeVal = totem === "human" ? adapted : human;
|
||||
|
||||
// Le Totem cible est déjà au maximum (3) : impossible d'empiler.
|
||||
if (current >= limitTotem) {
|
||||
if (oppositeVal > 0) {
|
||||
// Supprime un Dé du Totem opposé à la place (le gain n'augmente pas).
|
||||
const result = { human, adapted, unchanged: false, replaced: true };
|
||||
result[opposite] -= 1;
|
||||
return result;
|
||||
}
|
||||
return { human, adapted, unchanged: true };
|
||||
}
|
||||
|
||||
// Limite totale atteinte (5) : peut remplacer un Dé opposé.
|
||||
if (total >= 5) {
|
||||
if (oppositeVal > 0) {
|
||||
const result = { human, adapted, unchanged: false, exchanged: true };
|
||||
result[totem] += 1;
|
||||
result[opposite] -= 1;
|
||||
return result;
|
||||
}
|
||||
return { human, adapted, unchanged: true };
|
||||
}
|
||||
|
||||
// Gain normal.
|
||||
const result = { human, adapted, unchanged: false };
|
||||
result[totem] += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Résume les actions appliquées en un libellé localisé.
|
||||
* @param {Array} actions
|
||||
* @param {'human'|'adapted'} totem
|
||||
* @returns {Object}
|
||||
*/
|
||||
static _summarize(actions, totem) {
|
||||
const gained = actions.filter(a => a.ok && !a.exchanged && !a.replaced && !a.lost && !a.gainedAdapted).length;
|
||||
const exchanged = actions.filter(a => a.exchanged).length;
|
||||
const replaced = actions.filter(a => a.replaced).length;
|
||||
const lost = actions.filter(a => a.lost).length;
|
||||
const gainedAdapted = actions.filter(a => a.gainedAdapted).length;
|
||||
const refused = actions.some(a => !a.ok);
|
||||
return { totem, gained, exchanged, replaced, lost, gainedAdapted, refused };
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@
|
||||
<button type="button" data-action="openLearning">
|
||||
<i class="fas fa-graduation-cap"></i> {{ localize 'VERMINE.open_learning' }}
|
||||
</button>
|
||||
<button type="button" data-action="validatePhase">
|
||||
<i class="fas fa-check"></i> {{ localize 'VERMINE.validate_phase' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="exp-rewards">
|
||||
|
||||
@@ -74,6 +74,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Influence + gestion des Dés de Totems --}}
|
||||
<div class="totem-influence-row">
|
||||
<span class="totem-influence-label">
|
||||
{{ localize "VERMINE.influence" }}:
|
||||
{{#ifgt system.adaptation.totems.human.value system.adaptation.totems.adapted.value}}
|
||||
<strong class="totem-human">{{ localize "TOTEMS.human.name" }}</strong>
|
||||
{{else ifgt system.adaptation.totems.adapted.value system.adaptation.totems.human.value}}
|
||||
<strong class="totem-adapted">{{ localize "TOTEMS.adapted.name" }}</strong>
|
||||
{{else}}
|
||||
<strong>{{ localize "VERMINE.totem_neutral" }}</strong>
|
||||
{{/ifgt}}
|
||||
</span>
|
||||
<button type="button" class="totem-manage-btn" data-action="openTotemDice"
|
||||
title="{{ localize 'VERMINE.totem_manage' }}"><i class="fas fa-sliders"></i></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<ul class="padding-with-frieze unstyled paper">
|
||||
{{#if system.identity.totem }}
|
||||
|
||||
@@ -97,7 +97,6 @@
|
||||
data-action="edit"
|
||||
data-item-id="{{item._id}}"
|
||||
title="Edit Item">{{item.name}}</a>
|
||||
<span data-tooltip="Compétence">{{item.system.skill}}</span>
|
||||
</div>
|
||||
<div class="item-controls flexrow">
|
||||
{{#if item.system.description}}
|
||||
@@ -192,9 +191,8 @@
|
||||
{{#if isEditMode}}
|
||||
<a
|
||||
class="item-control item-create"
|
||||
title="Create item"
|
||||
data-action="create"
|
||||
data-type="evolution"><i class="fas fa-plus"></i></a>
|
||||
title="Buy evolution"
|
||||
data-action="buyEvolution"><i class="fas fa-cart-shopping"></i></a>
|
||||
{{/if}}
|
||||
</h4>
|
||||
<ol class="list-item">
|
||||
@@ -206,6 +204,11 @@
|
||||
data-action="edit"
|
||||
data-item-id="{{item._id}}"
|
||||
title="Edit Item">{{item.name}}</a>
|
||||
{{#if item.system.mutation}}
|
||||
<span class="evolution-badge mutation">{{ localize 'VERMINE.evolution_mutation' }}</span>
|
||||
{{else}}
|
||||
<span class="evolution-badge adaptation">{{ localize 'VERMINE.evolution_adaptation' }}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="item-controls flexrow">
|
||||
{{#if item.system.description}}
|
||||
|
||||
@@ -1,45 +1,50 @@
|
||||
<div class="tab gear sheet-part {{#if @root.tabs.gear.active}}active{{/if}}" data-group="sheet" data-tab="gear">
|
||||
<div class="grid grid-2col">
|
||||
<div>
|
||||
<ol class="items-list">
|
||||
<li class="item flexrow items-header">
|
||||
<div class="item-name" style="flex:4;">{{ localize 'IDENTITY.name'}}</div>
|
||||
<div class="item-quantity">{{ localize 'VERMINE.qty'}}</div>
|
||||
<div class="item-weight">{{ localize 'VERMINE.weight'}}</div>
|
||||
<div class="weapons">
|
||||
<h4>{{localize 'ITEMS.weapons'}}</h4>
|
||||
{{> "systems/vermine2047/templates/actor/parts/actor-weapons.hbs"}}
|
||||
</div>
|
||||
<div class="protections">
|
||||
<h4>{{localize 'ITEMS.defenses'}}</h4>
|
||||
{{> "systems/vermine2047/templates/actor/parts/actor-defenses.hbs"}}
|
||||
</div>
|
||||
<div class="objects">
|
||||
<h4>{{localize 'ITEMS.items'}}</h4>
|
||||
<ol class="items-list">
|
||||
<li class="item flexrow items-header">
|
||||
<div class="item-name" style="flex:4;">{{ localize 'IDENTITY.name'}}</div>
|
||||
<div class="item-quantity">{{ localize 'VERMINE.qty'}}</div>
|
||||
<div class="item-weight">{{ localize 'VERMINE.weight'}}</div>
|
||||
<div class="item-controls">
|
||||
{{#if isEditMode}}
|
||||
<a class="item-control item-create" data-action="create" data-type="item" title="Create item"><i class="fas fa-plus"></i></a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{#each gear as |item id|}}
|
||||
<li class="item flexrow flex-group-center" data-item-id="{{item._id}}">
|
||||
<div class="item-name" style="flex:4;">
|
||||
<div class="item-image">
|
||||
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" data-roll-type="item"><img src="{{item.img}}" title="{{item.name}}" width="24" height="24"/></a>
|
||||
</div>
|
||||
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.name}}</a>
|
||||
</div>
|
||||
<div><a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.system.quantity}}</a></div>
|
||||
<div><a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.system.weight}}</a></div>
|
||||
<div class="item-controls">
|
||||
{{#if isEditMode}}
|
||||
<a class="item-control item-create" data-action="create" data-type="item" title="Create item"><i class="fas fa-plus"></i></a>
|
||||
{{#if @root.isEditMode}}
|
||||
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="Delete Item"><i class="fas fa-trash"></i></a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{#each gear as |item id|}}
|
||||
<li class="item flexrow flex-group-center" data-item-id="{{item._id}}">
|
||||
<div class="item-name" style="flex:4;">
|
||||
<div class="item-image">
|
||||
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" data-roll-type="item"><img src="{{item.img}}" title="{{item.name}}" width="24" height="24"/></a>
|
||||
</div>
|
||||
<a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.name}}</a>
|
||||
</div>
|
||||
<div><a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.system.quantity}}</a></div>
|
||||
<div><a class="item-control item-edit" data-action="edit" data-item-id="{{item._id}}" title="Edit Item">{{item.system.weight}}</a></div>
|
||||
<div class="item-controls">
|
||||
{{#if @root.isEditMode}}
|
||||
<a class="item-control item-delete" data-action="delete" data-item-id="{{item._id}}" title="Delete Item"><i class="fas fa-trash"></i></a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{{ localize 'IDENTITY.notes'}}</h4>
|
||||
{{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</ol>
|
||||
</div>
|
||||
<div class="vehicles">
|
||||
<h4>{{localize 'ITEMS.vehicles'}}</h4>
|
||||
{{> "systems/vermine2047/templates/actor/group/group-vehicles.hbs"}}
|
||||
</div>
|
||||
<div class="equipment-notes">
|
||||
<h4>{{ localize 'IDENTITY.notes'}}</h4>
|
||||
{{formInput systemFields.equipment.fields.description enriched=enrichedEquipmentNotes value=system.equipment.description name="system.equipment.description" toggled=true}}
|
||||
</div>
|
||||
<h4>{{localize 'ITEMS.weapons'}}</h4>
|
||||
{{> "systems/vermine2047/templates/actor/parts/actor-weapons.hbs"}}
|
||||
<h4>{{localize 'ITEMS.defenses'}}</h4>
|
||||
{{> "systems/vermine2047/templates/actor/parts/actor-defenses.hbs"}}
|
||||
<h4>{{localize 'ITEMS.vehicles'}}</h4>
|
||||
{{> "systems/vermine2047/templates/actor/group/group-vehicles.hbs"}}
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
{{/if}}
|
||||
<span>/ {{system.reserve.max}}D</span>
|
||||
</div>
|
||||
<div class="reserve-dice flexrow">
|
||||
{{#repeat system.reserve.max 1 "d"}}
|
||||
<div class="hexa {{#iflteq @d @root.system.reserve.value}}checked{{/iflteq}}">
|
||||
<input type="radio" data-dtype="Number" name="system.reserve.value" value="{{@d}}" data-action="clickRadio" />
|
||||
</div>
|
||||
{{/repeat}}
|
||||
</div>
|
||||
<div class="morale-control flexrow flex-group-center">
|
||||
<label>{{ localize 'VERMINE.morale' }}:</label>
|
||||
{{#if isEditMode}}
|
||||
@@ -46,6 +53,7 @@
|
||||
<div class="group-experience-actions flexrow flex-group-center">
|
||||
<button type="button" data-action="openLearning"><i class="fas fa-graduation-cap"></i> {{ localize 'VERMINE.open_learning' }}</button>
|
||||
<button type="button" data-action="openExperiencePhase"><i class="fas fa-star"></i> {{ localize 'VERMINE.experience_phase' }}</button>
|
||||
<button type="button" data-action="validatePhase"><i class="fas fa-check"></i> {{ localize 'VERMINE.validate_phase' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<div class="roll-dialog-content">
|
||||
<h3 class="combat-mode-label">{{ localize 'VERMINE.buy_evolution' }}</h3>
|
||||
|
||||
<div class="learning-summary">
|
||||
<span class="label">{{ localize 'VERMINE.evolution_available_dice' }}:</span>
|
||||
<span class="exp-value">{{ evolutionDice }}D</span>
|
||||
</div>
|
||||
|
||||
<div class="flexrow bonus-item full-width">
|
||||
<label class="label" for="evolution-name">{{ localize 'VERMINE.evolution_name' }}</label>
|
||||
<input type="text" data-roll="true" id="evolution-name" name="evolution-name" value="{{ defaultName }}" />
|
||||
</div>
|
||||
|
||||
<div class="flexrow bonus-item full-width">
|
||||
<label class="label" for="evolution-level">{{ localize 'VERMINE.evolution_level' }}</label>
|
||||
<select class="info-value" data-roll="true" id="evolution-level" name="evolution-level">
|
||||
{{#each levels}}
|
||||
<option value="{{ this.value }}">{{ this.value }}D — {{ this.cost }}D</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flexrow bonus-item full-width">
|
||||
<label class="label" for="evolution-mutation">{{ localize 'VERMINE.evolution_mutation' }}</label>
|
||||
<input type="checkbox" data-roll="true" id="evolution-mutation" name="evolution-mutation" />
|
||||
</div>
|
||||
|
||||
<div class="learning-result">
|
||||
<div class="context-item">
|
||||
<span class="context-label">{{ localize 'VERMINE.evolution_total' }}</span>
|
||||
<span class="context-value" id="evolution-total-value">{{ totalLevels }}D</span>
|
||||
</div>
|
||||
<div class="context-item">
|
||||
<span class="context-label">{{ localize 'VERMINE.evolution_mode_max' }}</span>
|
||||
<span class="context-value">{{ modeMax }}D</span>
|
||||
</div>
|
||||
<div class="context-item">
|
||||
<span class="context-label">{{ localize 'VERMINE.game_mode' }}</span>
|
||||
<span class="context-value">{{ modeLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="learning-hint">{{ localize 'VERMINE.evolution_buy_hint' }}</p>
|
||||
|
||||
<footer class="sheet-footer flexrow">
|
||||
<button type="button" data-action="cancel">
|
||||
<i class="fas fa-times"></i> {{localize "VERMINE.cancel"}}
|
||||
</button>
|
||||
<button type="button" data-action="buy">
|
||||
<i class="fas fa-cart-shopping"></i> {{localize "VERMINE.buy_evolution"}}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -0,0 +1,44 @@
|
||||
<div class="roll-dialog-content">
|
||||
<h3 class="combat-mode-label">{{ localize 'VERMINE.totem_dice' }}</h3>
|
||||
|
||||
<div class="learning-summary">
|
||||
<span class="label">{{ localize 'VERMINE.influence' }}:</span>
|
||||
<span class="exp-value totem-influence">{{ influenceLabel }}</span>
|
||||
</div>
|
||||
|
||||
<div class="totem-dice-state flexrow flex-group-center">
|
||||
<div class="totem-state">
|
||||
<span class="label totem-human">{{ localize 'TOTEMS.human.name' }}:</span>
|
||||
<span class="value">{{ humanValue }} / {{ humanMax }}</span>
|
||||
</div>
|
||||
<div class="totem-state">
|
||||
<span class="label totem-adapted">{{ localize 'TOTEMS.adapted.name' }}:</span>
|
||||
<span class="value">{{ adaptedValue }} / {{ adaptedMax }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flexrow bonus-item full-width">
|
||||
<label class="label" for="totem-serious">{{ localize 'VERMINE.totem_serious' }}</label>
|
||||
<input type="checkbox" data-roll="true" id="totem-serious" name="totem-serious" {{checked serious}} />
|
||||
</div>
|
||||
|
||||
<p class="learning-hint">{{ localize 'VERMINE.totem_manage_hint' }}</p>
|
||||
|
||||
<div class="totem-actions flexrow flex-wrap">
|
||||
<button type="button" data-action="gainHuman">
|
||||
<i class="fas fa-hand-holding-heart"></i> {{ localize 'VERMINE.totem_instinct_human' }}
|
||||
</button>
|
||||
<button type="button" data-action="gainAdapted">
|
||||
<i class="fas fa-paw"></i> {{ localize 'VERMINE.totem_instinct_adapted' }}
|
||||
</button>
|
||||
<button type="button" data-action="loseHuman">
|
||||
<i class="fas fa-ban"></i> {{ localize 'VERMINE.totem_interdit_human' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<footer class="sheet-footer flexrow">
|
||||
<button type="button" data-action="close">
|
||||
<i class="fas fa-times"></i> {{localize "VERMINE.close"}}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -13,6 +13,10 @@
|
||||
<label class="resource-label ">{{ localize "VERMINE.level"}}</label>
|
||||
<span class="hexa"><input type="number" name="system.level.value" value="{{system.level.value }}" data-dtype="Number" min="{{system.level.min }}" max="{{system.level.max }}"/></span>
|
||||
</h4>
|
||||
<div class="flexrow bonus-item full-width">
|
||||
<label class="resource-label" for="system.mutation">{{ localize "VERMINE.evolution_mutation"}}</label>
|
||||
<input type="checkbox" name="system.mutation" {{checked system.mutation}}/>
|
||||
</div>
|
||||
<h2>description</h2>
|
||||
|
||||
<fieldset>
|
||||
|
||||
@@ -36,6 +36,18 @@
|
||||
{{param.weapon.system.ammo}}
|
||||
</span>
|
||||
</div>
|
||||
{{#if param.weapon.system.traits}}
|
||||
<div class="flexrow flex-wrap weapon-traits">
|
||||
<span class="weapon-detail weapon-traits-label">
|
||||
<strong>{{localize "VERMINE.traits"}}:</strong>
|
||||
</span>
|
||||
{{#each param.weapon.system.traits as |trait key|}}
|
||||
{{#if trait}}
|
||||
<span class="weapon-trait" data-tooltip="{{trait.description}}">{{trait.name}}{{#if trait.value}} ({{trait.value}}){{/if}}</span>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user