Compare commits

..

12 Commits

Author SHA1 Message Date
uberwald 709e69fac8 Fix progression roll
Release Creation / build (release) Successful in 1m33s
2026-04-29 09:34:16 +02:00
uberwald e26db56585 Fix for v14
Release Creation / build (release) Successful in 48s
2026-04-28 07:52:18 +02:00
uberwald bb6a6248f2 Foundryv14 migration
Release Creation / build (release) Successful in 46s
2026-04-01 22:57:43 +02:00
uberwald 7dc2492c96 COrrection sur bonus de force + bouton pour appliquer les dommages
Release Creation / build (release) Successful in 46s
2025-11-21 23:36:02 +01:00
uberwald 2c25820152 Combat/automation enhancements !
Release Creation / build (release) Successful in 58s
2025-11-13 13:59:02 +01:00
uberwald 6ad8226265 Attempt to add HUD core 2025-11-12 23:41:15 +01:00
uberwald 68a0d03740 Update fight options
Release Creation / build (release) Successful in 53s
2025-11-11 19:56:33 +01:00
uberwald 8d9cc1045c Fix licence 2025-10-01 15:14:58 +02:00
uberwald 4af277d8a2 Fight helper ! 2025-10-01 11:24:41 +02:00
uberwald bc9f397755 Fix initiative 2025-09-30 17:44:39 +02:00
uberwald 264a5c7a4c Fix HP and other derivated values not computed anymore
Release Creation / build (release) Successful in 2m57s
2025-08-10 19:33:28 +02:00
uberwald 4edbc9b618 Translate weapons + skills in columns
Release Creation / build (release) Successful in 3m43s
2025-07-29 18:29:02 +02:00
71 changed files with 2790 additions and 944 deletions
+1
View File
@@ -0,0 +1 @@
packs/** filter=lfs diff=lfs merge=lfs -text
+1 -1
View File
@@ -60,4 +60,4 @@ jobs:
manifest: 'https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal/releases/download/latest/system.json' 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' notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-cthulhu-eternal.zip'
compatibility-minimum: '13' compatibility-minimum: '13'
compatibility-verified: '13' compatibility-verified: '14'
+2
View File
@@ -5,4 +5,6 @@ styles/*.css
# Node Modules # Node Modules
node_modules/ node_modules/
.github/
.history/
+5
View File
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"CTHULHUETERNAL"
]
}
+6
View File
@@ -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"
},
"Smallcaliber pistol": {
"name": "Pistolet de petit calibre"
},
"Staff": {
"name": "Bâton"
},
"Steel-toe boot": {
"name": "Botte à embout en acier"
},
"Steeltoe 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"
}
}
}
+243 -15
View File
@@ -232,6 +232,32 @@ i.fvtt-cthulhu-eternal {
font-family: var(--font-primary); font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.02); 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 { .fvtt-cthulhu-eternal .protagonist-sheet-common label {
font-family: var(--font-secondary); font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1); font-size: calc(var(--font-size-standard) * 1);
@@ -651,6 +677,12 @@ i.fvtt-cthulhu-eternal {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 4px; 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 { .fvtt-cthulhu-eternal .tab.protagonist-skills .main-div .skills .skill {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -892,6 +924,20 @@ i.fvtt-cthulhu-eternal {
min-width: 1.8rem; min-width: 1.8rem;
max-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 { .fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .armors .armor .protection {
min-width: 5rem; min-width: 5rem;
max-width: 5rem; max-width: 5rem;
@@ -3041,6 +3087,9 @@ i.fvtt-cthulhu-eternal {
font-size: calc(var(--font-size-standard) * 2); font-size: calc(var(--font-size-standard) * 2);
color: var(--color-dark-1); color: var(--color-dark-1);
} }
.li-apply-wounds {
display: none;
}
.dice-roll { .dice-roll {
flex-direction: column; flex-direction: column;
} }
@@ -3084,21 +3133,6 @@ i.fvtt-cthulhu-eternal {
font-family: var(--font-primary); font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1); 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 { .dice-roll .intro-chat .intro-right ul .result-success {
color: var(--color-success); color: var(--color-success);
font-family: var(--font-title); font-family: var(--font-title);
@@ -3133,3 +3167,197 @@ i.fvtt-cthulhu-eternal {
font-size: calc(var(--font-size-standard) * 1.2); font-size: calc(var(--font-size-standard) * 1.2);
text-shadow: 0 0 10px var(--color-shadow-primary); text-shadow: 0 0 10px var(--color-shadow-primary);
} }
.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 {
display: none;
}
.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;
}
+67 -2
View File
@@ -14,6 +14,9 @@ import * as applications from "./module/applications/_module.mjs"
import { handleSocketEvent } from "./module/socket.mjs" import { handleSocketEvent } from "./module/socket.mjs"
import CthulhuEternalUtils from "./module/utils.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) } } } 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 () { Hooks.once("init", function () {
@@ -29,6 +32,12 @@ Hooks.once("init", function () {
models, models,
documents, documents,
} }
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "@characteristics.dex.value",
decimals: 1
};
CONFIG.Actor.documentClass = documents.CthulhuEternalActor CONFIG.Actor.documentClass = documents.CthulhuEternalActor
CONFIG.Actor.dataModels = { CONFIG.Actor.dataModels = {
@@ -93,13 +102,14 @@ Hooks.once("init", function () {
CthulhuEternalUtils.registerSettings() CthulhuEternalUtils.registerSettings()
CthulhuEternalUtils.registerHandlebarsHelpers() CthulhuEternalUtils.registerHandlebarsHelpers()
CthulhuEternalUtils.setupCSSRootVariables() CthulhuEternalUtils.setupCSSRootVariables()
CONFIG.debug.hooks = false;
console.info("CTHULHU ETERNAL | System Initialized") console.info("CTHULHU ETERNAL | System Initialized")
}) })
Hooks.once('babele.init', (babele) => { Hooks.once('babele.init', (babele) => {
babele.setSystemTranslationsDir("compendiums"); babele.setSystemTranslationsDir("compendiums");
CthulhuEternalUtils.registerBabeleTranslations(babele);
}); });
/** /**
@@ -135,9 +145,22 @@ 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) => { Hooks.on("renderChatMessageHTML", (message, html, data) => {
// Affichage des boutons de jet de dés uniquement pour les joueurs // 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) => { $(html).find(".nudge-roll").each((i, btn) => {
btn.style.display = "inline" btn.style.display = "inline"
}) })
@@ -147,6 +170,14 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
$(html).find(".healing-roll").each((i, btn) => { $(html).find(".healing-roll").each((i, btn) => {
btn.style.display = "inline" btn.style.display = "inline"
}) })
$(html).find(".worn-weapon-check").each((i, btn) => {
btn.style.display = "inline"
})
if (game.user.isGM) {
$(html).find(".opposed-roll").each((i, btn) => {
btn.style.display = "inline"
})
}
$(html).find(".nudge-roll").click((event) => { $(html).find(".nudge-roll").click((event) => {
CthulhuEternalUtils.nudgeRoll(message) CthulhuEternalUtils.nudgeRoll(message)
}) })
@@ -157,13 +188,47 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
$(html).find(".healing-roll").click((event) => { $(html).find(".healing-roll").click((event) => {
CthulhuEternalUtils.healingRoll(message) CthulhuEternalUtils.healingRoll(message)
}) })
$(html).find(".worn-weapon-check").click((event) => {
CthulhuEternalUtils.wornWeaponCheck(message)
})
$(html).find(".san-loose").click((event) => { $(html).find(".san-loose").click((event) => {
CthulhuEternalUtils.applySANLoss(message, event) CthulhuEternalUtils.applySANLoss(message, event)
}) })
$(html).find(".san-type").click((event) => { $(html).find(".san-type").click((event) => {
CthulhuEternalUtils.applySANType(message, event) CthulhuEternalUtils.applySANType(message, event)
}) })
$(html).find(".opposed-roll").click((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((event) => {
CthulhuEternalUtils.applyWounds(message, event)
})
$(html).find(".apply-wounds-btn").hover(
function (event) {
// Mouse enter - select the token
let combatantId = $(this).data("combatant-id")
if (combatantId && game.combat) {
let combatant = game.combat.combatants.get(combatantId)
if (combatant?.token) {
let token = canvas.tokens.get(combatant.token.id)
if (token) {
token.control({ releaseOthers: true })
}
}
}
},
function (event) {
// Mouse leave - release selection
canvas.tokens.releaseAll()
}
)
}
}) })
/** /**
+74 -4
View File
@@ -59,6 +59,12 @@
}, },
"hp": { "hp": {
"label": "HP", "label": "HP",
"value": {
"label": "Current"
},
"max": {
"label": "Max"
},
"stunned": { "stunned": {
"label": "Stun." "label": "Stun."
} }
@@ -126,6 +132,18 @@
"damageBonus": { "damageBonus": {
"label": "Dmg.Bonus" "label": "Dmg.Bonus"
}, },
"hp": {
"label": "HP",
"value": {
"label": "Current"
},
"max": {
"label": "Max"
},
"stunned": {
"label": "Stun."
}
},
"resources": { "resources": {
"permanentRating": { "permanentRating": {
"label": "Permanent Rating" "label": "Permanent Rating"
@@ -205,6 +223,7 @@
"Struggle": "Struggle" "Struggle": "Struggle"
}, },
"Skill": { "Skill": {
"DodgeName": "Dodge",
"Unnatural": "Unnatural", "Unnatural": "Unnatural",
"Melee": "Melee Weapons", "Melee": "Melee Weapons",
"Firearms": "Firearms", "Firearms": "Firearms",
@@ -552,10 +571,25 @@
} }
}, },
"Label": { "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).", "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.", "sanViolenceReset": "The violence SAN loss count has been reset.",
"sanHelplessnessReset": "The helplessness SAN loss count has been reset.", "sanHelplessnessReset": "The helplessness SAN loss count has been reset.",
"sanLoss": "You suffer a SAN loss", "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.", "selectSANType": "Select the type of SAN loss with the buttons below.",
"Violence" : "Violence", "Violence" : "Violence",
"Helplessness": "Helplessness", "Helplessness": "Helplessness",
@@ -574,6 +608,10 @@
"rollNudge": "Roll Nudge", "rollNudge": "Roll Nudge",
"rollDamage": "Roll Damage", "rollDamage": "Roll Damage",
"rollHealing": "Roll Healing", "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", "result": "Result",
"damageMessage": "Damage to apply", "damageMessage": "Damage to apply",
"lethalityRoll": "Lethality Roll", "lethalityRoll": "Lethality Roll",
@@ -585,6 +623,7 @@
"ZeroWP": "Zero WP : Automatic failure (ie 0%)", "ZeroWP": "Zero WP : Automatic failure (ie 0%)",
"LowWP": "Low WP", "LowWP": "Low WP",
"Exhausted": "Exhausted", "Exhausted": "Exhausted",
"wornWeaponWarning": "Worn weapon: Don't forget to make the luck roll after the attack!",
"creature": "Creature", "creature": "Creature",
"Rituals": "Rituals", "Rituals": "Rituals",
"Tomes": "Tomes", "Tomes": "Tomes",
@@ -695,6 +734,8 @@
"newMentalDisorder": "New Mental Disorder", "newMentalDisorder": "New Mental Disorder",
"newWeapon": "New Weapon", "newWeapon": "New Weapon",
"newArmor": "New Armor", "newArmor": "New Armor",
"equipped": "Equipped (click to unequip)",
"unequipped": "Not equipped (click to equip)",
"newInjury": "New Injury", "newInjury": "New Injury",
"newGear": "New Gear", "newGear": "New Gear",
"newArcane": "New Arcane", "newArcane": "New Arcane",
@@ -711,9 +752,17 @@
"skillProgress": "Skill Progress", "skillProgress": "Skill Progress",
"unconscious": "Unconscious", "unconscious": "Unconscious",
"dying": "Dying", "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.", "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", "Luck": "Luck",
"titleLuck": "Luck Roll", "titleLuck": "Luck Roll",
"healingRoll": "Healing Roll succes, the target gains", "healingRoll": "Healing Roll succes, the target gains",
@@ -730,7 +779,20 @@
"visibility": "Visibility", "visibility": "Visibility",
"rangedRange": "Range", "rangedRange": "Range",
"aimingLastRound": "Aiming Last Round (+20)", "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": { "ChatMessage": {
"exhausted": "Your protagonist is exhausted. He loses [[/r 1d6]] Willpower Points." "exhausted": "Your protagonist is exhausted. He loses [[/r 1d6]] Willpower Points."
@@ -755,17 +817,25 @@
"rollDamage": "Roll Damage" "rollDamage": "Roll Damage"
}, },
"Chat": { "Chat": {
"noArmor": "No armor absorbed damage.",
"armorAbsorbed": "Armor absorbed {armor} damage.",
"woundsApplied": "Wounds applied to {name}: {effectiveWounds} (armor absorbed : {armorText})"
}, },
"Notifications": { "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", "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", "NoWeaponType": "No weapon type found for this weapon subtype. Check Weapon definition or available skills/era",
"skillAlreadyExists": "Skill already exists", "skillAlreadyExists": "Skill already exists",
"WrongEra": "The era of the item does not match the ear of the system", "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.", "NoSelectiveFireChoices": "Not enough ammo fo Selective Fire choices for this weapon.",
"NoAmmo": "No more ammo for this weapon. ", "NoAmmo": "No more ammo for this weapon. ",
"WeaponBecameJunk": "Weapon {weapon} became junk!",
"noWeaponFound": "No weapon found.",
"noRollDataFound": "No roll data found", "noRollDataFound": "No roll data found",
"noActorFound": "No actor found for this item.", "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."
} }
} }
} }
+54 -6
View File
@@ -217,6 +217,7 @@
"Struggle": "Lutter" "Struggle": "Lutter"
}, },
"Skill": { "Skill": {
"DodgeName": "Esquive",
"Unnatural": "Inconcevable", "Unnatural": "Inconcevable",
"Melee": "Armes de mêlée", "Melee": "Armes de mêlée",
"Firearms": "Armes à feu", "Firearms": "Armes à feu",
@@ -564,6 +565,19 @@
} }
}, },
"Label": { "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).", "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é.", "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é.", "sanHelplessnessReset": "Le décompte des pertes de SAN d'impuissance a été réinitialisé.",
@@ -588,6 +602,10 @@
"rollNudge": "Modifier le jet", "rollNudge": "Modifier le jet",
"rollDamage": "Jet de dégâts", "rollDamage": "Jet de dégâts",
"rollHealing": "Jet de soin", "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", "result": "Resultat",
"damageMessage": "Dégâts à appliquer", "damageMessage": "Dégâts à appliquer",
"lethalityRoll": "Jet de Létalité", "lethalityRoll": "Jet de Létalité",
@@ -598,15 +616,14 @@
"Weapon": "Arme", "Weapon": "Arme",
"ZeroWP": "PVO à 0 : Echec automatique (ie 0%)", "ZeroWP": "PVO à 0 : Echec automatique (ie 0%)",
"LowWP": "PVO faibles", "LowWP": "PVO faibles",
"Exhausted": "Epuisé", "Exhausted": "Epuisé", "wornWeaponWarning": "Arme usée : N'oubliez pas de faire le jet de chance après l'attaque !", "creature": "Créature",
"creature": "Créature",
"Rituals": "Rituels", "Rituals": "Rituels",
"Tomes": "Ouvrages", "Tomes": "Ouvrages",
"otherBenefits": "Autres avantages", "otherBenefits": "Autres avantages",
"Unarmed": "Désarmé", "Unarmed": "Désarmé",
"Cured": "Soigné", "Cured": "Soigné",
"Uncured": "Non soigné", "Uncured": "Non soigné",
"nudgedRoll": "Modifier le jeu", "nudgedRoll": "Jet modifié : ",
"selectNewValue": "Sélectionner une nouvelle valeur", "selectNewValue": "Sélectionner une nouvelle valeur",
"wpCost": "Cout en PVO", "wpCost": "Cout en PVO",
"Hand": "A portée de main", "Hand": "A portée de main",
@@ -709,6 +726,8 @@
"newMentalDisorder": "Nouveau Trouble mental", "newMentalDisorder": "Nouveau Trouble mental",
"newWeapon": "Nouvelle Arme", "newWeapon": "Nouvelle Arme",
"newArmor": "Nouvelle Armure", "newArmor": "Nouvelle Armure",
"equipped": "Équipée (cliquer pour déséquiper)",
"unequipped": "Non équipée (cliquer pour équiper)",
"newInjury": "Nouvelle Blessure", "newInjury": "Nouvelle Blessure",
"newGear": "Nouvel Equipement", "newGear": "Nouvel Equipement",
"newArcane": "Nouvel Arcane", "newArcane": "Nouvel Arcane",
@@ -728,7 +747,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.", "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", "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.", "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", "titleLuck": "Jet de Chance",
"healingRoll": "Jet de soin, PV soignés", "healingRoll": "Jet de soin, PV soignés",
"healingRollFailure": "Jet de soin échoué critique, PV perdus", "healingRollFailure": "Jet de soin échoué critique, PV perdus",
@@ -744,7 +770,21 @@
"visibility": "Visibilité", "visibility": "Visibilité",
"rangedRange": "Portée", "rangedRange": "Portée",
"aimingLastRound": "Visée lors du dernier round (+20)", "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": { "ChatMessage": {
"exhausted": "Votre protagoniste est épuisé. Il perd [[/r 1d6]] Points de Volonté." "exhausted": "Votre protagoniste est épuisé. Il perd [[/r 1d6]] Points de Volonté."
@@ -769,17 +809,25 @@
"rollDamage": "Lancer les dégâts" "rollDamage": "Lancer les dégâts"
}, },
"Chat": { "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": { "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.", "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.", "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", "skillAlreadyExists": "La compétence existe déja",
"WrongEra": "L'époque de l'item ne correspond pas à celle du jeu en cours.", "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.", "NoSelectiveFireChoices": "Aucune option de tir sélectif n'est disponible pour cette arme : pas assez de munitions.",
"NoAmmo": "Aucune munition disponible pour cette arme.", "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.", "noRollDataFound": "Aucune donnée de jet trouvée.",
"noActorFound": "Aucun protagoniste trouvé.", "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."
} }
} }
} }
+75 -86
View File
@@ -1,6 +1,6 @@
// System Module Imports // System Module Imports
import { Utils } from './utils.js' import CthulhuEternalUtils from '../../utils.mjs'
import { SYSTEM } from "../../config/system.mjs"
export let ActionHandler = null export let ActionHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
@@ -15,8 +15,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @param {array} groupIds * @param {array} groupIds
*/ */
async buildSystemActions(groupIds) { async buildSystemActions(groupIds) {
// Set actor and token variables // this.actor and this.actors are provided by the base class (TAH Core v2+)
this.actors = (!this.actor) ? this._getActors() : [this.actor]
this.actorType = this.actor?.type this.actorType = this.actor?.type
// Set items variable // Set items variable
@@ -26,9 +25,11 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
this.items = items this.items = items
} }
if (this.actor) {
if (this.actorType !== 'vehicle') { if (this.actorType !== 'vehicle') {
this.#buildCharacterActions() this.#buildCharacterActions()
} else if (!this.actor) { }
} else {
this.#buildMultipleTokenActions() this.#buildMultipleTokenActions()
} }
} }
@@ -38,7 +39,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @private * @private
*/ */
#buildCharacterActions() { #buildCharacterActions() {
this.buildAttributes() this.buildCharacteristics()
this.buildOther() this.buildOther()
this.buildLuck() this.buildLuck()
this.buildSkills() this.buildSkills()
@@ -49,17 +50,17 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
return game.settings.get('token-action-hud-core', 'tooltips') === 'none' return game.settings.get('token-action-hud-core', 'tooltips') === 'none'
} }
async buildAttributes() { async buildCharacteristics() {
const actions = [] const actions = []
for (const key in this.actor.system.characteristics) { 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 = { const tooltip = {
content: String(this.actor.system.characteristics[key].value * 5), content: String(this.actor.system.characteristics[key].value * 5),
class: 'tah-system-tooltip', class: 'tah-system-tooltip',
direction: 'LEFT' direction: 'LEFT'
} }
actions.push({ actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.' + key), name: coreModule.api.Utils.i18n(`CTHULHUETERNAL.Label.${key}`),
id: key, id: key,
info1: this.#showValue() ? { text: tooltip.content } : null, info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip, tooltip,
@@ -67,7 +68,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
}) })
} }
await this.addActions(actions, { await this.addActions(actions, {
id: 'attributes', id: 'characteristics',
type: 'system' type: 'system'
}) })
} }
@@ -80,79 +81,83 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
direction: 'LEFT' direction: 'LEFT'
} }
actions.push({ actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Luck'), name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.luck'),
id: 'luck', id: 'luck',
info1: this.#showValue() ? { text: '50' } : null, info1: this.#showValue() ? { text: '50' } : null,
tooltip, tooltip,
encodedValue: ['attributes', 'luck'].join(this.delimiter) encodedValue: ['characteristics', 'luck'].join(this.delimiter)
}) })
await this.addActions(actions, { id: 'luck', type: 'system' }) await this.addActions(actions, { id: 'luck', type: 'system' })
} }
async buildOther() { async buildOther() {
if (typeof this.actor.system.sanity.value !== 'undefined') { if (typeof this.actor.system?.san?.value !== 'undefined') {
const actions = [] const actions = []
const groupData = { const groupData = {
id: 'other_sanity', id: 'other_san',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'), name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
type: 'system' type: 'system'
} }
this.addGroup(groupData, { id: 'other', type: 'system' }, true) this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = { 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', class: 'tah-system-tooltip',
direction: 'LEFT' direction: 'LEFT'
} }
actions.push({ actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'), name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
id: 'sanity', id: 'san',
info1: this.#showValue() ? { text: tooltip.content } : null, info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip, tooltip,
encodedValue: ['attributes', 'sanity'].join(this.delimiter) encodedValue: ['characteristics', 'san'].join(this.delimiter)
}, },
{ {
name: '+', name: '+',
id: 'sanity_add', id: 'san_add',
encodedValue: ['attributes', 'sanity_add'].join(this.delimiter) tooltip,
encodedValue: ['characteristics', 'san_add'].join(this.delimiter)
}, },
{ {
name: '-', name: '-',
id: 'sanity_subtract', id: 'san_subtract',
encodedValue: ['attributes', 'sanity_subtract'].join(this.delimiter) 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') { if (typeof this.actor.system.hp.value !== 'undefined') {
const actions = [] const actions = []
const groupData = { const groupData = {
id: 'other_health', id: 'other_hp',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'), name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
type: 'system' type: 'system'
} }
this.addGroup(groupData, { id: 'other', type: 'system' }, true) this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = { 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', class: 'tah-system-tooltip',
direction: 'LEFT' direction: 'LEFT'
} }
actions.push({ actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'), name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
id: 'health', id: 'hp',
info1: this.#showValue() ? { text: tooltip.content } : null, info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip, tooltip,
encodedValue: ['attributes', 'health'].join(this.delimiter) encodedValue: ['characteristics', 'hp'].join(this.delimiter)
}, },
{ {
name: '+', name: '+',
id: 'health_add', id: 'hp_add',
encodedValue: ['attributes', 'health_add'].join(this.delimiter) tooltip,
encodedValue: ['characteristics', 'hp_add'].join(this.delimiter)
}, },
{ {
name: '-', name: '-',
id: 'health_subtract', id: 'hp_subtract',
encodedValue: ['attributes', 'health_subtract'].join(this.delimiter) 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') { if (typeof this.actor.system.wp.value !== 'undefined') {
const actions = [] const actions = []
@@ -163,7 +168,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
} }
this.addGroup(groupData, { id: 'other', type: 'system' }, true) this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = { 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', class: 'tah-system-tooltip',
direction: 'LEFT' direction: 'LEFT'
} }
@@ -172,17 +177,19 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
id: 'wp', id: 'wp',
info1: this.#showValue() ? { text: tooltip.content } : null, info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip, tooltip,
encodedValue: ['attributes', 'wp'].join(this.delimiter) encodedValue: ['characteristics', 'wp'].join(this.delimiter)
}, },
{ {
name: '+', name: '+',
id: 'wp_add', id: 'wp_add',
encodedValue: ['attributes', 'wp_add'].join(this.delimiter) tooltip,
encodedValue: ['characteristics', 'wp_add'].join(this.delimiter)
}, },
{ {
name: '-', name: '-',
id: 'wp_subtract', 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' }) await this.addActions(actions, { id: 'other_wp', type: 'system' })
} }
@@ -190,19 +197,23 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
async buildSkills() { async buildSkills() {
const actions = [] const actions = []
let actorSkills = this.actor.items.filter(item => item.type === 'skill') // Build alpha sorted skill list
for (const skill in actorSkills) { let skills = this.actor.items.filter(i => i.type === 'skill')
if (skill.system.computeScore() > 0) { 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 = { const tooltip = {
content: String(skill.skill.system.computeScore()), content: String(skill.system.skillTotal),
direction: 'LEFT' direction: 'LEFT'
} }
actions.push({ actions.push({
name: skill.name, name: `${skill.name} (${skill.system.skillTotal})`,
id: skill.id, id: skill.id,
info1: this.#showValue() ? { text: tooltip.content } : null, info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip, 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() { async buildEquipment() {
let weapons = this.actor.items.filter(item => item.type === 'weapon') let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
let skills = this.actor.items.filter(item => item.type === 'skill')
const weapons = this.actor.items.filter(i => i.type === 'weapon')
for (const item of weapons) { for (const item of weapons) {
// Push the weapon name as a new group // Push the weapon name as a new group
const groupData = { const groupData = {
id: 'weapons_' + item._id, id: `weapons_${item._id}`,
name: item.name, name: item.name,
type: 'system' 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) this.addGroup(groupData, { id: 'weapons', type: 'system' }, true)
if (item.type === 'weapon') { let skill = CthulhuEternalUtils.getWeaponSkill(this.actor, item, era)
const weapons = [] item.skillTotal = skill ? skill.system.skillTotal : 0
let weaponActions = []
const tooltip = { const tooltip = {
content: String(skill.system.computeScore()), content: String(item.skillTotal),
direction: 'LEFT' direction: 'LEFT'
} }
weapons.push({ weaponActions.push({
name: skill.name, name: `${item.name} (${item.skillTotal})`,
id: skill._id, id: `weapon_${item._id}`,
info1: this.#showValue() ? { text: tooltip.content } : null, info1: this.#showValue() ? { text: tooltip.content } : null,
encodedValue: ['weapons', item._id].join(this.delimiter), encodedValue: ['weapons', item._id].join(this.delimiter),
tooltip tooltip
}) })
let damage = ''
if (item.system.lethality > 0) {
damage = `L:${item.system.lethality}%`
} else {
damage = item.system.damage
}
const damageTooltip = { const damageTooltip = {
content: String(item.system.damage), content: String(damage),
direction: 'LEFT' direction: 'LEFT'
} }
if (item.system.damage !== '') { if (item.system.damage !== '') {
weapons.push({ weaponActions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage'), name: `${coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage')} (${damage})`,
id: item._id, id: `damage_${item._id}`,
info1: this.#showValue() ? { text: damageTooltip.content } : null, info1: this.#showValue() ? { text: damageTooltip.content } : null,
encodedValue: ['damage', item._id].join(this.delimiter), encodedValue: ['damage', item._id].join(this.delimiter),
tooltip: damageTooltip tooltip: damageTooltip
}) })
} }
if (item.system.isLethal) { await this.addActions(weaponActions, {
const lethalityTooltip = { id: `weapons_${item._id}`,
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,
type: 'system' 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'
}) */
} }
} }
+11 -10
View File
@@ -1,8 +1,8 @@
/** /**
* Module-based constants * Module-based constants
*/ */
export const SYSTEM = { export const MODULE = {
ID: 'fvtt-cthulhu-eternal' ID: 'token-action-hud-cthulhu-eternal'
} }
/** /**
@@ -15,24 +15,25 @@ export const CORE_MODULE = {
/** /**
* Core module version required by the system 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 * Action types
*/ */
export const ACTION_TYPE = { export const ACTION_TYPE = {
attributes: 'CTHULHUETERNAL.Label.Characteristics', characteristics: 'CTHULHUETERNAL.Label.characteristics',
skills: 'CTHULHUETERNAL.Label.Skill', skills: 'CTHULHUETERNAL.Label.Skills',
equipment: 'CTHULHUETERNAL.Label.Gear' equipment: 'CTHULHUETERNAL.Label.gear'
} }
/** /**
* Groups * Groups
*/ */
export const GROUP = { export const GROUP = {
attributes: { id: 'attributes', name: 'CTHULHUETERNAL.Label.Characteristics', type: 'system' }, characteristics: { id: 'characteristics', name: 'CTHULHUETERNAL.Label.characteristics', type: 'system' },
luck: { id: 'luck', name: 'CTHULHUETERNAL.Label.Luck', 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' }, skills: { id: 'skills', name: 'CTHULHUETERNAL.Label.Skills', type: 'system' },
weapons: { id: 'weapons', name: 'CTHULHUETERNAL.Label.Weapons', type: 'system' }, weapons: { id: 'weapons', name: 'CTHULHUETERNAL.Label.weapons', type: 'system' },
rituals: { id: 'rituals', name: 'CTHULHUETERNAL.Label.Rituals', type: 'system' } rituals: { id: 'rituals', name: 'CTHULHUETERNAL.Label.rituals', type: 'system' }
} }
+5 -7
View File
@@ -17,9 +17,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
{ {
nestId: 'statistics', nestId: 'statistics',
id: 'statistics', id: 'statistics',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Characteristics'), name: game.i18n.localize('CTHULHUETERNAL.Label.characteristics'),
groups: [ groups: [
{ ...groups.attributes, nestId: 'statistics_attributes' }, { ...groups.characteristics, nestId: 'statistics_characteristics' },
{ ...groups.other, nestId: 'statistics_other' }, { ...groups.other, nestId: 'statistics_other' },
{ ...groups.luck, nestId: 'statistics_luck' } { ...groups.luck, nestId: 'statistics_luck' }
] ]
@@ -27,17 +27,15 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
{ {
nestId: 'skills', nestId: 'skills',
id: 'skills', id: 'skills',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Skills'), name: game.i18n.localize('CTHULHUETERNAL.Label.skills'),
groups: [ groups: [
{ ...groups.skills, nestId: 'skills_skills' }, { ...groups.skills, nestId: 'skills_skills' }
{ ...groups.typedSkills, nestId: 'skills_typed' },
{ ...groups.specialTraining, nestId: 'skills_special' }
] ]
}, },
{ {
nestId: 'equipment', nestId: 'equipment',
id: 'equipment', id: 'equipment',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Gear'), name: game.i18n.localize('CTHULHUETERNAL.Label.gear'),
groups: [ groups: [
{ ...groups.weapons, nestId: 'equipment_weapons' }, { ...groups.weapons, nestId: 'equipment_weapons' },
{ ...groups.rituals, nestId: 'equipment_rituals' } { ...groups.rituals, nestId: 'equipment_rituals' }
+41 -113
View File
@@ -1,6 +1,3 @@
import { SYSTEM } from "../../config/system.mjs"
import CthulhuEternalRoll from '../../documents/roll.mjs'
export let RollHandler = null export let RollHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
@@ -18,7 +15,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
async handleActionClick(event, encodedValue) { async handleActionClick(event, encodedValue) {
const [actionTypeId, actionId] = encodedValue.split('|') const [actionTypeId, actionId] = encodedValue.split('|')
const knownCharacters = ['character'] const knownCharacters = ['protagonist', 'creature']
// If single actor is selected // If single actor is selected
if (this.actor) { if (this.actor) {
@@ -67,8 +64,8 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
*/ */
async #handleAction(event, actor, token, actionTypeId, actionId) { async #handleAction(event, actor, token, actionTypeId, actionId) {
switch (actionTypeId) { switch (actionTypeId) {
case 'attributes': case 'characteristics':
await this.#handleAttributesAction(event, actor, actionId) await this.#handleCharacteristicsAction(event, actor, actionId)
break break
case 'skills': case 'skills':
await this.#handleSkillsAction(event, actor, actionId) await this.#handleSkillsAction(event, actor, actionId)
@@ -82,12 +79,6 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
case 'lethality': case 'lethality':
await this.#handleLethalityAction(event, actor, actionId) await this.#handleLethalityAction(event, actor, actionId)
break break
case 'specialTraining':
await this.#handleSpecialTrainingAction(event, actor, actionId)
break
case 'typedSkills':
await this.#handleCustomTypedAction(event, actor, actionId)
break
/* case 'rituals': /* case 'rituals':
await this.#handleRitualsAction(event, actor, actionId) await this.#handleRitualsAction(event, actor, actionId)
break */ break */
@@ -98,40 +89,42 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
} }
/** /**
* Handle Attribute action * Handle Characteristic action
* @private * @private
* @param {object} event The event * @param {object} event The event
* @param {object} actor The actor * @param {object} actor The actor
* @param {string} actionId The action id * @param {string} actionId The action id
*/ */
async #handleAttributesAction (event, actor, actionId) { async #handleCharacteristicsAction(event, actor, actionId) {
let rollType if (actionId === 'wp' || actionId === 'hp') return
if (actionId === 'wp' || actionId === 'health') return
if (actionId.includes('_add') || actionId.includes('_subtract')) { if (actionId.includes('_add') || actionId.includes('_subtract')) {
const attr = actionId.split('_')[0] const attr = actionId.split('_')[0]
const action = actionId.split('_')[1] const action = actionId.split('_')[1]
const update = {} const update = {}
update.system = {} update.system = {}
update.system[attr] = {} update.system[attr] = {}
update.system[attr].value = action === 'add' ? this.actor.system[attr].value + 1 : this.actor.system[attr].value - 1 update.system[attr].value = action === 'add' ? actor.system[attr].value + 1 : 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 if (update.system[attr].value > actor.system[attr].max || update.system[attr].value < actor.system[attr].min) return
return await this.actor.update(update) return await actor.update(update)
} }
if (actionId === 'sanity') { if (actionId === 'san') {
rollType = actionId 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') { } 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 { } 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)
} }
/** /**
@@ -142,63 +135,11 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @param {string} actionId The action id * @param {string} actionId The action id
*/ */
async #handleSkillsAction(event, actor, actionId) { async #handleSkillsAction(event, actor, actionId) {
const options = { const skill = actor.items.find(i => i.type === 'skill' && i.id === actionId)
actor: this.actor, if (!skill) return ui.notifications.warn(`Skill not found for action id '${actionId}'`)
rollType: 'skill', await actor.system.roll('skill', skill)
key: actionId
} }
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 * Handle Weapon action
@@ -208,15 +149,11 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @param {string} actionId The action id * @param {string} actionId The action id
*/ */
async #handleWeaponsAction(event, actor, actionId) { async #handleWeaponsAction(event, actor, actionId) {
const item = this.actor.items.get(actionId) const weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
const options = { if (!weapon) return ui.notifications.warn(`Weapon not found for action id '${actionId}'`)
actor: this.actor, weapon.damageFormula = weapon.system.damage
rollType: 'weapon', weapon.damageBonus = actor.system.damageBonus
key: item.system.skill, await actor.system.roll('weapon', weapon)
item
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
} }
/** /**
@@ -227,21 +164,11 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @param {string} actionId The action id * @param {string} actionId The action id
*/ */
async #handleDamageAction(event, actor, actionId) { async #handleDamageAction(event, actor, actionId) {
const item = this.actor.items.get(actionId) const weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
if (item.system.lethality > 0 && event.ctrlKey) { if (!weapon) return ui.notifications.warn(`Weapon not found for action id '${actionId}'`)
// Toggle on/off lethality weapon.damageFormula = weapon.system.damage
const isLethal = !item.system.isLethal weapon.damageBonus = actor.system.damageBonus
await item.update({ 'system.isLethal': isLethal }) await actor.system.roll('damage', weapon)
} 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)
}
} }
/** /**
@@ -252,19 +179,20 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @param {string} actionId The action id * @param {string} actionId The action id
*/ */
async #handleLethalityAction(event, actor, actionId) { async #handleLethalityAction(event, actor, actionId) {
const item = await this.actor.items.get(actionId) const item = actor.items.get(actionId)
if (item.system.damage !== '' && event.ctrlKey) { if (item.system.damage !== '' && event.ctrlKey) {
const isLethal = !item.system.isLethal const isLethal = !item.system.isLethal
await item.update({ 'system.isLethal': isLethal }) await item.update({ 'system.isLethal': isLethal })
} else { } else {
const options = { const options = {
actor: this.actor, actor,
rollType: 'lethality', rollType: 'lethality',
key: item.system.lethality, key: item.system.lethality,
item item
} }
/* TOFIX
const roll = new DGLethalityRoll(item.system.damage, {}, options) const roll = new DGLethalityRoll(item.system.damage, {}, options)
await this.actor.sheet.processRoll(event, roll) await this.actor.sheet.processRoll(event, roll)*/
} }
} }
+9
View File
@@ -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) {
}
+4 -10
View File
@@ -1,8 +1,8 @@
// System Module Imports // System Module Imports
import { ActionHandler } from './action-handler.js' import { ActionHandler } from './action-handler.js'
import { RollHandler as Core } from './roll-handler.js' import { RollHandler as Core } from './roll-handler.js'
import { SYSTEM } from './constants.js'
import { DEFAULTS } from './defaults.js' import { DEFAULTS } from './defaults.js'
import * as systemSettings from './settings.js'
export let SystemManager = null export let SystemManager = null
@@ -68,7 +68,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @param {function} coreUpdate The Token Action HUD Core update function * @param {function} coreUpdate The Token Action HUD Core update function
*/ */
registerSettings(coreUpdate) { registerSettings(coreUpdate) {
/*systemSettings.register(coreUpdate)*/ systemSettings.register(coreUpdate)
} }
/** /**
@@ -78,14 +78,8 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @returns {object} The TAH system styles * @returns {object} The TAH system styles
*/ */
registerStyles() { registerStyles() {
return { // No system-specific styles; use the core styles only
template: { return {}
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
}
}
} }
} }
}) })
+2 -17
View File
@@ -1,22 +1,7 @@
import { SYSTEM } from './constants.js' import { MODULE } from './constants.js'
export let Utils = null 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) => { Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/** /**
* Utility functions * Utility functions
@@ -31,7 +16,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
static getSetting(key, defaultValue = null) { static getSetting(key, defaultValue = null) {
let value = defaultValue ?? null let value = defaultValue ?? null
try { try {
value = game.settings.get(SYSTEM.ID, key) value = game.settings.get(MODULE.ID, key)
} catch { } catch {
coreModule.api.Logger.debug(`Setting '${key}' not found`) coreModule.api.Logger.debug(`Setting '${key}' not found`)
} }
@@ -140,7 +140,6 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
_onDragOver(event) {} _onDragOver(event) {}
async _onDropItem(item) { async _onDropItem(item) {
console.log("Dropped item", item)
let itemData = item.toObject() let itemData = item.toObject()
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false }) await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
} }
@@ -160,15 +159,12 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
} }
static #onUpdateCheckboxArray(event, target) { static #onUpdateCheckboxArray(event, target) {
console.log("Update checkbox array", event, target)
let arrayName = target.dataset.name let arrayName = target.dataset.name
let arrayIdx = Number(target.dataset.index) let arrayIdx = Number(target.dataset.index)
let dataPath = `system.san.${arrayName}` let dataPath = `system.san.${arrayName}`
let tab = foundry.utils.duplicate(this.document.system.san[arrayName]) let tab = foundry.utils.duplicate(this.document.system.san[arrayName])
tab[arrayIdx] = target.checked tab[arrayIdx] = target.checked
this.actor.update( { [dataPath]: tab } ) 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), dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this), drop: this._onDrop.bind(this),
} }
return new DragDrop(d) return new foundry.applications.ux.DragDrop.implementation(d)
}) })
} }
@@ -13,6 +13,7 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
}, },
actions: { actions: {
setBP: CthulhuEternalProtagonistSheet.#onSetBP, setBP: CthulhuEternalProtagonistSheet.#onSetBP,
toggleEquipped: CthulhuEternalProtagonistSheet.#onToggleEquipped,
createGear: CthulhuEternalProtagonistSheet.#onCreateGear, createGear: CthulhuEternalProtagonistSheet.#onCreateGear,
createArmor: CthulhuEternalProtagonistSheet.#onCreateArmor, createArmor: CthulhuEternalProtagonistSheet.#onCreateArmor,
createWeapon: CthulhuEternalProtagonistSheet.#onCreateWeapon, createWeapon: CthulhuEternalProtagonistSheet.#onCreateWeapon,
@@ -98,10 +99,20 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
switch (partId) { switch (partId) {
case "main": case "main":
break break
case "skills": case "skills": {
context.tab = context.tabs.skills context.tab = context.tabs.skills
context.skills = doc.itemTypes.skill let tmpSkills = doc.itemTypes.skill
context.skills.sort((a, b) => a.name.localeCompare(b.name)) 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 break
case "equipment": case "equipment":
context.tab = context.tabs.equipment context.tab = context.tabs.equipment
@@ -145,6 +156,12 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
this.document.system.setBP() 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) { static #onCreateGear(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newGear"), type: "gear" }]) this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newGear"), type: "gear" }])
} }
+1 -2
View File
@@ -32,7 +32,6 @@ export default class CthulhuEternalSkillSheet extends CthulhuEternalItemSheet {
} }
static async #onRollProgress(event, target) { static async #onRollProgress(event, target) {
console.log("Rolling progress for skill", this, event, target)
if (this.actor) { if (this.actor) {
const roll = await new Roll("1d4").evaluate() const roll = await new Roll("1d4").evaluate()
if (roll) { if (roll) {
@@ -41,7 +40,7 @@ export default class CthulhuEternalSkillSheet extends CthulhuEternalItemSheet {
user: game.user.id, user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }), speaker: ChatMessage.getSpeaker({ actor: this.actor }),
content: `<div class="progress-roll">${game.i18n.localize("CTHULHUETERNAL.Label.skillProgress")} - ${this.document.name} +${roll.total}</div>`, 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, roll: roll,
}; };
await ChatMessage.create(chatData); await ChatMessage.create(chatData);
+90
View File
@@ -77,6 +77,96 @@ export const RESOURCE_RATING = {
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichModern" }, 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" } 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: { victorian: {
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" }, 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" }, 4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 30, assets: "CTHULHUETERNAL.Resource.PoorVictorian" },
+47 -2
View File
@@ -23,8 +23,10 @@ export default class CthulhuEternalActor extends Actor {
data.items.push(skill.toObject()) data.items.push(skill.toObject())
} }
} }
data.items.push({ type:"weapon", img: "systems/fvtt-cthulhu-eternal/assets/icons/icon_fist.svg", data.items.push({
name: game.i18n.localize("CTHULHUETERNAL.Label.Unarmed"), system: { damage: "1d4-1", weaponType: "unarmed" } }) 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); return super.create(data, options);
@@ -40,9 +42,52 @@ export default class CthulhuEternalActor extends Actor {
type: CONST.CHAT_MESSAGE_STYLES.OTHER 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) 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) { async createEmbeddedDocuments(embeddedName, data, operation) {
let newData = [] let newData = []
if (embeddedName === "Item") { if (embeddedName === "Item") {
+147 -50
View File
@@ -1,5 +1,6 @@
import { SYSTEM } from "../config/system.mjs" import { SYSTEM } from "../config/system.mjs"
import CthulhuEternalUtils from "../utils.mjs"
export default class CthulhuEternalRoll extends Roll { export default class CthulhuEternalRoll extends Roll {
/** /**
@@ -128,51 +129,48 @@ export default class CthulhuEternalRoll extends Roll {
static async processWeaponDamage(actor, options) { static async processWeaponDamage(actor, options) {
let isLethal = false let isLethal = false
let weapon = options.rollItem let weapon = options.rollItem
let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
options.isNudge = false options.isNudge = false
// Selective fire management // Selective fire management
if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) { if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[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.selectiveFireChoiceLabel = choice.label // Store the choice in the weapon
weapon.system.lethality = choice.lethality // Override lethality weapon.system.lethality = choice.lethality // Override lethality
weapon.system.killRadius = choice.killRadius // Override kill radius weapon.system.killRadius = choice.killRadius // Override kill radius
ammoUsed = choice.ammoUsed // Override ammo used
} }
ammoUsed = Number(ammoUsed) let combatants = []
if (game?.combat?.combatants) {
for (let c of game.combat.combatants) {
if (c.actorid !== actor.id) {
combatants.push({ id: c.id, name: c.name })
}
}
}
if (weapon.system.lethality > 0) { if (weapon.system.lethality > 0) {
let lethalityRoll = new Roll("1d100") let lethalityRoll = new Roll("1d100")
await lethalityRoll.evaluate() await lethalityRoll.evaluate()
let lethalScore = (options?.previousResultType === "successCritical") ? weapon.system.lethality * 2 : weapon.system.lethality let lethalScore = (options?.previousResultType === "successCritical") ? weapon.system.lethality * 2 : weapon.system.lethality
isLethal = (lethalityRoll.total <= lethalScore) 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 wounds = Math.floor(lethalityRoll.total / 10) + (lethalityRoll.total % 10)
let msgData = { let msgData = {
actorId: actor.id,
weapon, weapon,
wounds, wounds,
lethalScore, lethalScore,
isLethal, isLethal,
ammoUsed, ammoUsed: weapon?.ammoUsed || 0,
rollResult: lethalityRoll.total, rollResult: lethalityRoll.total,
combatants: combatants
} }
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-lethal-damage.hbs", msgData) 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, user: game.user.id,
content: flavor, content: flavor,
speaker: ChatMessage.getSpeaker({ actor: actor }), speaker: ChatMessage.getSpeaker({ actor: actor }),
}, { rollMode: options.rollMode, create: true }) }, { rollMode: options.rollMode, create: true })
await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
return return
} }
@@ -184,27 +182,24 @@ export default class CthulhuEternalRoll extends Roll {
if (options?.previousResultType === "successCritical") { if (options?.previousResultType === "successCritical") {
formula = `( ${formula} ) * 2` 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) let damageRoll = new Roll(formula)
await damageRoll.evaluate() await damageRoll.evaluate()
let msgData = { let msgData = {
actorId: actor.id,
weapon, weapon,
formula, formula,
ammoUsed, ammoUsed: weapon?.ammoUsed || 0,
rollResult: damageRoll.total, rollResult: damageRoll.total,
combatants: combatants
} }
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-regular-damage.hbs", msgData) 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, user: game.user.id,
content: flavor, content: flavor,
speaker: ChatMessage.getSpeaker({ actor: actor }), speaker: ChatMessage.getSpeaker({ actor: actor }),
}, { rollMode: options.rollMode, create: true }) }, { rollMode: options.rollMode, create: true })
await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
} }
@@ -215,11 +210,32 @@ export default class CthulhuEternalRoll extends Roll {
modifier += SYSTEM.WEAPON_VISIBILITY[rollData.visibilityChoice]?.modifier || 0 modifier += SYSTEM.WEAPON_VISIBILITY[rollData.visibilityChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_ATTACKER_STATE[rollData.attackerStateChoice]?.modifier || 0 modifier += SYSTEM.WEAPON_ATTACKER_STATE[rollData.attackerStateChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_TARGET_SIZE[rollData.targetSizeChoice]?.modifier || 0 modifier += SYSTEM.WEAPON_TARGET_SIZE[rollData.targetSizeChoice]?.modifier || 0
modifier += (rollData.aimingLastRound) ? 20 : 0 modifier += (rollData.aimingLastRoundFlag) ? 20 : 0
modifier += (rollData.aimingWithSight) ? 20 : 0 modifier += (rollData.aimingWithSightFlag) ? 20 : 0
return modifier 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. * Prompt the user with a dialog to configure and execute a roll.
* *
@@ -241,9 +257,10 @@ export default class CthulhuEternalRoll extends Roll {
options.isNudge = true options.isNudge = true
let actor = game.actors.get(options.actorId) let actor = game.actors.get(options.actorId)
let target = CthulhuEternalUtils.getTarget()
switch (options.rollType) { switch (options.rollType) {
case "skill": case "skill":
console.log(options.rollItem)
options.initialScore = options.rollItem.system.computeScore() options.initialScore = options.rollItem.system.computeScore()
break break
case "luck": case "luck":
@@ -273,33 +290,30 @@ export default class CthulhuEternalRoll extends Roll {
let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era") let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
if (era !== options.rollItem.system.settings) { if (era !== options.rollItem.system.settings) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.WrongEra")) ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.WrongEra"))
console.log("WP Wrong Era", era, options.rollItem.system.weaponType)
return return
} }
if (!SYSTEM.WEAPON_SKILL_MAPPING[era]?.[options.rollItem.system.weaponType]) { if (!SYSTEM.WEAPON_SKILL_MAPPING[era]?.[options.rollItem.system.weaponType]) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponType")) ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponType"))
console.log("WP Not found", era, options.rollItem.system.weaponType)
return return
} }
/*if (!target) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.AttackNoTarget"))
}*/
// Check if the weapon has enouth ammo in case of a firearm // Check if the weapon has enouth ammo in case of a firearm
if (options.rollItem.system.isFireArm() && options.rollItem.system.ammo.value <= 0) { if (options.rollItem.system.isFireArm() && options.rollItem.system.ammo.value <= 0) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo")) ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
return return
} }
options.weapon = options.rollItem options.weapon = options.rollItem
if (options.rollItem.system.hasDirectSkill) {
let skillName = options.rollItem.name options.rollItem = CthulhuEternalUtils.getWeaponSkill(actor, options.rollItem, era)
options.rollItem = { type: "skill", name: skillName, system: { base: 0, bonus: options.weapon.system.directSkillValue } } options.initialScore = options.rollItem.system.skillTotal
options.initialScore = options.weapon.system.directSkillValue if (options.weapon.system.state === "junk") {
} else { options.isJunk = true
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType]) options.initialScore = Math.max(0, options.initialScore - 20) // -20% for junk weapons
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.initialScore = options.rollItem.system.computeScore() if (options.weapon.system.state === "worn") {
console.log("WEAPON", skillName, era, options.rollItem) options.isWorn = true
} }
} }
break break
@@ -331,6 +345,8 @@ export default class CthulhuEternalRoll extends Roll {
targetScore: options.initialScore, targetScore: options.initialScore,
isLowWP: options.isLowWP, isLowWP: options.isLowWP,
isZeroWP: options.isZeroWP, isZeroWP: options.isZeroWP,
isJunk: options.isJunk,
isWorn: options.isWorn,
isExhausted: options.isExhausted, isExhausted: options.isExhausted,
enableHand: options.rollItem.enableHand, enableHand: options.rollItem.enableHand,
enableStowed: options.rollItem.enableStowed, enableStowed: options.rollItem.enableStowed,
@@ -353,10 +369,11 @@ export default class CthulhuEternalRoll extends Roll {
visibilityChoice: "clear", visibilityChoice: "clear",
attackerStateChoice: "normal", attackerStateChoice: "normal",
targetSizeChoice: "normal", targetSizeChoice: "normal",
aimingLastRound: false, aimingLastRoundFlag: false,
aimingWithSight: false, aimingWithSightFlag: false,
modifier, modifier,
formula, formula,
targetName: target?.name,
hasTarget: options.hasTarget, hasTarget: options.hasTarget,
hasModifier, hasModifier,
hasMultiplier, hasMultiplier,
@@ -402,6 +419,14 @@ export default class CthulhuEternalRoll extends Roll {
options.multiplier = Number(event.target.value) options.multiplier = Number(event.target.value)
this.updateResourceDialog(options) 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 +434,12 @@ export default class CthulhuEternalRoll extends Roll {
if (rollContext === null) return if (rollContext === null) return
let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext) 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 rollData.rollMode = rollContext.visibility
// Update target score // Update target score
console.log("Rolldata", rollData, options)
if (options.rollType === "resource") { if (options.rollType === "resource") {
rollData.targetScore = options.initialScore * Number(rollContext.multiplier) rollData.targetScore = options.initialScore * Number(rollContext.multiplier)
} else { } else {
@@ -432,19 +459,24 @@ export default class CthulhuEternalRoll extends Roll {
rollData.modifier = "0" rollData.modifier = "0"
} }
if (options.rollType === "weapon") {
await this.processAmmoUsed(actor, rollData.weapon)
}
if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return
const roll = new this(formula, options.data, rollData) const roll = new this(formula, options.data, rollData)
await roll.evaluate() 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 if (Hooks.call("fvtt-cthulhu-eternal.Roll", options, rollData, roll) === false) return
return roll return roll
} }
displayRollResult(formula, options, rollData) { async displayRollResult(formula, options, rollData) {
// Compute the result quality // Compute the result quality
let resultType = "failure" let resultType = "failure"
@@ -462,6 +494,11 @@ export default class CthulhuEternalRoll extends Roll {
resultType = "failureCritical" 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 // As per the rules, a roll of 100 is always a failure, even if the target is above 100
if (this.total === 100) { if (this.total === 100) {
resultType = "failureCritical" resultType = "failureCritical"
@@ -492,7 +529,66 @@ export default class CthulhuEternalRoll extends Roll {
this.options.isLowWP = rollData.isLowWP this.options.isLowWP = rollData.isLowWP
this.options.isZeroWP = rollData.isZeroWP this.options.isZeroWP = rollData.isZeroWP
this.options.isExhausted = rollData.isExhausted 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) 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
}
}
}
} }
/** /**
@@ -590,7 +686,7 @@ export default class CthulhuEternalRoll extends Roll {
* @returns {Promise} - A promise that resolves when the message is created. * @returns {Promise} - A promise that resolves when the message is created.
*/ */
async toMessage(messageData = {}, { rollMode, create = true } = {}) { async toMessage(messageData = {}, { rollMode, create = true } = {}) {
super.toMessage( let rollMsg = await super.toMessage(
{ {
isFailure: this.resultType === "failure", isFailure: this.resultType === "failure",
actingCharName: this.actorName, actingCharName: this.actorName,
@@ -601,10 +697,11 @@ export default class CthulhuEternalRoll extends Roll {
}, },
{ rollMode: rollMode }, { rollMode: rollMode },
) )
// Manage the skill evolution if the roll is a failure
let rollData = this.options.rollData || this.options let rollData = this.options.rollData || this.options
let rollItem = this.options.rollItem let rollItem = this.options.rollItem
await rollMsg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData)
// Manage the skill evolution if the roll is a failure
if (rollData.resultType.includes("failure") && rollItem.type === "skill") { if (rollData.resultType.includes("failure") && rollItem.type === "skill") {
// Is the skill able to progress // Is the skill able to progress
if (rollItem.system.diceEvolved && !rollItem.system.rollFailed) { if (rollItem.system.diceEvolved && !rollItem.system.rollFailed) {
+1
View File
@@ -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.protection = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.resourceLevel = new fields.NumberField({ 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 return schema
} }
+8 -4
View File
@@ -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 }), 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({ schema.resources = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), // Unused but kept for compatibility value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), // Unused but kept for compatibility
@@ -127,9 +127,11 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
dmgBonus = -2 dmgBonus = -2
} else if (this.characteristics.str.value <= 8) { } else if (this.characteristics.str.value <= 8) {
dmgBonus = -1 dmgBonus = -1
} else if (this.characteristics.str.value <= 12) {
dmgBonus = 0
} else if (this.characteristics.str.value <= 16) { } else if (this.characteristics.str.value <= 16) {
dmgBonus = 1 dmgBonus = 1
} else if (this.characteristics.str.value <= 20) { } else if (this.characteristics.str.value <= 40) {
dmgBonus = 2 dmgBonus = 2
} }
if (this.damageBonus !== dmgBonus) { 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) { if (!this.san.breakingPointReached && this.san.value <= this.san.breakingPoint) {
updates[`system.san.breakingPointReached`] = true updates[`system.san.breakingPointReached`] = true
this.san.breakingPointReached = true // Force local update to 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({ ChatMessage.create({
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.breakingPointReached", { bp: this.san.breakingPoint, san: this.san.value })}</p>`, content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.breakingPointReached", { bp: this.san.breakingPoint, san: this.san.value })}</p>`,
speaker: ChatMessage.getSpeaker({ actor: this.parent }), speaker: ChatMessage.getSpeaker({ actor: this.parent }),
// Get the user id of the actor owner // 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 // Unconsciousness management
if (!this.hp.unconscious && this.hp.value <= 2) { if (!this.hp.unconscious && this.hp.value <= 2) {
@@ -272,7 +277,6 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-none.hbs" 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 content = await foundry.applications.handlebars.renderTemplate(template, msgData)
let msg = await ChatMessage.create({ let msg = await ChatMessage.create({
content: content, content: content,
-1
View File
@@ -54,7 +54,6 @@ export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel
} }
isRanged() { isRanged() {
console.log("isRanged", this.weaponType, this)
return this.weaponType.match("ranged") return this.weaponType.match("ranged")
} }
+316 -6
View File
@@ -16,6 +16,14 @@ export default class CthulhuEternalUtils {
config: true, config: true,
onChange: _ => window.location.reload() onChange: _ => window.location.reload()
}); });
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) { static async loadCompendiumData(compendium) {
@@ -110,9 +118,6 @@ export default class CthulhuEternalUtils {
return Object.keys(obj).length; return Object.keys(obj).length;
}) })
Handlebars.registerHelper('isEnabled', function (configKey) {
return game.settings.get("bol", configKey);
})
Handlebars.registerHelper('split', function (str, separator, keep) { Handlebars.registerHelper('split', function (str, separator, keep) {
return str.split(separator)[keep]; return str.split(separator)[keep];
}) })
@@ -156,7 +161,6 @@ export default class CthulhuEternalUtils {
return eval(expr); return eval(expr);
}) })
Handlebars.registerHelper('isOwnerOrGM', function (actor) { Handlebars.registerHelper('isOwnerOrGM', function (actor) {
console.log("Testing actor", actor.isOwner, game.userId)
return actor.isOwner || game.isGM; return actor.isOwner || game.isGM;
}) })
Handlebars.registerHelper('upperFirst', function (text) { Handlebars.registerHelper('upperFirst', function (text) {
@@ -180,6 +184,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) { static async applySANType(rollMessage, event) {
let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData") let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
if (!rollData) { if (!rollData) {
@@ -248,20 +299,243 @@ 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) { static async damageRoll(rollMessage, formula = null) {
let rollData = rollMessage.rolls[0]?.options?.rollData 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) let actor = game.actors.get(rollData.actorId)
if (!actor) { if (!actor) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noActorFound")) ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noActorFound"))
return return
} }
console.log("Damage roll data", rollData)
rollData.weapon.resultType = rollData.resultType // Keep the result type from the roll message 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.selectiveFireChoice = rollData.selectiveFireChoice // Keep the selected fire choice from the roll message
rollData.weapon.damageFormula = formula || rollData.weapon.system.damage rollData.weapon.damageFormula = formula || rollData.weapon.system.damage
actor.system.roll("damage", rollData.weapon) 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 nudgeRoll(rollMessage) { static async nudgeRoll(rollMessage) {
let dialogContext = rollMessage.rolls[0]?.options let dialogContext = rollMessage.rolls[0]?.options
@@ -274,7 +548,6 @@ export default class CthulhuEternalUtils {
// Build options table for the select operator between minValue and maxValue // Build options table for the select operator between minValue and maxValue
dialogContext.nudgeOptions = Array.from({ length: dialogContext.maxValue - dialogContext.minValue + 1 }, (_, i) => dialogContext.minValue + i) 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) const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/nudge-dialog.hbs", dialogContext)
@@ -327,6 +600,10 @@ export default class CthulhuEternalUtils {
roll.toMessage() roll.toMessage()
actor.system.modifyWP(-dialogContext.wpCost) actor.system.modifyWP(-dialogContext.wpCost)
// Delete the initial roll message
await rollMessage.delete()
} }
static setupCSSRootVariables() { static setupCSSRootVariables() {
@@ -345,4 +622,37 @@ 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")`); 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
}
// Remove the chat message
this.removeChatMessageId(message.id)
// Get the targetted actorId from the button's data attribute
let targetCombatantId = event.currentTarget.dataset.combatantId
let combatant = game.combat.combatants.get(targetCombatantId)
let targetActor = combatant.token?.actor || game.actors.get(combatant.actorId)
if (!targetActor) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noTargetActorFound") + targetCombatantId)
return
}
targetActor.applyWounds(woundData)
}
} }
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000217 MANIFEST-000293
+11 -7
View File
@@ -1,7 +1,11 @@
2025/07/14-21:08:15.948300 7f3fa67fc6c0 Recovering log #215 2026/04/28-23:36:37.192093 7f5797fff6c0 Delete type=3 #1
2025/07/14-21:08:15.959041 7f3fa67fc6c0 Delete type=3 #213 2026/04/28-23:37:29.068330 7f57977fe6c0 Level-0 table #296: started
2025/07/14-21:08:15.959103 7f3fa67fc6c0 Delete type=0 #215 2026/04/28-23:37:29.068358 7f57977fe6c0 Level-0 table #296: 0 bytes OK
2025/07/14-21:36:01.776546 7f3fa57fa6c0 Level-0 table #220: started 2026/04/28-23:37:29.074817 7f57977fe6c0 Delete type=0 #294
2025/07/14-21:36:01.776599 7f3fa57fa6c0 Level-0 table #220: 0 bytes OK 2026/04/28-23:37:29.106904 7f57977fe6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at '!items!zVFfp3o0G0Zg3Ia4' @ 52 : 1
2025/07/14-21:36:01.836135 7f3fa57fa6c0 Delete type=0 #218 2026/04/28-23:37:29.106913 7f57977fe6c0 Compacting 1@0 + 0@1 files
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/04/28-23:37:29.111066 7f57977fe6c0 Generated table #297@0: 26 keys, 60964 bytes
2026/04/28-23:37:29.111082 7f57977fe6c0 Compacted 1@0 + 0@1 files => 60964 bytes
2026/04/28-23:37:29.117095 7f57977fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/04/28-23:37:29.117212 7f57977fe6c0 Delete type=2 #248
2026/04/28-23:37:29.117383 7f57977fe6c0 Manual compaction at level-0 from '!items!zVFfp3o0G0Zg3Ia4' @ 52 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)
+4 -7
View File
@@ -1,7 +1,4 @@
2025/07/14-10:18:19.443254 7f3fa77fe6c0 Recovering log #211 2026/04/28-23:36:37.170404 7f5797fff6c0 Log #291: 0 ops saved to Table #292 OK
2025/07/14-10:18:19.474975 7f3fa77fe6c0 Delete type=3 #209 2026/04/28-23:36:37.170516 7f5797fff6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-cthulhu-eternal/packs-system/rituals/000291.log: OK
2025/07/14-10:18:19.475128 7f3fa77fe6c0 Delete type=0 #211 2026/04/28-23:36:37.171708 7f5797fff6c0 Table #248: 26 entries OK
2025/07/14-20:48:52.882063 7f3fa57fa6c0 Level-0 table #216: started 2026/04/28-23:36:37.175280 7f5797fff6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-cthulhu-eternal/packs-system/rituals; recovered 1 files; 60964 bytes. Some data may have been lost. ****
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)
Binary file not shown.
Binary file not shown.
View File
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000386 MANIFEST-000462
+11 -7
View File
@@ -1,7 +1,11 @@
2025/07/14-21:08:15.916959 7f3fa5ffb6c0 Recovering log #384 2026/04/28-23:36:37.131149 7f57a57ee6c0 Delete type=3 #1
2025/07/14-21:08:15.927715 7f3fa5ffb6c0 Delete type=3 #382 2026/04/28-23:37:29.054894 7f57977fe6c0 Level-0 table #465: started
2025/07/14-21:08:15.927846 7f3fa5ffb6c0 Delete type=0 #384 2026/04/28-23:37:29.054961 7f57977fe6c0 Level-0 table #465: 0 bytes OK
2025/07/14-21:36:01.925671 7f3fa57fa6c0 Level-0 table #389: started 2026/04/28-23:37:29.061529 7f57977fe6c0 Delete type=0 #463
2025/07/14-21:36:01.925708 7f3fa57fa6c0 Level-0 table #389: 0 bytes OK 2026/04/28-23:37:29.081738 7f57977fe6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at '!items!zvoUByzWSWZ87fxA' @ 1281 : 1
2025/07/14-21:36:01.980506 7f3fa57fa6c0 Delete type=0 #387 2026/04/28-23:37:29.081748 7f57977fe6c0 Compacting 1@0 + 0@1 files
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/04/28-23:37:29.088487 7f57977fe6c0 Generated table #466@0: 556 keys, 320457 bytes
2026/04/28-23:37:29.088505 7f57977fe6c0 Compacted 1@0 + 0@1 files => 320457 bytes
2026/04/28-23:37:29.094678 7f57977fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/04/28-23:37:29.094831 7f57977fe6c0 Delete type=2 #417
2026/04/28-23:37:29.117360 7f57977fe6c0 Manual compaction at level-0 from '!items!zvoUByzWSWZ87fxA' @ 1281 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)
+4 -7
View File
@@ -1,7 +1,4 @@
2025/07/14-10:18:19.359761 7f3fa67fc6c0 Recovering log #380 2026/04/28-23:36:36.935963 7f57a57ee6c0 Log #460: 0 ops saved to Table #461 OK
2025/07/14-10:18:19.394536 7f3fa67fc6c0 Delete type=3 #378 2026/04/28-23:36:36.936025 7f57a57ee6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-cthulhu-eternal/packs-system/skills/000460.log: OK
2025/07/14-10:18:19.394680 7f3fa67fc6c0 Delete type=0 #380 2026/04/28-23:36:36.942723 7f57a57ee6c0 Table #417: 556 entries OK
2025/07/14-20:48:52.889840 7f3fa57fa6c0 Level-0 table #385: started 2026/04/28-23:36:36.946588 7f57a57ee6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-cthulhu-eternal/packs-system/skills; recovered 1 files; 320457 bytes. Some data may have been lost. ****
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)
Binary file not shown.
Binary file not shown.
View File
View File
View File
View File
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000031 MANIFEST-000108
+11 -15
View File
@@ -1,15 +1,11 @@
2025/07/14-21:08:15.933872 7f3fa6ffd6c0 Recovering log #29 2026/04/28-23:36:37.166060 7f57a4fed6c0 Delete type=3 #1
2025/07/14-21:08:15.943722 7f3fa6ffd6c0 Delete type=3 #27 2026/04/28-23:37:29.061681 7f57977fe6c0 Level-0 table #111: started
2025/07/14-21:08:15.943845 7f3fa6ffd6c0 Delete type=0 #29 2026/04/28-23:37:29.061722 7f57977fe6c0 Level-0 table #111: 0 bytes OK
2025/07/14-21:36:01.980637 7f3fa57fa6c0 Level-0 table #34: started 2026/04/28-23:37:29.068229 7f57977fe6c0 Delete type=0 #109
2025/07/14-21:36:02.012185 7f3fa57fa6c0 Level-0 table #34: 24252 bytes OK 2026/04/28-23:37:29.095030 7f57977fe6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at '!items!zyxA9DhO36t5OBDv' @ 55 : 1
2025/07/14-21:36:02.071125 7f3fa57fa6c0 Delete type=0 #32 2026/04/28-23:37:29.095044 7f57977fe6c0 Compacting 1@0 + 0@1 files
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) 2026/04/28-23:37:29.100652 7f57977fe6c0 Generated table #112@0: 362 keys, 93592 bytes
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 2026/04/28-23:37:29.100675 7f57977fe6c0 Compacted 1@0 + 0@1 files => 93592 bytes
2025/07/14-21:36:02.071451 7f3fa57fa6c0 Compacting 1@1 + 1@2 files 2026/04/28-23:37:29.106685 7f57977fe6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/07/14-21:36:02.101879 7f3fa57fa6c0 Generated table #35@1: 362 keys, 93592 bytes 2026/04/28-23:37:29.106785 7f57977fe6c0 Delete type=2 #63
2025/07/14-21:36:02.101908 7f3fa57fa6c0 Compacted 1@1 + 1@2 files => 93592 bytes 2026/04/28-23:37:29.117373 7f57977fe6c0 Manual compaction at level-0 from '!items!zyxA9DhO36t5OBDv' @ 55 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
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)
+4 -8
View File
@@ -1,8 +1,4 @@
2025/07/14-10:18:19.404246 7f3fa5ffb6c0 Recovering log #25 2026/04/28-23:36:37.136506 7f57a4fed6c0 Log #106: 0 ops saved to Table #107 OK
2025/07/14-10:18:19.435628 7f3fa5ffb6c0 Delete type=3 #23 2026/04/28-23:36:37.136625 7f57a4fed6c0 Archiving /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-cthulhu-eternal/packs-system/weapons/000106.log: OK
2025/07/14-10:18:19.435765 7f3fa5ffb6c0 Delete type=0 #25 2026/04/28-23:36:37.141991 7f57a4fed6c0 Table #63: 362 entries OK
2025/07/14-20:48:52.908846 7f3fa57fa6c0 Level-0 table #30: started 2026/04/28-23:36:37.145196 7f57a4fed6c0 **** Repaired leveldb /home/morr/foundry/foundrydata-dev/Data/systems/fvtt-cthulhu-eternal/packs-system/weapons; recovered 1 files; 93592 bytes. Some data may have been lost. ****
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)
Binary file not shown.
Binary file not shown.
+27 -4
View File
@@ -111,8 +111,8 @@ i.fvtt-cthulhu-eternal {
padding: 2px 2px; padding: 2px 2px;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s; transition: background-color 0.3s;
min-width: 6.0rem; min-width: 6rem;
max-width: 6.0rem; max-width: 6rem;
} }
} }
.san-loose-buttons { .san-loose-buttons {
@@ -128,8 +128,8 @@ i.fvtt-cthulhu-eternal {
padding: 2px 2px; padding: 2px 2px;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s; transition: background-color 0.3s;
min-width: 3.0rem; min-width: 3rem;
max-width: 3.0rem; max-width: 3rem;
} }
} }
.result-non-lethal { .result-non-lethal {
@@ -141,5 +141,28 @@ i.fvtt-cthulhu-eternal {
font-family: var(--font-primary); font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.02); 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;
}
}
}
} }
} }
+228 -16
View File
@@ -53,6 +53,10 @@
color: var(--color-dark-1); color: var(--color-dark-1);
} }
.li-apply-wounds {
display: none;
}
&.dice-roll { &.dice-roll {
flex-direction: column; flex-direction: column;
@@ -91,22 +95,7 @@
li { li {
margin: 0 10px; margin: 0 10px;
font-family: var(--font-primary); font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.0); font-size: calc(var(--font-size-standard) * 1);
}
.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;
} }
.result-success { .result-success {
color: var(--color-success); color: var(--color-success);
@@ -145,4 +134,227 @@
font-size: calc(var(--font-size-standard) * 1.2); font-size: calc(var(--font-size-standard) * 1.2);
text-shadow: 0 0 10px var(--color-shadow-primary); 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
View File
@@ -6,7 +6,7 @@
"download": "#{DOWNLOAD}#", "download": "#{DOWNLOAD}#",
"url": "https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal", "url": "https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal",
"license": "LICENSE", "license": "LICENSE",
"version": "13.0.1", "version": "13.0.2",
"authors": [ "authors": [
{ {
"name": "Uberwald", "name": "Uberwald",
@@ -33,7 +33,7 @@
}, },
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13" "verified": "14"
}, },
"esmodules": [ "esmodules": [
"cthulhu-eternal.mjs" "cthulhu-eternal.mjs"
+1
View File
@@ -8,6 +8,7 @@
{{formField systemFields.settings value=system.settings localize=true}} {{formField systemFields.settings value=system.settings localize=true}}
{{formField systemFields.protection value=system.protection}} {{formField systemFields.protection value=system.protection}}
{{formField systemFields.resourceLevel value=system.resourceLevel}} {{formField systemFields.resourceLevel value=system.resourceLevel}}
{{formField systemFields.equipped value=system.equipped}}
</fieldset> </fieldset>
+9 -1
View File
@@ -21,12 +21,20 @@
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}: {{ammoUsed}} / {{weapon.system.ammo.value}}</li> <li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}: {{ammoUsed}} / {{weapon.system.ammo.value}}</li>
{{/if}} {{/if}}
<li class="li-apply-wounds">
<button type="button" class="apply-wounds">{{localize "CTHULHUETERNAL.Label.applyWounds"}}</button>
<select name="combatant" class="roll-skill-modifier">
{{selectOptions combatants valueAttr="id" labelAttr="name"}}
</select>
</li>
{{#if isLethal}} {{#if isLethal}}
<li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityLethal"}}</li> <li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityLethal"}}</li>
<li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityWounded"}}</li> <li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityWounded"}}</li>
{{else}} {{else}}
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.lethalityNotLethal"}}</li> <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}} {{/if}}
</ul> </ul>
+168 -53
View File
@@ -6,20 +6,28 @@
</div> </div>
<div class="intro-right"> <div class="intro-right">
<ul> <ul>
<li><strong>{{actingCharName}}</strong></li>
{{#if (eq rollType "char")}} {{#if (eq rollType "char")}}
<li><strong>{{localize "CTHULHUETERNAL.Label.charRoll"}}</strong></li> <li><strong>{{localize "CTHULHUETERNAL.Label.charRoll"}}</strong></li>
{{/if}} {{/if}}
{{#if (eq rollType "skill")}} {{#if (eq rollType "skill")}}
<li><strong>{{localize "CTHULHUETERNAL.Label.skillRoll"}}</strong></li> <li><strong>{{localize
"CTHULHUETERNAL.Label.skillRoll"
}}</strong></li>
{{/if}} {{/if}}
{{#if isNudgedRoll}} {{#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}}
{{#if weapon}} {{#if weapon}}
<li><strong>{{localize "CTHULHUETERNAL.Label.Weapon"}} : {{weapon.name}}</strong></li> <li><strong>{{localize "CTHULHUETERNAL.Label.Weapon"}}
:
{{weapon.name}}</strong></li>
{{/if}} {{/if}}
{{#if (eq rollType "resource")}} {{#if (eq rollType "resource")}}
@@ -29,95 +37,108 @@
{{/if}} {{/if}}
{{#if isZeroWP}} {{#if isZeroWP}}
<li class="red-warning">{{localize "CTHULHUETERNAL.Label.ZeroWP"}}</li> <li class="red-warning">{{localize
"CTHULHUETERNAL.Label.ZeroWP"
}}</li>
{{else}} {{else}}
{{#if isLowWP}} {{#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}} {{/if}}
{{#if isExhausted}} {{#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}}
{{#if (eq rollType "resource")}} {{#if (eq rollType "resource")}}
<li>{{localize "CTHULHUETERNAL.Label.multiplier"}} : {{multiplier}}</li> <li>{{localize "CTHULHUETERNAL.Label.multiplier"}}
:
{{multiplier}}</li>
{{else}} {{else}}
<li>{{localize "CTHULHUETERNAL.Label.modifier"}} : {{totalModifier}}%</li> <li>{{localize "CTHULHUETERNAL.Label.modifier"}}
:
{{totalModifier}}%</li>
{{/if}} {{/if}}
<li>{{localize "CTHULHUETERNAL.Label.targetScore"}} : {{targetScore}}%</li> <li>{{localize "CTHULHUETERNAL.Label.targetScore"}}
:
{{targetScore}}%</li>
{{#if isSuccess}} {{#if isSuccess}}
{{#if isCritical}} {{#if isCritical}}
<li class="result-critical-success">{{localize "CTHULHUETERNAL.Label.criticalSuccess"}} <li class="result-critical-success">{{localize
"CTHULHUETERNAL.Label.criticalSuccess"
}}
</li> </li>
{{else}} {{else}}
<li class="result-success"> <li class="result-success">
{{localize "CTHULHUETERNAL.Label.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> </li>
{{/if}} {{/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}}&nbsp;&nbsp;</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}}
{{#if isFailure}} {{#if isFailure}}
{{#if isCritical}} {{#if isCritical}}
<li class="result-critical-failure">{{localize "CTHULHUETERNAL.Label.criticalFailure"}} <li class="result-critical-failure">{{localize
"CTHULHUETERNAL.Label.criticalFailure"
}}
</li> </li>
{{else}} {{else}}
<li class="result-failure"> <li class="result-failure">
{{localize "CTHULHUETERNAL.Label.failure"}} {{localize "CTHULHUETERNAL.Label.failure"}}
</li> </li>
{{/if}} {{/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}}
<!-- {{#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}} {{/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> </ul>
</div> </div>
</div> </div>
{{#if isDamage}} {{#if isDamage}}
<div> <div>
{{#if (and isGM hasTarget)}} {{#if (and isGM hasTarget)}}
{{{localize "CTHULHUETERNAL.Roll.displayArmor" targetName=targetName targetArmor=targetArmor {{{localize
realDamage=realDamage}}} "CTHULHUETERNAL.Roll.displayArmor"
targetName=targetName
targetArmor=targetArmor
realDamage=realDamage
}}}
{{/if}} {{/if}}
</div> </div>
{{/if}} {{/if}}
@@ -128,4 +149,98 @@
{{{tooltip}}} {{{tooltip}}}
</div> </div>
{{/unless}} {{/unless}}
{{! 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}}
{{/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> </div>
+64
View File
@@ -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>
+43 -6
View File
@@ -1,22 +1,59 @@
<div class="{{cssClass}}"> <div class="{{cssClass}}">
<div class="chat-lethal-damage"> <div class="chat-lethal-damage">
<ul> <ul>
<li><strong>{{weapon.name}} : {{localize "CTHULHUETERNAL.Label.damageRoll"}}</strong></li> <li><strong>{{weapon.name}}
<li>{{localize "CTHULHUETERNAL.Label.result"}} :{{rollResult}} ({{formula}})</li> :
{{localize "CTHULHUETERNAL.Label.damageRoll"}}</strong></li>
<li>{{localize "CTHULHUETERNAL.Label.result"}}
:{{rollResult}}
({{formula}})</li>
{{#if (gt weapon.system.killRadius 0)}} {{#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> <li>{{localize "CTHULHUETERNAL.Label.killRadiusInfo"}}</li>
{{/if}} {{/if}}
{{#if (gt weapon.system.armorPiercing 0)}} {{#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}}
{{#if ammoUsed}} {{#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}} {{/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"
data-combatant-id="{{this.id}}"
title="{{this.name}}"
>
{{this.name}}
</button>
{{/each}}
</div>
</li>
<li class="result-non-lethal">{{localize
"CTHULHUETERNAL.Label.damageMessage"
}}:
<strong>{{rollResult}}</strong>
</li>
</ul> </ul>
+1 -1
View File
@@ -6,7 +6,7 @@
<fieldset> <fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.description"}}</legend> <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> </fieldset>
</section> </section>
+14 -1
View File
@@ -9,7 +9,7 @@
{{#each weapons as |item|}} {{#each weapons as |item|}}
{{!log 'weapon' this}} {{!log 'weapon' this}}
<div class="weapon item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true"> <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 class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" /> <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}}}"> <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> data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div> </div>
</div> </div>
{{else}}
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noWeapons"}}</div>
{{/each}} {{/each}}
</div> </div>
</fieldset> </fieldset>
@@ -99,6 +101,13 @@
{{item.name}} {{item.name}}
</div> </div>
<span class="protection">{{localize "CTHULHUETERNAL.Label.armor"}} : {{item.system.protection}}</span> <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"> <div class="controls">
<a data-tooltip="{{localize 'CTHULHUETERNAL.Edit'}}" data-action="edit" data-item-id="{{item.id}}" <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> 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> data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div> </div>
</div> </div>
{{else}}
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noArmors"}}</div>
{{/each}} {{/each}}
</div> </div>
</fieldset> </fieldset>
@@ -130,6 +141,8 @@
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a> data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div> </div>
</div> </div>
{{else}}
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noGears"}}</div>
{{/each}} {{/each}}
</div> </div>
</fieldset> </fieldset>
+2
View File
@@ -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> <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>
</div> </div>
{{else}}
<div class="empty-state">{{localize "CTHULHUETERNAL.Label.noSkills"}}</div>
{{/each}} {{/each}}
</div> </div>
</fieldset> </fieldset>
+121 -20
View File
@@ -9,25 +9,72 @@
{{#if (eq rollType "resource")}} {{#if (eq rollType "resource")}}
<legend>{{localize "CTHULHUETERNAL.Label.resourceRating"}}</legend> <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">{{rollItem.name}}
<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> <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"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.Storage"}} : {{rollItem.storage}} {{localize "CTHULHUETERNAL.Label.Storage"}}
<input type="checkbox" data-action="selectStorage" {{checked rollItem.enableStorage}}> :
{{rollItem.storage}}
<input
type="checkbox"
data-action="selectStorage"
{{checked rollItem.enableStorage}}
/>
</div> </div>
{{else}} {{else}}
<div class="dialog-skill">{{rollItem.name}} : {{initialScore}}%</div> <div class="dialog-skill">{{rollItem.name}} : {{initialScore}}%</div>
{{/if}} {{/if}}
{{#if weapon}} {{#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")}} {{#if (eq weapon.system.weaponType "melee")}}
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetMove"}} {{localize "CTHULHUETERNAL.Label.targetMove"}}
<select name="meleeTargetMoveChoice" class="roll-skill-modifier"> <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> </select>
</div> </div>
{{/if}} {{/if}}
@@ -35,24 +82,44 @@
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.rangedRange"}} {{localize "CTHULHUETERNAL.Label.rangedRange"}}
<select name="rangedRangeChoice" class="roll-skill-modifier"> <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> </select>
</div> </div>
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetMove"}} {{localize "CTHULHUETERNAL.Label.targetMove"}}
<select name="rangedTargetMoveChoice" class="roll-skill-modifier"> <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> </select>
</div> </div>
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.aimingLastRound"}} {{localize "CTHULHUETERNAL.Label.aimingLastRound"}}
<input type="checkbox" name="aimingLastRound"> <input
type="checkbox"
class="aimingLastRound"
name="aimingLastRound"
/>
</div> </div>
{{#if weapon.system.hasSight}} {{#if weapon.system.hasSight}}
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.aimingWithSight"}} {{localize "CTHULHUETERNAL.Label.aimingWithSight"}}
<input type="checkbox" name="aimingWithSight"> <input
type="checkbox"
class="aimingWithSight"
name="aimingWithSight"
/>
</div> </div>
{{/if}} {{/if}}
@@ -61,26 +128,50 @@
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.visibility"}} {{localize "CTHULHUETERNAL.Label.visibility"}}
<select name="visibilityChoice" class="roll-skill-modifier"> <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> </select>
</div> </div>
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.attackerState"}} {{localize "CTHULHUETERNAL.Label.attackerState"}}
<select name="attackerStateChoice" class="roll-skill-modifier"> <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> </select>
</div> </div>
<div class="dialog-skill"> <div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetSize"}} {{localize "CTHULHUETERNAL.Label.targetSize"}}
<select name="targetSizeChoice" class="roll-skill-modifier"> <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> </select>
</div> </div>
{{#if weapon.system.hasSelectiveFire}} {{#if weapon.system.hasSelectiveFire}}
<div class="dialog-skill">Selective Fire : <div class="dialog-skill">Selective Fire :
<select name="selectiveFireChoice" class="roll-skill-modifier"> <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> </select>
</div> </div>
{{/if}} {{/if}}
@@ -88,20 +179,27 @@
{{/if}} {{/if}}
{{#if isZeroWP}} {{#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}} {{else}}
{{#if isLowWP}} {{#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}} {{/if}}
{{#if isExhausted}} {{#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}} {{/if}}
</fieldSet> </fieldSet>
{{#if hasModifier}} {{#if hasModifier}}
<fieldSet class="dialog-modifier"> <fieldSet class="dialog-modifier">
<legend>{{localize "CTHULHUETERNAL.Label.modifier"}}</legend> <legend>{{localize "CTHULHUETERNAL.Label.modifier"}}</legend>
@@ -114,7 +212,10 @@
{{#if hasMultiplier}} {{#if hasMultiplier}}
<fieldSet class="dialog-modifier"> <fieldSet class="dialog-modifier">
<legend>{{localize "CTHULHUETERNAL.Label.multiplier"}}</legend> <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}} {{selectOptions choiceMultiplier selected=multiplier}}
</select> </select>
</fieldSet> </fieldSet>