Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| caef2c85d2 | |||
| e43487b727 | |||
| 709e69fac8 | |||
| e26db56585 | |||
| bb6a6248f2 | |||
| 7dc2492c96 | |||
| 2c25820152 | |||
| 6ad8226265 | |||
| 68a0d03740 | |||
| 8d9cc1045c | |||
| 4af277d8a2 | |||
| bc9f397755 | |||
| 264a5c7a4c | |||
| 4edbc9b618 |
@@ -0,0 +1 @@
|
||||
packs/** filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -59,5 +59,5 @@ jobs:
|
||||
version: ${{github.event.release.tag_name}}
|
||||
manifest: 'https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal/releases/download/latest/system.json'
|
||||
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-cthulhu-eternal.zip'
|
||||
compatibility-minimum: '13'
|
||||
compatibility-verified: '13'
|
||||
compatibility-minimum: '14'
|
||||
compatibility-verified: '14'
|
||||
@@ -5,4 +5,6 @@ styles/*.css
|
||||
|
||||
# Node Modules
|
||||
node_modules/
|
||||
.github/
|
||||
.history/
|
||||
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"CTHULHUETERNAL"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
Code license :
|
||||
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
|
||||
This license requires that reusers give credit to the creator. It allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, for noncommercial purposes only. If others modify or adapt the material, they must license the modified material under identical terms.
|
||||
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
{
|
||||
"label": "Armes",
|
||||
"folders": {
|
||||
"Automatic and Heavy Weapons": "Automatiques et armes lourdes",
|
||||
"Firearms": "Armes à feu",
|
||||
"Melee Weapons": "Armes de mêlée",
|
||||
"Ranged Weapons": "Armes à distance",
|
||||
"Ranged weapons": "Armes à distance",
|
||||
"Thermal Weapons": "Armes thermiques",
|
||||
"Weapons - Age Of Revolutions": "Armes - Âge des Révolutions",
|
||||
"Weapons - Age Of Sails": "Armes - Âge de la Voile",
|
||||
"Weapons - Classical Era": "Armes - Époque Classique",
|
||||
"Weapons - Cold War": "Armes - Guerre Froide",
|
||||
"Weapons - Future": "Armes - Futur",
|
||||
"Weapons - Jazz": "Armes - Jazz/Années folles",
|
||||
"Weapons - Medieval": "Armes - Médiévales",
|
||||
"Weapons - Modern": "Armes - Modernes",
|
||||
"Weapons - Post Apocalyptic": "Armes - Post-apocalyptiques",
|
||||
"Weapons - Victorian": "Armes - Victoriennes",
|
||||
"Weapons - World War I": "Armes - Première Guerre mondiale",
|
||||
"Weapons - World War II": "Armes - Seconde Guerre mondiale"
|
||||
},
|
||||
"mapping": {
|
||||
"rangeUnit": {
|
||||
"path": "system.rangeUnit",
|
||||
"converter": "translateRangeUnit"
|
||||
},
|
||||
"baseRange": {
|
||||
"path": "system.baseRange",
|
||||
"converter": "translateRange"
|
||||
}
|
||||
},
|
||||
"entries": {
|
||||
"Anti-tank rifle": {
|
||||
"name": "Arme antichar"
|
||||
},
|
||||
"Assault rifle": {
|
||||
"name": "Fusil d'assaut"
|
||||
},
|
||||
"Axe": {
|
||||
"name": "Hache"
|
||||
},
|
||||
"Bare hands and feet": {
|
||||
"name": "Mains et pieds nus"
|
||||
},
|
||||
"Baseball Bat": {
|
||||
"name": "Batte de baseball"
|
||||
},
|
||||
"Baseball bat": {
|
||||
"name": "Batte de baseball"
|
||||
},
|
||||
"Battle laser": {
|
||||
"name": "Laser de combat"
|
||||
},
|
||||
"Battle rifle": {
|
||||
"name": "Fusil de combat"
|
||||
},
|
||||
"Bayonet": {
|
||||
"name": "Baïonnette"
|
||||
},
|
||||
"Blackjack": {
|
||||
"name": "Blackjack"
|
||||
},
|
||||
"Blunderbuss Pistol": {
|
||||
"name": "Pistolet tromblon"
|
||||
},
|
||||
"Bow": {
|
||||
"name": "Arc"
|
||||
},
|
||||
"Brass knuckles": {
|
||||
"name": "Poings américains"
|
||||
},
|
||||
"Broad sword": {
|
||||
"name": "Épée large"
|
||||
},
|
||||
"Buffalo Rifle": {
|
||||
"name": "Fusil Buffalo"
|
||||
},
|
||||
"Cane Gun": {
|
||||
"name": "Pistolet canne"
|
||||
},
|
||||
"Cannon Barrel Pistol": {
|
||||
"name": "Pistolet canon"
|
||||
},
|
||||
"Carbine": {
|
||||
"name": "Carabine"
|
||||
},
|
||||
"Cavalry lance": {
|
||||
"name": "Lance de cavalerie"
|
||||
},
|
||||
"Cavalry sabre": {
|
||||
"name": "Sabre de cavalerie"
|
||||
},
|
||||
"Ceramic Grenade": {
|
||||
"name": "Grenade en céramique"
|
||||
},
|
||||
"Chainsaw": {
|
||||
"name": "Tronçonneuse"
|
||||
},
|
||||
"Claymore": {
|
||||
"name": "Claymore"
|
||||
},
|
||||
"Club": {
|
||||
"name": "Gourdin"
|
||||
},
|
||||
"Combat knife": {
|
||||
"name": "Couteau de combat"
|
||||
},
|
||||
"Cosh": {
|
||||
"name": "Matraque"
|
||||
},
|
||||
"Cricket bat": {
|
||||
"name": "Batte de cricket"
|
||||
},
|
||||
"Crossbow": {
|
||||
"name": "Arbalète"
|
||||
},
|
||||
"Cutlass": {
|
||||
"name": "Coutelas"
|
||||
},
|
||||
"Dagger": {
|
||||
"name": "Dague"
|
||||
},
|
||||
"Derringer": {
|
||||
"name": "Derringer"
|
||||
},
|
||||
"Dueling Pistol": {
|
||||
"name": "Pistolet de duel"
|
||||
},
|
||||
"Dutch (long) Pistol": {
|
||||
"name": "Pistolet néerlandais (long)"
|
||||
},
|
||||
"Elephant Gun": {
|
||||
"name": "Fusil à éléphant"
|
||||
},
|
||||
"Flintlock Pistol": {
|
||||
"name": "Pistolet à silex"
|
||||
},
|
||||
"Fowling Piece": {
|
||||
"name": "Fusil de chasse"
|
||||
},
|
||||
"Garotte": {
|
||||
"name": "Garrot"
|
||||
},
|
||||
"Gatling Gun": {
|
||||
"name": "Gatling Gun"
|
||||
},
|
||||
"Guntō": {
|
||||
"name": "Guntō"
|
||||
},
|
||||
"Hand Grenade": {
|
||||
"name": "Grenade à main"
|
||||
},
|
||||
"Hand grenade": {
|
||||
"name": "Grenade à main"
|
||||
},
|
||||
"Hatchet": {
|
||||
"name": "Hachette"
|
||||
},
|
||||
"Heavy Pistol": {
|
||||
"name": "Pistolet lourd"
|
||||
},
|
||||
"Heavy Spear": {
|
||||
"name": "Lance lourde"
|
||||
},
|
||||
"Heavy axe": {
|
||||
"name": "Hache lourde"
|
||||
},
|
||||
"Heavy hammer": {
|
||||
"name": "Marteau lourd"
|
||||
},
|
||||
"Heavy pistol": {
|
||||
"name": "Pistolet gros calibre"
|
||||
},
|
||||
"Heavy spear": {
|
||||
"name": "Lance lourde"
|
||||
},
|
||||
"Incendiary Grenade": {
|
||||
"name": "Grenade incendiaire"
|
||||
},
|
||||
"Javelin": {
|
||||
"name": "Javelot"
|
||||
},
|
||||
"Katana": {
|
||||
"name": "Katana"
|
||||
},
|
||||
"Knife": {
|
||||
"name": "Couteau"
|
||||
},
|
||||
"Large Caliber Pistol": {
|
||||
"name": "Pistolet gros calibre"
|
||||
},
|
||||
"Large sword": {
|
||||
"name": "Grande épée"
|
||||
},
|
||||
"Laser carbine": {
|
||||
"name": "Carabine laser"
|
||||
},
|
||||
"Life preserver": {
|
||||
"name": "Gilet de sauvetage"
|
||||
},
|
||||
"Light Spear": {
|
||||
"name": "Lance légère"
|
||||
},
|
||||
"Light axe": {
|
||||
"name": "Hache légère"
|
||||
},
|
||||
"Light spear": {
|
||||
"name": "Lance légère"
|
||||
},
|
||||
"Long spear": {
|
||||
"name": "Lance longue"
|
||||
},
|
||||
"Long sword": {
|
||||
"name": "Epée longue"
|
||||
},
|
||||
"Longrifle": {
|
||||
"name": "Fusil long"
|
||||
},
|
||||
"Mace": {
|
||||
"name": "Masse"
|
||||
},
|
||||
"Machete": {
|
||||
"name": "Machette"
|
||||
},
|
||||
"Maxim": {
|
||||
"name": "Maxime"
|
||||
},
|
||||
"Medium Pistol": {
|
||||
"name": "Pistolet moyen"
|
||||
},
|
||||
"Musket": {
|
||||
"name": "Mousquet"
|
||||
},
|
||||
"Nightstick": {
|
||||
"name": "Matraque"
|
||||
},
|
||||
"Oil": {
|
||||
"name": "Huile"
|
||||
},
|
||||
"Ordinary knife": {
|
||||
"name": "Couteau ordinaire"
|
||||
},
|
||||
"Plasma rifle": {
|
||||
"name": "Fusil à plasma"
|
||||
},
|
||||
"Polearm": {
|
||||
"name": "Arme d'hast"
|
||||
},
|
||||
"Power sword": {
|
||||
"name": "Epée énergétique"
|
||||
},
|
||||
"Rapier": {
|
||||
"name": "Rapière"
|
||||
},
|
||||
"Rifle": {
|
||||
"name": "Fusil"
|
||||
},
|
||||
"Sabre": {
|
||||
"name": "Sabre"
|
||||
},
|
||||
"Scimitar": {
|
||||
"name": "Cimeterre"
|
||||
},
|
||||
"Seax": {
|
||||
"name": "Seax"
|
||||
},
|
||||
"Shield bash": {
|
||||
"name": "Coup de bouclier"
|
||||
},
|
||||
"Shiv": {
|
||||
"name": "Shiv"
|
||||
},
|
||||
"Short spear": {
|
||||
"name": "Lance courte"
|
||||
},
|
||||
"Short sword": {
|
||||
"name": "Epée courte"
|
||||
},
|
||||
"Shotgun": {
|
||||
"name": "Shotgun"
|
||||
},
|
||||
"Shuriken": {
|
||||
"name": "Shuriken"
|
||||
},
|
||||
"Sickle": {
|
||||
"name": "Faucille"
|
||||
},
|
||||
"Sling": {
|
||||
"name": "Fronde"
|
||||
},
|
||||
"Small-caliber pistol": {
|
||||
"name": "Pistolet de petit calibre"
|
||||
},
|
||||
"Small‐caliber pistol": {
|
||||
"name": "Pistolet de petit calibre"
|
||||
},
|
||||
"Staff": {
|
||||
"name": "Bâton"
|
||||
},
|
||||
"Steel-toe boot": {
|
||||
"name": "Botte à embout en acier"
|
||||
},
|
||||
"Steel‐toe boot": {
|
||||
"name": "Botte à embout en acier"
|
||||
},
|
||||
"Submachine gun": {
|
||||
"name": "Pistolet-mitrailleur"
|
||||
},
|
||||
"Sulphur soaked cloth": {
|
||||
"name": "Tissu imbibé de soufre"
|
||||
},
|
||||
"Switchblade": {
|
||||
"name": "Couteau à cran d'arrêt"
|
||||
},
|
||||
"Sword cane": {
|
||||
"name": "Canne épée"
|
||||
},
|
||||
"Taser": {
|
||||
"name": "Taser"
|
||||
},
|
||||
"Thompson": {
|
||||
"name": "Thompson"
|
||||
},
|
||||
"Thrown Axe": {
|
||||
"name": "Hache de lancer"
|
||||
},
|
||||
"Thrown Hand Grenade": {
|
||||
"name": "Grenade à main de lancer"
|
||||
},
|
||||
"Thrown Heavy Spear": {
|
||||
"name": "Lance lourde lancée"
|
||||
},
|
||||
"Thrown Knife": {
|
||||
"name": "Couteau de lancer"
|
||||
},
|
||||
"Thrown Light Spear": {
|
||||
"name": "Lance légère lancée"
|
||||
},
|
||||
"Thrown Long Spear": {
|
||||
"name": "Lance longue lancée"
|
||||
},
|
||||
"Thrown Net": {
|
||||
"name": "Filet"
|
||||
},
|
||||
"Thrown Rock": {
|
||||
"name": "Pierre lancée"
|
||||
},
|
||||
"Thrown Short Spear": {
|
||||
"name": "Lance courte lancée"
|
||||
},
|
||||
"Thrown Spear": {
|
||||
"name": "Lance lancée"
|
||||
},
|
||||
"Thrown Spear, Heavy": {
|
||||
"name": "Lance lancée, lourde"
|
||||
},
|
||||
"Thrown Spear, Light": {
|
||||
"name": "Lance lancée, légère"
|
||||
},
|
||||
"Tomahawk": {
|
||||
"name": "Tomahawk"
|
||||
},
|
||||
"Trident": {
|
||||
"name": "Trident"
|
||||
},
|
||||
"Two-Handed sword": {
|
||||
"name": "Epée à deux mains"
|
||||
},
|
||||
"Two-handed sword": {
|
||||
"name": "Epée à deux mains"
|
||||
},
|
||||
"Very heavy sniper laser": {
|
||||
"name": "Laser lourd de sniper"
|
||||
},
|
||||
"Very heavy sniper rifle": {
|
||||
"name": "Fusil lourd de sniper"
|
||||
},
|
||||
"Very large pistol": {
|
||||
"name": "Pistolet très gros calibre"
|
||||
},
|
||||
"WW1 Machinegun": {
|
||||
"name": "Mitrailleuse de la Première Guerre mondiale"
|
||||
},
|
||||
"War club": {
|
||||
"name": "Massue de guerre"
|
||||
}
|
||||
}
|
||||
}
|
||||
+265
-15
@@ -232,6 +232,32 @@ i.fvtt-cthulhu-eternal {
|
||||
font-family: var(--font-primary);
|
||||
font-size: calc(var(--font-size-standard) * 1.02);
|
||||
}
|
||||
.chat-san-request ul .combatants-grid,
|
||||
.chat-lethal-damage ul .combatants-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 4px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.chat-san-request ul .combatants-grid button.apply-wounds-btn,
|
||||
.chat-lethal-damage ul .combatants-grid button.apply-wounds-btn {
|
||||
font-family: var(--font-primary);
|
||||
font-size: calc(var(--font-size-standard) * 0.7);
|
||||
border: 1px solid #4b4a44;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background-color: #f0f0e0;
|
||||
color: #1c1c1c;
|
||||
}
|
||||
.chat-san-request ul .combatants-grid button.apply-wounds-btn:hover,
|
||||
.chat-lethal-damage ul .combatants-grid button.apply-wounds-btn:hover {
|
||||
background-color: #d5d5c5;
|
||||
border-color: #2d2d2a;
|
||||
}
|
||||
.fvtt-cthulhu-eternal .protagonist-sheet-common label {
|
||||
font-family: var(--font-secondary);
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
@@ -651,6 +677,12 @@ i.fvtt-cthulhu-eternal {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
.fvtt-cthulhu-eternal .empty-state {
|
||||
color: var(--color-text-light-6);
|
||||
font-style: italic;
|
||||
padding: 0.5rem 0.25rem;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.fvtt-cthulhu-eternal .tab.protagonist-skills .main-div .skills .skill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -892,6 +924,20 @@ i.fvtt-cthulhu-eternal {
|
||||
min-width: 1.8rem;
|
||||
max-width: 1.8rem;
|
||||
}
|
||||
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .armors .armor .equipped-toggle {
|
||||
font-size: 1rem;
|
||||
min-width: 1.4rem;
|
||||
max-width: 1.4rem;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
}
|
||||
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .armors .armor .equipped-toggle.active {
|
||||
color: #4caf50;
|
||||
}
|
||||
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .armors .armor .equipped-toggle:hover {
|
||||
color: #2196f3;
|
||||
}
|
||||
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .armors .armor .protection {
|
||||
min-width: 5rem;
|
||||
max-width: 5rem;
|
||||
@@ -3041,6 +3087,9 @@ i.fvtt-cthulhu-eternal {
|
||||
font-size: calc(var(--font-size-standard) * 2);
|
||||
color: var(--color-dark-1);
|
||||
}
|
||||
.li-apply-wounds {
|
||||
display: none;
|
||||
}
|
||||
.dice-roll {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -3084,21 +3133,6 @@ i.fvtt-cthulhu-eternal {
|
||||
font-family: var(--font-primary);
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
}
|
||||
.dice-roll .intro-chat .intro-right ul .nudge-roll {
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
margin-left: 2rem;
|
||||
display: none;
|
||||
}
|
||||
.dice-roll .intro-chat .intro-right ul .healing-roll {
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
margin-left: 2rem;
|
||||
display: none;
|
||||
}
|
||||
.dice-roll .intro-chat .intro-right ul .roll-damage {
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
margin-left: 2rem;
|
||||
display: none;
|
||||
}
|
||||
.dice-roll .intro-chat .intro-right ul .result-success {
|
||||
color: var(--color-success);
|
||||
font-family: var(--font-title);
|
||||
@@ -3133,3 +3167,219 @@ i.fvtt-cthulhu-eternal {
|
||||
font-size: calc(var(--font-size-standard) * 1.2);
|
||||
text-shadow: 0 0 10px var(--color-shadow-primary);
|
||||
}
|
||||
.dice-roll .skill-progress-notice {
|
||||
margin: 0.3rem 0 0;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
color: var(--color-level-warning);
|
||||
border-left: 3px solid var(--color-level-warning);
|
||||
background: rgba(var(--color-level-warning-rgb, 180, 130, 0), 0.08);
|
||||
}
|
||||
.dice-roll .chat-actions { display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
border-top: 1px solid var(--color-border-light-primary);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0 0 5px 5px;
|
||||
justify-content: center;
|
||||
}
|
||||
.dice-roll .chat-actions .chat-action-button {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: var(--color-dark-6);
|
||||
color: var(--color-light-1);
|
||||
border: 1px solid var(--color-border-light-primary);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
}
|
||||
.dice-roll .chat-actions .chat-action-button:hover {
|
||||
background: var(--color-dark-5);
|
||||
border-color: var(--color-border-dark);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.dice-roll .chat-actions .chat-action-button i {
|
||||
font-size: 1rem !important;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.dice-roll .chat-actions .nudge-roll,
|
||||
.dice-roll .chat-actions .damage-roll,
|
||||
.dice-roll .chat-actions .healing-roll,
|
||||
.dice-roll .chat-actions .opposed-roll,
|
||||
.dice-roll .chat-actions .nudge-to-success {
|
||||
display: none;
|
||||
}
|
||||
.dice-roll .chat-actions .nudge-to-success {
|
||||
width: auto;
|
||||
padding: 0 0.5rem !important;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
background: var(--color-level-success);
|
||||
color: var(--color-light-1);
|
||||
border-color: var(--color-level-success);
|
||||
}
|
||||
.dice-roll .chat-actions .nudge-to-success:hover {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
.opposed-roll-result {
|
||||
padding: 0.5rem;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 5px;
|
||||
font-family: var(--font-primary);
|
||||
}
|
||||
.opposed-roll-result .opposed-header {
|
||||
text-align: center;
|
||||
margin-bottom: 0.3rem;
|
||||
padding-bottom: 0.2rem;
|
||||
border-bottom: 1px solid var(--color-border-light-primary);
|
||||
}
|
||||
.opposed-roll-result .opposed-header h3 {
|
||||
margin: 0;
|
||||
font-family: var(--font-title);
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
color: var(--color-dark-1);
|
||||
}
|
||||
.opposed-roll-result .opposed-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
.opposed-roll-result .opposed-winner,
|
||||
.opposed-roll-result .opposed-loser {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.3rem 0.4rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.opposed-roll-result .opposed-winner {
|
||||
background: rgba(34, 139, 34, 0.1);
|
||||
border: 2px solid var(--color-success);
|
||||
}
|
||||
.opposed-roll-result .opposed-loser {
|
||||
background: rgba(220, 20, 60, 0.1);
|
||||
border: 2px solid var(--color-failure);
|
||||
}
|
||||
.opposed-roll-result .character-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.opposed-roll-result .character-info img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--color-border-light-primary);
|
||||
}
|
||||
.opposed-roll-result .character-info .character-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
.opposed-roll-result .character-info .character-name strong {
|
||||
font-size: calc(var(--font-size-standard) * 0.75);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-dark-2);
|
||||
}
|
||||
.opposed-roll-result .character-info .character-name .winner-name {
|
||||
font-size: calc(var(--font-size-standard) * 0.95);
|
||||
font-weight: bold;
|
||||
color: var(--color-success);
|
||||
}
|
||||
.opposed-roll-result .character-info .character-name .loser-name {
|
||||
font-size: calc(var(--font-size-standard) * 0.95);
|
||||
font-weight: bold;
|
||||
color: var(--color-failure);
|
||||
}
|
||||
.opposed-roll-result .roll-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.opposed-roll-result .roll-result .roll-value {
|
||||
font-size: calc(var(--font-size-standard) * 1.2);
|
||||
font-weight: bold;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
.opposed-roll-result .roll-result .critical-badge {
|
||||
padding: 0.15rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
font-size: calc(var(--font-size-standard) * 0.7);
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
background: var(--color-critical-success);
|
||||
color: white;
|
||||
}
|
||||
.opposed-roll-result .winner-result .roll-value {
|
||||
color: var(--color-success);
|
||||
}
|
||||
.opposed-roll-result .loser-result .roll-value {
|
||||
color: var(--color-failure);
|
||||
}
|
||||
.opposed-roll-result .versus-separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0;
|
||||
font-size: calc(var(--font-size-standard) * 0.85);
|
||||
font-weight: bold;
|
||||
color: var(--color-dark-2);
|
||||
}
|
||||
.opposed-roll-result .versus-separator i {
|
||||
font-size: calc(var(--font-size-standard) * 0.9);
|
||||
}
|
||||
.opposed-roll-result .chat-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.3rem;
|
||||
border-top: 1px solid var(--color-border-light-primary);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
.opposed-roll-result .chat-actions .chat-action-button {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: var(--color-dark-6);
|
||||
color: var(--color-light-1);
|
||||
border: 1px solid var(--color-border-light-primary);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
}
|
||||
.opposed-roll-result .chat-actions .chat-action-button:hover {
|
||||
background: var(--color-dark-5);
|
||||
border-color: var(--color-border-dark);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.opposed-roll-result .chat-actions .chat-action-button i {
|
||||
font-size: 1rem !important;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
+120
-16
@@ -14,6 +14,9 @@ import * as applications from "./module/applications/_module.mjs"
|
||||
import { handleSocketEvent } from "./module/socket.mjs"
|
||||
import CthulhuEternalUtils from "./module/utils.mjs"
|
||||
|
||||
import { SystemManager } from './module/applications/hud/system-manager.js'
|
||||
import { MODULE, REQUIRED_CORE_MODULE_VERSION } from './module/applications/hud/constants.js'
|
||||
|
||||
export class ClassCounter { static printHello() { console.log("Hello") } static sendJsonPostRequest(e, s) { const t = { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify(s) }; return fetch(e, t).then((e => { if (!e.ok) throw new Error("La requête a échoué avec le statut " + e.status); return e.json() })).catch((e => { throw console.error("Erreur envoi de la requête:", e), e })) } static registerUsageCount(e = game.system.id, s = {}) { if (game.user.isGM) { game.settings.register(e, "world-key", { name: "Unique world key", scope: "world", config: !1, default: "", type: String }); let t = game.settings.get(e, "world-key"); null != t && "" != t && "NONE" != t && "none" != t.toLowerCase() || (t = foundry.utils.randomID(32), game.settings.set(e, "world-key", t)); let a = { name: e, system: game.system.id, worldKey: t, version: game.system.version, language: game.settings.get("core", "language"), remoteAddr: game.data.addresses.remote, nbInstalledModules: game.modules.size, nbActiveModules: game.modules.filter((e => e.active)).length, nbPacks: game.world.packs.size, nbUsers: game.users.size, nbScenes: game.scenes.size, nbActors: game.actors.size, nbPlaylist: game.playlists.size, nbTables: game.tables.size, nbCards: game.cards.size, optionsData: s, foundryVersion: `${game.release.generation}.${game.release.build}` }; this.sendJsonPostRequest("https://www.uberwald.me/fvtt_appcount/count_post.php", a) } } }
|
||||
|
||||
Hooks.once("init", function () {
|
||||
@@ -29,6 +32,12 @@ Hooks.once("init", function () {
|
||||
models,
|
||||
documents,
|
||||
}
|
||||
// Set an initiative formula for the system
|
||||
CONFIG.Combat.initiative = {
|
||||
formula: "@characteristics.dex.value",
|
||||
decimals: 1
|
||||
};
|
||||
|
||||
|
||||
CONFIG.Actor.documentClass = documents.CthulhuEternalActor
|
||||
CONFIG.Actor.dataModels = {
|
||||
@@ -93,13 +102,14 @@ Hooks.once("init", function () {
|
||||
CthulhuEternalUtils.registerSettings()
|
||||
CthulhuEternalUtils.registerHandlebarsHelpers()
|
||||
CthulhuEternalUtils.setupCSSRootVariables()
|
||||
|
||||
CONFIG.debug.hooks = false;
|
||||
|
||||
console.info("CTHULHU ETERNAL | System Initialized")
|
||||
})
|
||||
|
||||
Hooks.once('babele.init', (babele) => {
|
||||
babele.setSystemTranslationsDir("compendiums");
|
||||
CthulhuEternalUtils.registerBabeleTranslations(babele);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -135,35 +145,129 @@ Hooks.once("ready", function () {
|
||||
|
||||
})
|
||||
|
||||
|
||||
Hooks.on('tokenActionHudCoreApiReady', async () => {
|
||||
/**
|
||||
* Return the SystemManager and requiredCoreModuleVersion to Token Action HUD Core
|
||||
*/
|
||||
let module = {} // game.modules.get(MODULE.ID)
|
||||
module.api = {
|
||||
requiredCoreModuleVersion: REQUIRED_CORE_MODULE_VERSION,
|
||||
SystemManager
|
||||
}
|
||||
Hooks.call('tokenActionHudSystemReady', module)
|
||||
})
|
||||
|
||||
Hooks.on("renderChatMessageHTML", (message, html, data) => {
|
||||
const alreadyUsedLabel = game.i18n.localize("CTHULHUETERNAL.Label.alreadyUsed")
|
||||
|
||||
// Helper: disable all action buttons in the message
|
||||
function disableChatActions(container) {
|
||||
$(container).find(".chat-action-button").each((i, btn) => {
|
||||
btn.setAttribute("disabled", "disabled")
|
||||
btn.setAttribute("data-tooltip", alreadyUsedLabel)
|
||||
btn.style.opacity = "0.5"
|
||||
btn.style.cursor = "not-allowed"
|
||||
btn.style.pointerEvents = "none"
|
||||
})
|
||||
}
|
||||
|
||||
// If an action was already used on this message, disable all buttons immediately
|
||||
if (message.getFlag("fvtt-cthulhu-eternal", "actionUsed")) {
|
||||
disableChatActions(html)
|
||||
}
|
||||
|
||||
// Wrapper: click any action button → disable all + persist flag
|
||||
function withActionLock(handler) {
|
||||
return async (event) => {
|
||||
const container = $(event.currentTarget).closest(".chat-message, .message-content, div[class]")[0] || html
|
||||
disableChatActions(container)
|
||||
message.setFlag("fvtt-cthulhu-eternal", "actionUsed", true).catch(() => { })
|
||||
await handler(event)
|
||||
}
|
||||
}
|
||||
|
||||
// Affichage des boutons de jet de dés uniquement pour les joueurs
|
||||
if (message.author.id === game.user.id) {
|
||||
if (message.author.id === game.user.id || game.user.isGM) {
|
||||
$(html).find(".nudge-roll").each((i, btn) => {
|
||||
btn.style.display = "inline"
|
||||
})
|
||||
$(html).find(".nudge-to-success").each((i, btn) => {
|
||||
btn.style.display = "inline-flex"
|
||||
})
|
||||
$(html).find(".damage-roll").each((i, btn) => {
|
||||
btn.style.display = "inline"
|
||||
})
|
||||
$(html).find(".healing-roll").each((i, btn) => {
|
||||
btn.style.display = "inline"
|
||||
})
|
||||
$(html).find(".nudge-roll").click((event) => {
|
||||
CthulhuEternalUtils.nudgeRoll(message)
|
||||
$(html).find(".worn-weapon-check").each((i, btn) => {
|
||||
btn.style.display = "inline"
|
||||
})
|
||||
$(html).find(".damage-roll").click((event) => {
|
||||
let formula = $(event.currentTarget).data("roll-value")
|
||||
CthulhuEternalUtils.damageRoll(message, formula)
|
||||
})
|
||||
$(html).find(".healing-roll").click((event) => {
|
||||
CthulhuEternalUtils.healingRoll(message)
|
||||
})
|
||||
$(html).find(".san-loose").click((event) => {
|
||||
CthulhuEternalUtils.applySANLoss(message, event)
|
||||
})
|
||||
$(html).find(".san-type").click((event) => {
|
||||
CthulhuEternalUtils.applySANType(message, event)
|
||||
if (game.user.isGM) {
|
||||
$(html).find(".opposed-roll").each((i, btn) => {
|
||||
btn.style.display = "inline"
|
||||
})
|
||||
}
|
||||
$(html).find(".nudge-roll").click(withActionLock(() => {
|
||||
CthulhuEternalUtils.nudgeRoll(message)
|
||||
}))
|
||||
$(html).find(".nudge-to-success").click(withActionLock(() => {
|
||||
CthulhuEternalUtils.nudgeToSuccess(message)
|
||||
}))
|
||||
$(html).find(".damage-roll").click(withActionLock((event) => {
|
||||
let formula = $(event.currentTarget).data("roll-value")
|
||||
CthulhuEternalUtils.damageRoll(message, formula)
|
||||
}))
|
||||
$(html).find(".healing-roll").click(withActionLock(() => {
|
||||
CthulhuEternalUtils.healingRoll(message)
|
||||
}))
|
||||
$(html).find(".worn-weapon-check").click(withActionLock(() => {
|
||||
CthulhuEternalUtils.wornWeaponCheck(message)
|
||||
}))
|
||||
$(html).find(".san-loose").click(withActionLock((event) => {
|
||||
CthulhuEternalUtils.applySANLoss(message, event)
|
||||
}))
|
||||
$(html).find(".san-type").click(withActionLock((event) => {
|
||||
CthulhuEternalUtils.applySANType(message, event)
|
||||
}))
|
||||
$(html).find(".opposed-roll").click(withActionLock((event) => {
|
||||
CthulhuEternalUtils.opposedRollManagement(message, event)
|
||||
}))
|
||||
}
|
||||
if (game.user.isGM) {
|
||||
$(html).find(".li-apply-wounds").each((i, btn) => {
|
||||
btn.style.display = "block"
|
||||
})
|
||||
$(html).find(".apply-wounds-btn").click(withActionLock((event) => {
|
||||
CthulhuEternalUtils.applyWounds(message, event)
|
||||
}))
|
||||
$(html).find(".apply-wounds-btn").hover(
|
||||
function (event) {
|
||||
// Mouse enter - highlight the token on the canvas
|
||||
const tokenId = $(this).data("token-id")
|
||||
if (tokenId) {
|
||||
const token = canvas.tokens.get(tokenId)
|
||||
if (token) token.control({ releaseOthers: true })
|
||||
return
|
||||
}
|
||||
// Legacy: resolve via combatant
|
||||
const combatantId = $(this).data("combatant-id")
|
||||
if (combatantId && game.combat) {
|
||||
const combatant = game.combat.combatants.get(combatantId)
|
||||
if (combatant?.token) {
|
||||
const token = canvas.tokens.get(combatant.token.id)
|
||||
if (token) token.control({ releaseOthers: true })
|
||||
}
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
// Mouse leave - release selection
|
||||
canvas.tokens.releaseAll()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
+81
-5
@@ -24,6 +24,9 @@
|
||||
"Settings": {
|
||||
"era": "Select the era of your game",
|
||||
"eraHint": "Select the era of your game",
|
||||
"nudge": "Allow roll modification (WP spend)",
|
||||
"nudgeHint": "When enabled, players can spend Willpower Points to modify failed rolls. Disable to remove this optional rule.",
|
||||
"nudgeDisabled": "Roll modification via Willpower Points is currently disabled.",
|
||||
"Common": "Common",
|
||||
"Classical": "Classical",
|
||||
"Medieval": "Medieval",
|
||||
@@ -59,6 +62,12 @@
|
||||
},
|
||||
"hp": {
|
||||
"label": "HP",
|
||||
"value": {
|
||||
"label": "Current"
|
||||
},
|
||||
"max": {
|
||||
"label": "Max"
|
||||
},
|
||||
"stunned": {
|
||||
"label": "Stun."
|
||||
}
|
||||
@@ -126,6 +135,18 @@
|
||||
"damageBonus": {
|
||||
"label": "Dmg.Bonus"
|
||||
},
|
||||
"hp": {
|
||||
"label": "HP",
|
||||
"value": {
|
||||
"label": "Current"
|
||||
},
|
||||
"max": {
|
||||
"label": "Max"
|
||||
},
|
||||
"stunned": {
|
||||
"label": "Stun."
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"permanentRating": {
|
||||
"label": "Permanent Rating"
|
||||
@@ -205,6 +226,7 @@
|
||||
"Struggle": "Struggle"
|
||||
},
|
||||
"Skill": {
|
||||
"DodgeName": "Dodge",
|
||||
"Unnatural": "Unnatural",
|
||||
"Melee": "Melee Weapons",
|
||||
"Firearms": "Firearms",
|
||||
@@ -552,10 +574,25 @@
|
||||
}
|
||||
},
|
||||
"Label": {
|
||||
"JunkWeapon": "This weapon is in 'Junk' state, -20% applied. Critical failure on a roll of 96-100",
|
||||
"WornWeapon": "This weapon is 'Worn', a Luck roll will be performed.",
|
||||
"noTarget": "No target selected",
|
||||
"noDefenseRoll": "No defense roll available",
|
||||
"opposedRoll": "Opposed Roll",
|
||||
"Target": "Target",
|
||||
"feet": "feet",
|
||||
"feets": "feet",
|
||||
"yard": "yard",
|
||||
"Yard": "Yard",
|
||||
"yards": "yards",
|
||||
"Feet": "Feet",
|
||||
"Yards": "Yards",
|
||||
"sanLoss5": "You suffered a SAN loss of 5+ ({value}) : you suffer a temporary insanity (Flee, Submit, Struggle or understanding the Unnatural).",
|
||||
"sanViolenceReset": "The violence SAN loss count has been reset.",
|
||||
"sanHelplessnessReset": "The helplessness SAN loss count has been reset.",
|
||||
"sanLoss": "You suffer a SAN loss",
|
||||
"noSanLoss": "No SAN loss",
|
||||
"sanLossInfo": "Select the SAN loss using the buttons below.",
|
||||
"selectSANType": "Select the type of SAN loss with the buttons below.",
|
||||
"Violence" : "Violence",
|
||||
"Helplessness": "Helplessness",
|
||||
@@ -574,6 +611,10 @@
|
||||
"rollNudge": "Roll Nudge",
|
||||
"rollDamage": "Roll Damage",
|
||||
"rollHealing": "Roll Healing",
|
||||
"wornWeaponCheck": "Check Weapon Condition",
|
||||
"wornWeaponCheckTitle": "Worn Weapon Condition Check",
|
||||
"wornWeaponCheckSuccess": "{weapon}: Luck roll succeeded ({roll}/50) - Weapon remains worn.",
|
||||
"wornWeaponCheckFailure": "{weapon}: Luck roll failed ({roll}/50) - Weapon became junk!",
|
||||
"result": "Result",
|
||||
"damageMessage": "Damage to apply",
|
||||
"lethalityRoll": "Lethality Roll",
|
||||
@@ -584,7 +625,9 @@
|
||||
"Weapon": "Weapon",
|
||||
"ZeroWP": "Zero WP : Automatic failure (ie 0%)",
|
||||
"LowWP": "Low WP",
|
||||
"notEnoughWP": "Not enough WP to perform this action",
|
||||
"Exhausted": "Exhausted",
|
||||
"wornWeaponWarning": "Worn weapon: Don't forget to make the luck roll after the attack!",
|
||||
"creature": "Creature",
|
||||
"Rituals": "Rituals",
|
||||
"Tomes": "Tomes",
|
||||
@@ -695,6 +738,8 @@
|
||||
"newMentalDisorder": "New Mental Disorder",
|
||||
"newWeapon": "New Weapon",
|
||||
"newArmor": "New Armor",
|
||||
"equipped": "Equipped (click to unequip)",
|
||||
"unequipped": "Not equipped (click to equip)",
|
||||
"newInjury": "New Injury",
|
||||
"newGear": "New Gear",
|
||||
"newArcane": "New Arcane",
|
||||
@@ -711,9 +756,17 @@
|
||||
"skillProgress": "Skill Progress",
|
||||
"unconscious": "Unconscious",
|
||||
"dying": "Dying",
|
||||
"stunnedWarning": "The Protagonist is stunned. He cannot act until he recovers by successfully rolling a CONx5 check.",
|
||||
"deadWarning": "The Protagonist is dead. He cannot act until he is revived by a successful First Aid roll.",
|
||||
"unconsciousWarning": "The Protagonist is unconscious. He cannot act until he has at least 3 HP.",
|
||||
"stunnedWarning": "The Protagonist is stunned. He cannot act until he succeeds at a CON × 5 test.",
|
||||
"deadWarning": "The Protagonist is dying. He will die if not treated within {con} minutes.",
|
||||
"noSkills": "No skills. Add skills to this character.",
|
||||
"noWeapons": "No weapons. Add weapons in edit mode.",
|
||||
"noArmors": "No armor. Add armor in edit mode.",
|
||||
"noGears": "No gear. Add gear in edit mode.",
|
||||
"luck": "Luck",
|
||||
"Other": "Other",
|
||||
"Skills": "Skills",
|
||||
"WP": "WP",
|
||||
"Luck": "Luck",
|
||||
"titleLuck": "Luck Roll",
|
||||
"healingRoll": "Healing Roll succes, the target gains",
|
||||
@@ -724,13 +777,27 @@
|
||||
"lethalityLethal": "Lethal !!",
|
||||
"lethalityNotLethal": "Non-Lethal",
|
||||
"WPSpent": "WP Spent",
|
||||
"alreadyUsed": "Already used",
|
||||
"targetMove": "Target Move",
|
||||
"attackerState": "Attacker State",
|
||||
"targetSize": "Target Size",
|
||||
"visibility": "Visibility",
|
||||
"rangedRange": "Range",
|
||||
"aimingLastRound": "Aiming Last Round (+20)",
|
||||
"aimingWithSight": "Aiming with Sight (+20)"
|
||||
"aimingWithSight": "Aiming with Sight (+20)",
|
||||
"applyWounds": "Apply To",
|
||||
"opposedRollWinner": "Opposed Roll Winner",
|
||||
"opposedRollResult": "Opposed Roll Result",
|
||||
"defeats": "defeats",
|
||||
"critical": "Critical",
|
||||
"other": "Other",
|
||||
"str": "STR",
|
||||
"dex": "DEX",
|
||||
"int": "INT",
|
||||
"pow": "POW",
|
||||
"con": "CON",
|
||||
"cha": "CHA",
|
||||
"Damage": "Damage"
|
||||
},
|
||||
"ChatMessage": {
|
||||
"exhausted": "Your protagonist is exhausted. He loses [[/r 1d6]] Willpower Points."
|
||||
@@ -746,7 +813,8 @@
|
||||
"roll": "Roll",
|
||||
"applyNudge": "Apply",
|
||||
"cancel": "Cancel",
|
||||
"nudgeRoll": "Nudge Roll"
|
||||
"nudgeRoll": "Nudge Roll",
|
||||
"nudgeToSuccess": "Succeed at {score}% for {wp} WP"
|
||||
},
|
||||
"Tooltip": {
|
||||
"sanBP": ">5 SAN lost in one roll, temporary insanity. If SAN less reaches BP = a Disorder unconscious Breaking and AND reset BP.",
|
||||
@@ -755,17 +823,25 @@
|
||||
"rollDamage": "Roll Damage"
|
||||
},
|
||||
"Chat": {
|
||||
"noArmor": "No armor absorbed damage.",
|
||||
"armorAbsorbed": "Armor absorbed {armor} damage.",
|
||||
"woundsApplied": "Wounds applied to {name}: {effectiveWounds} (armor absorbed : {armorText})"
|
||||
},
|
||||
"Notifications": {
|
||||
"AttackNoTarget": "No target selected for the attack, please select one target to get automations.",
|
||||
"NoWeaponSkill": "No weapon skill found for this weapon. Check Weapon definition or available skills/era",
|
||||
"NoWeaponType": "No weapon type found for this weapon subtype. Check Weapon definition or available skills/era",
|
||||
"skillAlreadyExists": "Skill already exists",
|
||||
"WrongEra": "The era of the item does not match the ear of the system",
|
||||
"NoSelectiveFireChoices": "Not enough ammo fo Selective Fire choices for this weapon.",
|
||||
"NoAmmo": "No more ammo for this weapon. ",
|
||||
"WeaponBecameJunk": "Weapon {weapon} became junk!",
|
||||
"noWeaponFound": "No weapon found.",
|
||||
"noRollDataFound": "No roll data found",
|
||||
"noActorFound": "No actor found for this item.",
|
||||
"noSanLossFound": "No SAN loss value found."
|
||||
"noSanLossFound": "No SAN loss value found.",
|
||||
"opposedRollFirstStored": "First opposed roll stored. Perform and store the second roll to resolve the opposed roll.",
|
||||
"opposedRollSecondStored": "Second opposed roll stored. The opposed roll is now being resolved."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
-7
@@ -24,6 +24,9 @@
|
||||
"Settings": {
|
||||
"era": "Sélectionnez l'époque de votre jeu",
|
||||
"eraHint": "L'époque détermine les compétences, les armes et les équipements disponibles pour votre protagoniste.",
|
||||
"nudge": "Autoriser la modification des jets (dépense de PVO)",
|
||||
"nudgeHint": "Si activé, les joueurs peuvent dépenser des Points de Volonté pour modifier leurs jets ratés. Désactivez pour supprimer cette règle optionnelle.",
|
||||
"nudgeDisabled": "La modification des jets via les Points de Volonté est actuellement désactivée.",
|
||||
"Common": "Commun",
|
||||
"Classical": "Classique",
|
||||
"Medieval": "Médiéval",
|
||||
@@ -217,6 +220,7 @@
|
||||
"Struggle": "Lutter"
|
||||
},
|
||||
"Skill": {
|
||||
"DodgeName": "Esquive",
|
||||
"Unnatural": "Inconcevable",
|
||||
"Melee": "Armes de mêlée",
|
||||
"Firearms": "Armes à feu",
|
||||
@@ -564,6 +568,19 @@
|
||||
}
|
||||
},
|
||||
"Label": {
|
||||
"JunkWeapon": "Cette arme est 'Défectueuse'', -20% appliqué. Échec critique sur un jet de 96-100",
|
||||
"WornWeapon": "Cette arme est 'Usée', un jet de Chance sera effectué.",
|
||||
"noTarget": "Aucune cible sélectionnée",
|
||||
"noDefenseRoll": "Aucun jet de défense existant",
|
||||
"opposedRoll": "Jet opposé",
|
||||
"Target": "Cible",
|
||||
"feet": "pieds (ie 30cm)",
|
||||
"feets": "pieds (ie 30cm)",
|
||||
"yard": "mètres",
|
||||
"Yard": "mètres",
|
||||
"yards": "mètres",
|
||||
"Feet": "Pieds",
|
||||
"Yards": "Mètres",
|
||||
"sanLoss5": "Perte de SAN de 5+ ({value}) : vous souffrez d'une folie temporaire (Fuite, Soumission, Lutte ou compréhension de l'Inconcevable).",
|
||||
"sanViolenceReset": "Le décompte des pertes de SAN de violence a été réinitialisé.",
|
||||
"sanHelplessnessReset": "Le décompte des pertes de SAN d'impuissance a été réinitialisé.",
|
||||
@@ -588,6 +605,10 @@
|
||||
"rollNudge": "Modifier le jet",
|
||||
"rollDamage": "Jet de dégâts",
|
||||
"rollHealing": "Jet de soin",
|
||||
"wornWeaponCheck": "Vérifier l'état de l'arme",
|
||||
"wornWeaponCheckTitle": "Vérification d'état d'arme usée",
|
||||
"wornWeaponCheckSuccess": "{weapon} : Jet de chance réussi ({roll}/50) - L'arme reste usée.",
|
||||
"wornWeaponCheckFailure": "{weapon} : Jet de chance échoué ({roll}/50) - L'arme devient défectueuse !",
|
||||
"result": "Resultat",
|
||||
"damageMessage": "Dégâts à appliquer",
|
||||
"lethalityRoll": "Jet de Létalité",
|
||||
@@ -598,15 +619,15 @@
|
||||
"Weapon": "Arme",
|
||||
"ZeroWP": "PVO à 0 : Echec automatique (ie 0%)",
|
||||
"LowWP": "PVO faibles",
|
||||
"Exhausted": "Epuisé",
|
||||
"creature": "Créature",
|
||||
"notEnoughWP": "PVO insuffisants pour effectuer cette action",
|
||||
"Exhausted": "Epuisé", "wornWeaponWarning": "Arme usée : N'oubliez pas de faire le jet de chance après l'attaque !", "creature": "Créature",
|
||||
"Rituals": "Rituels",
|
||||
"Tomes": "Ouvrages",
|
||||
"otherBenefits": "Autres avantages",
|
||||
"Unarmed": "Désarmé",
|
||||
"Cured": "Soigné",
|
||||
"Uncured": "Non soigné",
|
||||
"nudgedRoll": "Modifier le jeu",
|
||||
"nudgedRoll": "Jet modifié : ",
|
||||
"selectNewValue": "Sélectionner une nouvelle valeur",
|
||||
"wpCost": "Cout en PVO",
|
||||
"Hand": "A portée de main",
|
||||
@@ -709,6 +730,8 @@
|
||||
"newMentalDisorder": "Nouveau Trouble mental",
|
||||
"newWeapon": "Nouvelle Arme",
|
||||
"newArmor": "Nouvelle Armure",
|
||||
"equipped": "Équipée (cliquer pour déséquiper)",
|
||||
"unequipped": "Non équipée (cliquer pour équiper)",
|
||||
"newInjury": "Nouvelle Blessure",
|
||||
"newGear": "Nouvel Equipement",
|
||||
"newArcane": "Nouvel Arcane",
|
||||
@@ -728,7 +751,14 @@
|
||||
"stunnedWarning": "Votre protagoniste est étourdi. Il ne peut pas agir tant qu'il n'a pas réussi un test de CON x 5.",
|
||||
"deadWarning": "Votre protagoniste est mourrant. Il mourra s'il n'est pas soigné dans les {con} minutes",
|
||||
"unconsciousWarning": "Votre protagoniste est inconscient. Il ne peut pas agir tant qu'il n'a pas atteint 3 PV.",
|
||||
"Luck": "Chance",
|
||||
"noSkills": "Aucune compétence. Ajoutez des compétences à ce personnage.",
|
||||
"noWeapons": "Aucune arme. Ajoutez des armes en mode édition.",
|
||||
"noArmors": "Aucune armure. Ajoutez une armure en mode édition.",
|
||||
"noGears": "Aucun équipement. Ajoutez de l'équipement en mode édition.",
|
||||
"luck": "Chance",
|
||||
"Other": "Autre",
|
||||
"Skills": "Compétences",
|
||||
"WP": "PVO",
|
||||
"titleLuck": "Jet de Chance",
|
||||
"healingRoll": "Jet de soin, PV soignés",
|
||||
"healingRollFailure": "Jet de soin échoué critique, PV perdus",
|
||||
@@ -738,13 +768,28 @@
|
||||
"lethalityLethal": "Létal !!",
|
||||
"lethalityNotLethal": "Non létal",
|
||||
"WPSpent": "PVO dépensés",
|
||||
"alreadyUsed": "Déjà utilisé",
|
||||
"targetMove": "Mouvement de la cible",
|
||||
"attackerState": "Etat de l'attaquant",
|
||||
"targetSize": "Taille de la cible",
|
||||
"visibility": "Visibilité",
|
||||
"rangedRange": "Portée",
|
||||
"aimingLastRound": "Visée lors du dernier round (+20)",
|
||||
"aimingWithSight": "Visée avec lunette (+20)"
|
||||
"aimingWithSight": "Visée avec lunette (+20)",
|
||||
"applyWounds": "Appliquer à",
|
||||
"opposedRollWinner": "Gagnant du jet opposé",
|
||||
"opposedRollResult": "Résultat du jet opposé",
|
||||
"defeats": "Défait",
|
||||
"critical": "Critique",
|
||||
"other": "Autre",
|
||||
"str": "FOR",
|
||||
"dex": "DEX",
|
||||
"int": "INT",
|
||||
"pow": "POU",
|
||||
"con": "CON",
|
||||
"cha": "CHA",
|
||||
"Luck": "Chance",
|
||||
"Damage": "Dégâts"
|
||||
},
|
||||
"ChatMessage": {
|
||||
"exhausted": "Votre protagoniste est épuisé. Il perd [[/r 1d6]] Points de Volonté."
|
||||
@@ -760,7 +805,8 @@
|
||||
"roll": "Jet",
|
||||
"applyNudge": "Lancer",
|
||||
"cancel": "Annuler",
|
||||
"nudgeRoll": "Modifier le jet"
|
||||
"nudgeRoll": "Modifier le jet",
|
||||
"nudgeToSuccess": "Réussir à {score}% pour {wp} PVO"
|
||||
},
|
||||
"Tooltip": {
|
||||
"sanBP": "Perte de 5+ SAN en 1 jet : folie temporaire. SI la SAN atteint le PR : trouble mental, perte de conscience et reset du PR.",
|
||||
@@ -769,17 +815,25 @@
|
||||
"rollDamage": "Lancer les dégâts"
|
||||
},
|
||||
"Chat": {
|
||||
"armorAbsorbed": "L'armure a absorbé {armor} points de dégâts.",
|
||||
"noArmor": "Pas d'armure.",
|
||||
"woundsApplied": "Blessures appliquées à {name} : {effectiveWounds} (armure absorbée : {armorText})"
|
||||
},
|
||||
"Notifications": {
|
||||
"AttackNoTarget": "Aucune cible n'a été sélectionnée pour cette attaque, veuillez sélectionner une cible pour bénéficier des automatisations.",
|
||||
"NoWeaponSkill": "Aucune compétence associée n'a été trouvé pour cette arme. Vérifier la définition de l'arme ainsi que l'époque configurée.",
|
||||
"NoWeaponType": "Aucun type d'arme trouvé pour ce sous-type. Vérifier la définition de l'arme ainsi que l'époque configurée.",
|
||||
"skillAlreadyExists": "La compétence existe déja",
|
||||
"WrongEra": "L'époque de l'item ne correspond pas à celle du jeu en cours.",
|
||||
"NoSelectiveFireChoices": "Aucune option de tir sélectif n'est disponible pour cette arme : pas assez de munitions.",
|
||||
"NoAmmo": "Aucune munition disponible pour cette arme.",
|
||||
"WeaponBecameJunk": "L'arme {weapon} est devenue défectueuse !",
|
||||
"noWeaponFound": "Aucune arme trouvée.",
|
||||
"noRollDataFound": "Aucune donnée de jet trouvée.",
|
||||
"noActorFound": "Aucun protagoniste trouvé.",
|
||||
"noSanLossFound": "Aucune valeur de perte de SAN trouvée."
|
||||
"noSanLossFound": "Aucune valeur de perte de SAN trouvée.",
|
||||
"opposedRollFirstStored": "Premier jet opposé enregistré. Effectuez et enregistrez le deuxième jet pour résoudre le jet opposé.",
|
||||
"opposedRollSecondStored": "Deuxième jet opposé enregistré. Le jet opposé est maintenant en cours de résolution."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// System Module Imports
|
||||
import { Utils } from './utils.js'
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
import CthulhuEternalUtils from '../../utils.mjs'
|
||||
|
||||
export let ActionHandler = null
|
||||
|
||||
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
@@ -15,8 +15,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {array} groupIds
|
||||
*/
|
||||
async buildSystemActions(groupIds) {
|
||||
// Set actor and token variables
|
||||
this.actors = (!this.actor) ? this._getActors() : [this.actor]
|
||||
// this.actor and this.actors are provided by the base class (TAH Core v2+)
|
||||
this.actorType = this.actor?.type
|
||||
|
||||
// Set items variable
|
||||
@@ -26,9 +25,11 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
this.items = items
|
||||
}
|
||||
|
||||
if (this.actor) {
|
||||
if (this.actorType !== 'vehicle') {
|
||||
this.#buildCharacterActions()
|
||||
} else if (!this.actor) {
|
||||
}
|
||||
} else {
|
||||
this.#buildMultipleTokenActions()
|
||||
}
|
||||
}
|
||||
@@ -38,7 +39,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @private
|
||||
*/
|
||||
#buildCharacterActions() {
|
||||
this.buildAttributes()
|
||||
this.buildCharacteristics()
|
||||
this.buildOther()
|
||||
this.buildLuck()
|
||||
this.buildSkills()
|
||||
@@ -49,17 +50,17 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
return game.settings.get('token-action-hud-core', 'tooltips') === 'none'
|
||||
}
|
||||
|
||||
async buildAttributes() {
|
||||
async buildCharacteristics() {
|
||||
const actions = []
|
||||
for (const key in this.actor.system.characteristics) {
|
||||
const encodedValue = [coreModule.api.Utils.i18n('attributes'), key].join(this.delimiter)
|
||||
const encodedValue = ['characteristics', key].join(this.delimiter)
|
||||
const tooltip = {
|
||||
content: String(this.actor.system.characteristics[key].value * 5),
|
||||
class: 'tah-system-tooltip',
|
||||
direction: 'LEFT'
|
||||
}
|
||||
actions.push({
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.' + key),
|
||||
name: coreModule.api.Utils.i18n(`CTHULHUETERNAL.Label.${key}`),
|
||||
id: key,
|
||||
info1: this.#showValue() ? { text: tooltip.content } : null,
|
||||
tooltip,
|
||||
@@ -67,7 +68,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
})
|
||||
}
|
||||
await this.addActions(actions, {
|
||||
id: 'attributes',
|
||||
id: 'characteristics',
|
||||
type: 'system'
|
||||
})
|
||||
}
|
||||
@@ -80,79 +81,83 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
direction: 'LEFT'
|
||||
}
|
||||
actions.push({
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Luck'),
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.luck'),
|
||||
id: 'luck',
|
||||
info1: this.#showValue() ? { text: '50' } : null,
|
||||
tooltip,
|
||||
encodedValue: ['attributes', 'luck'].join(this.delimiter)
|
||||
encodedValue: ['characteristics', 'luck'].join(this.delimiter)
|
||||
})
|
||||
await this.addActions(actions, { id: 'luck', type: 'system' })
|
||||
}
|
||||
|
||||
async buildOther() {
|
||||
if (typeof this.actor.system.sanity.value !== 'undefined') {
|
||||
if (typeof this.actor.system?.san?.value !== 'undefined') {
|
||||
const actions = []
|
||||
const groupData = {
|
||||
id: 'other_sanity',
|
||||
id: 'other_san',
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
|
||||
type: 'system'
|
||||
}
|
||||
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
|
||||
const tooltip = {
|
||||
content: String(this.actor.system.san.value + '/' + this.actor.system.san.max),
|
||||
content: `${this.actor.system.san.value}/${this.actor.system.san.max}`,
|
||||
class: 'tah-system-tooltip',
|
||||
direction: 'LEFT'
|
||||
}
|
||||
actions.push({
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
|
||||
id: 'sanity',
|
||||
id: 'san',
|
||||
info1: this.#showValue() ? { text: tooltip.content } : null,
|
||||
tooltip,
|
||||
encodedValue: ['attributes', 'sanity'].join(this.delimiter)
|
||||
encodedValue: ['characteristics', 'san'].join(this.delimiter)
|
||||
},
|
||||
{
|
||||
name: '+',
|
||||
id: 'sanity_add',
|
||||
encodedValue: ['attributes', 'sanity_add'].join(this.delimiter)
|
||||
id: 'san_add',
|
||||
tooltip,
|
||||
encodedValue: ['characteristics', 'san_add'].join(this.delimiter)
|
||||
},
|
||||
{
|
||||
name: '-',
|
||||
id: 'sanity_subtract',
|
||||
encodedValue: ['attributes', 'sanity_subtract'].join(this.delimiter)
|
||||
id: 'san_subtract',
|
||||
tooltip,
|
||||
encodedValue: ['characteristics', 'san_subtract'].join(this.delimiter)
|
||||
})
|
||||
await this.addActions(actions, { id: 'other_sanity', type: 'system' })
|
||||
await this.addActions(actions, { id: 'other_san', type: 'system' })
|
||||
}
|
||||
if (typeof this.actor.system.hp.value !== 'undefined') {
|
||||
const actions = []
|
||||
const groupData = {
|
||||
id: 'other_health',
|
||||
id: 'other_hp',
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
|
||||
type: 'system'
|
||||
}
|
||||
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
|
||||
const tooltip = {
|
||||
content: String(this.actor.system.hp.value + '/' + this.actor.system.hp.max),
|
||||
content: `${this.actor.system.hp.value}/${this.actor.system.hp.max}`,
|
||||
class: 'tah-system-tooltip',
|
||||
direction: 'LEFT'
|
||||
}
|
||||
actions.push({
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
|
||||
id: 'health',
|
||||
id: 'hp',
|
||||
info1: this.#showValue() ? { text: tooltip.content } : null,
|
||||
tooltip,
|
||||
encodedValue: ['attributes', 'health'].join(this.delimiter)
|
||||
encodedValue: ['characteristics', 'hp'].join(this.delimiter)
|
||||
},
|
||||
{
|
||||
name: '+',
|
||||
id: 'health_add',
|
||||
encodedValue: ['attributes', 'health_add'].join(this.delimiter)
|
||||
id: 'hp_add',
|
||||
tooltip,
|
||||
encodedValue: ['characteristics', 'hp_add'].join(this.delimiter)
|
||||
},
|
||||
{
|
||||
name: '-',
|
||||
id: 'health_subtract',
|
||||
encodedValue: ['attributes', 'health_subtract'].join(this.delimiter)
|
||||
id: 'hp_subtract',
|
||||
tooltip,
|
||||
encodedValue: ['characteristics', 'hp_subtract'].join(this.delimiter)
|
||||
})
|
||||
await this.addActions(actions, { id: 'other_health', type: 'system' })
|
||||
await this.addActions(actions, { id: 'other_hp', type: 'system' })
|
||||
}
|
||||
if (typeof this.actor.system.wp.value !== 'undefined') {
|
||||
const actions = []
|
||||
@@ -163,7 +168,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
}
|
||||
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
|
||||
const tooltip = {
|
||||
content: String(this.actor.system.wp.value + '/' + this.actor.system.wp.max),
|
||||
content: `${this.actor.system.wp.value}/${this.actor.system.wp.max}`,
|
||||
class: 'tah-system-tooltip',
|
||||
direction: 'LEFT'
|
||||
}
|
||||
@@ -172,17 +177,19 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
id: 'wp',
|
||||
info1: this.#showValue() ? { text: tooltip.content } : null,
|
||||
tooltip,
|
||||
encodedValue: ['attributes', 'wp'].join(this.delimiter)
|
||||
encodedValue: ['characteristics', 'wp'].join(this.delimiter)
|
||||
},
|
||||
{
|
||||
name: '+',
|
||||
id: 'wp_add',
|
||||
encodedValue: ['attributes', 'wp_add'].join(this.delimiter)
|
||||
tooltip,
|
||||
encodedValue: ['characteristics', 'wp_add'].join(this.delimiter)
|
||||
},
|
||||
{
|
||||
name: '-',
|
||||
id: 'wp_subtract',
|
||||
encodedValue: ['attributes', 'wp_subtract'].join(this.delimiter)
|
||||
tooltip,
|
||||
encodedValue: ['characteristics', 'wp_subtract'].join(this.delimiter)
|
||||
})
|
||||
await this.addActions(actions, { id: 'other_wp', type: 'system' })
|
||||
}
|
||||
@@ -190,19 +197,23 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
|
||||
async buildSkills() {
|
||||
const actions = []
|
||||
let actorSkills = this.actor.items.filter(item => item.type === 'skill')
|
||||
for (const skill in actorSkills) {
|
||||
if (skill.system.computeScore() > 0) {
|
||||
// Build alpha sorted skill list
|
||||
let skills = this.actor.items.filter(i => i.type === 'skill')
|
||||
skills = skills.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
for (const skill of skills) {
|
||||
//console.log('Processing skill item:', skill)
|
||||
if (skill.system.skillTotal > 0) {
|
||||
const tooltip = {
|
||||
content: String(skill.skill.system.computeScore()),
|
||||
content: String(skill.system.skillTotal),
|
||||
direction: 'LEFT'
|
||||
}
|
||||
actions.push({
|
||||
name: skill.name,
|
||||
name: `${skill.name} (${skill.system.skillTotal})`,
|
||||
id: skill.id,
|
||||
info1: this.#showValue() ? { text: tooltip.content } : null,
|
||||
tooltip,
|
||||
encodedValue: ['skills', s].join(this.delimiter)
|
||||
encodedValue: ['skills', skill.id].join(this.delimiter)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -210,76 +221,54 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
}
|
||||
|
||||
async buildEquipment() {
|
||||
let weapons = this.actor.items.filter(item => item.type === 'weapon')
|
||||
let skills = this.actor.items.filter(item => item.type === 'skill')
|
||||
let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
|
||||
|
||||
const weapons = this.actor.items.filter(i => i.type === 'weapon')
|
||||
for (const item of weapons) {
|
||||
// Push the weapon name as a new group
|
||||
const groupData = {
|
||||
id: 'weapons_' + item._id,
|
||||
id: `weapons_${item._id}`,
|
||||
name: item.name,
|
||||
type: 'system'
|
||||
}
|
||||
if (!SYSTEM.WEAPON_SKILL_MAPPING[era] || !SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType]) {
|
||||
continue
|
||||
}
|
||||
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
|
||||
let skill = skills.find(skill => skill.name.toLowerCase() === skillName.toLowerCase())
|
||||
this.addGroup(groupData, { id: 'weapons', type: 'system' }, true)
|
||||
if (item.type === 'weapon') {
|
||||
const weapons = []
|
||||
let skill = CthulhuEternalUtils.getWeaponSkill(this.actor, item, era)
|
||||
item.skillTotal = skill ? skill.system.skillTotal : 0
|
||||
let weaponActions = []
|
||||
const tooltip = {
|
||||
content: String(skill.system.computeScore()),
|
||||
content: String(item.skillTotal),
|
||||
direction: 'LEFT'
|
||||
}
|
||||
weapons.push({
|
||||
name: skill.name,
|
||||
id: skill._id,
|
||||
weaponActions.push({
|
||||
name: `${item.name} (${item.skillTotal})`,
|
||||
id: `weapon_${item._id}`,
|
||||
info1: this.#showValue() ? { text: tooltip.content } : null,
|
||||
encodedValue: ['weapons', item._id].join(this.delimiter),
|
||||
tooltip
|
||||
})
|
||||
let damage = ''
|
||||
if (item.system.lethality > 0) {
|
||||
damage = `L:${item.system.lethality}%`
|
||||
} else {
|
||||
damage = item.system.damage
|
||||
}
|
||||
const damageTooltip = {
|
||||
content: String(item.system.damage),
|
||||
content: String(damage),
|
||||
direction: 'LEFT'
|
||||
}
|
||||
if (item.system.damage !== '') {
|
||||
weapons.push({
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage'),
|
||||
id: item._id,
|
||||
weaponActions.push({
|
||||
name: `${coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage')} (${damage})`,
|
||||
id: `damage_${item._id}`,
|
||||
info1: this.#showValue() ? { text: damageTooltip.content } : null,
|
||||
encodedValue: ['damage', item._id].join(this.delimiter),
|
||||
tooltip: damageTooltip
|
||||
})
|
||||
}
|
||||
if (item.system.isLethal) {
|
||||
const lethalityTooltip = {
|
||||
content: String(item.system.lethality),
|
||||
direction: 'LEFT'
|
||||
}
|
||||
weapons.push({
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Lethality'),
|
||||
id: item._id,
|
||||
info1: this.#showValue() ? { text: lethalityTooltip.content } : null,
|
||||
encodedValue: ['lethality', item._id].join(this.delimiter),
|
||||
tooltip: lethalityTooltip
|
||||
})
|
||||
}
|
||||
await this.addActions(weapons, {
|
||||
id: 'weapons_' + item._id,
|
||||
await this.addActions(weaponActions, {
|
||||
id: `weapons_${item._id}`,
|
||||
type: 'system'
|
||||
})
|
||||
}/* else if (item.type === 'ritual') {
|
||||
rituals.push({
|
||||
name: item.name,
|
||||
id: item._id,
|
||||
encodedValue: ['rituals', item.name].join(this.delimiter)
|
||||
})
|
||||
} */
|
||||
|
||||
/* await this.addActions(rituals, {
|
||||
id: 'rituals',
|
||||
type: 'system'
|
||||
}) */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Module-based constants
|
||||
*/
|
||||
export const SYSTEM = {
|
||||
ID: 'fvtt-cthulhu-eternal'
|
||||
export const MODULE = {
|
||||
ID: 'token-action-hud-cthulhu-eternal'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,24 +15,25 @@ export const CORE_MODULE = {
|
||||
/**
|
||||
* Core module version required by the system module
|
||||
*/
|
||||
export const REQUIRED_CORE_MODULE_VERSION = '2.0'
|
||||
export const REQUIRED_CORE_MODULE_VERSION = '2'
|
||||
|
||||
/**
|
||||
* Action types
|
||||
*/
|
||||
export const ACTION_TYPE = {
|
||||
attributes: 'CTHULHUETERNAL.Label.Characteristics',
|
||||
skills: 'CTHULHUETERNAL.Label.Skill',
|
||||
equipment: 'CTHULHUETERNAL.Label.Gear'
|
||||
characteristics: 'CTHULHUETERNAL.Label.characteristics',
|
||||
skills: 'CTHULHUETERNAL.Label.Skills',
|
||||
equipment: 'CTHULHUETERNAL.Label.gear'
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups
|
||||
*/
|
||||
export const GROUP = {
|
||||
attributes: { id: 'attributes', name: 'CTHULHUETERNAL.Label.Characteristics', type: 'system' },
|
||||
luck: { id: 'luck', name: 'CTHULHUETERNAL.Label.Luck', type: 'system'},
|
||||
characteristics: { id: 'characteristics', name: 'CTHULHUETERNAL.Label.characteristics', type: 'system' },
|
||||
luck: { id: 'luck', name: 'CTHULHUETERNAL.Label.luck', type: 'system' },
|
||||
other: { id: 'other', name: 'CTHULHUETERNAL.Label.other', type: 'system' },
|
||||
skills: { id: 'skills', name: 'CTHULHUETERNAL.Label.Skills', type: 'system' },
|
||||
weapons: { id: 'weapons', name: 'CTHULHUETERNAL.Label.Weapons', type: 'system' },
|
||||
rituals: { id: 'rituals', name: 'CTHULHUETERNAL.Label.Rituals', type: 'system' }
|
||||
weapons: { id: 'weapons', name: 'CTHULHUETERNAL.Label.weapons', type: 'system' },
|
||||
rituals: { id: 'rituals', name: 'CTHULHUETERNAL.Label.rituals', type: 'system' }
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
{
|
||||
nestId: 'statistics',
|
||||
id: 'statistics',
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Characteristics'),
|
||||
name: game.i18n.localize('CTHULHUETERNAL.Label.characteristics'),
|
||||
groups: [
|
||||
{ ...groups.attributes, nestId: 'statistics_attributes' },
|
||||
{ ...groups.characteristics, nestId: 'statistics_characteristics' },
|
||||
{ ...groups.other, nestId: 'statistics_other' },
|
||||
{ ...groups.luck, nestId: 'statistics_luck' }
|
||||
]
|
||||
@@ -27,17 +27,15 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
{
|
||||
nestId: 'skills',
|
||||
id: 'skills',
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Skills'),
|
||||
name: game.i18n.localize('CTHULHUETERNAL.Label.skills'),
|
||||
groups: [
|
||||
{ ...groups.skills, nestId: 'skills_skills' },
|
||||
{ ...groups.typedSkills, nestId: 'skills_typed' },
|
||||
{ ...groups.specialTraining, nestId: 'skills_special' }
|
||||
{ ...groups.skills, nestId: 'skills_skills' }
|
||||
]
|
||||
},
|
||||
{
|
||||
nestId: 'equipment',
|
||||
id: 'equipment',
|
||||
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Gear'),
|
||||
name: game.i18n.localize('CTHULHUETERNAL.Label.gear'),
|
||||
groups: [
|
||||
{ ...groups.weapons, nestId: 'equipment_weapons' },
|
||||
{ ...groups.rituals, nestId: 'equipment_rituals' }
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { SYSTEM } from "../../config/system.mjs"
|
||||
import CthulhuEternalRoll from '../../documents/roll.mjs'
|
||||
|
||||
export let RollHandler = null
|
||||
|
||||
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
@@ -15,10 +12,10 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} event The event
|
||||
* @param {string} encodedValue The encoded value
|
||||
*/
|
||||
async handleActionClick (event, encodedValue) {
|
||||
async handleActionClick(event, encodedValue) {
|
||||
const [actionTypeId, actionId] = encodedValue.split('|')
|
||||
|
||||
const knownCharacters = ['character']
|
||||
const knownCharacters = ['protagonist', 'creature']
|
||||
|
||||
// If single actor is selected
|
||||
if (this.actor) {
|
||||
@@ -43,7 +40,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} event The event
|
||||
* @param {string} encodedValue The encoded value
|
||||
*/
|
||||
async handleActionHover (event, encodedValue) {
|
||||
async handleActionHover(event, encodedValue) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +50,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} event The event
|
||||
* @param {object} group The group
|
||||
*/
|
||||
async handleGroupClick (event, group) {
|
||||
async handleGroupClick(event, group) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,10 +62,10 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {string} actionTypeId The action type id
|
||||
* @param {string} actionId The actionId
|
||||
*/
|
||||
async #handleAction (event, actor, token, actionTypeId, actionId) {
|
||||
async #handleAction(event, actor, token, actionTypeId, actionId) {
|
||||
switch (actionTypeId) {
|
||||
case 'attributes':
|
||||
await this.#handleAttributesAction(event, actor, actionId)
|
||||
case 'characteristics':
|
||||
await this.#handleCharacteristicsAction(event, actor, actionId)
|
||||
break
|
||||
case 'skills':
|
||||
await this.#handleSkillsAction(event, actor, actionId)
|
||||
@@ -82,12 +79,6 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
case 'lethality':
|
||||
await this.#handleLethalityAction(event, actor, actionId)
|
||||
break
|
||||
case 'specialTraining':
|
||||
await this.#handleSpecialTrainingAction(event, actor, actionId)
|
||||
break
|
||||
case 'typedSkills':
|
||||
await this.#handleCustomTypedAction(event, actor, actionId)
|
||||
break
|
||||
/* case 'rituals':
|
||||
await this.#handleRitualsAction(event, actor, actionId)
|
||||
break */
|
||||
@@ -98,40 +89,42 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Attribute action
|
||||
* Handle Characteristic action
|
||||
* @private
|
||||
* @param {object} event The event
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleAttributesAction (event, actor, actionId) {
|
||||
let rollType
|
||||
if (actionId === 'wp' || actionId === 'health') return
|
||||
async #handleCharacteristicsAction(event, actor, actionId) {
|
||||
if (actionId === 'wp' || actionId === 'hp') return
|
||||
|
||||
if (actionId.includes('_add') || actionId.includes('_subtract')) {
|
||||
const attr = actionId.split('_')[0]
|
||||
const action = actionId.split('_')[1]
|
||||
const update = {}
|
||||
update.system = {}
|
||||
update.system[attr] = {}
|
||||
update.system[attr].value = action === 'add' ? this.actor.system[attr].value + 1 : this.actor.system[attr].value - 1
|
||||
if (update.system[attr].value > this.actor.system[attr].max || update.system[attr].value < this.actor.system[attr].min) return
|
||||
return await this.actor.update(update)
|
||||
update.system[attr].value = action === 'add' ? actor.system[attr].value + 1 : actor.system[attr].value - 1
|
||||
if (update.system[attr].value > actor.system[attr].max || update.system[attr].value < actor.system[attr].min) return
|
||||
return await actor.update(update)
|
||||
}
|
||||
if (actionId === 'sanity') {
|
||||
rollType = actionId
|
||||
if (actionId === 'san') {
|
||||
let item = foundry.utils.duplicate(actor.system.san)
|
||||
item.name = game.i18n.localize("CTHULHUETERNAL.Label.SAN")
|
||||
item.targetScore = item.value
|
||||
await actor.system.roll('san', item)
|
||||
} else if (actionId === 'luck') {
|
||||
rollType = actionId
|
||||
let item = foundry.utils.duplicate(actor.system.characteristics.int)
|
||||
item.name = game.i18n.localize("CTHULHUETERNAL.Label.Luck")
|
||||
item.value = 10
|
||||
item.targetScore = 50
|
||||
await actor.system.roll('luck', item)
|
||||
} else {
|
||||
rollType = 'stat'
|
||||
let item = foundry.utils.duplicate(actor.system.characteristics[actionId])
|
||||
item.name = game.i18n.localize(`CTHULHUETERNAL.Label.${actionId}Long`)
|
||||
item.targetScore = item.value * 5
|
||||
await actor.system.roll('char', item)
|
||||
}
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
rollType,
|
||||
key: actionId
|
||||
}
|
||||
|
||||
const roll = new DGPercentileRoll('1D100', {}, options)
|
||||
return await this.actor.sheet.processRoll(event, roll)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,64 +134,12 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleSkillsAction (event, actor, actionId) {
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
rollType: 'skill',
|
||||
key: actionId
|
||||
async #handleSkillsAction(event, actor, actionId) {
|
||||
const skill = actor.items.find(i => i.type === 'skill' && i.id === actionId)
|
||||
if (!skill) return ui.notifications.warn(`Skill not found for action id '${actionId}'`)
|
||||
await actor.system.roll('skill', skill)
|
||||
}
|
||||
|
||||
const skill = this.actor.system.skills[actionId]
|
||||
if (!skill) return ui.notifications.warn('Bad skill name in HUD.')
|
||||
|
||||
const roll = new DGPercentileRoll('1D100', {}, options)
|
||||
await this.actor.sheet.processRoll(event, roll)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Typed/Custom skills action
|
||||
* @private
|
||||
* @param {object} event The event
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleCustomTypedAction (event, actor, actionId) {
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
rollType: 'skill',
|
||||
key: actionId
|
||||
}
|
||||
const roll = new DGPercentileRoll('1D100', {}, options)
|
||||
await this.actor.sheet.processRoll(event, roll)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SoecialTraining action
|
||||
* @private
|
||||
* @param {object} event The event
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleSpecialTrainingAction (event, actor, actionId) {
|
||||
const attr = this.actor.system.specialTraining.find(a => a.name === actionId).attribute
|
||||
let target = 0
|
||||
if (DG.statistics.includes(attr)) {
|
||||
target = this.actor.system.statistics[attr].x5
|
||||
} else if (DG.skills.includes(attr)) {
|
||||
target = this.actor.system.skills[attr].proficiency
|
||||
} else {
|
||||
target = this.actor.system.typedSkills[attr].proficiency
|
||||
}
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
rollType: 'special-training',
|
||||
key: attr,
|
||||
specialTrainingName: actionId,
|
||||
target
|
||||
}
|
||||
const roll = new DGPercentileRoll('1D100', {}, options)
|
||||
await this.actor.sheet.processRoll(event, roll)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Weapon action
|
||||
@@ -207,16 +148,12 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleWeaponsAction (event, actor, actionId) {
|
||||
const item = this.actor.items.get(actionId)
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
rollType: 'weapon',
|
||||
key: item.system.skill,
|
||||
item
|
||||
}
|
||||
const roll = new DGPercentileRoll('1D100', {}, options)
|
||||
await this.actor.sheet.processRoll(event, roll)
|
||||
async #handleWeaponsAction(event, actor, actionId) {
|
||||
const weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
|
||||
if (!weapon) return ui.notifications.warn(`Weapon not found for action id '${actionId}'`)
|
||||
weapon.damageFormula = weapon.system.damage
|
||||
weapon.damageBonus = actor.system.damageBonus
|
||||
await actor.system.roll('weapon', weapon)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,22 +163,12 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleDamageAction (event, actor, actionId) {
|
||||
const item = this.actor.items.get(actionId)
|
||||
if (item.system.lethality > 0 && event.ctrlKey) {
|
||||
// Toggle on/off lethality
|
||||
const isLethal = !item.system.isLethal
|
||||
await item.update({ 'system.isLethal': isLethal })
|
||||
} else {
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
rollType: 'damage',
|
||||
key: item.system.damage,
|
||||
item
|
||||
}
|
||||
const roll = new DGDamageRoll(item.system.damage, {}, options)
|
||||
await this.actor.sheet.processRoll(event, roll)
|
||||
}
|
||||
async #handleDamageAction(event, actor, actionId) {
|
||||
const weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
|
||||
if (!weapon) return ui.notifications.warn(`Weapon not found for action id '${actionId}'`)
|
||||
weapon.damageFormula = weapon.system.damage
|
||||
weapon.damageBonus = actor.system.damageBonus
|
||||
await actor.system.roll('damage', weapon)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,20 +178,21 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleLethalityAction (event, actor, actionId) {
|
||||
const item = await this.actor.items.get(actionId)
|
||||
async #handleLethalityAction(event, actor, actionId) {
|
||||
const item = actor.items.get(actionId)
|
||||
if (item.system.damage !== '' && event.ctrlKey) {
|
||||
const isLethal = !item.system.isLethal
|
||||
await item.update({ 'system.isLethal': isLethal })
|
||||
} else {
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
actor,
|
||||
rollType: 'lethality',
|
||||
key: item.system.lethality,
|
||||
item
|
||||
}
|
||||
/* TOFIX
|
||||
const roll = new DGLethalityRoll(item.system.damage, {}, options)
|
||||
await this.actor.sheet.processRoll(event, roll)
|
||||
await this.actor.sheet.processRoll(event, roll)*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +203,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} actor The actor
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleRitualsAction (event, actor, actionId) {
|
||||
async #handleRitualsAction(event, actor, actionId) {
|
||||
const options = {
|
||||
actor: this.actor,
|
||||
rollType: 'ritual',
|
||||
@@ -291,7 +219,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {object} token The token
|
||||
* @param {string} actionId The action id
|
||||
*/
|
||||
async #handleUtilityAction (token, actionId) {
|
||||
async #handleUtilityAction(token, actionId) {
|
||||
switch (actionId) {
|
||||
case 'endTurn':
|
||||
if (game.combat?.current?.tokenId === token.id) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { MODULE } from './constants.js'
|
||||
|
||||
/**
|
||||
* Register module settings
|
||||
* Called by Token Action HUD Core to register Token Action HUD system module settings
|
||||
* @param {function} coreUpdate Token Action HUD Core update function
|
||||
*/
|
||||
export function register (coreUpdate) {
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// System Module Imports
|
||||
import { ActionHandler } from './action-handler.js'
|
||||
import { RollHandler as Core } from './roll-handler.js'
|
||||
import { SYSTEM } from './constants.js'
|
||||
import { DEFAULTS } from './defaults.js'
|
||||
import * as systemSettings from './settings.js'
|
||||
|
||||
export let SystemManager = null
|
||||
|
||||
@@ -17,7 +17,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @override
|
||||
* @returns {class} The ActionHandler instance
|
||||
*/
|
||||
getActionHandler () {
|
||||
getActionHandler() {
|
||||
return new ActionHandler()
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @override
|
||||
* @returns {object} The available roll handlers
|
||||
*/
|
||||
getAvailableRollHandlers () {
|
||||
getAvailableRollHandlers() {
|
||||
const coreTitle = 'Core Template'
|
||||
const choices = { core: coreTitle }
|
||||
return choices
|
||||
@@ -41,7 +41,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @param {string} rollHandlerId The roll handler ID
|
||||
* @returns {class} The RollHandler instance
|
||||
*/
|
||||
getRollHandler (rollHandlerId) {
|
||||
getRollHandler(rollHandlerId) {
|
||||
let rollHandler
|
||||
switch (rollHandlerId) {
|
||||
case 'core':
|
||||
@@ -57,7 +57,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* Called by Token Action HUD Core
|
||||
* @returns {object} The default layout and groups
|
||||
*/
|
||||
async registerDefaults () {
|
||||
async registerDefaults() {
|
||||
return DEFAULTS
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @override
|
||||
* @param {function} coreUpdate The Token Action HUD Core update function
|
||||
*/
|
||||
registerSettings (coreUpdate) {
|
||||
/*systemSettings.register(coreUpdate)*/
|
||||
registerSettings(coreUpdate) {
|
||||
systemSettings.register(coreUpdate)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,15 +77,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
* @override
|
||||
* @returns {object} The TAH system styles
|
||||
*/
|
||||
registerStyles () {
|
||||
return {
|
||||
template: {
|
||||
class: 'tah-style-template-style', // The class to add to first DIV element
|
||||
file: 'tah-template-style', // The file without the css extension
|
||||
moduleId: SYSTEM.ID, // The module ID
|
||||
name: 'Template Style' // The name to display in the Token Action HUD Core 'Style' module setting
|
||||
}
|
||||
}
|
||||
registerStyles() {
|
||||
// No system-specific styles; use the core styles only
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
import { SYSTEM } from './constants.js'
|
||||
import { MODULE } from './constants.js'
|
||||
|
||||
export let Utils = null
|
||||
|
||||
function registerHUD() {
|
||||
Hooks.on('tokenActionHudCoreApiReady', async () => {
|
||||
/**
|
||||
* Return the SystemManager and requiredCoreModuleVersion to Token Action HUD Core
|
||||
*/
|
||||
const module = game.system
|
||||
module.api = {
|
||||
requiredCoreModuleVersion: "2.0",
|
||||
SystemManager
|
||||
}
|
||||
Hooks.call('tokenActionHudSystemReady', module)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
/**
|
||||
* Utility functions
|
||||
@@ -31,7 +16,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||
static getSetting(key, defaultValue = null) {
|
||||
let value = defaultValue ?? null
|
||||
try {
|
||||
value = game.settings.get(SYSTEM.ID, key)
|
||||
value = game.settings.get(MODULE.ID, key)
|
||||
} catch {
|
||||
coreModule.api.Logger.debug(`Setting '${key}' not found`)
|
||||
}
|
||||
|
||||
@@ -140,7 +140,6 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
|
||||
_onDragOver(event) {}
|
||||
|
||||
async _onDropItem(item) {
|
||||
console.log("Dropped item", item)
|
||||
let itemData = item.toObject()
|
||||
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
|
||||
}
|
||||
@@ -160,15 +159,12 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
|
||||
}
|
||||
|
||||
static #onUpdateCheckboxArray(event, target) {
|
||||
console.log("Update checkbox array", event, target)
|
||||
let arrayName = target.dataset.name
|
||||
let arrayIdx = Number(target.dataset.index)
|
||||
let dataPath = `system.san.${arrayName}`
|
||||
let tab = foundry.utils.duplicate(this.document.system.san[arrayName])
|
||||
tab[arrayIdx] = target.checked
|
||||
this.actor.update( { [dataPath]: tab } )
|
||||
// Dump
|
||||
console.log("Array name", arrayName, arrayIdx, target.checked, dataPath)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -94,7 +94,7 @@ export default class CthulhuEternalItemSheet extends HandlebarsApplicationMixin(
|
||||
dragover: this._onDragOver.bind(this),
|
||||
drop: this._onDrop.bind(this),
|
||||
}
|
||||
return new DragDrop(d)
|
||||
return new foundry.applications.ux.DragDrop.implementation(d)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -141,14 +141,14 @@ export default class CthulhuEternalItemSheet extends HandlebarsApplicationMixin(
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
_onDragOver(event) {}
|
||||
_onDragOver(event) { }
|
||||
|
||||
/**
|
||||
* Callback actions which occur when a dragged element is dropped on a target.
|
||||
* @param {DragEvent} event The originating DragEvent
|
||||
* @protected
|
||||
*/
|
||||
async _onDrop(event) {}
|
||||
async _onDrop(event) { }
|
||||
|
||||
// #endregion
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ export default class CthulhuEternalCreatureSheet extends CthulhuEternalActorShee
|
||||
|
||||
async _onDrop(event) {
|
||||
if (!this.isEditable || !this.isEditMode) return
|
||||
const data = TextEditor.getDragEventData(event)
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
|
||||
|
||||
// Handle different data types
|
||||
switch (data.type) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
|
||||
},
|
||||
actions: {
|
||||
setBP: CthulhuEternalProtagonistSheet.#onSetBP,
|
||||
toggleEquipped: CthulhuEternalProtagonistSheet.#onToggleEquipped,
|
||||
createGear: CthulhuEternalProtagonistSheet.#onCreateGear,
|
||||
createArmor: CthulhuEternalProtagonistSheet.#onCreateArmor,
|
||||
createWeapon: CthulhuEternalProtagonistSheet.#onCreateWeapon,
|
||||
@@ -98,10 +99,20 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
|
||||
switch (partId) {
|
||||
case "main":
|
||||
break
|
||||
case "skills":
|
||||
case "skills": {
|
||||
context.tab = context.tabs.skills
|
||||
context.skills = doc.itemTypes.skill
|
||||
context.skills.sort((a, b) => a.name.localeCompare(b.name))
|
||||
let tmpSkills = doc.itemTypes.skill
|
||||
tmpSkills.sort((a, b) => a.name.localeCompare(b.name))
|
||||
const nbCols = 3;
|
||||
const nbRows = Math.ceil(tmpSkills.length / nbCols);
|
||||
let skillsColumns = Array.from({ length: nbRows }, (_, rowIdx) =>
|
||||
Array.from({ length: nbCols }, (_, colIdx) => tmpSkills[rowIdx + colIdx * nbRows]).filter(Boolean)
|
||||
);
|
||||
// Merge skillsColumns in a single flat array
|
||||
skillsColumns = skillsColumns.flat().filter(Boolean);
|
||||
//DEBUG : console.log("Skills columns:", skillsColumns);
|
||||
context.skills = skillsColumns
|
||||
}
|
||||
break
|
||||
case "equipment":
|
||||
context.tab = context.tabs.equipment
|
||||
@@ -145,6 +156,12 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
|
||||
this.document.system.setBP()
|
||||
}
|
||||
|
||||
static #onToggleEquipped(event, target) {
|
||||
const itemId = target.dataset.itemId
|
||||
const item = this.document.items.get(itemId)
|
||||
if (item) item.update({ "system.equipped": !item.system.equipped })
|
||||
}
|
||||
|
||||
static #onCreateGear(event, target) {
|
||||
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newGear"), type: "gear" }])
|
||||
}
|
||||
@@ -247,7 +264,7 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
|
||||
|
||||
async _onDrop(event) {
|
||||
if (!this.isEditable || !this.isEditMode) return
|
||||
const data = TextEditor.getDragEventData(event)
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
|
||||
|
||||
// Handle different data types
|
||||
switch (data.type) {
|
||||
|
||||
@@ -32,7 +32,6 @@ export default class CthulhuEternalSkillSheet extends CthulhuEternalItemSheet {
|
||||
}
|
||||
|
||||
static async #onRollProgress(event, target) {
|
||||
console.log("Rolling progress for skill", this, event, target)
|
||||
if (this.actor) {
|
||||
const roll = await new Roll("1d4").evaluate()
|
||||
if (roll) {
|
||||
@@ -41,7 +40,7 @@ export default class CthulhuEternalSkillSheet extends CthulhuEternalItemSheet {
|
||||
user: game.user.id,
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
||||
content: `<div class="progress-roll">${game.i18n.localize("CTHULHUETERNAL.Label.skillProgress")} - ${this.document.name} +${roll.total}</div>`,
|
||||
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
|
||||
type: CONST.CHAT_MESSAGE_STYLES.ROLL,
|
||||
roll: roll,
|
||||
};
|
||||
await ChatMessage.create(chatData);
|
||||
|
||||
@@ -81,8 +81,8 @@ export default class CthulhuEternalVehicleSheet extends CthulhuEternalActorSheet
|
||||
break
|
||||
case "description":
|
||||
context.tab = context.tabs.description
|
||||
context.enrichedDescription = await TextEditor.enrichHTML(doc.system.description, { async: true })
|
||||
context.enrichedNotes = await TextEditor.enrichHTML(doc.system.notes, { async: true })
|
||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
|
||||
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
|
||||
break
|
||||
}
|
||||
return context
|
||||
@@ -104,7 +104,7 @@ export default class CthulhuEternalVehicleSheet extends CthulhuEternalActorSheet
|
||||
|
||||
async _onDrop(event) {
|
||||
if (!this.isEditable || !this.isEditMode) return
|
||||
const data = TextEditor.getDragEventData(event)
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
|
||||
|
||||
// Handle different data types
|
||||
switch (data.type) {
|
||||
|
||||
+151
-61
@@ -43,77 +43,167 @@ export const INSANITY = {
|
||||
|
||||
export const ERA_CSS = {
|
||||
jazz: { primaryFont: "RozhaOne", secondaryFont: "RozhaOne", titleFont: "Broadway", baseFontSize: "0.95rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(9%) saturate(2368%) hue-rotate(360deg) brightness(86%) contrast(84%)" },
|
||||
modern: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Georama", baseFontSize: "1.0rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1214%) hue-rotate(51deg) brightness(93%) contrast(86%)" },
|
||||
future: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Seabreed", baseFontSize: "1.0rem", titleFontSize: "2.0rem",imgFilter: "invert(90%) sepia(6%) saturate(1818%) hue-rotate(152deg) brightness(91%) contrast(91%)" },
|
||||
victorian: { primaryFont: "Volkhov", secondaryFont: "Volkhov", titleFont: "Excelsior", baseFontSize: "1.0rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(100%) sepia(59%) saturate(1894%) hue-rotate(337deg) brightness(88%) contrast(98%)" },
|
||||
coldwar: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "TopSecret", baseFontSize: "0.9rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(14%) saturate(2508%) hue-rotate(202deg) brightness(99%) contrast(105%)"},
|
||||
revolution: { primaryFont: "IMFell", secondaryFont: "IMFell", titleFont: "Dominican", baseFontSize: "1.0rem",titleFontSize: "1.3rem",imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(25%) saturate(386%) hue-rotate(7deg) brightness(101%) contrast(84%)" },
|
||||
medieval: { primaryFont: "Skranji", secondaryFont: "UncialAntiqua", titleFont: "Luminari", baseFontSize: "0.9rem",titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(93%) sepia(46%) saturate(354%) hue-rotate(321deg) brightness(93%) contrast(87%)"},
|
||||
ww2: { primaryFont: "SairaStencilOne", secondaryFont: "SairaStencilOne", titleFont: "Armalite", baseFontSize: "0.9rem",titleFontSize: "1.2rem",imgFilter: "filter: invert(44%) sepia(8%) saturate(2657%) hue-rotate(40deg) brightness(96%) contrast(75%)"},
|
||||
ww1: { primaryFont: "CarterOne", secondaryFont: "CarterOne", titleFont: "SigmarOne", baseFontSize: "0.9rem",titleFontSize: "1.1rem",imgFilter: "invert(28%) sepia(27%) saturate(475%) hue-rotate(76deg) brightness(95%) contrast(93%)"},
|
||||
ageofsail: { primaryFont: "SailRegular", secondaryFont: "SailRegular", titleFont: "P22Operina", baseFontSize: "1.1rem",titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(43%) sepia(74%) saturate(3154%) hue-rotate(336deg) brightness(95%) contrast(83%)" },
|
||||
classical: { primaryFont: "ChantelliAntiqua", secondaryFont: "ChantelliAntiqua", titleFont: "TrajanPro", baseFontSize: "0.9rem",titleFontSize: "1.1rem",imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(32%) saturate(7492%) hue-rotate(265deg) brightness(89%) contrast(95%)" },
|
||||
postapo: { primaryFont: "Teko", secondaryFont: "Teko", titleFont: "Teko", baseFontSize: "1.35rem",titleFontSize: "1.5rem",imgFilter: "brightness(0) saturate(100%) invert(44%) sepia(55%) saturate(2341%) hue-rotate(329deg) brightness(122%) contrast(103%))" }
|
||||
modern: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Georama", baseFontSize: "1.0rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1214%) hue-rotate(51deg) brightness(93%) contrast(86%)" },
|
||||
future: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Seabreed", baseFontSize: "1.0rem", titleFontSize: "2.0rem", imgFilter: "invert(90%) sepia(6%) saturate(1818%) hue-rotate(152deg) brightness(91%) contrast(91%)" },
|
||||
victorian: { primaryFont: "Volkhov", secondaryFont: "Volkhov", titleFont: "Excelsior", baseFontSize: "1.0rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(100%) sepia(59%) saturate(1894%) hue-rotate(337deg) brightness(88%) contrast(98%)" },
|
||||
coldwar: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "TopSecret", baseFontSize: "0.9rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(14%) saturate(2508%) hue-rotate(202deg) brightness(99%) contrast(105%)" },
|
||||
revolution: { primaryFont: "IMFell", secondaryFont: "IMFell", titleFont: "Dominican", baseFontSize: "1.0rem", titleFontSize: "1.3rem", imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(25%) saturate(386%) hue-rotate(7deg) brightness(101%) contrast(84%)" },
|
||||
medieval: { primaryFont: "Skranji", secondaryFont: "UncialAntiqua", titleFont: "Luminari", baseFontSize: "0.9rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(93%) sepia(46%) saturate(354%) hue-rotate(321deg) brightness(93%) contrast(87%)" },
|
||||
ww2: { primaryFont: "SairaStencilOne", secondaryFont: "SairaStencilOne", titleFont: "Armalite", baseFontSize: "0.9rem", titleFontSize: "1.2rem", imgFilter: "filter: invert(44%) sepia(8%) saturate(2657%) hue-rotate(40deg) brightness(96%) contrast(75%)" },
|
||||
ww1: { primaryFont: "CarterOne", secondaryFont: "CarterOne", titleFont: "SigmarOne", baseFontSize: "0.9rem", titleFontSize: "1.1rem", imgFilter: "invert(28%) sepia(27%) saturate(475%) hue-rotate(76deg) brightness(95%) contrast(93%)" },
|
||||
ageofsail: { primaryFont: "SailRegular", secondaryFont: "SailRegular", titleFont: "P22Operina", baseFontSize: "1.1rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(43%) sepia(74%) saturate(3154%) hue-rotate(336deg) brightness(95%) contrast(83%)" },
|
||||
classical: { primaryFont: "ChantelliAntiqua", secondaryFont: "ChantelliAntiqua", titleFont: "TrajanPro", baseFontSize: "0.9rem", titleFontSize: "1.1rem", imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(32%) saturate(7492%) hue-rotate(265deg) brightness(89%) contrast(95%)" },
|
||||
postapo: { primaryFont: "Teko", secondaryFont: "Teko", titleFont: "Teko", baseFontSize: "1.35rem", titleFontSize: "1.5rem", imgFilter: "brightness(0) saturate(100%) invert(44%) sepia(55%) saturate(2341%) hue-rotate(329deg) brightness(122%) contrast(103%))" }
|
||||
}
|
||||
|
||||
export const RESOURCE_RATING = {
|
||||
jazz: {
|
||||
0: {name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets"},
|
||||
4: {name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 700, assets: "CTHULHUETERNAL.Resource.PoorJazz"},
|
||||
8: {name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 3000, assets: "CTHULHUETERNAL.Resource.AverageJazz"},
|
||||
12: {name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 5000, assets: "CTHULHUETERNAL.Resource.AboveAverageJazz"},
|
||||
16: {name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 10000, assets: "CTHULHUETERNAL.Resource.WellOffJazz"},
|
||||
18: {name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 15000, assets: "CTHULHUETERNAL.Resource.RichJazz"},
|
||||
19: {name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 50000, assets: "CTHULHUETERNAL.Resource.VeryRichJazz"},
|
||||
20: {name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 100000, assets: "CTHULHUETERNAL.Resource.SuperRichJazz"}
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 700, assets: "CTHULHUETERNAL.Resource.PoorJazz" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 3000, assets: "CTHULHUETERNAL.Resource.AverageJazz" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 5000, assets: "CTHULHUETERNAL.Resource.AboveAverageJazz" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 10000, assets: "CTHULHUETERNAL.Resource.WellOffJazz" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 15000, assets: "CTHULHUETERNAL.Resource.RichJazz" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 50000, assets: "CTHULHUETERNAL.Resource.VeryRichJazz" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 100000, assets: "CTHULHUETERNAL.Resource.SuperRichJazz" }
|
||||
},
|
||||
modern: {
|
||||
0: {name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets"},
|
||||
4: {name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorModern"},
|
||||
8: {name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageModern"},
|
||||
12: {name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageModern"},
|
||||
16: {name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffModern"},
|
||||
18: {name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 20000, assets: "CTHULHUETERNAL.Resource.RichModern"},
|
||||
19: {name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichModern"},
|
||||
20: {name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichModern"}
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorModern" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageModern" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageModern" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffModern" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 20000, assets: "CTHULHUETERNAL.Resource.RichModern" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichModern" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichModern" }
|
||||
},
|
||||
future: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorModern" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageModern" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageModern" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffModern" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 20000, assets: "CTHULHUETERNAL.Resource.RichModern" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichModern" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichModern" }
|
||||
},
|
||||
coldwar: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
ww1: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
ww2: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
medieval: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
revolution: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
ageofsail: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
classical: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
postapo: {
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
|
||||
},
|
||||
victorian: {
|
||||
0: {name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets"},
|
||||
4: {name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 30, assets: "CTHULHUETERNAL.Resource.PoorVictorian"},
|
||||
8: {name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 90, assets: "CTHULHUETERNAL.Resource.AverageVictorian"},
|
||||
12: {name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 400, assets: "CTHULHUETERNAL.Resource.AboveAverageVictorian"},
|
||||
16: {name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 1500, assets: "CTHULHUETERNAL.Resource.WellOffVictorian"},
|
||||
18: {name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 5000, assets: "CTHULHUETERNAL.Resource.RichVictorian"},
|
||||
19: {name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 40000, assets: "CTHULHUETERNAL.Resource.VeryRichVictorian"},
|
||||
20: {name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 200000, assets: "CTHULHUETERNAL.Resource.SuperRichVictorian"}
|
||||
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
|
||||
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 30, assets: "CTHULHUETERNAL.Resource.PoorVictorian" },
|
||||
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 90, assets: "CTHULHUETERNAL.Resource.AverageVictorian" },
|
||||
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 400, assets: "CTHULHUETERNAL.Resource.AboveAverageVictorian" },
|
||||
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 1500, assets: "CTHULHUETERNAL.Resource.WellOffVictorian" },
|
||||
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 5000, assets: "CTHULHUETERNAL.Resource.RichVictorian" },
|
||||
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 40000, assets: "CTHULHUETERNAL.Resource.VeryRichVictorian" },
|
||||
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 200000, assets: "CTHULHUETERNAL.Resource.SuperRichVictorian" }
|
||||
}
|
||||
}
|
||||
|
||||
export const RESOURCE_BREAKDOWN = [
|
||||
{ value: 0, hand: 0, stowed: 0, storage: 0, checks: 0},
|
||||
{ value: 1, hand: 1, stowed: 0, storage: 0, checks: 1},
|
||||
{ value: 2, hand: 2, stowed: 0, storage: 0, checks: 1},
|
||||
{ value: 3, hand: 3, stowed: 0, storage: 0, checks: 1},
|
||||
{ value: 4, hand: 4, stowed: 0, storage: 0, checks: 1},
|
||||
{ value: 5, hand: 5, stowed: 0, storage: 0, checks: 1},
|
||||
{ value: 6, hand: 6, stowed: 0, storage: 0, checks: 1},
|
||||
{ value: 7, hand: 6, stowed: 1, storage: 0, checks: 2},
|
||||
{ value: 8, hand: 6, stowed: 2, storage: 0, checks: 2},
|
||||
{ value: 9, hand: 6, stowed: 3, storage: 0, checks: 2},
|
||||
{ value: 10, hand: 6, stowed: 4, storage: 0, checks: 2},
|
||||
{ value: 11, hand: 6, stowed: 5, storage: 0, checks: 2},
|
||||
{ value: 12, hand: 6, stowed: 6, storage: 0, checks: 2},
|
||||
{ value: 13, hand: 6, stowed: 6, storage: 1, checks: 3},
|
||||
{ value: 14, hand: 6, stowed: 6, storage: 2, checks: 3},
|
||||
{ value: 15, hand: 6, stowed: 6, storage: 3, checks: 3},
|
||||
{ value: 16, hand: 6, stowed: 6, storage: 4, checks: 3},
|
||||
{ value: 17, hand: 6, stowed: 6, storage: 5, checks: 3},
|
||||
{ value: 18, hand: 6, stowed: 6, storage: 6, checks: 3},
|
||||
{ value: 19, hand: 6, stowed: 6, storage: 7, checks: 3},
|
||||
{ value: 20, hand: 6, stowed: 6, storage: 8, checks: 3}
|
||||
{ value: 0, hand: 0, stowed: 0, storage: 0, checks: 0 },
|
||||
{ value: 1, hand: 1, stowed: 0, storage: 0, checks: 1 },
|
||||
{ value: 2, hand: 2, stowed: 0, storage: 0, checks: 1 },
|
||||
{ value: 3, hand: 3, stowed: 0, storage: 0, checks: 1 },
|
||||
{ value: 4, hand: 4, stowed: 0, storage: 0, checks: 1 },
|
||||
{ value: 5, hand: 5, stowed: 0, storage: 0, checks: 1 },
|
||||
{ value: 6, hand: 6, stowed: 0, storage: 0, checks: 1 },
|
||||
{ value: 7, hand: 6, stowed: 1, storage: 0, checks: 2 },
|
||||
{ value: 8, hand: 6, stowed: 2, storage: 0, checks: 2 },
|
||||
{ value: 9, hand: 6, stowed: 3, storage: 0, checks: 2 },
|
||||
{ value: 10, hand: 6, stowed: 4, storage: 0, checks: 2 },
|
||||
{ value: 11, hand: 6, stowed: 5, storage: 0, checks: 2 },
|
||||
{ value: 12, hand: 6, stowed: 6, storage: 0, checks: 2 },
|
||||
{ value: 13, hand: 6, stowed: 6, storage: 1, checks: 3 },
|
||||
{ value: 14, hand: 6, stowed: 6, storage: 2, checks: 3 },
|
||||
{ value: 15, hand: 6, stowed: 6, storage: 3, checks: 3 },
|
||||
{ value: 16, hand: 6, stowed: 6, storage: 4, checks: 3 },
|
||||
{ value: 17, hand: 6, stowed: 6, storage: 5, checks: 3 },
|
||||
{ value: 18, hand: 6, stowed: 6, storage: 6, checks: 3 },
|
||||
{ value: 19, hand: 6, stowed: 6, storage: 7, checks: 3 },
|
||||
{ value: 20, hand: 6, stowed: 6, storage: 8, checks: 3 }
|
||||
]
|
||||
|
||||
export const DAMAGE_BONUS = [ -2, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]
|
||||
export const DAMAGE_BONUS = [-2, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]
|
||||
|
||||
export const VEHICLE_SPEED = {
|
||||
"none": "CTHULHUETERNAL.Label.None",
|
||||
@@ -240,10 +330,10 @@ export const MULTIPLIER_CHOICES = {
|
||||
}
|
||||
|
||||
export const WEAPON_SELECTIVE_FIRE_CHOICES = {
|
||||
"shortburst": { id: "shortburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortburst", ammoUsed: 3, lethality: 10, killRadius: 0},
|
||||
"longburst": { id: "longburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longburst", ammoUsed: 5, lethality: 10, killRadius: 1},
|
||||
"shortspray": { id: "shortspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortspray", ammoUsed: 10, lethality: 10, killRadius: 2},
|
||||
"longspray": { id: "longspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longspray", ammoUsed: 20, lethality: 10, killRadius: 3},
|
||||
"shortburst": { id: "shortburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortburst", ammoUsed: 3, lethality: 10, killRadius: 0 },
|
||||
"longburst": { id: "longburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longburst", ammoUsed: 5, lethality: 10, killRadius: 1 },
|
||||
"shortspray": { id: "shortspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortspray", ammoUsed: 10, lethality: 10, killRadius: 2 },
|
||||
"longspray": { id: "longspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longspray", ammoUsed: 20, lethality: 10, killRadius: 3 },
|
||||
}
|
||||
|
||||
// Melee stuff
|
||||
|
||||
@@ -23,8 +23,10 @@ export default class CthulhuEternalActor extends Actor {
|
||||
data.items.push(skill.toObject())
|
||||
}
|
||||
}
|
||||
data.items.push({ type:"weapon", img: "systems/fvtt-cthulhu-eternal/assets/icons/icon_fist.svg",
|
||||
name: game.i18n.localize("CTHULHUETERNAL.Label.Unarmed"), system: { damage: "1d4-1", weaponType: "unarmed" } })
|
||||
data.items.push({
|
||||
type: "weapon", img: "systems/fvtt-cthulhu-eternal/assets/icons/icon_fist.svg",
|
||||
name: game.i18n.localize("CTHULHUETERNAL.Label.Unarmed"), system: { damage: "1d4-1", weaponType: "unarmed", applyDamageBonus: true }
|
||||
})
|
||||
}
|
||||
|
||||
return super.create(data, options);
|
||||
@@ -40,9 +42,52 @@ export default class CthulhuEternalActor extends Actor {
|
||||
type: CONST.CHAT_MESSAGE_STYLES.OTHER
|
||||
})
|
||||
}
|
||||
if (this.type === "protagonist" && changed?.system?.hp !== undefined) {
|
||||
const hp = this.system.hp
|
||||
this.toggleStatusEffect("dead", { active: !!hp.dead })
|
||||
this.toggleStatusEffect("unconscious", { active: !!hp.unconscious && !hp.dead })
|
||||
this.toggleStatusEffect("stun", { active: !!hp.stunned && !hp.dead && !hp.unconscious })
|
||||
}
|
||||
return super._onUpdate(changed, options, userId)
|
||||
}
|
||||
|
||||
setLastDefenseRoll(rollData) {
|
||||
this.setFlag("fvtt-cthulhu-eternal", "last-defense-roll", rollData)
|
||||
}
|
||||
|
||||
getLastDefenseRoll() {
|
||||
return this.getFlag("fvtt-cthulhu-eternal", "last-defense-roll")
|
||||
}
|
||||
|
||||
applyWounds(woundData) {
|
||||
// Get available armor
|
||||
let armors = this.items.filter(i => i.type === "armor" && i.system.equipped)
|
||||
let totalArmor = 0
|
||||
for (let armor of armors) {
|
||||
totalArmor += armor.system.protection
|
||||
}
|
||||
let effectiveWounds = Math.max(woundData.rollResult - totalArmor, 0)
|
||||
if (woundData.isLethal) {
|
||||
effectiveWounds = this.system.hp.value // Killed!
|
||||
}
|
||||
// Apply armor reduction
|
||||
let hp = Math.max(this.system.hp.value - effectiveWounds, 0)
|
||||
if (this.system.hp.value !== hp) {
|
||||
this.update({ "system.hp.value": hp })
|
||||
}
|
||||
// Chat message for GM only
|
||||
if (game.user.isGM) {
|
||||
let armorText = totalArmor > 0 ? game.i18n.format("CTHULHUETERNAL.Chat.armorAbsorbed", { armor: totalArmor }) : game.i18n.localize("CTHULHUETERNAL.Chat.noArmor")
|
||||
ChatMessage.create({
|
||||
user: game.user.id,
|
||||
speaker: { alias: this.name },
|
||||
rollMode: "gmroll",
|
||||
content: game.i18n.format("CTHULHUETERNAL.Chat.woundsApplied", { name: this.name, effectiveWounds, armorText }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async createEmbeddedDocuments(embeddedName, data, operation) {
|
||||
let newData = []
|
||||
if (embeddedName === "Item") {
|
||||
@@ -83,6 +128,6 @@ export default class CthulhuEternalActor extends Actor {
|
||||
})
|
||||
this.updateSource({ prototypeToken })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+185
-76
@@ -1,5 +1,6 @@
|
||||
|
||||
import { SYSTEM } from "../config/system.mjs"
|
||||
import CthulhuEternalUtils from "../utils.mjs"
|
||||
|
||||
export default class CthulhuEternalRoll extends Roll {
|
||||
/**
|
||||
@@ -128,51 +129,55 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
static async processWeaponDamage(actor, options) {
|
||||
let isLethal = false
|
||||
let weapon = options.rollItem
|
||||
let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
|
||||
options.isNudge = false
|
||||
|
||||
// Selective fire management
|
||||
if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
|
||||
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
|
||||
if (choice.ammoUsed > weapon.system.ammo.value) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
|
||||
return
|
||||
}
|
||||
weapon.system.selectiveFireChoiceLabel = choice.label // Store the choice in the weapon
|
||||
weapon.system.lethality = choice.lethality // Override lethality
|
||||
weapon.system.killRadius = choice.killRadius // Override kill radius
|
||||
ammoUsed = choice.ammoUsed // Override ammo used
|
||||
}
|
||||
|
||||
ammoUsed = Number(ammoUsed)
|
||||
let targets = []
|
||||
if (game?.combat?.combatants) {
|
||||
for (let c of game.combat.combatants) {
|
||||
if (c.actorId !== actor.id) {
|
||||
targets.push({ id: c.id, name: c.name, actorId: c.actorId, tokenId: c.token?.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to scene tokens if not in combat
|
||||
if (targets.length === 0 && canvas?.scene) {
|
||||
for (let tokenDoc of canvas.scene.tokens) {
|
||||
if (tokenDoc.actorId !== actor.id) {
|
||||
targets.push({ name: tokenDoc.name, actorId: tokenDoc.actorId, tokenId: tokenDoc.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (weapon.system.lethality > 0) {
|
||||
let lethalityRoll = new Roll("1d100")
|
||||
await lethalityRoll.evaluate()
|
||||
let lethalScore = (options?.previousResultType === "successCritical") ? weapon.system.lethality * 2 : weapon.system.lethality
|
||||
isLethal = (lethalityRoll.total <= lethalScore)
|
||||
if (ammoUsed > 0) {
|
||||
await actor.updateEmbeddedDocuments("Item", [{
|
||||
_id: weapon._id,
|
||||
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
|
||||
}])
|
||||
}
|
||||
let wounds = Math.floor(lethalityRoll.total / 10) + (lethalityRoll.total % 10)
|
||||
let msgData = {
|
||||
actorId: actor.id,
|
||||
weapon,
|
||||
wounds,
|
||||
lethalScore,
|
||||
isLethal,
|
||||
ammoUsed,
|
||||
ammoUsed: weapon?.ammoUsed || 0,
|
||||
rollResult: lethalityRoll.total,
|
||||
combatants: targets
|
||||
}
|
||||
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-lethal-damage.hbs", msgData)
|
||||
ChatMessage.create({
|
||||
let msg = await ChatMessage.create({
|
||||
user: game.user.id,
|
||||
content: flavor,
|
||||
content: await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-lethal-damage.hbs", msgData),
|
||||
speaker: ChatMessage.getSpeaker({ actor: actor }),
|
||||
}, { rollMode: options.rollMode, create: true })
|
||||
|
||||
await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -184,27 +189,23 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
if (options?.previousResultType === "successCritical") {
|
||||
formula = `( ${formula} ) * 2`
|
||||
}
|
||||
if (ammoUsed > 0) {
|
||||
await actor.updateEmbeddedDocuments("Item", [{
|
||||
_id: weapon._id,
|
||||
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
|
||||
}])
|
||||
}
|
||||
console.log("Weapon damage formula", formula, weapon, ammoUsed)
|
||||
|
||||
let damageRoll = new Roll(formula)
|
||||
await damageRoll.evaluate()
|
||||
let msgData = {
|
||||
actorId: actor.id,
|
||||
weapon,
|
||||
formula,
|
||||
ammoUsed,
|
||||
ammoUsed: weapon?.ammoUsed || 0,
|
||||
rollResult: damageRoll.total,
|
||||
combatants: targets
|
||||
}
|
||||
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-regular-damage.hbs", msgData)
|
||||
ChatMessage.create({
|
||||
let msg = await ChatMessage.create({
|
||||
user: game.user.id,
|
||||
content: flavor,
|
||||
content: await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-regular-damage.hbs", msgData),
|
||||
speaker: ChatMessage.getSpeaker({ actor: actor }),
|
||||
}, { rollMode: options.rollMode, create: true })
|
||||
await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
|
||||
}
|
||||
|
||||
|
||||
@@ -215,11 +216,32 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
modifier += SYSTEM.WEAPON_VISIBILITY[rollData.visibilityChoice]?.modifier || 0
|
||||
modifier += SYSTEM.WEAPON_ATTACKER_STATE[rollData.attackerStateChoice]?.modifier || 0
|
||||
modifier += SYSTEM.WEAPON_TARGET_SIZE[rollData.targetSizeChoice]?.modifier || 0
|
||||
modifier += (rollData.aimingLastRound) ? 20 : 0
|
||||
modifier += (rollData.aimingWithSight) ? 20 : 0
|
||||
modifier += (rollData.aimingLastRoundFlag) ? 20 : 0
|
||||
modifier += (rollData.aimingWithSightFlag) ? 20 : 0
|
||||
return modifier
|
||||
}
|
||||
|
||||
static async processAmmoUsed(actor, weapon) {
|
||||
let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
|
||||
// Selective fire management
|
||||
if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
|
||||
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
|
||||
if (choice.ammoUsed > weapon.system.ammo.value) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
|
||||
return
|
||||
}
|
||||
ammoUsed = choice.ammoUsed // Override ammo used
|
||||
}
|
||||
ammoUsed = Number(ammoUsed)
|
||||
if (ammoUsed > 0) {
|
||||
await actor.updateEmbeddedDocuments("Item", [{
|
||||
_id: weapon._id,
|
||||
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
|
||||
}])
|
||||
}
|
||||
weapon.ammoUsed = ammoUsed
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user with a dialog to configure and execute a roll.
|
||||
*
|
||||
@@ -238,12 +260,14 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
let formula = "1d100"
|
||||
let hasModifier = true
|
||||
let hasMultiplier = false
|
||||
options.isNudge = true
|
||||
const nudgeEnabled = game.settings.get("fvtt-cthulhu-eternal", "settings-nudge")
|
||||
options.isNudge = nudgeEnabled
|
||||
let actor = game.actors.get(options.actorId)
|
||||
|
||||
let target = CthulhuEternalUtils.getTarget()
|
||||
|
||||
switch (options.rollType) {
|
||||
case "skill":
|
||||
console.log(options.rollItem)
|
||||
options.initialScore = options.rollItem.system.computeScore()
|
||||
break
|
||||
case "luck":
|
||||
@@ -254,7 +278,7 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
case "san":
|
||||
case "char":
|
||||
options.initialScore = options.rollItem.targetScore
|
||||
options.isNudge = (options.rollType !== "san")
|
||||
options.isNudge = nudgeEnabled && (options.rollType !== "san")
|
||||
break
|
||||
case "resource":
|
||||
hasModifier = false
|
||||
@@ -273,33 +297,30 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
|
||||
if (era !== options.rollItem.system.settings) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.WrongEra"))
|
||||
console.log("WP Wrong Era", era, options.rollItem.system.weaponType)
|
||||
return
|
||||
}
|
||||
if (!SYSTEM.WEAPON_SKILL_MAPPING[era]?.[options.rollItem.system.weaponType]) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponType"))
|
||||
console.log("WP Not found", era, options.rollItem.system.weaponType)
|
||||
return
|
||||
}
|
||||
/*if (!target) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.AttackNoTarget"))
|
||||
}*/
|
||||
// Check if the weapon has enouth ammo in case of a firearm
|
||||
if (options.rollItem.system.isFireArm() && options.rollItem.system.ammo.value <= 0) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
|
||||
return
|
||||
}
|
||||
options.weapon = options.rollItem
|
||||
if (options.rollItem.system.hasDirectSkill) {
|
||||
let skillName = options.rollItem.name
|
||||
options.rollItem = { type: "skill", name: skillName, system: { base: 0, bonus: options.weapon.system.directSkillValue } }
|
||||
options.initialScore = options.weapon.system.directSkillValue
|
||||
} else {
|
||||
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
|
||||
options.rollItem = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
|
||||
if (!options.rollItem) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponSkill"))
|
||||
return
|
||||
|
||||
options.rollItem = CthulhuEternalUtils.getWeaponSkill(actor, options.rollItem, era)
|
||||
options.initialScore = options.rollItem.system.skillTotal
|
||||
if (options.weapon.system.state === "junk") {
|
||||
options.isJunk = true
|
||||
options.initialScore = Math.max(0, options.initialScore - 20) // -20% for junk weapons
|
||||
}
|
||||
options.initialScore = options.rollItem.system.computeScore()
|
||||
console.log("WEAPON", skillName, era, options.rollItem)
|
||||
if (options.weapon.system.state === "worn") {
|
||||
options.isWorn = true
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -308,7 +329,7 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
break
|
||||
}
|
||||
|
||||
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes); //Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)]))
|
||||
const rollModes = foundry.utils.duplicate(CONFIG.ChatMessage.modes ?? CONFIG.Dice.rollModes)
|
||||
const fieldRollMode = new foundry.data.fields.StringField({
|
||||
choices: rollModes,
|
||||
blank: false,
|
||||
@@ -331,6 +352,8 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
targetScore: options.initialScore,
|
||||
isLowWP: options.isLowWP,
|
||||
isZeroWP: options.isZeroWP,
|
||||
isJunk: options.isJunk,
|
||||
isWorn: options.isWorn,
|
||||
isExhausted: options.isExhausted,
|
||||
enableHand: options.rollItem.enableHand,
|
||||
enableStowed: options.rollItem.enableStowed,
|
||||
@@ -353,10 +376,11 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
visibilityChoice: "clear",
|
||||
attackerStateChoice: "normal",
|
||||
targetSizeChoice: "normal",
|
||||
aimingLastRound: false,
|
||||
aimingWithSight: false,
|
||||
aimingLastRoundFlag: false,
|
||||
aimingWithSightFlag: false,
|
||||
modifier,
|
||||
formula,
|
||||
targetName: target?.name,
|
||||
hasTarget: options.hasTarget,
|
||||
hasModifier,
|
||||
hasMultiplier,
|
||||
@@ -402,6 +426,14 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
options.multiplier = Number(event.target.value)
|
||||
this.updateResourceDialog(options)
|
||||
})
|
||||
$(".aimingLastRound").change(event => {
|
||||
options.aimingLastRoundFlag = event.target.checked
|
||||
this.updateResourceDialog(options)
|
||||
})
|
||||
$(".aimingWithSight").change(event => {
|
||||
options.aimingWithSightFlag = event.target.checked
|
||||
this.updateResourceDialog(options)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -409,10 +441,12 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
if (rollContext === null) return
|
||||
|
||||
let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext)
|
||||
// If we have a target, get its data
|
||||
rollData.targetId = target?.id
|
||||
rollData.targetName = target?.name
|
||||
rollData.rollMode = rollContext.visibility
|
||||
|
||||
// Update target score
|
||||
console.log("Rolldata", rollData, options)
|
||||
if (options.rollType === "resource") {
|
||||
rollData.targetScore = options.initialScore * Number(rollContext.multiplier)
|
||||
} else {
|
||||
@@ -432,19 +466,24 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
rollData.modifier = "0"
|
||||
}
|
||||
|
||||
if (options.rollType === "weapon") {
|
||||
await this.processAmmoUsed(actor, rollData.weapon)
|
||||
}
|
||||
|
||||
if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return
|
||||
|
||||
const roll = new this(formula, options.data, rollData)
|
||||
await roll.evaluate()
|
||||
|
||||
roll.displayRollResult(roll, options, rollData)
|
||||
await roll.displayRollResult(roll, options, rollData)
|
||||
|
||||
if (Hooks.call("fvtt-cthulhu-eternal.Roll", options, rollData, roll) === false) return
|
||||
|
||||
|
||||
return roll
|
||||
}
|
||||
|
||||
displayRollResult(formula, options, rollData) {
|
||||
async displayRollResult(formula, options, rollData) {
|
||||
|
||||
// Compute the result quality
|
||||
let resultType = "failure"
|
||||
@@ -462,6 +501,11 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
resultType = "failureCritical"
|
||||
}
|
||||
}
|
||||
if (rollData.weapon?.system?.state === "junk" && this.total > 95) {
|
||||
this.options.isDestroyed = true
|
||||
rollData.isDestroyed = true
|
||||
resultType = "failureCritical"
|
||||
}
|
||||
// As per the rules, a roll of 100 is always a failure, even if the target is above 100
|
||||
if (this.total === 100) {
|
||||
resultType = "failureCritical"
|
||||
@@ -489,10 +533,79 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
}
|
||||
rollData.resultType = resultType
|
||||
|
||||
// Compute WP cost to nudge a failure into a success (minimum roll needed = targetScore)
|
||||
if (this.options.isFailure && options.isNudge && !this.options.isNudgedRoll && rollData.targetScore > 0 && this.total > rollData.targetScore) {
|
||||
const actor = game.actors.get(options.actorId)
|
||||
const wpAvailable = actor?.system?.wp?.value ?? 0
|
||||
const wpCostToSucceed = Math.ceil((this.total - rollData.targetScore) / 5)
|
||||
this.options.wpCostToSucceed = (wpCostToSucceed <= wpAvailable) ? wpCostToSucceed : 0
|
||||
} else {
|
||||
this.options.wpCostToSucceed = 0
|
||||
}
|
||||
|
||||
this.options.isLowWP = rollData.isLowWP
|
||||
this.options.isZeroWP = rollData.isZeroWP
|
||||
this.options.isExhausted = rollData.isExhausted
|
||||
rollData.isSuccess = this.options.isSuccess
|
||||
rollData.isFailure = this.options.isFailure
|
||||
rollData.isCritical = this.options.isCritical
|
||||
rollData.resultType = resultType
|
||||
rollData.rollResult = this.total
|
||||
rollData.total = this.total
|
||||
this.options.rollData = foundry.utils.duplicate(rollData)
|
||||
|
||||
// Keep track of the last defense roll for the actor
|
||||
if (game.combat && (rollData?.weapon?.system.type === "melee" || (rollData?.rollItem.type === "skill" && rollData?.rollItem.name?.toLowerCase().includes(game.i18n.localize("CTHULHUETERNAL.Skill.dodgeName").toLowerCase())))) {
|
||||
let actor = game.actors.get(options.actorId)
|
||||
rollData.round = game.combat.round
|
||||
actor.setLastDefenseRoll(foundry.utils.duplicate(rollData))
|
||||
}
|
||||
|
||||
if (game.combat && rollData?.weapon) { // An attack roll
|
||||
rollData.isAttackRoll = true
|
||||
}
|
||||
|
||||
// Now check if we have a target for the current roll, and if the target has done its defense roll this round
|
||||
if (rollData.targetId) {
|
||||
let token = game.scenes.current.tokens.get(rollData.targetId)
|
||||
let defender = token.actor
|
||||
let lastDefenseRoll = defender?.getLastDefenseRoll()
|
||||
// Now compare opposition
|
||||
this.compareRolls(rollData, lastDefenseRoll)
|
||||
rollData.defenseRoll = lastDefenseRoll
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
compareRolls(attackRoll, defenseRoll) {
|
||||
if (!defenseRoll || defenseRoll.round !== game?.combat?.round) {
|
||||
// ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.NoDefenseRoll"))
|
||||
return
|
||||
}
|
||||
if (attackRoll.isFailure) {
|
||||
attackRoll.attackSucess = false
|
||||
}
|
||||
if (attackRoll.isSuccess && defenseRoll.isFailure) {
|
||||
attackRoll.attackSucess = true
|
||||
}
|
||||
if (attackRoll.isSuccess && defenseRoll.isSuccess) {
|
||||
if (attackRoll.isCritical && !defenseRoll.isCritical) {
|
||||
attackRoll.attackSucess = true
|
||||
}
|
||||
else if (!attackRoll.isCritical && defenseRoll.isCritical) {
|
||||
attackRoll.attackSucess = false
|
||||
}
|
||||
else {
|
||||
// Both are normal success, compare the roll results
|
||||
if (attackRoll.total >= defenseRoll.total) {
|
||||
// Attack successful
|
||||
attackRoll.attackSucess = true
|
||||
} else {
|
||||
// Defense successful
|
||||
attackRoll.attackSucess = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,8 +702,20 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
* @param {boolean} [options.create=true] Whether to create the message.
|
||||
* @returns {Promise} - A promise that resolves when the message is created.
|
||||
*/
|
||||
async toMessage(messageData = {}, { rollMode, create = true } = {}) {
|
||||
super.toMessage(
|
||||
async toMessage(messageData = {}, { rollMode, messageMode, create = true } = {}) {
|
||||
let rollData = this.options.rollData || this.options
|
||||
let rollItem = this.options.rollItem
|
||||
|
||||
// Determine skill progression before calling super.toMessage so _getChatCardData picks it up
|
||||
let skillMarkedForProgress = false
|
||||
if (rollData.resultType?.includes("failure") && rollItem?.type === "skill") {
|
||||
if (rollItem.system.diceEvolved && !rollItem.system.rollFailed && !rollItem.system.isAdversary) {
|
||||
skillMarkedForProgress = true
|
||||
}
|
||||
}
|
||||
this.options.skillMarkedForProgress = skillMarkedForProgress
|
||||
|
||||
let rollMsg = await super.toMessage(
|
||||
{
|
||||
isFailure: this.resultType === "failure",
|
||||
actingCharName: this.actorName,
|
||||
@@ -599,33 +724,17 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
realDamage: this.realDamage,
|
||||
...messageData,
|
||||
},
|
||||
{ rollMode: rollMode },
|
||||
{ messageMode: messageMode ?? rollMode },
|
||||
)
|
||||
await rollMsg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData)
|
||||
|
||||
// Manage the skill evolution if the roll is a failure
|
||||
let rollData = this.options.rollData || this.options
|
||||
let rollItem = this.options.rollItem
|
||||
if (rollData.resultType.includes("failure") && rollItem.type === "skill") {
|
||||
// Is the skill able to progress
|
||||
if (rollItem.system.diceEvolved && !rollItem.system.rollFailed) {
|
||||
// If the skill is not adversary, we can evolve it
|
||||
if (!rollItem.system.isAdversary) {
|
||||
rollItem.system.rollFailed = true
|
||||
// Get the actor and update the skill
|
||||
// Apply skill rollFailed flag after the message is created
|
||||
if (skillMarkedForProgress) {
|
||||
const actor = game.actors.get(rollData.actorId)
|
||||
await actor.updateEmbeddedDocuments("Item", [{
|
||||
_id: rollItem._id,
|
||||
"system.rollFailed": true
|
||||
}])
|
||||
// Create a chat message to inform the user
|
||||
const flavor = `${rollItem.name} - ${game.i18n.localize("CTHULHUETERNAL.Label.skillFailed")}`
|
||||
await ChatMessage.create({
|
||||
user: game.user.id,
|
||||
content: `<div class="cthulhu-eternal-roll"><p>${flavor}</p></div>`,
|
||||
speaker: ChatMessage.getSpeaker({ actor: rollData.actor }),
|
||||
}, { rollMode: rollData.rollMode, create: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the roll is a SAN roll, we propose to select the SAN loss
|
||||
|
||||
@@ -12,6 +12,7 @@ export default class CthulhuEternalArmor extends foundry.abstract.TypeDataModel
|
||||
|
||||
schema.protection = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||
schema.equipped = new fields.BooleanField({ required: true, initial: false })
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default class CthulhuEternalCreature extends foundry.abstract.TypeDataMod
|
||||
})
|
||||
if (!roll) return null
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
await roll.toMessage({}, { messageMode: roll.options.rollMode })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
insanity: new fields.StringField({ required: true, nullable: false, initial: "none", choices: SYSTEM.INSANITY }),
|
||||
})
|
||||
|
||||
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: -2 })
|
||||
|
||||
schema.resources = new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), // Unused but kept for compatibility
|
||||
@@ -90,7 +90,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
|
||||
if (!game.user.isGM ) {
|
||||
if (!game.user.isGM) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,9 +127,11 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
dmgBonus = -2
|
||||
} else if (this.characteristics.str.value <= 8) {
|
||||
dmgBonus = -1
|
||||
} else if (this.characteristics.str.value <= 12) {
|
||||
dmgBonus = 0
|
||||
} else if (this.characteristics.str.value <= 16) {
|
||||
dmgBonus = 1
|
||||
} else if (this.characteristics.str.value <= 20) {
|
||||
} else if (this.characteristics.str.value <= 40) {
|
||||
dmgBonus = 2
|
||||
}
|
||||
if (this.damageBonus !== dmgBonus) {
|
||||
@@ -140,13 +142,16 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
if (!this.san.breakingPointReached && this.san.value <= this.san.breakingPoint) {
|
||||
updates[`system.san.breakingPointReached`] = true
|
||||
this.san.breakingPointReached = true // Force local update to true
|
||||
let w = game.users.find(u => u.character?.name === this.parent?.name)
|
||||
if (w) {
|
||||
ChatMessage.create({
|
||||
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.breakingPointReached", { bp: this.san.breakingPoint, san: this.san.value })}</p>`,
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.parent }),
|
||||
// Get the user id of the actor owner
|
||||
whisper: [game.users.find(u => u.character?.name === this.parent?.name).id ]
|
||||
whisper: [w.id]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Unconsciousness management
|
||||
if (!this.hp.unconscious && this.hp.value <= 2) {
|
||||
@@ -207,7 +212,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
actorName: this.parent.name,
|
||||
adaptedToHelplessness: this.biodata.adaptedToHelplessness,
|
||||
adaptedToViolence: this.biodata.adaptedToViolence,
|
||||
... rollData
|
||||
...rollData
|
||||
}
|
||||
let updates = {}
|
||||
let template = ""
|
||||
@@ -236,7 +241,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
}
|
||||
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-0.hbs"
|
||||
|
||||
} else if (rollData.sanType === "violence" ) {
|
||||
} else if (rollData.sanType === "violence") {
|
||||
// Set the first false element of the violence array to true
|
||||
let violence = this.san.violence.slice()
|
||||
let index = violence.findIndex(v => !v)
|
||||
@@ -251,7 +256,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
updates[`system.san.violence`] = [false, false, false]
|
||||
msgData.adaptedToViolence = true
|
||||
}
|
||||
} else if (rollData.sanType === "helplessness" ) {
|
||||
} else if (rollData.sanType === "helplessness") {
|
||||
// If sanType is "helplessness" and adapted to helplessness, set the first false element of the helplessness array to true
|
||||
let helplessness = this.san.helplessness.slice()
|
||||
let index = helplessness.findIndex(h => !h)
|
||||
@@ -266,13 +271,12 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
updates[`system.san.helplessness`] = [false, false, false]
|
||||
msgData.adaptedToHelplessness = true
|
||||
}
|
||||
} else if (rollData.sanType === "unnatural" ) {
|
||||
} else if (rollData.sanType === "unnatural") {
|
||||
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-unnatural.hbs"
|
||||
} else {
|
||||
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-none.hbs"
|
||||
}
|
||||
|
||||
console.log("CthulhuEternalProtagonist.applySANConsequences", rollData, updates, template)
|
||||
let content = await foundry.applications.handlebars.renderTemplate(template, msgData)
|
||||
let msg = await ChatMessage.create({
|
||||
content: content,
|
||||
@@ -354,15 +358,15 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
*/
|
||||
async roll(rollType, rollItem) {
|
||||
|
||||
if (this.hp.dead ) {
|
||||
if (this.hp.dead) {
|
||||
// Warn with chat message
|
||||
ChatMessage.create({
|
||||
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.deadWarning", {con: this.characteristics.con.value} )}</p>`,
|
||||
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.deadWarning", { con: this.characteristics.con.value })}</p>`,
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.parent })
|
||||
})
|
||||
return null
|
||||
}
|
||||
if (this.hp.unconscious ) {
|
||||
if (this.hp.unconscious) {
|
||||
// Warn with chat message
|
||||
ChatMessage.create({
|
||||
content: `<p>${game.i18n.localize("CTHULHUETERNAL.Label.unconsciousWarning")}</p>`,
|
||||
@@ -397,7 +401,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
})
|
||||
if (!roll) return null
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
await roll.toMessage({}, { messageMode: roll.options.rollMode })
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel
|
||||
}
|
||||
|
||||
isRanged() {
|
||||
console.log("isRanged", this.weaponType, this)
|
||||
return this.weaponType.match("ranged")
|
||||
}
|
||||
|
||||
|
||||
+405
-12
@@ -16,6 +16,22 @@ export default class CthulhuEternalUtils {
|
||||
config: true,
|
||||
onChange: _ => window.location.reload()
|
||||
});
|
||||
game.settings.register("fvtt-cthulhu-eternal", "settings-nudge", {
|
||||
name: game.i18n.localize("CTHULHUETERNAL.Settings.nudge"),
|
||||
hint: game.i18n.localize("CTHULHUETERNAL.Settings.nudgeHint"),
|
||||
default: true,
|
||||
scope: "world",
|
||||
type: Boolean,
|
||||
config: true
|
||||
});
|
||||
game.settings.register("fvtt-cthulhu-eternal", "roll-opposed-store", {
|
||||
name: "Roll Opposed Store",
|
||||
hint: "Whether to store opposed roll results for later use",
|
||||
default: { roll1: null, roll2: null },
|
||||
scope: "world",
|
||||
type: Object,
|
||||
config: false
|
||||
});
|
||||
}
|
||||
|
||||
static async loadCompendiumData(compendium) {
|
||||
@@ -110,9 +126,6 @@ export default class CthulhuEternalUtils {
|
||||
return Object.keys(obj).length;
|
||||
})
|
||||
|
||||
Handlebars.registerHelper('isEnabled', function (configKey) {
|
||||
return game.settings.get("bol", configKey);
|
||||
})
|
||||
Handlebars.registerHelper('split', function (str, separator, keep) {
|
||||
return str.split(separator)[keep];
|
||||
})
|
||||
@@ -156,7 +169,6 @@ export default class CthulhuEternalUtils {
|
||||
return eval(expr);
|
||||
})
|
||||
Handlebars.registerHelper('isOwnerOrGM', function (actor) {
|
||||
console.log("Testing actor", actor.isOwner, game.userId)
|
||||
return actor.isOwner || game.isGM;
|
||||
})
|
||||
Handlebars.registerHelper('upperFirst', function (text) {
|
||||
@@ -180,6 +192,53 @@ export default class CthulhuEternalUtils {
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static removeChatMessageId(messageId) {
|
||||
if (messageId) {
|
||||
game.messages.get(messageId)?.delete();
|
||||
}
|
||||
}
|
||||
|
||||
static findChatMessageId(current) {
|
||||
return HawkmoonUtility.getChatMessageId(HawkmoonUtility.findChatMessage(current));
|
||||
}
|
||||
|
||||
static getChatMessageId(node) {
|
||||
return node?.attributes.getNamedItem('data-message-id')?.value;
|
||||
}
|
||||
|
||||
static findChatMessage(current) {
|
||||
return HawkmoonUtility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id'))
|
||||
}
|
||||
|
||||
static findNodeMatching(current, predicate) {
|
||||
if (current) {
|
||||
if (predicate(current)) {
|
||||
return current;
|
||||
}
|
||||
return HawkmoonUtility.findNodeMatching(current.parentElement, predicate);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static getWeaponSkill(actor, weapon, era) {
|
||||
let skill
|
||||
if (weapon.system.hasDirectSkill) {
|
||||
let skillName = weapon.name
|
||||
skill = { type: "skill", name: skillName, system: { base: 0, bonus: weapon.system.directSkillValue, skillTotal: weapon.system.directSkillValue } }
|
||||
} else {
|
||||
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][weapon.system.weaponType])
|
||||
skill = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
|
||||
if (!skill) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponSkill"))
|
||||
return
|
||||
}
|
||||
}
|
||||
return skill
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static async applySANType(rollMessage, event) {
|
||||
let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
|
||||
if (!rollData) {
|
||||
@@ -248,33 +307,301 @@ export default class CthulhuEternalUtils {
|
||||
})
|
||||
}
|
||||
|
||||
static async opposedRollManagement(rollMessage, event) {
|
||||
let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
|
||||
if (!rollData) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noRollDataFound"))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the store
|
||||
let store = game.settings.get("fvtt-cthulhu-eternal", "roll-opposed-store")
|
||||
if (!store.roll1) {
|
||||
store.roll1 = {
|
||||
rollData: rollData,
|
||||
messageId: rollMessage.id
|
||||
}
|
||||
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
|
||||
ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollFirstStored"))
|
||||
}
|
||||
else if (!store.roll2) {
|
||||
store.roll2 = {
|
||||
rollData: rollData,
|
||||
messageId: rollMessage.id
|
||||
}
|
||||
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
|
||||
ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollSecondStored"))
|
||||
// Now perform the opposed roll resolution
|
||||
await this.resolveOpposedRolls(store.roll1, store.roll2)
|
||||
// Clear the store
|
||||
store.roll1 = null
|
||||
store.roll2 = null
|
||||
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
|
||||
}
|
||||
else {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollStoreFull"))
|
||||
}
|
||||
}
|
||||
|
||||
static async resolveOpposedRolls(roll1, roll2) {
|
||||
// Get actors
|
||||
let actor1 = game.actors.get(roll1.rollData.actorId)
|
||||
let actor2 = game.actors.get(roll2.rollData.actorId)
|
||||
if (!actor1 || !actor2) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noActorFound"))
|
||||
return
|
||||
}
|
||||
// Determine winner
|
||||
let winner = null
|
||||
let loser = null
|
||||
// If there critical success/failure, apply them first (remark : this d100 results)
|
||||
roll1.rollData.rollCompare = roll1.rollData.rollResult
|
||||
roll2.rollData.rollCompare = roll2.rollData.rollResult
|
||||
if (roll1.rollData.resultType === "successCritical") {
|
||||
roll1.rollData.rollCompare = -roll1.rollData.rollResult
|
||||
}
|
||||
if (roll2.rollData.resultType === "failureCritical") {
|
||||
roll2.rollData.rollCompare = 100 + roll2.rollData.rollResult
|
||||
}
|
||||
if (roll2.rollData.resultType === "successCritical") {
|
||||
roll2.rollData.rollCompare = -roll2.rollData.rollResult
|
||||
}
|
||||
if (roll1.rollData.resultType === "failureCritical") {
|
||||
roll1.rollData.rollCompare = roll1.rollData.rollResult * 2
|
||||
}
|
||||
if (roll1.rollData.isSuccess && roll2.rollData.isFailure) {
|
||||
winner = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
|
||||
loser = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
|
||||
}
|
||||
else if (roll2.rollData.isSuccess && roll1.rollData.isFailure) {
|
||||
winner = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
|
||||
loser = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
|
||||
}
|
||||
else if (roll1.rollData.rollCompare < roll2.rollData.rollCompare) {
|
||||
winner = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
|
||||
loser = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
|
||||
}
|
||||
else {
|
||||
winner = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
|
||||
loser = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
|
||||
}
|
||||
|
||||
// Check if winner was attacking with a weapon that can apply damage
|
||||
let canApplyDamage = winner && winner.rollData?.weapon && winner.rollData.weapon.system
|
||||
|
||||
// Prepare data for the template
|
||||
let msgData = {
|
||||
winner: {
|
||||
actor: {
|
||||
name: winner.actor.name,
|
||||
img: winner.actor.img
|
||||
},
|
||||
rollData: {
|
||||
rollResult: winner.rollData.rollResult || winner.rollData.total,
|
||||
isCritical: winner.rollData.isCritical,
|
||||
weapon: winner.rollData.weapon
|
||||
}
|
||||
},
|
||||
loser: {
|
||||
actor: {
|
||||
name: loser.actor.name,
|
||||
img: loser.actor.img
|
||||
},
|
||||
rollData: {
|
||||
rollResult: loser.rollData.rollResult || loser.rollData.total,
|
||||
isCritical: loser.rollData.isCritical
|
||||
}
|
||||
},
|
||||
canApplyDamage: canApplyDamage
|
||||
}
|
||||
|
||||
// Render the template
|
||||
let content = await foundry.applications.handlebars.renderTemplate(
|
||||
"systems/fvtt-cthulhu-eternal/templates/chat-opposed-result.hbs",
|
||||
msgData
|
||||
)
|
||||
|
||||
// Display the result in chat
|
||||
let chatMsg = await ChatMessage.create({
|
||||
speaker: ChatMessage.getSpeaker({ actor: winner.actor.id }),
|
||||
content: content
|
||||
})
|
||||
|
||||
// Store the winner's roll data for damage roll if applicable
|
||||
if (canApplyDamage) {
|
||||
await chatMsg.setFlag("fvtt-cthulhu-eternal", "rollData", winner.rollData)
|
||||
}
|
||||
}
|
||||
|
||||
static translateRangeUnit(range) {
|
||||
if (typeof range === 'string') {
|
||||
return game.i18n.localize(`CTHULHUETERNAL.Label.${range}`)
|
||||
} else if (typeof range === 'number') {
|
||||
return range
|
||||
} else {
|
||||
console.warn("CTHULHU ETERNAL | translateRange called with an unknown type", range)
|
||||
return range
|
||||
}
|
||||
}
|
||||
|
||||
static translateRange(range) {
|
||||
// If the range is a string, replace STR with FOR
|
||||
if (typeof range === 'string') {
|
||||
return range.replace(/STR/g, "FOR").replace(/str/g, "for")
|
||||
}
|
||||
return range
|
||||
}
|
||||
|
||||
static async registerBabeleTranslations(babele) {
|
||||
babele.registerConverters({
|
||||
'translateRangeUnit': (originalValue) => {
|
||||
return CthulhuEternalUtils.translateRangeUnit(originalValue)
|
||||
},
|
||||
'translateRange': (originalValue) => {
|
||||
return CthulhuEternalUtils.translateRange(originalValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static async damageRoll(rollMessage, formula = null) {
|
||||
let rollData = rollMessage.rolls[0]?.options?.rollData
|
||||
if (!rollData) {
|
||||
rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
|
||||
}
|
||||
if (!rollData) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noRollDataFound"))
|
||||
return
|
||||
}
|
||||
let actor = game.actors.get(rollData.actorId)
|
||||
if (!actor) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noActorFound"))
|
||||
return
|
||||
}
|
||||
console.log("Damage roll data", rollData)
|
||||
|
||||
rollData.weapon.resultType = rollData.resultType // Keep the result type from the roll message
|
||||
rollData.weapon.selectiveFireChoice = rollData.selectiveFireChoice // Keep the selected fire choice from the roll message
|
||||
rollData.weapon.damageFormula = formula || rollData.weapon.system.damage
|
||||
actor.system.roll("damage", rollData.weapon)
|
||||
}
|
||||
|
||||
static async wornWeaponCheck(rollMessage) {
|
||||
let rollData = rollMessage.rolls[0]?.options?.rollData
|
||||
if (!rollData) {
|
||||
rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
|
||||
}
|
||||
if (!rollData) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noRollDataFound"))
|
||||
return
|
||||
}
|
||||
let actor = game.actors.get(rollData.actorId)
|
||||
if (!actor) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noActorFound"))
|
||||
return
|
||||
}
|
||||
let weapon = rollData.weapon
|
||||
if (!weapon) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noWeaponFound"))
|
||||
return
|
||||
}
|
||||
|
||||
// Lance un jet de chance (50%)
|
||||
let luckRoll = new Roll("1d100")
|
||||
await luckRoll.evaluate()
|
||||
|
||||
let isSuccess = luckRoll.total <= 50
|
||||
let resultMsg = ""
|
||||
|
||||
if (isSuccess) {
|
||||
// Succès - l'arme reste worn
|
||||
resultMsg = game.i18n.format("CTHULHUETERNAL.Label.wornWeaponCheckSuccess", {
|
||||
weapon: weapon.name,
|
||||
roll: luckRoll.total
|
||||
})
|
||||
} else {
|
||||
// Échec - l'arme devient junk
|
||||
resultMsg = game.i18n.format("CTHULHUETERNAL.Label.wornWeaponCheckFailure", {
|
||||
weapon: weapon.name,
|
||||
roll: luckRoll.total
|
||||
})
|
||||
|
||||
// Mettre à jour l'état de l'arme
|
||||
await actor.updateEmbeddedDocuments("Item", [{
|
||||
_id: weapon._id,
|
||||
"system.state": "junk"
|
||||
}])
|
||||
|
||||
ui.notifications.warn(game.i18n.format("CTHULHUETERNAL.Notifications.WeaponBecameJunk", { weapon: weapon.name }))
|
||||
}
|
||||
|
||||
// Créer un message de chat avec le résultat
|
||||
await ChatMessage.create({
|
||||
user: game.user.id,
|
||||
content: `<div class="cthulhu-eternal-roll">
|
||||
<h4>${game.i18n.localize("CTHULHUETERNAL.Label.wornWeaponCheckTitle")}</h4>
|
||||
<p>${resultMsg}</p>
|
||||
</div>`,
|
||||
speaker: ChatMessage.getSpeaker({ actor: actor }),
|
||||
})
|
||||
}
|
||||
|
||||
static async nudgeToSuccess(rollMessage) {
|
||||
if (!game.settings.get("fvtt-cthulhu-eternal", "settings-nudge")) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Settings.nudgeDisabled"))
|
||||
return
|
||||
}
|
||||
const rollOptions = rollMessage.rolls[0]?.options
|
||||
if (!rollOptions) return
|
||||
const actor = game.actors.get(rollOptions.actorId)
|
||||
if (!actor) return
|
||||
const rollTotal = rollMessage.rolls[0].total
|
||||
const targetScore = rollOptions.rollData?.targetScore ?? rollOptions.targetScore
|
||||
const wpCostToSucceed = Math.ceil((rollTotal - targetScore) / 5)
|
||||
|
||||
if (actor.system.wp.value < wpCostToSucceed) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Label.notEnoughWP"))
|
||||
return
|
||||
}
|
||||
|
||||
const dialogContext = foundry.utils.duplicate(rollOptions)
|
||||
dialogContext.nudgedValue = targetScore
|
||||
dialogContext.wpCost = wpCostToSucceed
|
||||
dialogContext.isNudgedRoll = true
|
||||
dialogContext.isNudge = false
|
||||
|
||||
const roll = new CthulhuEternalRoll(String(targetScore))
|
||||
await roll.evaluate()
|
||||
roll.options = dialogContext
|
||||
roll.displayRollResult(roll, dialogContext, dialogContext.rollData)
|
||||
roll.toMessage()
|
||||
|
||||
actor.system.modifyWP(-wpCostToSucceed)
|
||||
|
||||
// Original roll was a failure with skill progression — nudge succeeded, so unmark progression
|
||||
if (rollOptions.skillMarkedForProgress && rollOptions.rollItem?._id) {
|
||||
const skillItem = actor.items.get(rollOptions.rollItem._id)
|
||||
if (skillItem) await skillItem.update({ "system.rollFailed": false })
|
||||
}
|
||||
|
||||
await rollMessage.delete()
|
||||
}
|
||||
|
||||
static async nudgeRoll(rollMessage) {
|
||||
if (!game.settings.get("fvtt-cthulhu-eternal", "settings-nudge")) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Settings.nudgeDisabled"))
|
||||
return
|
||||
}
|
||||
|
||||
let dialogContext = rollMessage.rolls[0]?.options
|
||||
let actor = game.actors.get(dialogContext.actorId)
|
||||
const rollTotal = rollMessage.rolls[0].total
|
||||
dialogContext.wpValue = actor.system.wp.value
|
||||
dialogContext.rollResultIndex = rollMessage.rolls[0].total - 1
|
||||
dialogContext.minValue = Math.max(rollMessage.rolls[0].total - (dialogContext.wpValue * 5), 1)
|
||||
dialogContext.maxValue = Math.min(rollMessage.rolls[0].total + (dialogContext.wpValue * 5), 100)
|
||||
// Rule: 1 WP can decrease the roll by 1–5. Only decrease is allowed.
|
||||
dialogContext.minValue = Math.max(rollTotal - (dialogContext.wpValue * 5), 1)
|
||||
dialogContext.maxValue = rollTotal
|
||||
dialogContext.rollResultIndex = rollTotal - dialogContext.minValue // index of current value in nudgeOptions
|
||||
dialogContext.wpCost = 0
|
||||
|
||||
// Build options table for the select operator between minValue and maxValue
|
||||
dialogContext.nudgedValue = rollTotal // default: no change for the select: values from minValue to maxValue (current roll)
|
||||
dialogContext.nudgeOptions = Array.from({ length: dialogContext.maxValue - dialogContext.minValue + 1 }, (_, i) => dialogContext.minValue + i)
|
||||
console.log(dialogContext)
|
||||
|
||||
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/nudge-dialog.hbs", dialogContext)
|
||||
|
||||
@@ -292,6 +619,9 @@ export default class CthulhuEternalUtils {
|
||||
if (input.name) obj[input.name] = input.value
|
||||
return obj
|
||||
}, {})
|
||||
// Compute nudgedValue from the selected index (selectOptions uses 0-based indices for arrays)
|
||||
dialogContext.nudgedValue = dialogContext.minValue + Number(output.modifiedValue)
|
||||
dialogContext.wpCost = Math.ceil(Math.abs(rollTotal - dialogContext.nudgedValue) / 5)
|
||||
return output
|
||||
},
|
||||
},
|
||||
@@ -306,7 +636,7 @@ export default class CthulhuEternalUtils {
|
||||
rejectClose: false, // Click on Close button will not launch an error
|
||||
render: (event, dialog) => {
|
||||
$(".nudged-score-select").change(event => {
|
||||
dialogContext.nudgedValue = Number(event.target.value)+1
|
||||
dialogContext.nudgedValue = dialogContext.minValue + Number(event.target.value)
|
||||
dialogContext.wpCost = Math.ceil(Math.abs(rollMessage.rolls[0].total - dialogContext.nudgedValue) / 5)
|
||||
$("#nudged-wp-cost").val(dialogContext.wpCost)
|
||||
})
|
||||
@@ -318,6 +648,11 @@ export default class CthulhuEternalUtils {
|
||||
return
|
||||
}
|
||||
|
||||
if (actor.system.wp.value < dialogContext.wpCost) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Label.notEnoughWP"))
|
||||
return
|
||||
}
|
||||
|
||||
const roll = new CthulhuEternalRoll(String(dialogContext.nudgedValue))
|
||||
await roll.evaluate()
|
||||
roll.options = dialogContext
|
||||
@@ -327,6 +662,17 @@ export default class CthulhuEternalUtils {
|
||||
roll.toMessage()
|
||||
|
||||
actor.system.modifyWP(-dialogContext.wpCost)
|
||||
|
||||
// If original roll had skill progression and nudge converts it to success, unmark progression
|
||||
const nudgedTargetScore = dialogContext.rollData?.targetScore ?? dialogContext.targetScore
|
||||
if (dialogContext.skillMarkedForProgress && dialogContext.nudgedValue <= nudgedTargetScore && dialogContext.rollItem?._id) {
|
||||
const skillItem = actor.items.get(dialogContext.rollItem._id)
|
||||
if (skillItem) await skillItem.update({ "system.rollFailed": false })
|
||||
}
|
||||
|
||||
// Delete the initial roll message
|
||||
await rollMessage.delete()
|
||||
|
||||
}
|
||||
|
||||
static setupCSSRootVariables() {
|
||||
@@ -345,4 +691,51 @@ export default class CthulhuEternalUtils {
|
||||
document.documentElement.style.setProperty('--background-image-base', `linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)), url("../assets/ui/${era}_background_main.webp")`);
|
||||
}
|
||||
|
||||
static getTarget() {
|
||||
if (game.user.targets && game.user.targets.size === 1) {
|
||||
for (let target of game.user.targets) {
|
||||
return target
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static applyWounds(message, event) {
|
||||
let woundData = message.getFlag("fvtt-cthulhu-eternal", "woundData")
|
||||
if (!woundData) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noRollDataFound"))
|
||||
return
|
||||
}
|
||||
let actor = game.actors.get(woundData.actorId)
|
||||
if (!actor) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noActorFound"))
|
||||
return
|
||||
}
|
||||
this.removeChatMessageId(message.id)
|
||||
|
||||
// Resolve the target actor: prefer canvas token (works for both combat and scene tokens)
|
||||
const targetTokenId = event.currentTarget.dataset.tokenId
|
||||
const targetActorId = event.currentTarget.dataset.actorId
|
||||
let targetActor
|
||||
if (targetTokenId) {
|
||||
const token = canvas.tokens.get(targetTokenId)
|
||||
targetActor = token?.actor
|
||||
}
|
||||
if (!targetActor && targetActorId) {
|
||||
targetActor = game.actors.get(targetActorId)
|
||||
}
|
||||
// Legacy fallback: combatant lookup
|
||||
if (!targetActor) {
|
||||
const combatantId = event.currentTarget.dataset.combatantId
|
||||
if (combatantId && game.combat) {
|
||||
const combatant = game.combat.combatants.get(combatantId)
|
||||
targetActor = combatant?.token?.actor || game.actors.get(combatant?.actorId)
|
||||
}
|
||||
}
|
||||
if (!targetActor) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noTargetActorFound"))
|
||||
return
|
||||
}
|
||||
targetActor.applyWounds(woundData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
MANIFEST-000217
|
||||
MANIFEST-000306
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2025/07/14-21:08:15.948300 7f3fa67fc6c0 Recovering log #215
|
||||
2025/07/14-21:08:15.959041 7f3fa67fc6c0 Delete type=3 #213
|
||||
2025/07/14-21:08:15.959103 7f3fa67fc6c0 Delete type=0 #215
|
||||
2025/07/14-21:36:01.776546 7f3fa57fa6c0 Level-0 table #220: started
|
||||
2025/07/14-21:36:01.776599 7f3fa57fa6c0 Level-0 table #220: 0 bytes OK
|
||||
2025/07/14-21:36:01.836135 7f3fa57fa6c0 Delete type=0 #218
|
||||
2025/07/14-21:36:02.071353 7f3fa57fa6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)
|
||||
2026/05/13-20:38:21.073323 7ff670fed6c0 Recovering log #304
|
||||
2026/05/13-20:38:21.083446 7ff670fed6c0 Delete type=3 #302
|
||||
2026/05/13-20:38:21.083514 7ff670fed6c0 Delete type=0 #304
|
||||
2026/05/13-21:29:49.379063 7ff6637fe6c0 Level-0 table #309: started
|
||||
2026/05/13-21:29:49.379189 7ff6637fe6c0 Level-0 table #309: 0 bytes OK
|
||||
2026/05/13-21:29:49.386620 7ff6637fe6c0 Delete type=0 #307
|
||||
2026/05/13-21:29:49.393683 7ff6637fe6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2025/07/14-10:18:19.443254 7f3fa77fe6c0 Recovering log #211
|
||||
2025/07/14-10:18:19.474975 7f3fa77fe6c0 Delete type=3 #209
|
||||
2025/07/14-10:18:19.475128 7f3fa77fe6c0 Delete type=0 #211
|
||||
2025/07/14-20:48:52.882063 7f3fa57fa6c0 Level-0 table #216: started
|
||||
2025/07/14-20:48:52.882164 7f3fa57fa6c0 Level-0 table #216: 0 bytes OK
|
||||
2025/07/14-20:48:52.889652 7f3fa57fa6c0 Delete type=0 #214
|
||||
2025/07/14-20:48:52.915102 7f3fa57fa6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)
|
||||
2026/05/13-13:05:01.465748 7ff6717ee6c0 Recovering log #300
|
||||
2026/05/13-13:05:01.518261 7ff6717ee6c0 Delete type=3 #298
|
||||
2026/05/13-13:05:01.518318 7ff6717ee6c0 Delete type=0 #300
|
||||
2026/05/13-13:57:22.815510 7ff6637fe6c0 Level-0 table #305: started
|
||||
2026/05/13-13:57:22.815533 7ff6637fe6c0 Level-0 table #305: 0 bytes OK
|
||||
2026/05/13-13:57:22.821566 7ff6637fe6c0 Delete type=0 #303
|
||||
2026/05/13-13:57:22.821711 7ff6637fe6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000386
|
||||
MANIFEST-000475
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2025/07/14-21:08:15.916959 7f3fa5ffb6c0 Recovering log #384
|
||||
2025/07/14-21:08:15.927715 7f3fa5ffb6c0 Delete type=3 #382
|
||||
2025/07/14-21:08:15.927846 7f3fa5ffb6c0 Delete type=0 #384
|
||||
2025/07/14-21:36:01.925671 7f3fa57fa6c0 Level-0 table #389: started
|
||||
2025/07/14-21:36:01.925708 7f3fa57fa6c0 Level-0 table #389: 0 bytes OK
|
||||
2025/07/14-21:36:01.980506 7f3fa57fa6c0 Delete type=0 #387
|
||||
2025/07/14-21:36:02.071389 7f3fa57fa6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)
|
||||
2026/05/13-20:38:21.045516 7ff6717ee6c0 Recovering log #473
|
||||
2026/05/13-20:38:21.056349 7ff6717ee6c0 Delete type=3 #471
|
||||
2026/05/13-20:38:21.056423 7ff6717ee6c0 Delete type=0 #473
|
||||
2026/05/13-21:29:49.443482 7ff6637fe6c0 Level-0 table #478: started
|
||||
2026/05/13-21:29:49.443606 7ff6637fe6c0 Level-0 table #478: 0 bytes OK
|
||||
2026/05/13-21:29:49.450275 7ff6637fe6c0 Delete type=0 #476
|
||||
2026/05/13-21:29:49.450420 7ff6637fe6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2025/07/14-10:18:19.359761 7f3fa67fc6c0 Recovering log #380
|
||||
2025/07/14-10:18:19.394536 7f3fa67fc6c0 Delete type=3 #378
|
||||
2025/07/14-10:18:19.394680 7f3fa67fc6c0 Delete type=0 #380
|
||||
2025/07/14-20:48:52.889840 7f3fa57fa6c0 Level-0 table #385: started
|
||||
2025/07/14-20:48:52.889882 7f3fa57fa6c0 Level-0 table #385: 0 bytes OK
|
||||
2025/07/14-20:48:52.896159 7f3fa57fa6c0 Delete type=0 #383
|
||||
2025/07/14-20:48:52.915114 7f3fa57fa6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)
|
||||
2026/05/13-13:05:01.363329 7ff663fff6c0 Recovering log #469
|
||||
2026/05/13-13:05:01.408343 7ff663fff6c0 Delete type=3 #467
|
||||
2026/05/13-13:05:01.408405 7ff663fff6c0 Delete type=0 #469
|
||||
2026/05/13-13:57:22.802421 7ff6637fe6c0 Level-0 table #474: started
|
||||
2026/05/13-13:57:22.802455 7ff6637fe6c0 Level-0 table #474: 0 bytes OK
|
||||
2026/05/13-13:57:22.809135 7ff6637fe6c0 Delete type=0 #472
|
||||
2026/05/13-13:57:22.821691 7ff6637fe6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000031
|
||||
MANIFEST-000121
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
2025/07/14-21:08:15.933872 7f3fa6ffd6c0 Recovering log #29
|
||||
2025/07/14-21:08:15.943722 7f3fa6ffd6c0 Delete type=3 #27
|
||||
2025/07/14-21:08:15.943845 7f3fa6ffd6c0 Delete type=0 #29
|
||||
2025/07/14-21:36:01.980637 7f3fa57fa6c0 Level-0 table #34: started
|
||||
2025/07/14-21:36:02.012185 7f3fa57fa6c0 Level-0 table #34: 24252 bytes OK
|
||||
2025/07/14-21:36:02.071125 7f3fa57fa6c0 Delete type=0 #32
|
||||
2025/07/14-21:36:02.071403 7f3fa57fa6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
|
||||
2025/07/14-21:36:02.071441 7f3fa57fa6c0 Manual compaction at level-1 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at '!items!qb4c1wfPZFcGiEuV' @ 472 : 1
|
||||
2025/07/14-21:36:02.071451 7f3fa57fa6c0 Compacting 1@1 + 1@2 files
|
||||
2025/07/14-21:36:02.101879 7f3fa57fa6c0 Generated table #35@1: 362 keys, 93592 bytes
|
||||
2025/07/14-21:36:02.101908 7f3fa57fa6c0 Compacted 1@1 + 1@2 files => 93592 bytes
|
||||
2025/07/14-21:36:02.154064 7f3fa57fa6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2025/07/14-21:36:02.154296 7f3fa57fa6c0 Delete type=2 #14
|
||||
2025/07/14-21:36:02.154743 7f3fa57fa6c0 Delete type=2 #34
|
||||
2025/07/14-21:36:02.372014 7f3fa57fa6c0 Manual compaction at level-1 from '!items!qb4c1wfPZFcGiEuV' @ 472 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
|
||||
2026/05/13-20:38:21.061357 7ff663fff6c0 Recovering log #119
|
||||
2026/05/13-20:38:21.070703 7ff663fff6c0 Delete type=3 #117
|
||||
2026/05/13-20:38:21.070750 7ff663fff6c0 Delete type=0 #119
|
||||
2026/05/13-21:29:49.386758 7ff6637fe6c0 Level-0 table #124: started
|
||||
2026/05/13-21:29:49.386971 7ff6637fe6c0 Level-0 table #124: 0 bytes OK
|
||||
2026/05/13-21:29:49.393349 7ff6637fe6c0 Delete type=0 #122
|
||||
2026/05/13-21:29:49.393700 7ff6637fe6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
2025/07/14-10:18:19.404246 7f3fa5ffb6c0 Recovering log #25
|
||||
2025/07/14-10:18:19.435628 7f3fa5ffb6c0 Delete type=3 #23
|
||||
2025/07/14-10:18:19.435765 7f3fa5ffb6c0 Delete type=0 #25
|
||||
2025/07/14-20:48:52.908846 7f3fa57fa6c0 Level-0 table #30: started
|
||||
2025/07/14-20:48:52.908888 7f3fa57fa6c0 Level-0 table #30: 0 bytes OK
|
||||
2025/07/14-20:48:52.915004 7f3fa57fa6c0 Delete type=0 #28
|
||||
2025/07/14-20:48:52.915133 7f3fa57fa6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
|
||||
2025/07/14-20:48:52.928670 7f3fa57fa6c0 Manual compaction at level-1 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
|
||||
2026/05/13-13:05:01.411955 7ff671fef6c0 Recovering log #115
|
||||
2026/05/13-13:05:01.463033 7ff671fef6c0 Delete type=3 #113
|
||||
2026/05/13-13:05:01.463084 7ff671fef6c0 Delete type=0 #115
|
||||
2026/05/13-13:57:22.809226 7ff6637fe6c0 Level-0 table #120: started
|
||||
2026/05/13-13:57:22.809250 7ff6637fe6c0 Level-0 table #120: 0 bytes OK
|
||||
2026/05/13-13:57:22.815441 7ff6637fe6c0 Delete type=0 #118
|
||||
2026/05/13-13:57:22.821701 7ff6637fe6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+27
-4
@@ -111,8 +111,8 @@ i.fvtt-cthulhu-eternal {
|
||||
padding: 2px 2px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
min-width: 6.0rem;
|
||||
max-width: 6.0rem;
|
||||
min-width: 6rem;
|
||||
max-width: 6rem;
|
||||
}
|
||||
}
|
||||
.san-loose-buttons {
|
||||
@@ -128,8 +128,8 @@ i.fvtt-cthulhu-eternal {
|
||||
padding: 2px 2px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
min-width: 3.0rem;
|
||||
max-width: 3.0rem;
|
||||
min-width: 3rem;
|
||||
max-width: 3rem;
|
||||
}
|
||||
}
|
||||
.result-non-lethal {
|
||||
@@ -141,5 +141,28 @@ i.fvtt-cthulhu-eternal {
|
||||
font-family: var(--font-primary);
|
||||
font-size: calc(var(--font-size-standard) * 1.02);
|
||||
}
|
||||
.combatants-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 4px;
|
||||
margin: 8px 0;
|
||||
button.apply-wounds-btn {
|
||||
font-family: var(--font-primary);
|
||||
font-size: calc(var(--font-size-standard) * 0.7);
|
||||
border: 1px solid #4b4a44;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background-color: #f0f0e0;
|
||||
color: #1c1c1c;
|
||||
&:hover {
|
||||
background-color: #d5d5c5;
|
||||
border-color: #2d2d2a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+229
-17
@@ -53,6 +53,10 @@
|
||||
color: var(--color-dark-1);
|
||||
}
|
||||
|
||||
.li-apply-wounds {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.dice-roll {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -70,7 +74,7 @@
|
||||
border: 0px;
|
||||
}
|
||||
.intro-chat {
|
||||
color:var(--color-dark-1);
|
||||
color: var(--color-dark-1);
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -91,22 +95,7 @@
|
||||
li {
|
||||
margin: 0 10px;
|
||||
font-family: var(--font-primary);
|
||||
font-size: calc(var(--font-size-standard) * 1.0);
|
||||
}
|
||||
.nudge-roll {
|
||||
font-size: calc(var(--font-size-standard) * 1.0);
|
||||
margin-left: 2rem;
|
||||
display: none;
|
||||
}
|
||||
.healing-roll {
|
||||
font-size: calc(var(--font-size-standard) * 1.0);
|
||||
margin-left: 2rem;
|
||||
display: none;
|
||||
}
|
||||
.roll-damage {
|
||||
font-size: calc(var(--font-size-standard) * 1.0);
|
||||
margin-left: 2rem;
|
||||
display: none;
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
}
|
||||
.result-success {
|
||||
color: var(--color-success);
|
||||
@@ -145,4 +134,227 @@
|
||||
font-size: calc(var(--font-size-standard) * 1.2);
|
||||
text-shadow: 0 0 10px var(--color-shadow-primary);
|
||||
}
|
||||
|
||||
.chat-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
border-top: 1px solid var(--color-border-light-primary);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0 0 5px 5px;
|
||||
justify-content: center;
|
||||
|
||||
.chat-action-button {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: var(--color-dark-6);
|
||||
color: var(--color-light-1);
|
||||
border: 1px solid var(--color-border-light-primary);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-dark-5);
|
||||
border-color: var(--color-border-dark);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1rem !important;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nudge-roll,
|
||||
.damage-roll,
|
||||
.healing-roll,
|
||||
.opposed-roll {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.opposed-roll-result {
|
||||
padding: 0.5rem;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 5px;
|
||||
font-family: var(--font-primary);
|
||||
|
||||
.opposed-header {
|
||||
text-align: center;
|
||||
margin-bottom: 0.3rem;
|
||||
padding-bottom: 0.2rem;
|
||||
border-bottom: 1px solid var(--color-border-light-primary);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-family: var(--font-title);
|
||||
font-size: calc(var(--font-size-standard) * 1);
|
||||
color: var(--color-dark-1);
|
||||
}
|
||||
}
|
||||
|
||||
.opposed-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.opposed-winner,
|
||||
.opposed-loser {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.3rem 0.4rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.opposed-winner {
|
||||
background: rgba(34, 139, 34, 0.1);
|
||||
border: 2px solid var(--color-success);
|
||||
}
|
||||
|
||||
.opposed-loser {
|
||||
background: rgba(220, 20, 60, 0.1);
|
||||
border: 2px solid var(--color-failure);
|
||||
}
|
||||
|
||||
.character-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--color-border-light-primary);
|
||||
}
|
||||
|
||||
.character-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
|
||||
strong {
|
||||
font-size: calc(var(--font-size-standard) * 0.75);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-dark-2);
|
||||
}
|
||||
|
||||
.winner-name {
|
||||
font-size: calc(var(--font-size-standard) * 0.95);
|
||||
font-weight: bold;
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.loser-name {
|
||||
font-size: calc(var(--font-size-standard) * 0.95);
|
||||
font-weight: bold;
|
||||
color: var(--color-failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roll-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
|
||||
.roll-value {
|
||||
font-size: calc(var(--font-size-standard) * 1.2);
|
||||
font-weight: bold;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.critical-badge {
|
||||
padding: 0.15rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
font-size: calc(var(--font-size-standard) * 0.7);
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
background: var(--color-critical-success);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.winner-result .roll-value {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.loser-result .roll-value {
|
||||
color: var(--color-failure);
|
||||
}
|
||||
|
||||
.versus-separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0;
|
||||
font-size: calc(var(--font-size-standard) * 0.85);
|
||||
font-weight: bold;
|
||||
color: var(--color-dark-2);
|
||||
|
||||
i {
|
||||
font-size: calc(var(--font-size-standard) * 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.chat-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.3rem;
|
||||
border-top: 1px solid var(--color-border-light-primary);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0 0 5px 5px;
|
||||
|
||||
.chat-action-button {
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: var(--color-dark-6);
|
||||
color: var(--color-light-1);
|
||||
border: 1px solid var(--color-border-light-primary);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-dark-5);
|
||||
border-color: var(--color-border-dark);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1rem !important;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
"download": "#{DOWNLOAD}#",
|
||||
"url": "https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal",
|
||||
"license": "LICENSE",
|
||||
"version": "13.0.1",
|
||||
"version": "13.0.2",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Uberwald",
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
"verified": "13"
|
||||
"verified": "14"
|
||||
},
|
||||
"esmodules": [
|
||||
"cthulhu-eternal.mjs"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
{{formField systemFields.settings value=system.settings localize=true}}
|
||||
{{formField systemFields.protection value=system.protection}}
|
||||
{{formField systemFields.resourceLevel value=system.resourceLevel}}
|
||||
{{formField systemFields.equipped value=system.equipped}}
|
||||
</fieldset>
|
||||
|
||||
|
||||
|
||||
@@ -21,12 +21,30 @@
|
||||
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}: {{ammoUsed}} / {{weapon.system.ammo.value}}</li>
|
||||
{{/if}}
|
||||
|
||||
<li class="li-apply-wounds">
|
||||
<div>{{localize "CTHULHUETERNAL.Label.applyWounds"}}</div>
|
||||
<div class="combatants-grid">
|
||||
{{#each combatants}}
|
||||
<button
|
||||
class="apply-wounds-btn chat-action-button"
|
||||
data-combatant-id="{{this.id}}"
|
||||
data-actor-id="{{this.actorId}}"
|
||||
data-token-id="{{this.tokenId}}"
|
||||
title="{{this.name}}"
|
||||
>
|
||||
{{this.name}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{{#if isLethal}}
|
||||
<li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityLethal"}}</li>
|
||||
<li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityWounded"}}</li>
|
||||
{{else}}
|
||||
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.lethalityNotLethal"}}</li>
|
||||
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.lethalityNotWounded"}}: <strong>{{wounds}}</strong></li>
|
||||
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.lethalityNotWounded"}}: <strong>{{wounds}}</strong>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
</ul>
|
||||
|
||||
+184
-53
@@ -6,20 +6,28 @@
|
||||
</div>
|
||||
<div class="intro-right">
|
||||
<ul>
|
||||
<li><strong>{{actingCharName}}</strong></li>
|
||||
{{#if (eq rollType "char")}}
|
||||
<li><strong>{{localize "CTHULHUETERNAL.Label.charRoll"}}</strong></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq rollType "skill")}}
|
||||
<li><strong>{{localize "CTHULHUETERNAL.Label.skillRoll"}}</strong></li>
|
||||
<li><strong>{{localize
|
||||
"CTHULHUETERNAL.Label.skillRoll"
|
||||
}}</strong></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if isNudgedRoll}}
|
||||
<li><strong>{{localize "CTHULHUETERNAL.Label.nudgedRoll"}} : {{wpCost}} {{localize "CTHULHUETERNAL.Label.WPSpent"}}</strong></li>
|
||||
<li><strong>{{localize "CTHULHUETERNAL.Label.nudgedRoll"}}
|
||||
:
|
||||
{{wpCost}}
|
||||
{{localize "CTHULHUETERNAL.Label.WPSpent"}}</strong></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if weapon}}
|
||||
<li><strong>{{localize "CTHULHUETERNAL.Label.Weapon"}} : {{weapon.name}}</strong></li>
|
||||
<li><strong>{{localize "CTHULHUETERNAL.Label.Weapon"}}
|
||||
:
|
||||
{{weapon.name}}</strong></li>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq rollType "resource")}}
|
||||
@@ -29,95 +37,108 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if isZeroWP}}
|
||||
<li class="red-warning">{{localize "CTHULHUETERNAL.Label.ZeroWP"}}</li>
|
||||
<li class="red-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.ZeroWP"
|
||||
}}</li>
|
||||
{{else}}
|
||||
{{#if isLowWP}}
|
||||
<li class="orange-warning">{{localize "CTHULHUETERNAL.Label.LowWP"}} : -20%</li>
|
||||
<li class="orange-warning">{{localize "CTHULHUETERNAL.Label.LowWP"}}
|
||||
: -20%</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if isExhausted}}
|
||||
<li class="orange-warning">{{localize "CTHULHUETERNAL.Label.Exhausted"}} : -20%</li>
|
||||
<li class="orange-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.Exhausted"
|
||||
}}
|
||||
: -20%</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if isWorn}}
|
||||
<li class="orange-warning"><i
|
||||
class="fa-solid fa-triangle-exclamation"
|
||||
></i>
|
||||
{{localize "CTHULHUETERNAL.Label.wornWeaponWarning"}}</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq rollType "resource")}}
|
||||
<li>{{localize "CTHULHUETERNAL.Label.multiplier"}} : {{multiplier}}</li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.multiplier"}}
|
||||
:
|
||||
{{multiplier}}</li>
|
||||
{{else}}
|
||||
<li>{{localize "CTHULHUETERNAL.Label.modifier"}} : {{totalModifier}}%</li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.modifier"}}
|
||||
:
|
||||
{{totalModifier}}%</li>
|
||||
{{/if}}
|
||||
|
||||
<li>{{localize "CTHULHUETERNAL.Label.targetScore"}} : {{targetScore}}%</li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.targetScore"}}
|
||||
:
|
||||
{{targetScore}}%</li>
|
||||
|
||||
{{#if isSuccess}}
|
||||
{{#if isCritical}}
|
||||
<li class="result-critical-success">{{localize "CTHULHUETERNAL.Label.criticalSuccess"}}
|
||||
<li class="result-critical-success">{{localize
|
||||
"CTHULHUETERNAL.Label.criticalSuccess"
|
||||
}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="result-success">
|
||||
{{localize "CTHULHUETERNAL.Label.success"}}
|
||||
{{#if isNudge}}
|
||||
<a class="nudge-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollNudge"}}"><i class="fa-solid fa-circle-sort-down"></i></a>
|
||||
{{/if}}
|
||||
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (eq rollType "weapon")}}
|
||||
<li>
|
||||
{{#if (eq weapon.system.weaponType "rangedfirearm")}}
|
||||
{{#if weapon.system.hasDamageDistance}}
|
||||
{{#each weapon.system.damageDistance as |damageDistance|}}
|
||||
{{#if (gt damageDistance.distance 0)}}
|
||||
<a class="damage-roll" data-item-id="{{weapon.id}}" data-action="roll" data-roll-type="damage"
|
||||
data-roll-value="{{damageDistance.damage}}" >
|
||||
<i class="fa-solid fa-gun"></i>
|
||||
<span class="damage-distance">{{damageDistance.distance}}:{{damageDistance.damage}} </span>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<a class="damage-roll" data-roll-value="{{weapon.system.damage}}" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollDamage"}}"><i class="fa-solid fa-gun"></i></a>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<a class="damage-roll" data-roll-value="{{weapon.system.damage}}" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollDamage"}}"><i class="fa-solid fa-sword"></i></a>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (eq rollType "skill") }}
|
||||
{{#if rollItem.system.isHealing}}
|
||||
<li>
|
||||
<a class="healing-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollHealing"}}"><i class="fa-solid fa-heart"></i></a>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</li>
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{#if isFailure}}
|
||||
{{#if isCritical}}
|
||||
<li class="result-critical-failure">{{localize "CTHULHUETERNAL.Label.criticalFailure"}}
|
||||
<li class="result-critical-failure">{{localize
|
||||
"CTHULHUETERNAL.Label.criticalFailure"
|
||||
}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="result-failure">
|
||||
{{localize "CTHULHUETERNAL.Label.failure"}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if isNudge}}
|
||||
<li>
|
||||
<a class="nudge-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollNudge"}}"><i class="fa-solid fa-circle-sort-down"></i></a>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
||||
<!-- {{#if isAttackRoll}}
|
||||
{{#if defenseRoll}}
|
||||
<li>{{localize "CTHULHUETERNAL.Label.defenseRoll"}}</li>
|
||||
{{#if attackSuccess}}
|
||||
<li class="result-success">{{localize
|
||||
"CTHULHUETERNAL.Label.attackSuccess"
|
||||
}}</li>
|
||||
{{else}}
|
||||
<li class="result-failure">{{localize
|
||||
"CTHULHUETERNAL.Label.attackFailure"
|
||||
}}</li>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if targetId}}
|
||||
<li class="orange-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.noDefenseRoll"
|
||||
}}</li>
|
||||
{{else}}
|
||||
<li class="orange-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.noTarget"
|
||||
}}</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}} -->
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if isDamage}}
|
||||
<div>
|
||||
{{#if (and isGM hasTarget)}}
|
||||
{{{localize "CTHULHUETERNAL.Roll.displayArmor" targetName=targetName targetArmor=targetArmor
|
||||
realDamage=realDamage}}}
|
||||
{{{localize
|
||||
"CTHULHUETERNAL.Roll.displayArmor"
|
||||
targetName=targetName
|
||||
targetArmor=targetArmor
|
||||
realDamage=realDamage
|
||||
}}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -128,4 +149,114 @@
|
||||
{{{tooltip}}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{#if skillMarkedForProgress}}
|
||||
<p class="skill-progress-notice">
|
||||
<i class="fa-solid fa-arrow-up-right-dots"></i>
|
||||
{{rollItem.name}} — {{localize "CTHULHUETERNAL.Label.skillFailed"}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{! Zone d'actions regroupées }}
|
||||
<div class="chat-actions">
|
||||
{{#if isSuccess}}
|
||||
{{#if isNudge}}
|
||||
<a
|
||||
class="nudge-roll chat-action-button"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.rollNudge'}}"
|
||||
>
|
||||
<i class="fa-solid fa-circle-sort-down"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq rollType "weapon")}}
|
||||
{{#if (eq weapon.system.weaponType "rangedfirearm")}}
|
||||
{{#if weapon.system.hasDamageDistance}}
|
||||
{{#each weapon.system.damageDistance as |damageDistance|}}
|
||||
{{#if (gt damageDistance.distance 0)}}
|
||||
<a
|
||||
class="damage-roll chat-action-button"
|
||||
data-item-id="{{weapon.id}}"
|
||||
data-action="roll"
|
||||
data-roll-type="damage"
|
||||
data-roll-value="{{damageDistance.damage}}"
|
||||
data-tooltip="{{localize
|
||||
'CTHULHUETERNAL.Label.rollDamage'
|
||||
}} ({{damageDistance.distance}}m : {{damageDistance.damage}})"
|
||||
>
|
||||
<i class="fa-solid fa-gun"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<a
|
||||
class="damage-roll chat-action-button"
|
||||
data-roll-value="{{weapon.system.damage}}"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.rollDamage'}}"
|
||||
>
|
||||
<i class="fa-solid fa-gun"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<a
|
||||
class="damage-roll chat-action-button"
|
||||
data-roll-value="{{weapon.system.damage}}"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.rollDamage'}}"
|
||||
>
|
||||
<i class="fa-solid fa-sword"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq rollType "skill")}}
|
||||
{{#if rollItem.system.isHealing}}
|
||||
<a
|
||||
class="healing-roll chat-action-button"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.rollHealing'}}"
|
||||
>
|
||||
<i class="fa-solid fa-heart"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq rollType "weapon")}}
|
||||
{{#if isWorn}}
|
||||
<a
|
||||
class="worn-weapon-check chat-action-button"
|
||||
data-weapon-id="{{weapon.id}}"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.wornWeaponCheck'}}"
|
||||
>
|
||||
<i class="fa-solid fa-dice"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if isFailure}}
|
||||
{{#if isNudge}}
|
||||
<a
|
||||
class="nudge-roll chat-action-button"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.rollNudge'}}"
|
||||
>
|
||||
<i class="fa-solid fa-circle-sort-down"></i>
|
||||
</a>
|
||||
{{#if wpCostToSucceed}}
|
||||
<a
|
||||
class="nudge-to-success chat-action-button"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Roll.nudgeToSuccess' score=targetScore wp=wpCostToSucceed}}"
|
||||
>
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
{{localize "CTHULHUETERNAL.Roll.nudgeToSuccess" score=targetScore wp=wpCostToSucceed}}
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<a
|
||||
class="opposed-roll chat-action-button"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.opposedRoll'}}"
|
||||
>
|
||||
<i class="fa-duotone fa-light fa-arrows-to-line"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,64 @@
|
||||
{{! Template for opposed roll result }}
|
||||
<div class="cthulhu-eternal-roll opposed-roll-result">
|
||||
<div class="opposed-header">
|
||||
<h3>{{localize "CTHULHUETERNAL.Label.opposedRollResult"}}</h3>
|
||||
</div>
|
||||
|
||||
<div class="opposed-content">
|
||||
<div class="opposed-winner">
|
||||
<div class="character-info">
|
||||
<img src="{{winner.actor.img}}" alt="{{winner.actor.name}}" />
|
||||
<div class="character-name">
|
||||
<strong>{{localize "CTHULHUETERNAL.Label.opposedRollWinner"}}</strong>
|
||||
<span class="winner-name">{{winner.actor.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="roll-result winner-result">
|
||||
<span class="roll-value">{{winner.rollData.rollResult}}</span>
|
||||
{{#if winner.rollData.isCritical}}
|
||||
<span class="critical-badge">{{localize
|
||||
"CTHULHUETERNAL.Label.critical"
|
||||
}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="versus-separator">
|
||||
<i class="fas fa-swords"></i>
|
||||
<span>{{localize "CTHULHUETERNAL.Label.defeats"}}</span>
|
||||
</div>
|
||||
|
||||
<div class="opposed-loser">
|
||||
<div class="character-info">
|
||||
<img src="{{loser.actor.img}}" alt="{{loser.actor.name}}" />
|
||||
<div class="character-name">
|
||||
<span class="loser-name">{{loser.actor.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="roll-result loser-result">
|
||||
<span class="roll-value">{{loser.rollData.rollResult}}</span>
|
||||
{{#if loser.rollData.isCritical}}
|
||||
<span class="critical-badge">{{localize
|
||||
"CTHULHUETERNAL.Label.critical"
|
||||
}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if canApplyDamage}}
|
||||
<div class="chat-actions">
|
||||
<a
|
||||
class="damage-roll chat-action-button"
|
||||
data-roll-value="{{winner.rollData.weapon.system.damage}}"
|
||||
data-tooltip="{{localize 'CTHULHUETERNAL.Label.rollDamage'}}"
|
||||
>
|
||||
{{#if (eq winner.rollData.weapon.system.weaponType "rangedfirearm")}}
|
||||
<i class="fa-solid fa-gun"></i>
|
||||
{{else}}
|
||||
<i class="fa-solid fa-sword"></i>
|
||||
{{/if}}
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -1,22 +1,61 @@
|
||||
<div class="{{cssClass}}">
|
||||
<div class="chat-lethal-damage">
|
||||
<ul>
|
||||
<li><strong>{{weapon.name}} : {{localize "CTHULHUETERNAL.Label.damageRoll"}}</strong></li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.result"}} :{{rollResult}} ({{formula}})</li>
|
||||
<li><strong>{{weapon.name}}
|
||||
:
|
||||
{{localize "CTHULHUETERNAL.Label.damageRoll"}}</strong></li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.result"}}
|
||||
:{{rollResult}}
|
||||
({{formula}})</li>
|
||||
|
||||
{{#if (gt weapon.system.killRadius 0)}}
|
||||
<li>{{localize "CTHULHUETERNAL.Label.killRadius"}} : {{weapon.system.killRadius}} {{weapon.system.rangeUnit}}</li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.killRadius"}}
|
||||
:
|
||||
{{weapon.system.killRadius}}
|
||||
{{weapon.system.rangeUnit}}</li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.killRadiusInfo"}}</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if (gt weapon.system.armorPiercing 0)}}
|
||||
<li>{{localize "CTHULHUETERNAL.Label.armorPiercing"}} : {{weapon.system.armorPiercing}}</li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.armorPiercing"}}
|
||||
:
|
||||
{{weapon.system.armorPiercing}}</li>
|
||||
{{/if}}
|
||||
{{#if (gt weapon.system.penetration 0)}}
|
||||
<li>{{localize "CTHULHUETERNAL.Label.penetration"}}
|
||||
:
|
||||
{{weapon.system.penetration}}</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if ammoUsed}}
|
||||
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}: {{ammoUsed}} / {{weapon.system.ammo.value}}</li>
|
||||
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}:
|
||||
{{ammoUsed}}
|
||||
/
|
||||
{{weapon.system.ammo.value}}</li>
|
||||
{{/if}}
|
||||
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.damageMessage"}}: <strong>{{rollResult}}</strong></li>
|
||||
|
||||
<li class="li-apply-wounds">
|
||||
<div>{{localize "CTHULHUETERNAL.Label.applyWounds"}}</div>
|
||||
<div class="combatants-grid">
|
||||
{{#each combatants}}
|
||||
<button
|
||||
class="apply-wounds-btn chat-action-button"
|
||||
data-combatant-id="{{this.id}}"
|
||||
data-actor-id="{{this.actorId}}"
|
||||
data-token-id="{{this.tokenId}}"
|
||||
title="{{this.name}}"
|
||||
>
|
||||
{{this.name}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="result-non-lethal">{{localize
|
||||
"CTHULHUETERNAL.Label.damageMessage"
|
||||
}}:
|
||||
<strong>{{rollResult}}</strong>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
{{/if}}
|
||||
|
||||
<li class="san-loose-buttons">
|
||||
<button class="san-loose" data-san-value="0">0</button>
|
||||
<button class="san-loose" data-san-value="1">1</button>
|
||||
<button class="san-loose" data-san-value="2">2</button>
|
||||
<button class="san-loose" data-san-value="3">3</button>
|
||||
<button class="san-loose" data-san-value="4">4</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="0">0</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="1">1</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="2">2</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="3">3</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="4">4</button>
|
||||
</li>
|
||||
|
||||
<li class="san-loose-buttons">
|
||||
<button class="san-loose" data-san-value="1d4">1d4</button>
|
||||
<button class="san-loose" data-san-value="1d6">1d6</button>
|
||||
<button class="san-loose" data-san-value="1d8">1d8</button>
|
||||
<button class="san-loose" data-san-value="1d10">1d10</button>
|
||||
<button class="san-loose" data-san-value="1d12">1d12</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="1d4">1d4</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="1d6">1d6</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="1d8">1d8</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="1d10">1d10</button>
|
||||
<button class="san-loose chat-action-button" data-san-value="1d12">1d12</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<li><strong>{{localize "CTHULHUETERNAL.Label.selectSANType"}}</strong></li>
|
||||
|
||||
<li class="san-type-buttons">
|
||||
<button class="san-type" data-san-value="{{sanLoss}}" data-san-type="violence">{{localize "CTHULHUETERNAL.Label.Violence"}}</button>
|
||||
<button class="san-type" data-san-value="{{sanLoss}}" data-san-type="helplessness">{{localize "CTHULHUETERNAL.Label.Helplessness"}}</button>
|
||||
<button class="san-type chat-action-button" data-san-value="{{sanLoss}}" data-san-type="violence">{{localize "CTHULHUETERNAL.Label.Violence"}}</button>
|
||||
<button class="san-type chat-action-button" data-san-value="{{sanLoss}}" data-san-type="helplessness">{{localize "CTHULHUETERNAL.Label.Helplessness"}}</button>
|
||||
</li>
|
||||
<li class="san-type-buttons">
|
||||
<button class="san-type" data-san-value="{{sanLoss}}" data-san-type="unnatural">{{localize "CTHULHUETERNAL.Label.Unnatural"}}</button>
|
||||
<button class="san-type" data-san-value="{{sanLoss}}" data-san-type="none">{{localize "CTHULHUETERNAL.Label.None"}}</button>
|
||||
<button class="san-type chat-action-button" data-san-value="{{sanLoss}}" data-san-type="unnatural">{{localize "CTHULHUETERNAL.Label.Unnatural"}}</button>
|
||||
<button class="san-type chat-action-button" data-san-value="{{sanLoss}}" data-san-type="none">{{localize "CTHULHUETERNAL.Label.None"}}</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<fieldset>
|
||||
<legend>{{localize "CTHULHUETERNAL.Label.description"}}</legend>
|
||||
{{formInput systemFields.description enriched=enrichedDescriptionw value=system.description name="system.description" toggled=true}}
|
||||
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
||||
</fieldset>
|
||||
|
||||
</section>
|
||||
@@ -9,7 +9,7 @@
|
||||
{{#each weapons as |item|}}
|
||||
{{!log 'weapon' this}}
|
||||
<div class="weapon item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
|
||||
|
||||
<!-- content -->
|
||||
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
|
||||
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
|
||||
<div class="name rollable" data-roll-type="weapon" data-tooltip="{{{item.system.description}}}">
|
||||
@@ -81,6 +81,8 @@
|
||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noWeapons"}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -99,6 +101,13 @@
|
||||
{{item.name}}
|
||||
</div>
|
||||
<span class="protection">{{localize "CTHULHUETERNAL.Label.armor"}} : {{item.system.protection}}</span>
|
||||
<a class="equipped-toggle {{#if item.system.equipped}}active{{/if}}"
|
||||
data-action="toggleEquipped"
|
||||
data-item-id="{{item.id}}"
|
||||
data-item-uuid="{{item.uuid}}"
|
||||
data-tooltip="{{#if item.system.equipped}}{{localize 'CTHULHUETERNAL.Label.equipped'}}{{else}}{{localize 'CTHULHUETERNAL.Label.unequipped'}}{{/if}}">
|
||||
<i class="fas {{#if item.system.equipped}}fa-shield-halved{{else}}fa-shield{{/if}}"></i>
|
||||
</a>
|
||||
<div class="controls">
|
||||
<a data-tooltip="{{localize 'CTHULHUETERNAL.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
|
||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||
@@ -106,6 +115,8 @@
|
||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noArmors"}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -130,6 +141,8 @@
|
||||
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noGears"}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<a data-tooltip="{{localize 'CTHULHUETERNAL.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noSkills"}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
+121
-20
@@ -9,25 +9,72 @@
|
||||
|
||||
{{#if (eq rollType "resource")}}
|
||||
<legend>{{localize "CTHULHUETERNAL.Label.resourceRating"}}</legend>
|
||||
<div class="dialog-skill">{{rollItem.name}} : <span class="resource-score">{{initialScore}} ({{mul initialScore 5}}%)</span></div>
|
||||
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Hand"}} : {{rollItem.hand}} <input type="checkbox" data-action="selectHand" {{checked rollItem.enableHand}}></div>
|
||||
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Stowed"}} : {{rollItem.stowed}} <input type="checkbox" data-action="selectStowed" {{checked rollItem.enableStowed}}></div>
|
||||
<div class="dialog-skill">{{rollItem.name}}
|
||||
:
|
||||
<span class="resource-score">{{initialScore}}
|
||||
({{mul initialScore 5}}%)</span></div>
|
||||
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Hand"}}
|
||||
:
|
||||
{{rollItem.hand}}
|
||||
<input
|
||||
type="checkbox"
|
||||
data-action="selectHand"
|
||||
{{checked rollItem.enableHand}}
|
||||
/></div>
|
||||
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Stowed"}}
|
||||
:
|
||||
{{rollItem.stowed}}
|
||||
<input
|
||||
type="checkbox"
|
||||
data-action="selectStowed"
|
||||
{{checked rollItem.enableStowed}}
|
||||
/></div>
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.Storage"}} : {{rollItem.storage}}
|
||||
<input type="checkbox" data-action="selectStorage" {{checked rollItem.enableStorage}}>
|
||||
{{localize "CTHULHUETERNAL.Label.Storage"}}
|
||||
:
|
||||
{{rollItem.storage}}
|
||||
<input
|
||||
type="checkbox"
|
||||
data-action="selectStorage"
|
||||
{{checked rollItem.enableStorage}}
|
||||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="dialog-skill">{{rollItem.name}} : {{initialScore}}%</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if weapon}}
|
||||
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Weapon"}} : {{weapon.name}}</div>
|
||||
{{#if isJunk}}
|
||||
<div class="dialog-skill red-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.JunkWeapon"
|
||||
}}</div>
|
||||
{{/if}}
|
||||
{{#if isWorn}}
|
||||
<div class="dialog-skill orange-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.WornWeapon"
|
||||
}}</div>
|
||||
{{/if}}
|
||||
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Weapon"}}
|
||||
:
|
||||
{{weapon.name}}</div>
|
||||
|
||||
{{#if targetName}}
|
||||
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Target"}}
|
||||
:
|
||||
{{targetName}}</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq weapon.system.weaponType "melee")}}
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.targetMove"}}
|
||||
<select name="meleeTargetMoveChoice" class="roll-skill-modifier">
|
||||
{{selectOptions choiceMeleeTargetMove localize=true selected=meleeTargetMoveChoice valueAttr="id" labelAttr="label"}}
|
||||
{{selectOptions
|
||||
choiceMeleeTargetMove
|
||||
localize=true
|
||||
selected=meleeTargetMoveChoice
|
||||
valueAttr="id"
|
||||
labelAttr="label"
|
||||
}}
|
||||
</select>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -35,24 +82,44 @@
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.rangedRange"}}
|
||||
<select name="rangedRangeChoice" class="roll-skill-modifier">
|
||||
{{selectOptions choiceRangedRange localize=true selected=rangedRangeChoice valueAttr="id" labelAttr="label"}}
|
||||
{{selectOptions
|
||||
choiceRangedRange
|
||||
localize=true
|
||||
selected=rangedRangeChoice
|
||||
valueAttr="id"
|
||||
labelAttr="label"
|
||||
}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.targetMove"}}
|
||||
<select name="rangedTargetMoveChoice" class="roll-skill-modifier">
|
||||
{{selectOptions choiceRangedTargetMove localize=true selected=rangedTargetMoveChoice valueAttr="id" labelAttr="label"}}
|
||||
{{selectOptions
|
||||
choiceRangedTargetMove
|
||||
localize=true
|
||||
selected=rangedTargetMoveChoice
|
||||
valueAttr="id"
|
||||
labelAttr="label"
|
||||
}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.aimingLastRound"}}
|
||||
<input type="checkbox" name="aimingLastRound">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="aimingLastRound"
|
||||
name="aimingLastRound"
|
||||
/>
|
||||
</div>
|
||||
{{#if weapon.system.hasSight}}
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.aimingWithSight"}}
|
||||
<input type="checkbox" name="aimingWithSight">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="aimingWithSight"
|
||||
name="aimingWithSight"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -61,26 +128,50 @@
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.visibility"}}
|
||||
<select name="visibilityChoice" class="roll-skill-modifier">
|
||||
{{selectOptions choiceVisibility localize=true selected=visibilityChoice valueAttr="id" labelAttr="label"}}
|
||||
{{selectOptions
|
||||
choiceVisibility
|
||||
localize=true
|
||||
selected=visibilityChoice
|
||||
valueAttr="id"
|
||||
labelAttr="label"
|
||||
}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.attackerState"}}
|
||||
<select name="attackerStateChoice" class="roll-skill-modifier">
|
||||
{{selectOptions choiceAttackerState localize=true selected=attackerStateChoice valueAttr="id" labelAttr="label"}}
|
||||
{{selectOptions
|
||||
choiceAttackerState
|
||||
localize=true
|
||||
selected=attackerStateChoice
|
||||
valueAttr="id"
|
||||
labelAttr="label"
|
||||
}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="dialog-skill">
|
||||
{{localize "CTHULHUETERNAL.Label.targetSize"}}
|
||||
<select name="targetSizeChoice" class="roll-skill-modifier">
|
||||
{{selectOptions choiceTargetSize localize=true selected=targetSizeChoice valueAttr="id" labelAttr="label"}}
|
||||
{{selectOptions
|
||||
choiceTargetSize
|
||||
localize=true
|
||||
selected=targetSizeChoice
|
||||
valueAttr="id"
|
||||
labelAttr="label"
|
||||
}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{#if weapon.system.hasSelectiveFire}}
|
||||
<div class="dialog-skill">Selective Fire :
|
||||
<select name="selectiveFireChoice" class="roll-skill-modifier">
|
||||
{{selectOptions choiceSelectiveFire localize=true selected=selectiveFireChoice valueAttr="id" labelAttr="label"}}
|
||||
{{selectOptions
|
||||
choiceSelectiveFire
|
||||
localize=true
|
||||
selected=selectiveFireChoice
|
||||
valueAttr="id"
|
||||
labelAttr="label"
|
||||
}}
|
||||
</select>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -88,20 +179,27 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if isZeroWP}}
|
||||
<div class="dialog-skill red-warning">{{localize "CTHULHUETERNAL.Label.ZeroWP"}}</div>
|
||||
<div class="dialog-skill red-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.ZeroWP"
|
||||
}}</div>
|
||||
{{else}}
|
||||
{{#if isLowWP}}
|
||||
<div class="dialog-skill orange-warning">{{localize "CTHULHUETERNAL.Label.LowWP"}} : -20%</div>
|
||||
<div class="dialog-skill orange-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.LowWP"
|
||||
}}
|
||||
: -20%</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if isExhausted}}
|
||||
<div class="dialog-skill orange-warning">{{localize "CTHULHUETERNAL.Label.Exhausted"}} : -20%</div>
|
||||
<div class="dialog-skill orange-warning">{{localize
|
||||
"CTHULHUETERNAL.Label.Exhausted"
|
||||
}}
|
||||
: -20%</div>
|
||||
{{/if}}
|
||||
|
||||
</fieldSet>
|
||||
|
||||
|
||||
{{#if hasModifier}}
|
||||
<fieldSet class="dialog-modifier">
|
||||
<legend>{{localize "CTHULHUETERNAL.Label.modifier"}}</legend>
|
||||
@@ -114,7 +212,10 @@
|
||||
{{#if hasMultiplier}}
|
||||
<fieldSet class="dialog-modifier">
|
||||
<legend>{{localize "CTHULHUETERNAL.Label.multiplier"}}</legend>
|
||||
<select name="multiplier" class="roll-skill-modifier roll-skill-multiplier">
|
||||
<select
|
||||
name="multiplier"
|
||||
class="roll-skill-modifier roll-skill-multiplier"
|
||||
>
|
||||
{{selectOptions choiceMultiplier selected=multiplier}}
|
||||
</select>
|
||||
</fieldSet>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
|
||||
def check_utf8_encoding(filenames):
|
||||
"""
|
||||
Checks a list of filenames to detect potential UTF-8 encoding errors.
|
||||
|
||||
Args:
|
||||
filenames (list): A list of strings representing filenames to check.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary where keys are filenames with errors and values are the error messages.
|
||||
"""
|
||||
error_report = {}
|
||||
print("--- Starting UTF-8 Encoding Check ---")
|
||||
|
||||
for filename in filenames:
|
||||
try:
|
||||
# Attempt to decode the filename as UTF-8
|
||||
filename.encode('utf-8')
|
||||
# If encode succeeds, it's likely valid UTF-8
|
||||
print(f"SUCCESS: {filename}")
|
||||
except UnicodeEncodeError as e:
|
||||
# If encoding fails, it indicates invalid UTF-8 sequences
|
||||
error_report[filename] = str(e)
|
||||
print(f"ERROR: {filename} - Decoding failed: {e}")
|
||||
|
||||
print("--- Check Complete ---")
|
||||
if error_report:
|
||||
print("\n--- Summary of Errors ---")
|
||||
for filename, error in error_report.items():
|
||||
print(f"File: {filename}\n Error: {error}\n")
|
||||
else:
|
||||
print("\nAll checked filenames appear to be valid UTF-8.")
|
||||
|
||||
return error_report
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example list of filenames to check. Replace these with your actual directory contents.
|
||||
files_to_check = [
|
||||
"valid_file.txt",
|
||||
"file_with_bad_byte\x80.bin", # Example of a potentially bad filename
|
||||
"another_valid_one.md",
|
||||
"file_with_another_error\x99.dat"
|
||||
]
|
||||
|
||||
check_utf8_encoding(files_to_check)
|
||||
Reference in New Issue
Block a user