Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1e73f0761 | ||
|
|
222aa75a1d | ||
|
|
caa78d7c45 | ||
|
|
5edcaa373c | ||
|
|
08e412b32f | ||
|
|
4269946c30 | ||
|
|
607817302b | ||
|
|
dd39fa6113 |
@@ -6,6 +6,11 @@ Date format : day/month/year
|
||||
> - `foundry-version`: Stick to the major version of FoundryVTT.
|
||||
> - `system-version`: System functionalities and Fixes.
|
||||
|
||||
## 1.13.3 - 01/02/2026 - Tactical Grid & Fixes
|
||||
- Updated demeanors from books up to Imperfect Land (included), thanks to Olivier Brencklé (!48).
|
||||
- Added Tactical Grid Range Band, thanks to Litasa (!49).
|
||||
- Fix Title advancement auto-name/icon, thanks to SagaTympana (!50).
|
||||
|
||||
## 1.13.2 - 18/10/2025 - Conditions Icons & Fixes
|
||||
- Fix Actor Sheet for pressing key `Enter` in input trigger `no active Encounter...` message.
|
||||
- Fix Compendium `Astrolab` is duplicate with `Mantis Clan` and `Children of the Five Winds`. Renamed the `cotfw` version to `Astrolabe (Unicorn)`.
|
||||
@@ -14,8 +19,8 @@ Date format : day/month/year
|
||||
- Spanish language updated thanks to Alejabarr.
|
||||
|
||||
## 1.13.1 - 21/09/2025 - Conditions & Fixes
|
||||
- Fix for Clicking on items doesn't show item window (#65 Thx to Litasa)
|
||||
- Fix for fade configuration (#66)
|
||||
- Fix for Clicking on items doesn't show item window (#65 Thx to Litasa).
|
||||
- Fix for fade configuration (#66).
|
||||
- Added some Tooltips loading optimizations (#62 Thanks to KitCat).
|
||||
- Added some Properties loading optimizations (#63 Thanks to KitCat).
|
||||
- Conditions changes :
|
||||
|
||||
@@ -705,6 +705,7 @@
|
||||
"demeanor": {
|
||||
"adaptable": "Adaptable",
|
||||
"aggressive": "Aggressive",
|
||||
"alluring": "Alluring",
|
||||
"ambitious": "Ambitious",
|
||||
"amiable": "Amiable",
|
||||
"analytical": "Analytical",
|
||||
@@ -713,6 +714,7 @@
|
||||
"assertive": "Assertive",
|
||||
"beguiling": "Beguiling",
|
||||
"bitter": "Bitter",
|
||||
"bloodthirsty": "Bloodthirsty",
|
||||
"bold": "Bold",
|
||||
"calculating": "Calculating",
|
||||
"calm": "Calm",
|
||||
@@ -723,37 +725,68 @@
|
||||
"confused": "Confused",
|
||||
"courageous": "Courageous",
|
||||
"cowardly": "Cowardly",
|
||||
"crestfallen": "Crestfallen",
|
||||
"curious": "Curious",
|
||||
"defensive": "Defensive",
|
||||
"dependable": "Dependable",
|
||||
"detached": "Detached",
|
||||
"determined": "Determined",
|
||||
"devoted": "Devoted",
|
||||
"direct": "Direct",
|
||||
"disheartened": "Disheartened",
|
||||
"dour": "Dour",
|
||||
"duplicitous": "Duplicitous",
|
||||
"effusive": "Effusive",
|
||||
"enraged": "Enraged",
|
||||
"fanatical": "Fanatical",
|
||||
"feral": "Feral",
|
||||
"fervent": "Fervent",
|
||||
"fickle": "Fickle",
|
||||
"fierce": "Fierce",
|
||||
"flighty": "Flighty",
|
||||
"flippant": "Flippant",
|
||||
"friendly": "Friendly",
|
||||
"gruff": "Gruff",
|
||||
"honorable": "Honorable",
|
||||
"hubristic": "Prétentieuse",
|
||||
"hungry": "Hungry",
|
||||
"idealistic": "Idealistic",
|
||||
"imposing": "Imposing",
|
||||
"inquisitive": "Inquisitive",
|
||||
"intense": "Intense",
|
||||
"intimidating": "Intimidating",
|
||||
"irritable": "Irritable",
|
||||
"loyal": "Loyal",
|
||||
"methodical": "Methodical",
|
||||
"meticulous": "Meticulous",
|
||||
"mischievous": "Mischievous",
|
||||
"moon_blessed": "Moon-blessed",
|
||||
"morose": "Morose",
|
||||
"near_feral": "Near feral",
|
||||
"nurturing": "Nurturing",
|
||||
"obsessed": "Obsessed",
|
||||
"obstinate": "Obstinate",
|
||||
"opportunistic": "Opportunistic",
|
||||
"otherworldly": "Otherworldly",
|
||||
"outgoing": "Outgoing",
|
||||
"passionate": "Passionate",
|
||||
"patient": "Patient",
|
||||
"personable": "Personable",
|
||||
"playful": "Playful",
|
||||
"power_hungry": "Power hungry",
|
||||
"proud": "Proud",
|
||||
"refined": "Refined",
|
||||
"reserved": "Reserved",
|
||||
"restrained": "Restrained",
|
||||
"righteous": "Righteous",
|
||||
"scheming": "Scheming",
|
||||
"serene": "Serene",
|
||||
"serious": "Serious",
|
||||
"shrewd": "Shrewd",
|
||||
"sinister": "Sinister",
|
||||
"sociable": "Sociable",
|
||||
"stoic": "Stoic",
|
||||
"starved": "Starved",
|
||||
"stubborn": "Stubborn",
|
||||
"suspicious": "Suspicious",
|
||||
"teasing": "Teasing",
|
||||
@@ -761,7 +794,12 @@
|
||||
"uncertain": "Uncertain",
|
||||
"unenthused": "Unenthused",
|
||||
"vain": "Vain",
|
||||
"wary": "Wary"
|
||||
"vengeful": "Vengeful",
|
||||
"vindictive": "Vindictive",
|
||||
"wary": "Wary",
|
||||
"watchful": "Watchful",
|
||||
"wrathful": "Wrathful",
|
||||
"zealous": "Zealous"
|
||||
},
|
||||
"compendium": {
|
||||
"filter_rank": "Show Rank",
|
||||
@@ -799,6 +837,32 @@
|
||||
"the_scroll_or_the_blade": "The Scroll or the Blade",
|
||||
"legacies_of_war": "Legacies of War",
|
||||
"children_of_the_five_winds": "Children of the Five Winds"
|
||||
},
|
||||
"tactical_grid": {
|
||||
"settings": {
|
||||
"title": "Tactical Grid Settings",
|
||||
"label": "Tactical Grid Settings",
|
||||
"hint": "Configures tactical grid range band distances (GM only) and their visual appearance colors and transparency (all users).",
|
||||
"cells": "spaces",
|
||||
"world": {
|
||||
"enabled": "Enable Tactical Grid",
|
||||
"enabled_hint": "Enables or Disable tactical grid for everyone",
|
||||
"start": "Start"
|
||||
},
|
||||
"client": {
|
||||
"color": "Color",
|
||||
"alpha": "Alpha"
|
||||
},
|
||||
"range": "Range {index}",
|
||||
"validate": {
|
||||
"start-too-small": "Must be greater than Range Band {previousRangeIndex} ({previousStart})",
|
||||
"start-too-large": "Must be lower then Range Band {nextRangeIndex} ({nextStart})"
|
||||
},
|
||||
"reset": "Reset to Default",
|
||||
"submit": "Save"
|
||||
},
|
||||
"range_band": "Range Band {band}",
|
||||
"range_abbreviation": "RB {range}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,6 +705,7 @@
|
||||
"demeanor": {
|
||||
"adaptable": "Adaptable",
|
||||
"aggressive": "Agresivo",
|
||||
"alluring": "Alluring",
|
||||
"ambitious": "Ambicioso",
|
||||
"amiable": "Amigable",
|
||||
"analytical": "Analítico",
|
||||
@@ -713,6 +714,7 @@
|
||||
"assertive": "Firme",
|
||||
"beguiling": "Seductor",
|
||||
"bitter": "Amargado",
|
||||
"bloodthirsty": "Bloodthirsty",
|
||||
"bold": "Atrevido",
|
||||
"calculating": "Calculador",
|
||||
"calm": "Calmado",
|
||||
@@ -723,37 +725,68 @@
|
||||
"confused": "Confuso",
|
||||
"courageous": "Valiente",
|
||||
"cowardly": "Cobarde",
|
||||
"crestfallen": "Crestfallen",
|
||||
"curious": "Curioso",
|
||||
"defensive": "Defensive",
|
||||
"dependable": "Fiable",
|
||||
"detached": "Desapegado",
|
||||
"determined": "Determined",
|
||||
"devoted": "Devoted",
|
||||
"direct": "Direct",
|
||||
"disheartened": "Desanimado",
|
||||
"dour": "Dour",
|
||||
"duplicitous": "Duplicitous",
|
||||
"effusive": "Effusive",
|
||||
"enraged": "Furioso",
|
||||
"fanatical": "Fanatical",
|
||||
"feral": "Salvaje",
|
||||
"fervent": "Fervent",
|
||||
"fickle": "Voluble",
|
||||
"fierce": "Fiero",
|
||||
"flighty": "Veleidoso",
|
||||
"flippant": "Frívolo",
|
||||
"friendly": "Amable",
|
||||
"gruff": "Hosco",
|
||||
"honorable": "Honorable",
|
||||
"hubristic": "Hubristic",
|
||||
"hungry": "Hambriento",
|
||||
"idealistic": "Idealistic",
|
||||
"imposing": "Imposing",
|
||||
"inquisitive": "Inquisitive",
|
||||
"intense": "Intenso",
|
||||
"intimidating": "Intimidante",
|
||||
"irritable": "Irritable",
|
||||
"loyal": "Leal",
|
||||
"methodical": "Methodical",
|
||||
"meticulous": "Meticulous",
|
||||
"mischievous": "Travieso",
|
||||
"moon_blessed": "Moon-blessed",
|
||||
"morose": "Taciturno",
|
||||
"near_feral": "Near feral",
|
||||
"nurturing": "Animador",
|
||||
"obsessed": "Obsessed",
|
||||
"obstinate": "Obstinado",
|
||||
"opportunistic": "Oportunista",
|
||||
"otherworldly": "Otherworldly",
|
||||
"outgoing": "Outgoing",
|
||||
"passionate": "Apasionado",
|
||||
"patient": "Patient",
|
||||
"personable": "Personable",
|
||||
"playful": "Juguetón",
|
||||
"power_hungry": "Ávido de poder",
|
||||
"proud": "Orgulloso",
|
||||
"refined": "Refined",
|
||||
"reserved": "Reserved",
|
||||
"restrained": "Contenido",
|
||||
"righteous": "Righteous",
|
||||
"scheming": "Taimado",
|
||||
"serene": "Sereno",
|
||||
"serious": "Serio",
|
||||
"shrewd": "Artero",
|
||||
"sinister": "Sinister",
|
||||
"sociable": "Sociable",
|
||||
"stoic": "Stoic",
|
||||
"starved": "Starved",
|
||||
"stubborn": "Testarudo",
|
||||
"suspicious": "Suspicaz",
|
||||
"teasing": "Bromista",
|
||||
@@ -761,7 +794,12 @@
|
||||
"uncertain": "Inseguro",
|
||||
"unenthused": "Sin entusiasmo",
|
||||
"vain": "Vanidoso",
|
||||
"wary": "Precavido"
|
||||
"vengeful": "Vengeful",
|
||||
"vindictive": "Vindictive",
|
||||
"wary": "Precavido",
|
||||
"watchful": "Watchful",
|
||||
"wrathful": "Wrathful",
|
||||
"zealous": "Zealous"
|
||||
},
|
||||
"compendium": {
|
||||
"filter_rank": "Mostrar rango",
|
||||
@@ -799,6 +837,32 @@
|
||||
"the_scroll_or_the_blade": "El pergamino o la espada",
|
||||
"legacies_of_war": "Legacies of War",
|
||||
"children_of_the_five_winds": "Children of the Five Winds"
|
||||
},
|
||||
"tactical_grid": {
|
||||
"settings": {
|
||||
"title": "Tactical Grid Settings",
|
||||
"label": "Tactical Grid Settings",
|
||||
"hint": "Configures tactical grid range band distances (GM only) and their visual appearance colors and transparency (all users).",
|
||||
"cells": "spaces",
|
||||
"world": {
|
||||
"enabled": "Enable Tactical Grid",
|
||||
"enabled_hint": "Enables or Disable tactical grid for everyone",
|
||||
"start": "Start"
|
||||
},
|
||||
"client": {
|
||||
"color": "Color",
|
||||
"alpha": "Alpha"
|
||||
},
|
||||
"range": "Range {index}",
|
||||
"validate": {
|
||||
"start-too-small": "Must be greater than Range Band {previousRangeIndex} ({previousStart})",
|
||||
"start-too-large": "Must be lower then Range Band {nextRangeIndex} ({nextStart})"
|
||||
},
|
||||
"reset": "Reset to Default",
|
||||
"submit": "Save"
|
||||
},
|
||||
"range_band": "Range Band {band}",
|
||||
"range_abbreviation": "RB {range}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,65 +703,103 @@
|
||||
"ujik": "Ujik"
|
||||
},
|
||||
"demeanor": {
|
||||
"adaptable": "Adaptable",
|
||||
"adaptable": "Malléable",
|
||||
"aggressive": "Agressive",
|
||||
"alluring": "Attirante",
|
||||
"ambitious": "Ambitieuse",
|
||||
"amiable": "Sympathique",
|
||||
"analytical": "Réfléchie",
|
||||
"angry": "Enervée",
|
||||
"amiable": "Aimable",
|
||||
"analytical": "Analytique",
|
||||
"angry": "En colère",
|
||||
"arrogant": "Arrogante",
|
||||
"assertive": "Assurée",
|
||||
"beguiling": "Séduisante",
|
||||
"assertive": "Sûre de soi",
|
||||
"beguiling": "Envoûtante",
|
||||
"bitter": "Amère",
|
||||
"bold": "Audacieuse",
|
||||
"bloodthirsty": "Sanguinaire",
|
||||
"bold": "Courageuse",
|
||||
"calculating": "Calculatrice",
|
||||
"calm": "Calme",
|
||||
"capricious": "Capricieuse",
|
||||
"cautious": "Prudente",
|
||||
"clever": "Astucieuse",
|
||||
"clever": "Malicieuse",
|
||||
"compassionate": "Compatissante",
|
||||
"confused": "Confuse",
|
||||
"courageous": "Courageuse",
|
||||
"cowardly": "Lâche",
|
||||
"crestfallen": "Démoralisée",
|
||||
"curious": "Curieuse",
|
||||
"defensive": "Sur la défensive",
|
||||
"dependable": "Fiable",
|
||||
"detached": "Détachée",
|
||||
"disheartened": "Découragée",
|
||||
"determined": "Déterminée",
|
||||
"devoted": "Fervente",
|
||||
"direct": "Directe",
|
||||
"disheartened": "Abattue",
|
||||
"dour": "Renfrognée",
|
||||
"duplicitous": "Sournoise",
|
||||
"effusive": "Communicative",
|
||||
"enraged": "Enragée",
|
||||
"fanatical": "Fanatique",
|
||||
"feral": "Sauvage",
|
||||
"fickle": "Inconstante",
|
||||
"fervent": "Dévote",
|
||||
"fickle": "Volatile",
|
||||
"fierce": "Féroce",
|
||||
"flighty": "Volage",
|
||||
"flighty": "Inconstante",
|
||||
"flippant": "Désinvolte",
|
||||
"friendly": "Amicale",
|
||||
"gruff": "Bourrue",
|
||||
"honorable": "Honorable",
|
||||
"hubristic": "Prétentieuse",
|
||||
"hungry": "Affamée",
|
||||
"intense": "Intense",
|
||||
"idealistic": "Idéaliste",
|
||||
"imposing": "Impressionnante",
|
||||
"inquisitive": "Inquisitrice",
|
||||
"intense": "Excessive",
|
||||
"intimidating": "Intimidante",
|
||||
"irritable": "Irritable",
|
||||
"loyal": "Fidèle",
|
||||
"mischievous": "Malicieuse",
|
||||
"irritable": "Colérique",
|
||||
"loyal": "Loyale",
|
||||
"methodical": "Méthodique",
|
||||
"meticulous": "Méticuleuse",
|
||||
"mischievous": "Taquine",
|
||||
"moon_blessed": "Bénie par la Lune",
|
||||
"morose": "Morose",
|
||||
"nurturing": "Encourageante",
|
||||
"near_feral": "Presque sauvage",
|
||||
"nurturing": "Maternelle",
|
||||
"obsessed": "Obsessionnelle",
|
||||
"obstinate": "Obstinée",
|
||||
"opportunistic": "Opportuniste",
|
||||
"otherworldly": "Mystique",
|
||||
"outgoing": "Agréable",
|
||||
"passionate": "Passionnée",
|
||||
"playful": "Enjouée",
|
||||
"patient": "Patiente",
|
||||
"personable": "Avenante",
|
||||
"playful": "Joueuse",
|
||||
"power_hungry": "Avide de pouvoir",
|
||||
"proud": "Fière",
|
||||
"restrained": "Restreinte",
|
||||
"scheming": "Intrigante",
|
||||
"refined": "Raffinée",
|
||||
"restrained": "Modérée",
|
||||
"reserved": "Réservée",
|
||||
"righteous": "Intègre",
|
||||
"scheming": "Fourbe",
|
||||
"serene": "Sereine",
|
||||
"serious": "Sérieuse",
|
||||
"shrewd": "Astucieuse",
|
||||
"shrewd": "Rusée",
|
||||
"sinister": "Sinistre",
|
||||
"sociable": "Affable",
|
||||
"starved": "Famélique",
|
||||
"stoic": "Stoïque",
|
||||
"stubborn": "Têtue",
|
||||
"suspicious": "Soupçonneuse",
|
||||
"teasing": "Taquine",
|
||||
"suspicious": "Suspicieuse",
|
||||
"teasing": "Moqueuse",
|
||||
"territorial": "Territoriale",
|
||||
"uncertain": "Incertaine",
|
||||
"unenthused": "Peu enthousiaste",
|
||||
"vain": "Vaine",
|
||||
"wary": "Méfiante"
|
||||
"uncertain": "Peu sûre de soi",
|
||||
"unenthused": "Amorphe",
|
||||
"vain": "Orgueilleuse",
|
||||
"vengeful": "Vengeuse",
|
||||
"vindictive": "Vindicative",
|
||||
"wary": "Méfiante",
|
||||
"watchful": "Attentif",
|
||||
"wrathful": "Furieuse",
|
||||
"zealous": "Zélée"
|
||||
},
|
||||
"compendium": {
|
||||
"filter_rank": "Aff. Rangs",
|
||||
@@ -799,6 +837,32 @@
|
||||
"the_scroll_or_the_blade": "Le Parchemin ou le Sabre",
|
||||
"legacies_of_war": "Les Flambeaux de la Guerre",
|
||||
"children_of_the_five_winds": "Les Enfants des Cinq Vents"
|
||||
},
|
||||
"tactical_grid": {
|
||||
"settings": {
|
||||
"title": "Plan Tactique",
|
||||
"label": "Paramètres du Plan Tactique",
|
||||
"hint": "Configure les Niveaux de Portée (GM uniquement), ainsi que les différentes couleurs et transparence (tous les utilisateurs).",
|
||||
"cells": "cases",
|
||||
"world": {
|
||||
"enabled": "Activer le Plan Tactique",
|
||||
"enabled_hint": "Active ou désactive le plan tactique pour tout le monde",
|
||||
"start": "Début"
|
||||
},
|
||||
"client": {
|
||||
"color": "Couleur",
|
||||
"alpha": "Alpha"
|
||||
},
|
||||
"range": "Portée {index}",
|
||||
"validate": {
|
||||
"start-too-small": "Doit être supérieur à la Portée {previousRangeIndex} ({previousStart})",
|
||||
"start-too-large": "Doit être inférieur à la Portée {nextRangeIndex} ({nextStart})"
|
||||
},
|
||||
"reset": "Réinitialiser les paramètres par défaut",
|
||||
"submit": "Enregistrer"
|
||||
},
|
||||
"range_band": "Portée {band}",
|
||||
"range_abbreviation": "NP {range}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,6 +799,32 @@
|
||||
"the_scroll_or_the_blade": "The Scroll or the Blade",
|
||||
"legacies_of_war": "Legacies of War",
|
||||
"children_of_the_five_winds": "Children of the Five Winds"
|
||||
},
|
||||
"tactical_grid": {
|
||||
"settings": {
|
||||
"title": "Tactical Grid Settings",
|
||||
"label": "Tactical Grid Settings",
|
||||
"hint": "Configures tactical grid range band distances (GM only) and their visual appearance colors and transparency (all users).",
|
||||
"cells": "spaces",
|
||||
"world": {
|
||||
"enabled": "Enable Tactical Grid",
|
||||
"enabled_hint": "Enables or Disable tactical grid for everyone",
|
||||
"start": "Start"
|
||||
},
|
||||
"client": {
|
||||
"color": "Color",
|
||||
"alpha": "Alpha"
|
||||
},
|
||||
"range": "Range {index}",
|
||||
"validate": {
|
||||
"start-too-small": "Must be greater than Range Band {previousRangeIndex} ({previousStart})",
|
||||
"start-too-large": "Must be lower then Range Band {nextRangeIndex} ({nextStart})"
|
||||
},
|
||||
"reset": "Reset to Default",
|
||||
"submit": "Save"
|
||||
},
|
||||
"range_band": "Range Band {band}",
|
||||
"range_abbreviation": "RB {range}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,6 +456,7 @@ L5R5E.demeanors = [
|
||||
{ id: "adaptable", mod: { water: 2, earth: -2 } },
|
||||
{ id: "aggressive", mod: { fire: 2, air: -2 } },
|
||||
{ id: "aggressive", mod: { fire: 2, water: -2 } },
|
||||
{ id: "alluring", mod: { air: 2, earth: -1, fire: -1 } },
|
||||
{ id: "ambitious", mod: { fire: 2, water: -2 } },
|
||||
{ id: "amiable", mod: { air: 2, earth: -2 } },
|
||||
{ id: "analytical", mod: { fire: 2, air: -2 } },
|
||||
@@ -466,23 +467,38 @@ L5R5E.demeanors = [
|
||||
{ id: "beguiling", mod: { air: 2, earth: -2 } },
|
||||
{ id: "beguiling", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "bitter", mod: { fire: 2, air: -2 } },
|
||||
{ id: "bloodthirsty", mod: { fire: 2, water: -2 } },
|
||||
{ id: "bold", mod: { fire: 1, earth: -1 } },
|
||||
{ id: "calculating", mod: { air: 2, fire: -2 } },
|
||||
{ id: "calm", mod: { fire: 2, air: -2 } },
|
||||
{ id: "capricious", mod: { air: 2, earth: -2 } },
|
||||
{ id: "cautious", mod: { air: 2, earth: -2 } },
|
||||
{ id: "cautious", mod: { water: 1, void: -1 } },
|
||||
{ id: "clever", mod: { air: 2, earth: -2 } },
|
||||
{ id: "compassionate", mod: { fire: 2, air: -1, water: -1}},
|
||||
{ id: "compassionate", mod: { water: 2, fire: -2 } },
|
||||
{ id: "compassionate", mod: { water: 2, void: -2 } },
|
||||
{ id: "confused", mod: { fire: 1, void: 1, air: -2 } },
|
||||
{ id: "courageous", mod: { air: 2, earth: -2 } },
|
||||
{ id: "cowardly", mod: { earth: 2, fire: -2 } },
|
||||
{ id: "crestfallen", mod: { void: 2, fire: -2 } },
|
||||
{ id: "curious", mod: { earth: 1, void: -2 } },
|
||||
{ id: "curious", mod: { fire: 1, void: 1, air: -2 } },
|
||||
{ id: "defensive", mod: { fire: 2, air: -2 } },
|
||||
{ id: "dependable", mod: { fire: 1, water: 1, earth: -2 } },
|
||||
{ id: "detached", mod: { earth: 1, fire: 1, void: -2 } },
|
||||
{ id: "determined", mod: { earth: 2, air: -2 } },
|
||||
{ id: "devoted", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "direct", mod: { air: 2, fire: -1, water: -1 } },
|
||||
{ id: "disheartened", mod: { fire: 1, earth: -1 } },
|
||||
{ id: "dour", mod: { earth: 1, water: 1, air: -1 } },
|
||||
{ id: "duplicitous", mod: { water: 2, fire: -2 } },
|
||||
{ id: "effusive", mod: { air: 2, earth: -2 } },
|
||||
{ id: "enraged", mod: { air: 1, fire: -2 } },
|
||||
{ id: "fanatical", mod: { earth: 1, air: 1, fire: -2 } },
|
||||
{ id: "feral", mod: { air: 2, fire: -2 } },
|
||||
{ id: "fervent", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "fervent", mod: { air: 1, water: 1, fire: -1, void: -1 } },
|
||||
{ id: "fickle", mod: { fire: 2, air: -2 } },
|
||||
{ id: "fierce", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "flighty", mod: { air: 2, fire: -2 } },
|
||||
@@ -490,32 +506,55 @@ L5R5E.demeanors = [
|
||||
{ id: "flippant", mod: { fire: 2, air: -2 } },
|
||||
{ id: "friendly", mod: { fire: 1, earth: -2, water: -2 } },
|
||||
{ id: "gruff", mod: { water: 2, earth: -2 } },
|
||||
{ id: "honorable", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "hubristic", mod: { earth: 2, air: -2 } },
|
||||
{ id: "hungry", mod: { fire: 2, air: -2 } },
|
||||
{ id: "idealistic", mod: { water: 2, earth: -2 } },
|
||||
{ id: "idealistic", mod: { earth: 1, water: -1 } },
|
||||
{ id: "imposing", mod: { fire: 2, water: -2 } },
|
||||
{ id: "inquisitive", mod: { earth: 2, water: -2 } },
|
||||
{ id: "intense", mod: { air: 2, water: -2 } },
|
||||
{ id: "intense", mod: { fire: 2, water: -2 } },
|
||||
{ id: "intimidating", mod: { fire: 2, air: -2 } },
|
||||
{ id: "irritable", mod: { fire: 2, air: -1, water: -1 } },
|
||||
{ id: "loyal", mod: { air: 1, earth: -2, fire: -2 } },
|
||||
{ id: "loyal", mod: { water: 2, fire: -2 } },
|
||||
{ id: "methodical", mod: { earth: 2, fire: -2 } },
|
||||
{ id: "meticulous", mod: { fire: 1, water: 1, air: -1, earth: -1 } },
|
||||
{ id: "meticulous", mod: { fire: 1, water: 1, earth: -2 } },
|
||||
{ id: "mischievous", mod: { fire: 2, air: -2 } },
|
||||
{ id: "mischievous", mod: { air: 2, earth: -2 } },
|
||||
{ id: "mischievous", mod: { earth: 2, fire: -2 } },
|
||||
{ id: "moon_blessed", mod: { water: 2, fire: -2 } },
|
||||
{ id: "morose", mod: { water: 2, fire: -2 } },
|
||||
{ id: "near_feral", mod: { air: 1, fire: -1 } },
|
||||
{ id: "nurturing", mod: { earth: 2, fire: -2 } },
|
||||
{ id: "obsessed", mod: { earth: 2, air: -2 } },
|
||||
{ id: "obstinate", mod: { earth: 2, air: -2 } },
|
||||
{ id: "obstinate", mod: { water: 2, air: -2 } },
|
||||
{ id: "otherworldly", mod: { water: 1, void: -1 } },
|
||||
{ id: "outgoing", mod: { air: 2, earth: -2 } },
|
||||
{ id: "opportunistic", mod: { water: 2, fire: -2 } },
|
||||
{ id: "passionate", mod: { earth: 2, air: -2 } },
|
||||
{ id: "patient", mod: { fire: 1, water: 1, air: -1, void: -1 } },
|
||||
{ id: "personable", mod: { fire: 2, air: 1, void: -2 } },
|
||||
{ id: "playful", mod: { earth: 2, water: -2 } },
|
||||
{ id: "playful", mod: { fire: 1, air: 1, void: -2 } },
|
||||
{ id: "power_hungry", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "proud", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "refined", mod: { earth: 1, water: 1, air: -1, fire: -1 } },
|
||||
{ id: "reserved", mod: { earth: 2, water: -2 } },
|
||||
{ id: "restrained", mod: { earth: 2, air: -2 } },
|
||||
{ id: "righteous", mod: { water: 2, fire: -1, void: -1 } },
|
||||
{ id: "scheming", mod: { air: 2, void: -2 } },
|
||||
{ id: "serene", mod: { fire: 2, void: -2 } },
|
||||
{ id: "serene", mod: { void: 2, fire: -2 } },
|
||||
{ id: "serious", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "shrewd", mod: { air: 2, fire: -2 } },
|
||||
{ id: "sinister", mod: { fire: 2, air: -2 } },
|
||||
{ id: "sociable", mod: { air: 1, earth: 1, fire: -1, water: -1 } },
|
||||
{ id: "starved", mod: { water: 2, fire: -2 } },
|
||||
{ id: "stoic", mod: { earth: 2, fire: -2 } },
|
||||
{ id: "stubborn", mod: { earth: 2, water: -2 } },
|
||||
{ id: "suspicious", mod: { air: 2, earth: -2 } },
|
||||
{ id: "teasing", mod: { air: 2, earth: -2 } },
|
||||
@@ -523,5 +562,10 @@ L5R5E.demeanors = [
|
||||
{ id: "uncertain", mod: { air: 2, fire: -2 } },
|
||||
{ id: "unenthused", mod: { earth: 2, fire: -2 } },
|
||||
{ id: "vain", mod: { earth: 2, air: -2 } },
|
||||
{ id: "vengeful", mod: { fire: 2, void: -2 } },
|
||||
{ id: "vindictive", mod: { fire: 2, water: -2 } },
|
||||
{ id: "wary", mod: { earth: 2, fire: -2 } },
|
||||
{ id: "watchful", mod: { fire: 2, earth: -1, void: -1 } },
|
||||
{ id: "wrathful", mod: { fire: 2, earth: -2 } },
|
||||
{ id: "zealous", mod: { earth: 2, fire: -2 } },
|
||||
];
|
||||
|
||||
@@ -149,5 +149,8 @@ export class AdvancementSheetL5r5e extends ItemSheetL5r5e {
|
||||
xp_used: xp_used,
|
||||
},
|
||||
});
|
||||
|
||||
// Re-render sheet
|
||||
this.render(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ActorL5r5e } from "./actor.js";
|
||||
import { CharacterSheetL5r5e } from "./actors/character-sheet.js";
|
||||
import { NpcSheetL5r5e } from "./actors/npc-sheet.js";
|
||||
import { ArmySheetL5r5e } from "./actors/army-sheet.js";
|
||||
import { RulerL5r5e, TokenRulerL5r5e } from "./tatical-grid-rulers.js";
|
||||
// Dice and rolls
|
||||
import { L5rBaseDie } from "./dice/dietype/l5r-base-die.js";
|
||||
import { AbilityDie } from "./dice/dietype/ability-die.js";
|
||||
@@ -72,6 +73,8 @@ Hooks.once("init", async () => {
|
||||
CONFIG.Item.documentClass = ItemL5r5e;
|
||||
CONFIG.JournalEntry.documentClass = JournalL5r5e;
|
||||
CONFIG.JournalEntry.sheetClass = BaseJournalSheetL5r5e;
|
||||
CONFIG.Token.rulerClass = TokenRulerL5r5e;
|
||||
CONFIG.Canvas.rulerClass = RulerL5r5e;
|
||||
|
||||
// Define custom Roll class
|
||||
CONFIG.Dice.rolls.unshift(RollL5r5e);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { L5r5eSetField } from "./data/l5r5e-setfield.js";
|
||||
import { TacticalGridSettingsL5R5E } from "./settings/tactical-grid-settings.js"
|
||||
|
||||
/**
|
||||
* Custom system settings register
|
||||
@@ -236,4 +237,29 @@ export const RegisterSettings = function () {
|
||||
default: [],
|
||||
onChange: () => game.l5r5e.HelpersL5r5e.refreshLocalAndSocket("l5r5e-gm-monitor"),
|
||||
});
|
||||
|
||||
/* -------------------------------------- */
|
||||
/* Grid Settings (GM only) */
|
||||
/* -------------------------------------- */
|
||||
|
||||
// UI Configuration
|
||||
game.settings.register(CONFIG.l5r5e.namespace, "tactical-grid-settings-world", {
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: TacticalGridSettingsL5R5E.worldSchema,
|
||||
});
|
||||
|
||||
game.settings.register(CONFIG.l5r5e.namespace, "tactical-grid-settings-client", {
|
||||
scope: "client",
|
||||
config: false,
|
||||
type: TacticalGridSettingsL5R5E.clientSchema,
|
||||
});
|
||||
|
||||
game.settings.registerMenu(CONFIG.l5r5e.namespace, "tactical-grid-settings", {
|
||||
name: "l5r5e.tactical_grid.settings.title",
|
||||
label: "l5r5e.tactical_grid.settings.label",
|
||||
hint: "l5r5e.tactical_grid.settings.hint",
|
||||
icon: "fa-solid fa-table-layout",
|
||||
type: TacticalGridSettingsL5R5E
|
||||
});
|
||||
};
|
||||
|
||||
351
system/scripts/settings/tactical-grid-settings.js
Normal file
351
system/scripts/settings/tactical-grid-settings.js
Normal file
@@ -0,0 +1,351 @@
|
||||
const HandlebarsApplicationMixin = foundry.applications.api.HandlebarsApplicationMixin;
|
||||
const ApplicationV2 = foundry.applications.api.ApplicationV2;
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {Object} RangeBand
|
||||
* @property {number} start
|
||||
*
|
||||
* @typedef {Object} ClientRangeBand
|
||||
* @property {string} color
|
||||
* @property {number} alpha
|
||||
*
|
||||
* @typedef {Object} WorldSettings
|
||||
* @property {boolean} enabled
|
||||
* @property {Record<number, RangeBand>} ranges - Indexed 0-6
|
||||
*
|
||||
* @typedef {Object} ClientSettings
|
||||
* @property {Record<number, ClientRangeBand>} ranges - Indexed 0-6
|
||||
*/
|
||||
|
||||
export class TacticalGridSettingsL5R5E extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
||||
/** @inheritDoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: "tactical-grid-settings",
|
||||
tag: "form",
|
||||
classes: [""], // We could add l5r here but that would add styling that is not matching the default settings menu
|
||||
window: {
|
||||
title: "l5r5e.tactical_grid.settings.title",
|
||||
contentClasses: ["standard-form"]
|
||||
},
|
||||
form: {
|
||||
closeOnSubmit: true,
|
||||
handler: TacticalGridSettingsL5R5E.#onSubmit
|
||||
},
|
||||
position: { width: 540 },
|
||||
actions: {
|
||||
reset: TacticalGridSettingsL5R5E.#onReset
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
form: {
|
||||
template: "systems/l5r5e/templates/" + "settings/tactical-grid-settings.html",
|
||||
scrollable: [""],
|
||||
},
|
||||
footer: {
|
||||
template: "templates/generic/form-footer.hbs"
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SchemaField defining a world range band.
|
||||
* @param {{start: number}} initial - Initial range values.
|
||||
* `start` must be ≥ 0.
|
||||
* * @returns {SchemaField} A schema field containing a 'start' field
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
static #createWorldRangeBandSchema(initial) {
|
||||
return new fields.SchemaField({
|
||||
start: new fields.NumberField({ initial: initial.start, label: "l5r5e.tactical_grid.settings.world.start", min: 0, max:Infinity, nullable: false, required: true, gmOnly: true})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SchemaField defining a client range band.
|
||||
* @param {{color: string, alpha: number}} initial - Initial range band values.
|
||||
* `color` should be a valid CSS color string and `alpha` a valid alpha value.
|
||||
* @returns {SchemaField} A schema field containing `color` and `alpha` fields.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
static #createClientRangeBandSchema(initial) {
|
||||
return new fields.SchemaField({
|
||||
color: new fields.ColorField({initial: initial.color, label: "l5r5e.tactical_grid.settings.client.color", required: true}),
|
||||
alpha: new fields.AlphaField({initial: initial.alpha, label: "l5r5e.tactical_grid.settings.client.alpha", required: true}),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined Foundry VTT settings schema representing both:
|
||||
* - **World (GM-controlled)** configuration
|
||||
* - **Client (per-user)** visual configuration
|
||||
*
|
||||
* This variable serves as a single source-of-truth definition for the module’s
|
||||
* tactical grid settings structure, including field types, defaults, labels, and
|
||||
* validation rules for world ranges.
|
||||
* @private
|
||||
*/
|
||||
static #schema = {
|
||||
world: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({ initial: true, label: "l5r5e.tactical_grid.settings.world.enabled", hint: "l5r5e.tactical_grid.settings.world.enabled_hint"}),
|
||||
ranges: new fields.SchemaField({
|
||||
0: TacticalGridSettingsL5R5E.#createWorldRangeBandSchema({ start: 0}),
|
||||
1: TacticalGridSettingsL5R5E.#createWorldRangeBandSchema({ start: 1}),
|
||||
2: TacticalGridSettingsL5R5E.#createWorldRangeBandSchema({ start: 2}),
|
||||
3: TacticalGridSettingsL5R5E.#createWorldRangeBandSchema({ start: 3}),
|
||||
4: TacticalGridSettingsL5R5E.#createWorldRangeBandSchema({ start: 6}),
|
||||
5: TacticalGridSettingsL5R5E.#createWorldRangeBandSchema({ start: 10}),
|
||||
6: TacticalGridSettingsL5R5E.#createWorldRangeBandSchema({ start: 15})
|
||||
})
|
||||
}, {
|
||||
validate: TacticalGridSettingsL5R5E.#validateWorldRangeConfiguration
|
||||
}),
|
||||
client: new fields.SchemaField({
|
||||
ranges: new fields.SchemaField({
|
||||
0: TacticalGridSettingsL5R5E.#createClientRangeBandSchema({color: "#00FFFF", alpha: 0.5}),
|
||||
1: TacticalGridSettingsL5R5E.#createClientRangeBandSchema({color: "#FF00FF", alpha: 0.5}),
|
||||
2: TacticalGridSettingsL5R5E.#createClientRangeBandSchema({color: "#FFFF00", alpha: 0.5}),
|
||||
3: TacticalGridSettingsL5R5E.#createClientRangeBandSchema({color: "#0000FF", alpha: 0.5}),
|
||||
4: TacticalGridSettingsL5R5E.#createClientRangeBandSchema({color: "#7FFF00", alpha: 0.5}),
|
||||
5: TacticalGridSettingsL5R5E.#createClientRangeBandSchema({color: "#4B0082", alpha: 0.5}),
|
||||
6: TacticalGridSettingsL5R5E.#createClientRangeBandSchema({color: "#FF8800", alpha: 0.5})
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Exposes the **world (GM-controlled)** portion of the tactical grid settings schema.
|
||||
* @return {SchemaField}
|
||||
*/
|
||||
static get worldSchema() {
|
||||
return TacticalGridSettingsL5R5E.#schema.world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the **client (per-user visual)** portion of the tactical grid settings schema.
|
||||
* @return {SchemaField}
|
||||
*/
|
||||
static get clientSchema() {
|
||||
return TacticalGridSettingsL5R5E.#schema.client;
|
||||
}
|
||||
|
||||
/** Holds a mutable copy of the tactical grid settings so the form can operate on current values without altering the schema. */
|
||||
static #setting = null;
|
||||
|
||||
|
||||
/** @override ApplicationV2 */
|
||||
async _prepareContext(options) {
|
||||
if (options.isFirstRender) {
|
||||
const client = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-client");
|
||||
const world = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-world");
|
||||
TacticalGridSettingsL5R5E.#setting = foundry.utils.deepClone({client: client, world: world});
|
||||
}
|
||||
|
||||
// Pre-process range bands for easier template access
|
||||
const rangeBands = Object.entries(this.constructor.worldSchema.fields.ranges.fields).map(([index, field]) => ({
|
||||
index: Number(index),
|
||||
worldField: field,
|
||||
clientFields: this.constructor.clientSchema.fields.ranges.fields[index],
|
||||
worldValue: TacticalGridSettingsL5R5E.#setting.world.ranges[index],
|
||||
clientValue: TacticalGridSettingsL5R5E.#setting.client.ranges[index]
|
||||
}));
|
||||
|
||||
return {
|
||||
isGm: game.user.isGM,
|
||||
tactical_grid_enabled: {
|
||||
field: this.constructor.worldSchema.fields.enabled,
|
||||
value: TacticalGridSettingsL5R5E.#setting.world.enabled,
|
||||
},
|
||||
rangeBands,
|
||||
buttons: [
|
||||
{ type: "reset", label: "l5r5e.tactical_grid.settings.reset", icon: "fa-solid fa-arrow-rotate-left", action: "reset" },
|
||||
{ type: "submit", label: "l5r5e.tactical_grid.settings.submit", icon: "fa-solid fa-floppy-disk" }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/** @override ApplicationV2 */
|
||||
_onChangeForm(formConfig, event) {
|
||||
const formData = new foundry.applications.ux.FormDataExtended(this.form, { readonly: true });
|
||||
|
||||
const {
|
||||
cleaned: cleanedWorldSettings,
|
||||
failure: validationFailures
|
||||
} = TacticalGridSettingsL5R5E.#validateAndCleanWorldSettings(formData, event);
|
||||
TacticalGridSettingsL5R5E.#applyValidationState(validationFailures);
|
||||
|
||||
TacticalGridSettingsL5R5E.#setting.world = cleanedWorldSettings
|
||||
TacticalGridSettingsL5R5E.#setting.client = foundry.utils.expandObject(formData.object).l5r5e["tactical-grid-settings-client"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates world schema ensuring range bands are properly ordered and connected.
|
||||
* Note: internal field validation takes precedence, and will result in this validation potentially not running
|
||||
* Checks that:
|
||||
* - Sequential range bands connect properly (range[n].start < range[n+1].start)
|
||||
* @param {*} value - The world settings object to validate
|
||||
* @param {DataFieldValidationOptions} options - Validation options including lastElementChange
|
||||
* @returns {boolean|foundry.data.validation.DataModelValidationFailure} True if valid, otherwise validation failure object
|
||||
*/
|
||||
static #validateWorldRangeConfiguration(value, options) {
|
||||
if(!value.enabled) // don't validate if tactical_grids are disabled
|
||||
return true;
|
||||
|
||||
let previousStart = -1;
|
||||
let previousRangeIndex = null;
|
||||
const failure = new foundry.data.validation.DataModelValidationFailure({ unresolved: true });
|
||||
const changedElementName = options?.element?.name;
|
||||
|
||||
for (const [rangeIndex, range] of Object.entries(value.ranges)) {
|
||||
if (range.start <= previousStart) {
|
||||
let errorKey = TacticalGridSettingsL5R5E.worldSchema.fields.ranges.fields[rangeIndex].fields.start.fieldPath;
|
||||
const previousErrorKey = errorKey.replace(/\.(\d+)\./, `.${previousRangeIndex}.`);
|
||||
|
||||
let isErrorOnPrevious = false;
|
||||
|
||||
// If the previous field was changed, show error there instead
|
||||
if (changedElementName === previousErrorKey) {
|
||||
errorKey = previousErrorKey;
|
||||
isErrorOnPrevious = true;
|
||||
}
|
||||
|
||||
failure.fields[errorKey] = new foundry.data.validation.DataModelValidationFailure({
|
||||
invalidValue: isErrorOnPrevious ? previousStart : range.start,
|
||||
unresolved: true,
|
||||
message: game.i18n.format(
|
||||
isErrorOnPrevious
|
||||
? "l5r5e.tactical_grid.settings.validate.start-too-large"
|
||||
: "l5r5e.tactical_grid.settings.validate.start-too-small",
|
||||
isErrorOnPrevious
|
||||
? { nextRangeIndex: Number(rangeIndex), nextStart: range.start }
|
||||
: { previousRangeIndex: Number(previousRangeIndex), previousStart: previousStart }
|
||||
)
|
||||
});
|
||||
}
|
||||
previousStart = range.start;
|
||||
previousRangeIndex = rangeIndex;
|
||||
}
|
||||
|
||||
return Object.keys(failure.fields).length > 0 ? failure : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and cleans the world portion of the tactical grid settings.
|
||||
*
|
||||
* Expands raw form data, validates it against the schema, updates form input
|
||||
* error states and tooltips, and returns a cleaned object ready to save.
|
||||
*
|
||||
* @param {foundry.applications.ux.FormDataExtended} formData - The submitted form data.
|
||||
* @param {Event} [event] - Optional event for determining which field changed.
|
||||
* @returns {WorldSettings} A cleaned and validated copy of the world settings.
|
||||
* @private
|
||||
*/
|
||||
static #validateAndCleanWorldSettings(formData, event) {
|
||||
const expanded = foundry.utils.expandObject(formData.object).l5r5e["tactical-grid-settings-world"];
|
||||
const validate = TacticalGridSettingsL5R5E.#schema.world.validate(expanded, { element: event.target});
|
||||
|
||||
// validation from Number etc. itself has the error key just as "ranges.0.start"
|
||||
// so fixing that here so that we can directly reference they html elements
|
||||
const prefix = "l5r5e.tactical-grid-settings-world.";
|
||||
const failures = Object.fromEntries(
|
||||
Object.entries(validate?.asError()?.getAllFailures() ?? {}).map(([key, value]) => [
|
||||
key.startsWith(prefix) ? key : `${prefix}${key}`,
|
||||
value
|
||||
])
|
||||
);
|
||||
|
||||
// Return cleaned schema so that we have something that is somewhat correct we can save
|
||||
return {
|
||||
cleaned: TacticalGridSettingsL5R5E.#schema.world.clean(expanded),
|
||||
failure: failures
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a validation message to a form element.
|
||||
*
|
||||
* @param {HTMLElement} element - The element to apply validation to
|
||||
* @param {string|null} message - The validation message, or null to clear
|
||||
* @private
|
||||
*/
|
||||
static #applyValidationMessage(element, message) {
|
||||
if (message) {
|
||||
element.setCustomValidity(message);
|
||||
element.dataset.tooltip = message;
|
||||
element.ariaLabel = game.i18n.localize(element.dataset.tooltip);
|
||||
game.tooltip.activate(element, {
|
||||
direction: foundry.CONFIG.ux.TooltipManager.TOOLTIP_DIRECTIONS.RIGHT,
|
||||
locked: true
|
||||
});
|
||||
}
|
||||
else {
|
||||
element?.setCustomValidity("");
|
||||
delete element?.dataset?.tooltip
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies validation state to all range band start fields.
|
||||
*
|
||||
* @param {Object} failures - Validation failures keyed by field name
|
||||
* @private
|
||||
*/
|
||||
static #applyValidationState(failures) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const name = `l5r5e.tactical-grid-settings-world.ranges.${i}.start`;
|
||||
this.#applyValidationMessage(
|
||||
document.getElementsByName(name)[0],
|
||||
failures?.[name]?.message || null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles form submission.
|
||||
*
|
||||
* @param {Event} event - The submission event
|
||||
* @param {HTMLFormElement} form - The form element
|
||||
* @param {foundry.applications.ux.FormDataExtended} formData - The submitted form data
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
static async #onSubmit(event, form, formData) {
|
||||
const {
|
||||
cleaned: cleanedWorldSettings,
|
||||
failure: validationFailures
|
||||
} = TacticalGridSettingsL5R5E.#validateAndCleanWorldSettings(formData, event);
|
||||
TacticalGridSettingsL5R5E.#applyValidationState(validationFailures);
|
||||
|
||||
TacticalGridSettingsL5R5E.#setting.world = cleanedWorldSettings;
|
||||
TacticalGridSettingsL5R5E.#setting.client = foundry.utils.expandObject(formData.object).l5r5e["tactical-grid-settings-client"];
|
||||
|
||||
const promises = [];
|
||||
promises.push(game.settings.set(CONFIG.l5r5e.namespace, "tactical-grid-settings-world", TacticalGridSettingsL5R5E.#setting.world));
|
||||
promises.push(game.settings.set(CONFIG.l5r5e.namespace, "tactical-grid-settings-client", TacticalGridSettingsL5R5E.#setting.client));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles reset action to restore default settings.
|
||||
*
|
||||
* @param {Event} event - The reset event
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
static async #onReset(event) {
|
||||
const client = TacticalGridSettingsL5R5E.clientSchema.clean();
|
||||
const world = TacticalGridSettingsL5R5E.worldSchema.clean();
|
||||
TacticalGridSettingsL5R5E.#setting = foundry.utils.deepClone({client: client, world: world});
|
||||
|
||||
await this.render({ force: false });
|
||||
}
|
||||
}
|
||||
|
||||
81
system/scripts/tatical-grid-rulers.js
Normal file
81
system/scripts/tatical-grid-rulers.js
Normal file
@@ -0,0 +1,81 @@
|
||||
function getRangeband(gridSettings, distance) {
|
||||
const entries = Object.entries(gridSettings.ranges);
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
const [range, { start }] = entries[i];
|
||||
if (distance >= start) {
|
||||
return Number(range);
|
||||
}
|
||||
}
|
||||
return NaN;
|
||||
}
|
||||
|
||||
export class RulerL5r5e extends foundry.canvas.interaction.Ruler {
|
||||
|
||||
static WAYPOINT_LABEL_TEMPLATE = "systems/l5r5e/templates/" + "hud/tactical-grid-ruler.html"
|
||||
|
||||
/** @override */
|
||||
_getWaypointLabelContext(waypoint, state) {
|
||||
const context = super._getWaypointLabelContext(waypoint, state);
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
const gridSettings = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-world");
|
||||
if(gridSettings.enabled) {
|
||||
const diagonalCost = game.canvas.grid.distance * waypoint.measurement.diagonals;
|
||||
context.distance.total = waypoint.measurement.distance.toNearest(0.1) + diagonalCost; //Diagonals count twice
|
||||
context.additional = {
|
||||
label: game.i18n.format("l5r5e.tactical_grid.range_abbreviation", {range: getRangeband(gridSettings, waypoint.measurement.distance)})
|
||||
};
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_getSegmentStyle(waypoint) {
|
||||
const context = super._getSegmentStyle(waypoint);
|
||||
const client = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-client");
|
||||
const gridSettings = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-world");
|
||||
if(gridSettings.enabled) {
|
||||
const rangeband = getRangeband(gridSettings, waypoint.measurement.distance);
|
||||
context.color = client.ranges[rangeband].color;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
export class TokenRulerL5r5e extends foundry.canvas.placeables.tokens.TokenRuler {
|
||||
static WAYPOINT_LABEL_TEMPLATE = "systems/l5r5e/templates/" + "hud/tactical-grid-ruler.html"
|
||||
|
||||
/** @override */
|
||||
_getWaypointLabelContext(waypoint, state) {
|
||||
const context = super._getWaypointLabelContext(waypoint, state);
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
if (!this.token.actor)
|
||||
return context;
|
||||
|
||||
const gridSettings = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-world");
|
||||
if(gridSettings.enabled) {
|
||||
const diagonalCost = game.canvas.grid.distance * waypoint.measurement.diagonals;
|
||||
context.cost.total = waypoint.measurement.cost.toNearest(0.1) + diagonalCost; //Diagonals count twice
|
||||
context.additional = {
|
||||
label: game.i18n.format("l5r5e.tactical_grid.range_abbreviation", {range: getRangeband(gridSettings, waypoint.measurement.distance)})
|
||||
};
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_getGridHighlightStyle(waypoint, offset) {
|
||||
const context = super._getGridHighlightStyle(waypoint, offset);
|
||||
const client = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-client");
|
||||
const gridSettings = game.settings.get(CONFIG.l5r5e.namespace, "tactical-grid-settings-world");
|
||||
if(gridSettings.enabled) {
|
||||
const rangeband = getRangeband(gridSettings, waypoint.measurement.distance);
|
||||
context.color = client.ranges[rangeband].color;
|
||||
context.alpha = client.ranges[rangeband].alpha;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
@import "../scss/skills";
|
||||
@import "../scss/items";
|
||||
@import "../scss/twenty-questions";
|
||||
@import "../scss/tactical-grid";
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
32
system/styles/scss/tactical-grid.scss
Normal file
32
system/styles/scss/tactical-grid.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
// Set the label for in-world measurement to be the same as normal waypoint-label
|
||||
@at-root #measurement .waypoint-label-additional {
|
||||
color: var(--color-text-emphatic);
|
||||
font-size: var(--font-size-24);
|
||||
}
|
||||
|
||||
@at-root #tactical-grid-settings {
|
||||
input[type="number"]:invalid {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
input[type="number"]:read-only {
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
pointer-events: none; /* not clickable or focusable */
|
||||
user-select: none; /* text cannot be selected */
|
||||
-webkit-user-select: none; /* Safari/Chrome */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
}
|
||||
|
||||
.range_band {
|
||||
display: flex;
|
||||
flex-flow: wrap;
|
||||
fieldset {
|
||||
flex: 25%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"changelog": "https://gitlab.com/teaml5r/l5r5e/-/blob/master/CHANGELOG.md",
|
||||
"license": "https://gitlab.com/teaml5r/l5r5e/-/blob/master/LICENSE.md",
|
||||
"manifest": "https://gitlab.com/teaml5r/l5r5e/-/raw/master/system/system.json",
|
||||
"download": "https://gitlab.com/teaml5r/l5r5e/-/jobs/artifacts/v1.13.2/raw/l5r5e.zip?job=build",
|
||||
"version": "1.13.2",
|
||||
"download": "https://gitlab.com/teaml5r/l5r5e/-/jobs/artifacts/v1.13.3/raw/l5r5e.zip?job=build",
|
||||
"version": "1.13.3",
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
"verified": "13",
|
||||
|
||||
45
system/templates/hud/tactical-grid-ruler.html
Normal file
45
system/templates/hud/tactical-grid-ruler.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<div class="waypoint-label vertical {{cssClass}}">
|
||||
<div>
|
||||
{{#if action.icon}}
|
||||
<i class="icon {{action.icon}}"></i>
|
||||
{{else if action.label}}
|
||||
<label class="action-label">Action: {{localize action.label}}</label>
|
||||
{{/if}}
|
||||
{{#if cost}}
|
||||
<span class="total-measurement">{{cost.total}}</span>
|
||||
{{#if cost.delta}}
|
||||
<span class="delta-measurement">Cost Delta: ({{cost.delta}})</span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="total-measurement">{{distance.total}} {{units}}</span>
|
||||
{{#if distance.delta}}
|
||||
<span class="delta-measurement">Total Measure: ({{distance.delta}})</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (and elevation (not elevation.hidden))}}
|
||||
<i class="icon {{elevation.icon}}"></i>
|
||||
<span class="total-elevation">{{elevation.total}} {{units}}</span>
|
||||
{{#if elevation.delta}}
|
||||
<span class="delta-elevation">({{elevation.delta}})</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if secret}}
|
||||
<i class="icon fa-solid fa-eye-slash"></i>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if additional}}
|
||||
<div class="waypoint-label-additional {{additional.cssClass}}">
|
||||
{{#if additional.icon}}
|
||||
<i class="icon {{additional.icon}}"></i>
|
||||
{{/if}}
|
||||
<span class="waypoint-label-text">{{additional.label}} {{additional.cost}}</span>
|
||||
{{#if additional.imgs.length}}
|
||||
{{#each additional.imgs as |img|}}
|
||||
<img class="icon" src="{{img}}">
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<img class="icon" src="{{additional.img}}">
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
37
system/templates/settings/tactical-grid-settings.html
Normal file
37
system/templates/settings/tactical-grid-settings.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<section class="standard-form scrollable">
|
||||
{{!-- GM-only: Enable/Disable Tactical Grid --}}
|
||||
{{#if isGm}}
|
||||
<fieldset>
|
||||
{{formGroup tactical_grid_enabled.field value=tactical_grid_enabled.value localize=true}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
|
||||
{{!-- Range Band Configuration --}}
|
||||
<div class="range_band">
|
||||
{{#each rangeBands as |band|}}
|
||||
<fieldset>
|
||||
<legend>
|
||||
{{localize "l5r5e.tactical_grid.range_band" band=band.worldField.name}}
|
||||
</legend>
|
||||
|
||||
{{!-- GM-only: Range start distance --}}
|
||||
{{#if @root.isGm}}
|
||||
{{formGroup band.worldField.fields.start
|
||||
value=band.worldValue.start
|
||||
localize=true
|
||||
type="number"
|
||||
readonly=(eq band.index 0)}}
|
||||
{{/if}}
|
||||
|
||||
{{!-- Client: Visual settings --}}
|
||||
{{formGroup band.clientFields.fields.color
|
||||
value=band.clientValue.color
|
||||
localize=true}}
|
||||
|
||||
{{formGroup band.clientFields.fields.alpha
|
||||
value=band.clientValue.alpha
|
||||
localize=true}}
|
||||
</fieldset>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user