11 Commits
13.2.3 ... main

Author SHA1 Message Date
7dc2492c96 COrrection sur bonus de force + bouton pour appliquer les dommages
All checks were successful
Release Creation / build (release) Successful in 46s
2025-11-21 23:36:02 +01:00
2c25820152 Combat/automation enhancements !
All checks were successful
Release Creation / build (release) Successful in 58s
2025-11-13 13:59:02 +01:00
6ad8226265 Attempt to add HUD core 2025-11-12 23:41:15 +01:00
68a0d03740 Update fight options
All checks were successful
Release Creation / build (release) Successful in 53s
2025-11-11 19:56:33 +01:00
8d9cc1045c Fix licence 2025-10-01 15:14:58 +02:00
4af277d8a2 Fight helper ! 2025-10-01 11:24:41 +02:00
bc9f397755 Fix initiative 2025-09-30 17:44:39 +02:00
264a5c7a4c Fix HP and other derivated values not computed anymore
All checks were successful
Release Creation / build (release) Successful in 2m57s
2025-08-10 19:33:28 +02:00
4edbc9b618 Translate weapons + skills in columns
All checks were successful
Release Creation / build (release) Successful in 3m43s
2025-07-29 18:29:02 +02:00
47c2aea941 New weapon management, including shotgun
All checks were successful
Release Creation / build (release) Successful in 1m14s
2025-07-14 21:36:08 +02:00
cdefecdeba New weapon management, including shotgun 2025-07-14 21:33:32 +02:00
67 changed files with 3132 additions and 900 deletions

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.

View File

@@ -20,7 +20,7 @@
"entries": { "entries": {
"Pilot (Type)": { "Pilot (Type)": {
"name": "Pilotage (Type)", "name": "Pilotage (Type)",
"description": "<pPiloter, naviguer et diriger des véhicules nautiques ou aériens. Utilisez cette compétence pour assurer la sécurité d'un navire en cas de danger, par exemple lors d'une tempête ou d'une poursuite dangereuse. Chaque type de véhicule requiert une compétence distincte : Avion, Drone, Hélicoptère, Dirigeable, Petite embarcation, Navire, etc.</p>" "description": "<p>Piloter, naviguer et diriger des véhicules nautiques ou aériens. Utilisez cette compétence pour assurer la sécurité d'un navire en cas de danger, par exemple lors d'une tempête ou d'une poursuite dangereuse. Chaque type de véhicule requiert une compétence distincte : Avion, Drone, Hélicoptère, Dirigeable, Petite embarcation, Navire, etc.</p>"
}, },
"Anthropology": { "Anthropology": {
"name": "Anthropologie", "name": "Anthropologie",

View File

@@ -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"
}
}
}

View File

@@ -183,6 +183,25 @@ i.fvtt-cthulhu-eternal {
color: var(--color-critical-failure); color: var(--color-critical-failure);
font-family: var(--font-title); font-family: var(--font-title);
} }
.chat-san-request ul .san-type-buttons,
.chat-lethal-damage ul .san-type-buttons {
display: flex;
justify-content: center;
align-items: center;
margin: 10px 0;
}
.chat-san-request ul .san-type-buttons button,
.chat-lethal-damage ul .san-type-buttons button {
margin: 0 2px;
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 0.9);
border: none;
padding: 2px 2px;
cursor: pointer;
transition: background-color 0.3s;
min-width: 6rem;
max-width: 6rem;
}
.chat-san-request ul .san-loose-buttons, .chat-san-request ul .san-loose-buttons,
.chat-lethal-damage ul .san-loose-buttons { .chat-lethal-damage ul .san-loose-buttons {
display: flex; display: flex;
@@ -213,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);
@@ -804,7 +849,7 @@ i.fvtt-cthulhu-eternal {
} }
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons { .fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(1, 1fr);
gap: 4px; gap: 4px;
} }
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon { .fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon {
@@ -824,10 +869,24 @@ 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 .weapons .weapon .damage { .fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon .range {
min-width: 6rem; min-width: 6rem;
max-width: 6rem; max-width: 6rem;
} }
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon .ammo {
min-width: 4rem;
max-width: 4rem;
}
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon .lethality {
display: flex;
min-width: 3.2rem;
max-width: 3.2rem;
}
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon .damage {
display: flex;
min-width: 12rem;
max-width: 12rem;
}
.fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon .name { .fvtt-cthulhu-eternal .tab.protagonist-equipment .main-div .weapons .weapon .name {
min-width: 10rem; min-width: 10rem;
max-width: 10rem; max-width: 10rem;
@@ -2153,6 +2212,24 @@ i.fvtt-cthulhu-eternal {
margin-top: 8px; margin-top: 8px;
background-color: var(--color-light-1); background-color: var(--color-light-1);
} }
.fvtt-cthulhu-eternal .weapon-content fieldset input {
max-width: 5rem;
min-width: 5rem;
}
.fvtt-cthulhu-eternal .weapon-content fieldset select {
max-width: 14rem;
min-width: 14rem;
}
.fvtt-cthulhu-eternal .weapon-content fieldset input[type="checkbox"] {
max-width: 1.5rem;
min-width: 1.5rem;
}
.fvtt-cthulhu-eternal .weapon-content fieldset .flexrow > *:not(:first-child) {
margin-left: 1rem;
}
.fvtt-cthulhu-eternal .weapon-content fieldset .damage-distance {
margin-left: 2rem;
}
.fvtt-cthulhu-eternal .weapon-content label { .fvtt-cthulhu-eternal .weapon-content label {
flex: 10%; flex: 10%;
} }
@@ -2990,6 +3067,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;
} }
@@ -3033,21 +3113,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);
@@ -3082,3 +3147,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;
}

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,11 +170,17 @@ 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"
}) })
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)
}) })
$(html).find(".damage-roll").click((event) => { $(html).find(".damage-roll").click((event) => {
CthulhuEternalUtils.damageRoll(message) let formula = $(event.currentTarget).data("roll-value")
CthulhuEternalUtils.damageRoll(message, formula)
}) })
$(html).find(".healing-roll").click((event) => { $(html).find(".healing-roll").click((event) => {
CthulhuEternalUtils.healingRoll(message) CthulhuEternalUtils.healingRoll(message)
@@ -162,7 +191,38 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
$(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()
}
)
}
}) })
/** /**

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",
@@ -212,6 +231,7 @@
"UnarmedCombat": "Unarmed Combat", "UnarmedCombat": "Unarmed Combat",
"RangedWeapons": "Ranged Weapons", "RangedWeapons": "Ranged Weapons",
"FirearmsBeams": "Firearms / Beam Weapons", "FirearmsBeams": "Firearms / Beam Weapons",
"MilitaryTrainingExplosive": "Military Training (Explosives)",
"FIELDS": { "FIELDS": {
"isHealing": { "isHealing": {
"label": "Healing skill" "label": "Healing skill"
@@ -268,7 +288,8 @@
"rangedprimitive": "Ranged Primitive", "rangedprimitive": "Ranged Primitive",
"rangedthrown": "Ranged Thrown", "rangedthrown": "Ranged Thrown",
"rangedfirearm": "Ranged Firearm", "rangedfirearm": "Ranged Firearm",
"unarmed": "Unarmed" "unarmed": "Unarmed",
"rangedexplosive": "Explosive"
}, },
"WeaponSubtype": { "WeaponSubtype": {
"basicfirearm": "Basic Firearm", "basicfirearm": "Basic Firearm",
@@ -283,7 +304,41 @@
"shortspray": "Short Spray", "shortspray": "Short Spray",
"longspray": "Long Spray" "longspray": "Long Spray"
}, },
"Target": {
"Normal": "Normal",
"Stationary": "Stationary",
"MovingRange": "Moving or on the ground",
"MovingFast": "Moving Fast (ie running)",
"MovingVeryFast": "Moving Very Fast",
"HalfCovered": "Half Covered",
"Covered": "Full Covered"
},
"Range": {
"PointBlank": "Point Blank",
"Normal": "Normal",
"Range2x": "Up to Range x2",
"Range5x": "Up to Range x5"
},
"Visibility": {
"Clear": "Clear",
"Obscured": "Obscured",
"Darkness": "Darkness"
},
"Attacker": {
"Normal": "Normal",
"Irritated": "Irritated/Annoyed",
"Corrosive": "Deeply Annoyed"
},
"FIELDS": { "FIELDS": {
"hasDamageDistance": {
"label": "Is damage distance based?"
},
"hasSight": {
"label": "Has sight"
},
"isStunning": {
"label": "Is Stunning"
},
"hasDirectSkill": { "hasDirectSkill": {
"label": "Has direct skill" "label": "Has direct skill"
}, },
@@ -516,21 +571,37 @@
} }
}, },
"Label": { "Label": {
"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",
"Unnatural": "Unnatural", "Unnatural": "Unnatural",
"sanLossViolence": "You suffered a SAN loss due to violence : violence progress path has evolved.", "sanLossViolence": "You suffered a SAN loss due to violence : violence progress path has evolved.",
"sanLossHelplessness": "You suffered a SAN loss due to helplessness : helplessness progress path has evolved.", "sanLossHelplessness": "You suffered a SAN loss due to helplessness : helplessness progress path has evolved.",
"SANLossUnnatural": "You suffered a SAN loss due to unnatural : you may gain an Unnatural skill, check with the GM.",
"SANLossNone": "You suffered a SAN loss, but no special consequences apply.",
"adaptedToViolence": "You are now : Adapted to Violence.", "adaptedToViolence": "You are now : Adapted to Violence.",
"adaptedToViolenceShort": "Adapted to Violence", "adaptedToViolenceShort": "Adapted to Violence",
"adaptedToHelplessness": "You are now : Adapted to Helplessness.", "adaptedToHelplessness": "You are now : Adapted to Helplessness.",
"adaptedToHelplessnessShort": "Adapted to Helplessness", "adaptedToHelplessnessShort": "Adapted to Helplessness",
"SANTest": "You just rolled a SAN test : please select the SAN loss with the buttons below.", "SANTestSuccess": "You just rolled a SAN test with success, but you may still loose SAN points. Select the value of SAN loss with the buttons below.",
"SANTestFailure": "You just rolled a SAN test with failure, you loose SAN points. Select the value of SAN loss with the buttons below.",
"breakingPointReached": "Your SAN has reached your Breaking Point : you suffer a disorder (to be discussed with the GM). Reset the BP to its new value.", "breakingPointReached": "Your SAN has reached your Breaking Point : you suffer a disorder (to be discussed with the GM). Reset the BP to its new value.",
"rollNudge": "Roll Nudge", "rollNudge": "Roll Nudge",
"rollDamage": "Roll Damage", "rollDamage": "Roll Damage",
@@ -613,6 +684,11 @@
"biography": "Biography", "biography": "Biography",
"notes": "Notes", "notes": "Notes",
"weapons": "Weapons", "weapons": "Weapons",
"melee": "Melee",
"Lethality": "Lethality",
"baseRange": "Base Range",
"Ammo": "Ammo",
"armorPiercing": "Armor Piercing",
"HP": "HP", "HP": "HP",
"SAN": "SAN", "SAN": "SAN",
"current": "Current", "current": "Current",
@@ -667,9 +743,11 @@
"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.",
"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",
@@ -678,7 +756,28 @@
"killRadiusInfo": "All targets within the kill radius suffer the damage", "killRadiusInfo": "All targets within the kill radius suffer the damage",
"ammoUsed": "Ammo used", "ammoUsed": "Ammo used",
"lethalityLethal": "Lethal !!", "lethalityLethal": "Lethal !!",
"lethalityNotLethal": "Non-Lethal" "lethalityNotLethal": "Non-Lethal",
"WPSpent": "WP Spent",
"targetMove": "Target Move",
"attackerState": "Attacker State",
"targetSize": "Target Size",
"visibility": "Visibility",
"rangedRange": "Range",
"aimingLastRound": "Aiming Last Round (+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."
@@ -699,11 +798,16 @@
"Tooltip": { "Tooltip": {
"sanBP": ">5 SAN lost in one roll, temporary insanity. If SAN less reaches BP = a Disorder unconscious Breaking and AND reset BP.", "sanBP": ">5 SAN lost in one roll, temporary insanity. If SAN less reaches BP = a Disorder unconscious Breaking and AND reset BP.",
"setBP": "Set the current Breaking Point based on the current SAN value", "setBP": "Set the current Breaking Point based on the current SAN value",
"addBond": "Add a new Bond" "addBond": "Add a new Bond",
"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",
@@ -712,7 +816,9 @@
"NoAmmo": "No more ammo for this weapon. ", "NoAmmo": "No more ammo for this weapon. ",
"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."
} }
} }
} }

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",
@@ -224,6 +225,7 @@
"UnarmedCombat": "Combat à mains nues", "UnarmedCombat": "Combat à mains nues",
"RangedWeapons": "Armes de tir", "RangedWeapons": "Armes de tir",
"FirearmsBeams": "Armes à feu / à rayons", "FirearmsBeams": "Armes à feu / à rayons",
"MilitaryTrainingExplosive": "Entraînement militaire (Explosifs)",
"FIELDS": { "FIELDS": {
"isHealing": { "isHealing": {
"label": "Compétence de soin" "label": "Compétence de soin"
@@ -280,7 +282,8 @@
"rangedprimitive": "A distance - Primitive", "rangedprimitive": "A distance - Primitive",
"rangedthrown": "A distance - Lancer", "rangedthrown": "A distance - Lancer",
"rangedfirearm": "A distance - Arme à feu", "rangedfirearm": "A distance - Arme à feu",
"unarmed": "Non armé" "unarmed": "Non armé",
"rangedexplosive": "Explosif"
}, },
"WeaponSubtype": { "WeaponSubtype": {
"basicfirearm": "Arme à feu de base", "basicfirearm": "Arme à feu de base",
@@ -295,7 +298,41 @@
"shortspray": "Barrage court", "shortspray": "Barrage court",
"longspray": "Barrage long" "longspray": "Barrage long"
}, },
"Target": {
"Normal": "Normal",
"Stationary": "Immobile (+20)",
"MovingRange": "En mouvement ou au sol (-20)",
"MovingFast": "En mouvement rapide (ie course) (-20)",
"MovingVeryFast": "En mouvement très rapide (ie véhicule) (-40)",
"HalfCovered": "A moitié couvert (-20)",
"Covered": "A Couvert (-40)"
},
"Range": {
"PointBlank": "Bout portant (+20)",
"Normal": "Normal",
"Range2x": "de portée à portée x2 (-20)",
"Range5x": "de portéex2 à portée x5 (-40)"
},
"Visibility": {
"Clear": "Normale",
"Obscured": "Obscurci (-20)",
"Darkness": "Obscurité (-40)"
},
"Attacker": {
"Normal": "Normal",
"Irritated": "Irrité/Gêné (-20)",
"Corrosive": "Fortement gêné (-40)"
},
"FIELDS": { "FIELDS": {
"hasDamageDistance": {
"label": "Dégâts basés sur la distance ?"
},
"hasSight": {
"label": "Lunette de visée"
},
"isStunning": {
"label": "Etourdissante"
},
"ammo": { "ammo": {
"label": "Munitions", "label": "Munitions",
"value": { "value": {
@@ -528,20 +565,34 @@
} }
}, },
"Label": { "Label": {
"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é.",
"sanLoss": "Perte de SAN", "sanLoss": "Vous venez de perdre des points de SAN",
"noSanLoss": "Aucune perte de SAN", "noSanLoss": "Aucune perte de SAN",
"sanLossInfo": "Sélectionnez la perte de SAN à l'aide des boutons ci-dessous.", "sanLossInfo": "Sélectionnez la perte de SAN à l'aide des boutons ci-dessous.",
"selectSANType": "Sélectionnez le type de perte de SAN à l'aide des boutons ci-dessous.", "selectSANType": "Sélectionnez maintenant le type SAN perdue à l'aide des boutons ci-dessous.",
"sanLossViolence": "Vous avez subi une perte de SAN due à la violence : le chemin de progression de la violence a évolué.", "sanLossViolence": "Vous avez subi une perte de SAN due à la violence : le chemin de progression de la violence a évolué.",
"sanLossHelplessness": "Vous avez subi une perte de SAN due à l'impuissance : le chemin de progression de l'impuissance a évolué.", "sanLossHelplessness": "Vous avez subi une perte de SAN due à l'impuissance : le chemin de progression de l'impuissance a évolué.",
"SANLossUnnatural": "Vous avez subi une perte de SAN due à l'inconcevable : vous pouvez peut-être faire progresser la compétence Inconcevable, vérifiez avec le MJ.",
"SANLossNone": "Vous avez subi une perte de SAN, mais aucune conséquence spéciale n'est appliquée.",
"adaptedToViolence": "Vous êtes maintenant : Habitué à la violence", "adaptedToViolence": "Vous êtes maintenant : Habitué à la violence",
"adaptedToViolenceShort": "Habitué à la violence", "adaptedToViolenceShort": "Habitué à la violence",
"adaptedToHelplessness": "Vous êtes maintenant : Habitué à l'impuissance", "adaptedToHelplessness": "Vous êtes maintenant : Habitué à l'impuissance",
"adaptedToHelplessnessShort": "Habitué à l'impuissance", "adaptedToHelplessnessShort": "Habitué à l'impuissance",
"SANTest": "Vous venez de faire un jet de SAN : selectionnez la perte de SAN à l'aide des boutons ci-dessous.", "SANTestSuccess": "Vous venez de réussir votre jet de SAN, mais vous pouvez toujours perdre des points de SAN. Sélectionnez la valeur à l'aide des boutons ci-dessous.",
"SANTestFailure": "Vous venez d'échouer votre test de SAN, vous perdez des points de SAN. Sélectionnez la valeur à l'aide des boutons ci-dessous.",
"breakingPointReached": "Vous avez atteint votre Point de Rupture (PR) : vous souffrez d'un trouble mental. Vous devez re-initialiser votre PR à l'aide du bouton disponible dans la section SAN de la fiche de PJ.", "breakingPointReached": "Vous avez atteint votre Point de Rupture (PR) : vous souffrez d'un trouble mental. Vous devez re-initialiser votre PR à l'aide du bouton disponible dans la section SAN de la fiche de PJ.",
"Violence" : "Violence", "Violence" : "Violence",
"Helplessness": "Impuissance", "Helplessness": "Impuissance",
@@ -567,7 +618,7 @@
"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",
@@ -627,6 +678,11 @@
"biography": "Biographie", "biography": "Biographie",
"notes": "Notes", "notes": "Notes",
"weapons": "Armes", "weapons": "Armes",
"melee": "Mêlée",
"Lethality": "Létalité",
"baseRange": "Portée de base",
"Ammo": "Munitions",
"armorPiercing": "Pénétration d'armure",
"HP": "PV", "HP": "PV",
"SAN": "SAN", "SAN": "SAN",
"current": "Actuel", "current": "Actuel",
@@ -684,7 +740,10 @@
"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", "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",
@@ -692,7 +751,29 @@
"killRadiusInfo": "Si la cible est dans le rayon de mortalité, elle subit les dommages.", "killRadiusInfo": "Si la cible est dans le rayon de mortalité, elle subit les dommages.",
"ammoUsed": "Munitions utilisées", "ammoUsed": "Munitions utilisées",
"lethalityLethal": "Létal !!", "lethalityLethal": "Létal !!",
"lethalityNotLethal": "Non létal" "lethalityNotLethal": "Non létal",
"WPSpent": "PVO dépensés",
"targetMove": "Mouvement de la cible",
"attackerState": "Etat de l'attaquant",
"targetSize": "Taille de la cible",
"visibility": "Visibilité",
"rangedRange": "Portée",
"aimingLastRound": "Visée lors du dernier round (+20)",
"aimingWithSight": "Visée avec lunette (+20)",
"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é."
@@ -713,11 +794,16 @@
"Tooltip": { "Tooltip": {
"sanBP": "Perte de 5+ SAN en 1 jet : folie temporaire. SI la SAN atteint le PR : trouble mental, perte de conscience et reset du PR.", "sanBP": "Perte de 5+ SAN en 1 jet : folie temporaire. SI la SAN atteint le PR : trouble mental, perte de conscience et reset du PR.",
"setBP": "Positionner le Point de Rupture à la valeur courant de la SAN", "setBP": "Positionner le Point de Rupture à la valeur courant de la SAN",
"addBond": "Ajouter une Attache" "addBond": "Ajouter une Attache",
"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",
@@ -726,7 +812,9 @@
"NoAmmo": "Aucune munition disponible pour cette arme.", "NoAmmo": "Aucune munition disponible pour cette arme.",
"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."
} }
} }
} }

19
macro_roll.js Normal file
View File

@@ -0,0 +1,19 @@
let nb = 10000
let sum = 0
let results = []
for (let i = 0; i < nb; i++) {
let r = new Roll("1d100")
await r.evaluate()
sum += r.total
results.push(r.total)
}
let mean = sum / nb
let variance = results.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / nb
let stddev = Math.sqrt(variance)
console.log("Average : ", mean)
console.log("Standard deviation : ", stddev)
console.log("Coefficient of variation : ", stddev / mean )
console.log("Min : ", Math.min(...results))
console.log("Max : ", Math.max(...results))

View File

@@ -1,6 +1,8 @@
// System Module Imports // System Module Imports
import { Utils } from './utils.js' import { Utils } from './utils.js'
import { SYSTEM } from "../../config/system.mjs" import { SYSTEM } from "../../config/system.mjs"
import CthulhuEternalUtils from '../../utils.mjs'
export let ActionHandler = null export let ActionHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => { Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
@@ -38,7 +40,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 +51,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 +69,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
}) })
} }
await this.addActions(actions, { await this.addActions(actions, {
id: 'attributes', id: 'characteristics',
type: 'system' type: 'system'
}) })
} }
@@ -80,79 +82,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 +169,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 +178,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 +198,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))
console.log('Building skills actions for skills:', skills)
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,62 +222,55 @@ 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')
for (const item of weapons) { // const rituals = []
for (const item of this.actor.items) {
// 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') { if (item.type === 'weapon') {
const weapons = [] let skill = CthulhuEternalUtils.getWeaponSkill(this.actor, item, era)
item.skillTotal = skill ? skill.system.skillTotal : 0
let weapons = []
const tooltip = { const tooltip = {
content: String(skill.system.computeScore()), content: String(item.skillTotal),
direction: 'LEFT' direction: 'LEFT'
} }
console.log('Weapon skill total for', item.name, 'is', item.skillTotal, item._id)
weapons.push({ weapons.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({ weapons.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) { console.log('Adding weapon actions for', item.name, weapons)
const lethalityTooltip = {
content: String(item.system.lethality),
direction: 'LEFT'
}
weapons.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Lethality'),
id: item._id,
info1: this.#showValue() ? { text: lethalityTooltip.content } : null,
encodedValue: ['lethality', item._id].join(this.delimiter),
tooltip: lethalityTooltip
})
}
await this.addActions(weapons, { await this.addActions(weapons, {
id: 'weapons_' + item._id, id: `weapons_${item._id}`,
type: 'system' type: 'system'
}) })
}/* else if (item.type === 'ritual') { }/* else if (item.type === 'ritual') {

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'
} }
/** /**
@@ -21,18 +21,19 @@ export const REQUIRED_CORE_MODULE_VERSION = '2.0'
* 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' }
} }

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' }

View File

@@ -1,5 +1,5 @@
import { SYSTEM } from "../../config/system.mjs" import { SYSTEM } from "../../config/system.mjs"
import CthulhuEternalRoll from '../../documents/roll.mjs'
export let RollHandler = null export let RollHandler = null
@@ -18,7 +18,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) {
@@ -66,9 +66,10 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @param {string} actionId The actionId * @param {string} actionId The actionId
*/ */
async #handleAction(event, actor, token, actionTypeId, actionId) { async #handleAction(event, actor, token, actionTypeId, actionId) {
console.log('Handling action', actionId, 'of type', actionTypeId, 'for actor', actor.name)
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 +83,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,18 +93,20 @@ 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 let rollType
if (actionId === 'wp' || actionId === 'health') return if (actionId === 'wp' || actionId === 'hp') 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]
console.log('Updating', attr, 'with action', action)
const update = {} const update = {}
update.system = {} update.system = {}
update.system[attr] = {} update.system[attr] = {}
@@ -117,21 +114,23 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
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 > this.actor.system[attr].max || update.system[attr].value < this.actor.system[attr].min) return
return await this.actor.update(update) return await this.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 +141,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 +155,10 @@ 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) let weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
const options = { weapon.damageFormula = weapon.system.damage
actor: this.actor, weapon.damageBonus = actor.system.damageBonus
rollType: 'weapon', await actor.system.roll('weapon', weapon)
key: item.system.skill,
item
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
} }
/** /**
@@ -227,21 +169,10 @@ 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) let weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
if (item.system.lethality > 0 && event.ctrlKey) { weapon.damageFormula = weapon.system.damage
// Toggle on/off lethality weapon.damageBonus = actor.system.damageBonus
const isLethal = !item.system.isLethal await actor.system.roll('damage', weapon)
await item.update({ 'system.isLethal': isLethal })
} else {
const options = {
actor: this.actor,
rollType: 'damage',
key: item.system.damage,
item
}
const roll = new DGDamageRoll(item.system.damage, {}, options)
await this.actor.sheet.processRoll(event, roll)
}
} }
/** /**
@@ -263,8 +194,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
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)*/
} }
} }

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) {
}

View File

@@ -1,8 +1,9 @@
// 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 { MODULE } 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 +69,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)
} }
/** /**
@@ -82,7 +83,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
template: { template: {
class: 'tah-style-template-style', // The class to add to first DIV element class: 'tah-style-template-style', // The class to add to first DIV element
file: 'tah-template-style', // The file without the css extension file: 'tah-template-style', // The file without the css extension
moduleId: SYSTEM.ID, // The module ID moduleId: MODULE.ID, // The module ID
name: 'Template Style' // The name to display in the Token Action HUD Core 'Style' module setting name: 'Template Style' // The name to display in the Token Action HUD Core 'Style' module setting
} }
} }

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`)
} }

View File

@@ -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)
}) })
} }

View File

@@ -98,10 +98,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
@@ -225,6 +235,7 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
case "damage": case "damage":
li = $(event.currentTarget).parents(".item"); li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id")); item = this.actor.items.get(li.data("item-id"));
item.damageFormula = $(event.currentTarget).data("roll-value") || item.system.damage
item.damageBonus = this.actor.system.damageBonus item.damageBonus = this.actor.system.damageBonus
break break
case "san": case "san":

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" },
@@ -154,7 +244,8 @@ export const WEAPON_SKILL_MAPPING = {
"rangedprimitive": "CTHULHUETERNAL.Skill.Firearms", "rangedprimitive": "CTHULHUETERNAL.Skill.Firearms",
"rangedthrown": "CTHULHUETERNAL.Skill.Athletics", "rangedthrown": "CTHULHUETERNAL.Skill.Athletics",
"rangedfirearm": "CTHULHUETERNAL.Skill.Firearms", "rangedfirearm": "CTHULHUETERNAL.Skill.Firearms",
"unarmed": "CTHULHUETERNAL.Skill.UnarmedCombat" "unarmed": "CTHULHUETERNAL.Skill.UnarmedCombat",
"rangedexplosive": "CTHULHUETERNAL.Skill.MilitaryTrainingExplosive"
}, },
victorian: { victorian: {
"melee": "CTHULHUETERNAL.Skill.Melee", "melee": "CTHULHUETERNAL.Skill.Melee",
@@ -245,6 +336,45 @@ export const WEAPON_SELECTIVE_FIRE_CHOICES = {
"longspray": { id: "longspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longspray", ammoUsed: 20, lethality: 10, killRadius: 3 }, "longspray": { id: "longspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longspray", ammoUsed: 20, lethality: 10, killRadius: 3 },
} }
// Melee stuff
export const WEAPON_MELEE_TARGET_MOVE = {
"normal": { id: "normal", label: "CTHULHUETERNAL.Weapon.Target.Normal", modifier: 0 },
"stationary": { id: "stationary", label: "CTHULHUETERNAL.Weapon.Target.Stationary", modifier: 20 },
"movingfast": { id: "movingfast", label: "CTHULHUETERNAL.Weapon.Target.MovingFast", modifier: -20 },
"movingveryfast": { id: "movingveryfast", label: "CTHULHUETERNAL.Weapon.Target.MovingVeryFast", modifier: -40 },
}
// Ranged stuff
export const WEAPON_RANGED_RANGE = {
"pointblank": { id: "pointblank", label: "CTHULHUETERNAL.Weapon.Range.PointBlank", modifier: +20 },
"normal": { id: "normal", label: "CTHULHUETERNAL.Weapon.Range.Normal", modifier: 0 },
"range2x": { id: "range2x", label: "CTHULHUETERNAL.Weapon.Range.Range2x", modifier: -20 },
"range5x": { id: "range5x", label: "CTHULHUETERNAL.Weapon.Range.Range5x", modifier: -40 }
}
export const WEAPON_RANGED_TARGET_MOVE = {
"normal": { id: "normal", label: "CTHULHUETERNAL.Weapon.Target.Normal", modifier: 0 },
"stationary": { id: "stationary", label: "CTHULHUETERNAL.Weapon.Target.Stationary", modifier: 20 },
"movingfast": { id: "movingfast", label: "CTHULHUETERNAL.Weapon.Target.MovingRange", modifier: -20 },
"movingveryfast": { id: "movingveryfast", label: "CTHULHUETERNAL.Weapon.Target.MovingVeryFast", modifier: -40 },
}
// Common stuff
export const WEAPON_ATTACKER_STATE = {
"normal": { id: "normal", label: "CTHULHUETERNAL.Weapon.Target.Normal", modifier: 0 },
"irritated": { id: "irritated", label: "CTHULHUETERNAL.Weapon.Attacker.Irritated", modifier: -20 },
"corrosive": { id: "corrosive", label: "CTHULHUETERNAL.Weapon.Attacker.Corrosive", modifier: -40 },
}
export const WEAPON_TARGET_SIZE = {
"normal": { id: "normal", label: "CTHULHUETERNAL.Weapon.Target.Normal", modifier: 0 },
"halfcovered": { id: "halfcovered", label: "CTHULHUETERNAL.Weapon.Target.HalfCovered", modifier: -20 },
"covered": { id: "covered", label: "CTHULHUETERNAL.Weapon.Target.Covered", modifier: -40 },
}
export const WEAPON_VISIBILITY = {
"clear": { id: "clear", label: "CTHULHUETERNAL.Weapon.Visibility.Clear", modifier: 0 },
"obscured": { id: "obscured", label: "CTHULHUETERNAL.Weapon.Visibility.Obscured", modifier: -20 },
"darkness": { id: "darkness", label: "CTHULHUETERNAL.Weapon.Visibility.Darkness", modifier: -40 },
}
export const RITUAL_TYPES = { export const RITUAL_TYPES = {
"simple": "CTHULHUETERNAL.Ritual.Simple", "simple": "CTHULHUETERNAL.Ritual.Simple",
"difficult": "CTHULHUETERNAL.Ritual.Difficult", "difficult": "CTHULHUETERNAL.Ritual.Difficult",
@@ -277,5 +407,11 @@ export const SYSTEM = {
MULTIPLIER_CHOICES, MULTIPLIER_CHOICES,
ASCII, ASCII,
DAMAGE_BONUS, DAMAGE_BONUS,
RITUAL_TYPES RITUAL_TYPES,
WEAPON_MELEE_TARGET_MOVE,
WEAPON_RANGED_RANGE,
WEAPON_RANGED_TARGET_MOVE,
WEAPON_ATTACKER_STATE,
WEAPON_TARGET_SIZE,
WEAPON_VISIBILITY
} }

View File

@@ -3,7 +3,8 @@ export const WEAPON_TYPE = {
"rangedprimitive": "CTHULHUETERNAL.Weapon.WeaponType.rangedprimitive", "rangedprimitive": "CTHULHUETERNAL.Weapon.WeaponType.rangedprimitive",
"rangedthrown": "CTHULHUETERNAL.Weapon.WeaponType.rangedthrown", "rangedthrown": "CTHULHUETERNAL.Weapon.WeaponType.rangedthrown",
"rangedfirearm": "CTHULHUETERNAL.Weapon.WeaponType.rangedfirearm", "rangedfirearm": "CTHULHUETERNAL.Weapon.WeaponType.rangedfirearm",
"unarmed": "CTHULHUETERNAL.Weapon.WeaponType.unarmed" "unarmed": "CTHULHUETERNAL.Weapon.WeaponType.unarmed",
"rangedexplosive": "CTHULHUETERNAL.Weapon.WeaponType.rangedexplosive",
} }
export const WEAPON_SUBTYPE = { export const WEAPON_SUBTYPE = {

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);
@@ -43,6 +45,44 @@ export default class CthulhuEternalActor extends Actor {
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 })
}
console.log("Applying wounds", { woundData, totalArmor, effectiveWounds })
// 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") {

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 {
/** /**
@@ -106,7 +107,7 @@ export default class CthulhuEternalRoll extends Roll {
} }
static buildSelectiveFireChoices(actor, weapon) { static buildSelectiveFireChoices(actor, weapon) {
if (!weapon || !weapon?.system?.hasSelectiveFire) { if (!weapon?.system?.hasSelectiveFire) {
return {} return {}
} }
// Loop thru the selective fire choices and build the choices object when enough ammo in the weapon // Loop thru the selective fire choices and build the choices object when enough ammo in the weapon
@@ -128,20 +129,23 @@ 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 }
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) {
@@ -149,60 +153,87 @@ export default class CthulhuEternalRoll extends Roll {
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
} }
// If the weapon is not lethal, we can proceed with the regular damage roll // If the weapon is not lethal, we can proceed with the regular damage roll
let formula = weapon.system.damage let formula = weapon?.damageFormula || weapon.system.damage || "0"
if (weapon.system.weaponType === "melee" || weapon.system.weaponType === "unarmed") { if (weapon.system.applyDamageBonus) {
formula += ` + ${actor.system?.damageBonus}` formula += ` + ${actor.system?.damageBonus}`
} }
if (options?.previousResultType === "successCritical") { if (options?.previousResultType === "successCritical") {
formula = `( ${formula} ) * 2` formula = `( ${formula} ) * 2`
} }
let damageRoll = new Roll(formula)
await damageRoll.evaluate()
let msgData = {
actorId: actor.id,
weapon,
formula,
ammoUsed: weapon?.ammoUsed || 0,
rollResult: damageRoll.total,
combatants: combatants
}
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-regular-damage.hbs", msgData)
let msg = await ChatMessage.create({
user: game.user.id,
content: flavor,
speaker: ChatMessage.getSpeaker({ actor: actor }),
}, { rollMode: options.rollMode, create: true })
await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
}
static computeWeaponModifiers(rollData) {
let modifier = SYSTEM.WEAPON_MELEE_TARGET_MOVE[rollData.meleeTargetMoveChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_RANGED_RANGE[rollData.rangedRangeChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_RANGED_TARGET_MOVE[rollData.rangedTargetMoveChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_VISIBILITY[rollData.visibilityChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_ATTACKER_STATE[rollData.attackerStateChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_TARGET_SIZE[rollData.targetSizeChoice]?.modifier || 0
modifier += (rollData.aimingLastRoundFlag) ? 20 : 0
modifier += (rollData.aimingWithSightFlag) ? 20 : 0
return modifier
}
static async processAmmoUsed(actor, weapon) {
let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
// Selective fire management
if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
if (choice.ammoUsed > weapon.system.ammo.value) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
return
}
ammoUsed = choice.ammoUsed // Override ammo used
}
ammoUsed = Number(ammoUsed)
if (ammoUsed > 0) { if (ammoUsed > 0) {
await actor.updateEmbeddedDocuments("Item", [{ await actor.updateEmbeddedDocuments("Item", [{
_id: weapon._id, _id: weapon._id,
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed) "system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
}]) }])
} }
console.log("Weapon damage formula", formula, weapon, ammoUsed) weapon.ammoUsed = ammoUsed
let damageRoll = new Roll(formula)
await damageRoll.evaluate()
let msgData = {
weapon,
formula,
ammoUsed,
rollResult: damageRoll.total,
}
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-regular-damage.hbs", msgData)
ChatMessage.create({
user: game.user.id,
content: flavor,
speaker: ChatMessage.getSpeaker({ actor: actor }),
}, { rollMode: options.rollMode, create: true })
} }
/** /**
@@ -226,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":
@@ -266,26 +298,19 @@ export default class CthulhuEternalRoll extends Roll {
console.log("WP Not found", era, options.rollItem.system.weaponType) 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 console.log("WEAPON", era, options.rollItem)
} else {
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
options.rollItem = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
if (!options.rollItem) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponSkill"))
return
}
options.initialScore = options.rollItem.system.computeScore()
console.log("WEAPON", skillName, era, options.rollItem)
}
} }
break break
default: default:
@@ -311,6 +336,7 @@ export default class CthulhuEternalRoll extends Roll {
rollType: options.rollType, rollType: options.rollType,
rollItem: foundry.utils.duplicate(options.rollItem), // Object only, no class rollItem: foundry.utils.duplicate(options.rollItem), // Object only, no class
weapon: options?.weapon, weapon: options?.weapon,
isRangedWeapon: options?.weapon?.system?.isRanged(),
initialScore: options.initialScore, initialScore: options.initialScore,
targetScore: options.initialScore, targetScore: options.initialScore,
isLowWP: options.isLowWP, isLowWP: options.isLowWP,
@@ -324,12 +350,27 @@ export default class CthulhuEternalRoll extends Roll {
choiceModifier, choiceModifier,
choiceMultiplier, choiceMultiplier,
choiceSelectiveFire, choiceSelectiveFire,
choiceMeleeTargetMove: SYSTEM.WEAPON_MELEE_TARGET_MOVE,
choiceRangedRange: SYSTEM.WEAPON_RANGED_RANGE,
choiceRangedTargetMove: SYSTEM.WEAPON_RANGED_TARGET_MOVE,
choiceVisibility: SYSTEM.WEAPON_VISIBILITY,
choiceAttackerState: SYSTEM.WEAPON_ATTACKER_STATE,
choiceTargetSize: SYSTEM.WEAPON_TARGET_SIZE,
selectiveFireChoice: "shortburst",
meleeTargetMoveChoice: "normal",
rangedRangeChoice: "normal",
rangedTargetMoveChoice: "normal",
visibilityChoice: "clear",
attackerStateChoice: "normal",
targetSizeChoice: "normal",
aimingLastRoundFlag: false,
aimingWithSightFlag: false,
modifier,
formula, formula,
targetName: target?.name,
hasTarget: options.hasTarget, hasTarget: options.hasTarget,
hasModifier, hasModifier,
hasMultiplier, hasMultiplier,
modifier,
selectiveFireChoice: "shortburst",
multiplier multiplier
} }
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/roll-dialog.hbs", dialogContext) const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/roll-dialog.hbs", dialogContext)
@@ -372,6 +413,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)
})
} }
}) })
@@ -379,14 +428,20 @@ 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) console.log("Rolldata", rollData)
if (options.rollType === "resource") { if (options.rollType === "resource") {
rollData.targetScore = options.initialScore * Number(rollContext.multiplier) rollData.targetScore = options.initialScore * Number(rollContext.multiplier)
} else { } else {
rollData.targetScore = Math.min(Math.max(options.initialScore + Number(rollData.modifier), 0), 100) let totalModifier = this.computeWeaponModifiers(rollData) + Number(rollData.modifier)
rollData.totalModifier = Math.min(totalModifier, 40)
rollData.targetScore = Math.min(Math.max(options.initialScore + Number(rollData.totalModifier), 0), 100)
if (rollData.isLowWP || rollData.isExhausted) { if (rollData.isLowWP || rollData.isExhausted) {
rollData.targetScore -= 20 rollData.targetScore -= 20
} }
@@ -395,11 +450,15 @@ export default class CthulhuEternalRoll extends Roll {
} }
rollData.targetScore = Math.min(Math.max(rollData.targetScore, 0), 100) rollData.targetScore = Math.min(Math.max(rollData.targetScore, 0), 100)
} }
if (!rollData.targetScore) { if (rollData.targetScore === undefined || rollData.targetScore === null) {
rollData.targetScore = options.initialScore rollData.targetScore = options.initialScore
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)
@@ -456,10 +515,70 @@ export default class CthulhuEternalRoll extends Roll {
this.options.isCritical = resultType === "successCritical" || resultType === "failureCritical" this.options.isCritical = resultType === "successCritical" || resultType === "failureCritical"
} }
rollData.resultType = resultType rollData.resultType = resultType
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
}
}
}
} }
/** /**
@@ -557,7 +676,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,
@@ -568,10 +687,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) {
@@ -601,12 +721,14 @@ export default class CthulhuEternalRoll extends Roll {
rollItem: rollItem, rollItem: rollItem,
rollData: rollData rollData: rollData
} }
// Get array of gamemaster ID
let msg = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-san-request.hbs", msgData) let msg = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-san-request.hbs", msgData)
let chatMsg = await ChatMessage.create({ let chatMsg = await ChatMessage.create({
user: game.user.id, user: game.user.id,
content: msg, content: msg,
speaker: ChatMessage.getSpeaker({ actor: rollData.actor }) speaker: ChatMessage.getSpeaker({ actor: rollData.actor }),
}, { rollMode: rollData.rollMode, create: true }) whisper: game.users.filter(u => u.isGM).map(u => u.id),
})
await chatMsg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData) await chatMsg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData)
} }
} }

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
@@ -90,6 +90,10 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
prepareDerivedData() { prepareDerivedData() {
super.prepareDerivedData(); super.prepareDerivedData();
if (!game.user.isGM) {
return
}
let updates = {} let updates = {}
if (this.wp.max !== this.characteristics.pow.value) { if (this.wp.max !== this.characteristics.pow.value) {
updates[`system.wp.max`] = this.characteristics.pow.value updates[`system.wp.max`] = this.characteristics.pow.value
@@ -127,7 +131,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
dmgBonus = 0 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) {
@@ -137,11 +141,17 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
// BP (Breaking Point) management // BP (Breaking Point) management
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
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
whisper: [w.id]
}) })
} }
}
// Unconsciousness management // Unconsciousness management
if (!this.hp.unconscious && this.hp.value <= 2) { if (!this.hp.unconscious && this.hp.value <= 2) {
@@ -195,10 +205,6 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
} }
async applySANConsequences(rollData) { async applySANConsequences(rollData) {
// If sanType is "non", do nothing
if (rollData.sanType === "none") {
return
}
let msgData = { let msgData = {
sanType: rollData.sanType, sanType: rollData.sanType,
sanLoss: rollData.sanLoss, sanLoss: rollData.sanLoss,
@@ -265,14 +271,21 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
updates[`system.san.helplessness`] = [false, false, false] updates[`system.san.helplessness`] = [false, false, false]
msgData.adaptedToHelplessness = true msgData.adaptedToHelplessness = true
} }
} else if (rollData.sanType === "unnatural") {
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-unnatural.hbs"
} else {
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-none.hbs"
} }
console.log("CthulhuEternalProtagonist.applySANConsequences", rollData, updates, template)
let content = await foundry.applications.handlebars.renderTemplate(template, msgData) let content = await foundry.applications.handlebars.renderTemplate(template, msgData)
let msg = await ChatMessage.create({ let msg = await ChatMessage.create({
content: content, content: content,
speaker: ChatMessage.getSpeaker({ actor: this.parent }) speaker: ChatMessage.getSpeaker({ actor: this.parent }),
whisper: game.users.filter(u => u.isGM).map(u => u.id),
}) })
msg.setFlag("fvtt-cthulhu-eternal", "rollData", msgData) await msg.setFlag("fvtt-cthulhu-eternal", "rollData", msgData)
if (Object.keys(updates).length > 0) { if (Object.keys(updates).length > 0) {
this.parent.update(updates) this.parent.update(updates)
} }
@@ -284,12 +297,14 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
let san = Math.max(Math.min(this.san.value + rollData.sanLoss, this.san.max), 0) let san = Math.max(Math.min(this.san.value + rollData.sanLoss, this.san.max), 0)
if (this.san.value !== san) { if (this.san.value !== san) {
updates[`system.san.value`] = san updates[`system.san.value`] = san
rollData.sanValue = san
const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-san-type-request.hbs", rollData) const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-san-type-request.hbs", rollData)
let msg = await ChatMessage.create({ let msg = await ChatMessage.create({
content: content, content: content,
speaker: ChatMessage.getSpeaker({ actor: this.parent }) speaker: ChatMessage.getSpeaker({ actor: this.parent }),
whisper: game.users.filter(u => u.isGM).map(u => u.id)
}) })
msg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData) await msg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData)
} }
if (Object.keys(updates).length > 0) { if (Object.keys(updates).length > 0) {
this.parent.update(updates) this.parent.update(updates)

View File

@@ -15,7 +15,18 @@ export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel
schema.hasDirectSkill = new fields.BooleanField({ required: true, initial: false }) schema.hasDirectSkill = new fields.BooleanField({ required: true, initial: false })
schema.directSkillValue = new fields.NumberField({ required: true, initial: 0, min: 0, max: 99 }) schema.directSkillValue = new fields.NumberField({ required: true, initial: 0, min: 0, max: 99 })
schema.hasDamageDistance = new fields.BooleanField({ required: true, initial: false })
schema.damageDistance = new fields.SchemaField(Array.fromRange(6, 1).reduce((damageDistance, i) => {
damageDistance[`dist${i}`] = new fields.SchemaField({
damage: new fields.StringField({ required: true, initial: "1d6" }),
distance: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
return damageDistance
}, {}));
schema.hasSelectiveFire = new fields.BooleanField({ required: true, initial: false }) schema.hasSelectiveFire = new fields.BooleanField({ required: true, initial: false })
schema.hasSight = new fields.BooleanField({ required: true, initial: false })
schema.isStunning = new fields.BooleanField({ required: true, initial: false })
schema.damage = new fields.StringField({ required: true, initial: "1d6" }) schema.damage = new fields.StringField({ required: true, initial: "1d6" })
schema.applyDamageBonus = new fields.BooleanField({ required: true, initial: false }) schema.applyDamageBonus = new fields.BooleanField({ required: true, initial: false })
schema.baseRange = new fields.StringField({ required: true, initial: "" }) schema.baseRange = new fields.StringField({ required: true, initial: "" })
@@ -43,7 +54,8 @@ export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel
} }
isRanged() { isRanged() {
return this.weaponType.includes("ranged") console.log("isRanged", this.weaponType, this)
return this.weaponType.match("ranged")
} }
isFireArm() { isFireArm() {

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) {
@@ -180,6 +188,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) {
@@ -196,11 +251,6 @@ export default class CthulhuEternalUtils {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noSanTypeFound")) ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noSanTypeFound"))
return return
} }
// If the sanType is "none", we don't apply any SAN processing
if (sanType === "none") {
ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.noSanLossApplied"))
return
}
rollData.sanType = sanType rollData.sanType = sanType
await actor.system.applySANConsequences(rollData) await actor.system.applySANConsequences(rollData)
// Delete the roll message // Delete the roll message
@@ -253,16 +303,183 @@ export default class CthulhuEternalUtils {
}) })
} }
static async damageRoll(rollMessage) { 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 }
}
console.log("Opposed roll result", winner, loser)
// Check if winner was attacking with a weapon that can apply damage
let canApplyDamage = winner && winner.rollData?.weapon && winner.rollData.weapon.system
// Prepare data for the template
let msgData = {
winner: {
actor: {
name: winner.actor.name,
img: winner.actor.img
},
rollData: {
rollResult: winner.rollData.rollResult || winner.rollData.total,
isCritical: winner.rollData.isCritical,
weapon: winner.rollData.weapon
}
},
loser: {
actor: {
name: loser.actor.name,
img: loser.actor.img
},
rollData: {
rollResult: loser.rollData.rollResult || loser.rollData.total,
isCritical: loser.rollData.isCritical
}
},
canApplyDamage: canApplyDamage
}
// Render the template
let content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-cthulhu-eternal/templates/chat-opposed-result.hbs",
msgData
)
// Display the result in chat
let chatMsg = await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: winner.actor.id }),
content: content
})
// Store the winner's roll data for damage roll if applicable
if (canApplyDamage) {
await chatMsg.setFlag("fvtt-cthulhu-eternal", "rollData", winner.rollData)
}
}
static translateRangeUnit(range) {
if (typeof range === 'string') {
return game.i18n.localize(`CTHULHUETERNAL.Label.${range}`)
} else if (typeof range === 'number') {
return range
} else {
console.warn("CTHULHU ETERNAL | translateRange called with an unknown type", range)
return range
}
}
static translateRange(range) {
// If the range is a string, replace STR with FOR
if (typeof range === 'string') {
return range.replace(/STR/g, "FOR").replace(/str/g, "for")
}
return range
}
static async registerBabeleTranslations(babele) {
babele.registerConverters({
'translateRangeUnit': (originalValue) => {
return CthulhuEternalUtils.translateRangeUnit(originalValue)
},
'translateRange': (originalValue) => {
return CthulhuEternalUtils.translateRange(originalValue)
}
})
}
static async damageRoll(rollMessage, formula = null) {
let rollData = rollMessage.rolls[0]?.options?.rollData 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) 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
actor.system.roll("damage", rollData.weapon) actor.system.roll("damage", rollData.weapon)
} }
@@ -331,6 +548,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() {
@@ -349,4 +570,38 @@ 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
}
console.log("Applying wounds", woundData)
// 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)
}
} }

View File

@@ -1 +1 @@
MANIFEST-000171 MANIFEST-000273

View File

@@ -1,7 +1,7 @@
2025/07/09-17:04:59.082095 7f2a0effd6c0 Recovering log #169 2025/11/21-23:23:35.521610 7f21eeffd6c0 Recovering log #271
2025/07/09-17:04:59.092675 7f2a0effd6c0 Delete type=3 #167 2025/11/21-23:23:35.612353 7f21eeffd6c0 Delete type=3 #269
2025/07/09-17:04:59.092780 7f2a0effd6c0 Delete type=0 #169 2025/11/21-23:23:35.612430 7f21eeffd6c0 Delete type=0 #271
2025/07/09-17:18:46.206680 7f276ffff6c0 Level-0 table #174: started 2025/11/21-23:35:34.796894 7f21ed7fa6c0 Level-0 table #276: started
2025/07/09-17:18:46.206717 7f276ffff6c0 Level-0 table #174: 0 bytes OK 2025/11/21-23:35:34.796917 7f21ed7fa6c0 Level-0 table #276: 0 bytes OK
2025/07/09-17:18:46.235941 7f276ffff6c0 Delete type=0 #172 2025/11/21-23:35:34.806397 7f21ed7fa6c0 Delete type=0 #274
2025/07/09-17:18:46.287862 7f276ffff6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end) 2025/11/21-23:35:34.806600 7f21ed7fa6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2025/06/29-22:25:29.091880 7fda6d9f96c0 Recovering log #165 2025/11/12-23:48:30.686758 7f4781ffb6c0 Recovering log #267
2025/06/29-22:25:29.103184 7fda6d9f96c0 Delete type=3 #163 2025/11/12-23:48:30.697449 7f4781ffb6c0 Delete type=3 #265
2025/06/29-22:25:29.103305 7fda6d9f96c0 Delete type=0 #165 2025/11/12-23:48:30.697507 7f4781ffb6c0 Delete type=0 #267
2025/06/29-22:26:56.370766 7fda5bbff6c0 Level-0 table #170: started 2025/11/13-13:58:42.410831 7f4780bff6c0 Level-0 table #272: started
2025/06/29-22:26:56.370832 7fda5bbff6c0 Level-0 table #170: 0 bytes OK 2025/11/13-13:58:42.410865 7f4780bff6c0 Level-0 table #272: 0 bytes OK
2025/06/29-22:26:56.379673 7fda5bbff6c0 Delete type=0 #168 2025/11/13-13:58:42.417362 7f4780bff6c0 Delete type=0 #270
2025/06/29-22:26:56.380010 7fda5bbff6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end) 2025/11/13-13:58:42.434870 7f4780bff6c0 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

View File

View File

@@ -1 +1 @@
MANIFEST-000340 MANIFEST-000442

View File

@@ -1,7 +1,7 @@
2025/07/09-17:04:59.062504 7f2a0dffb6c0 Recovering log #338 2025/11/21-23:23:35.280848 7f21edffb6c0 Recovering log #440
2025/07/09-17:04:59.072990 7f2a0dffb6c0 Delete type=3 #336 2025/11/21-23:23:35.368467 7f21edffb6c0 Delete type=3 #438
2025/07/09-17:04:59.073130 7f2a0dffb6c0 Delete type=0 #338 2025/11/21-23:23:35.368552 7f21edffb6c0 Delete type=0 #440
2025/07/09-17:18:46.236069 7f276ffff6c0 Level-0 table #343: started 2025/11/21-23:35:34.777225 7f21ed7fa6c0 Level-0 table #445: started
2025/07/09-17:18:46.236095 7f276ffff6c0 Level-0 table #343: 0 bytes OK 2025/11/21-23:35:34.777275 7f21ed7fa6c0 Level-0 table #445: 0 bytes OK
2025/07/09-17:18:46.287671 7f276ffff6c0 Delete type=0 #341 2025/11/21-23:35:34.786357 7f21ed7fa6c0 Delete type=0 #443
2025/07/09-17:18:46.287875 7f276ffff6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end) 2025/11/21-23:35:34.806576 7f21ed7fa6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)

View File

@@ -1,7 +1,7 @@
2025/06/29-22:25:29.069935 7fda6d1f86c0 Recovering log #334 2025/11/12-23:48:30.659183 7f47817fa6c0 Recovering log #436
2025/06/29-22:25:29.081064 7fda6d1f86c0 Delete type=3 #332 2025/11/12-23:48:30.670042 7f47817fa6c0 Delete type=3 #434
2025/06/29-22:25:29.081192 7fda6d1f86c0 Delete type=0 #334 2025/11/12-23:48:30.670124 7f47817fa6c0 Delete type=0 #436
2025/06/29-22:26:56.360514 7fda5bbff6c0 Level-0 table #339: started 2025/11/13-13:58:42.417482 7f4780bff6c0 Level-0 table #441: started
2025/06/29-22:26:56.360577 7fda5bbff6c0 Level-0 table #339: 0 bytes OK 2025/11/13-13:58:42.417509 7f4780bff6c0 Level-0 table #441: 0 bytes OK
2025/06/29-22:26:56.370559 7fda5bbff6c0 Delete type=0 #337 2025/11/13-13:58:42.423396 7f4780bff6c0 Delete type=0 #439
2025/06/29-22:26:56.379992 7fda5bbff6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end) 2025/11/13-13:58:42.434882 7f4780bff6c0 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

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000088

View File

7
packs-system/weapons/LOG Normal file
View File

@@ -0,0 +1,7 @@
2025/11/21-23:23:35.396467 7f21ef7fe6c0 Recovering log #86
2025/11/21-23:23:35.502570 7f21ef7fe6c0 Delete type=3 #84
2025/11/21-23:23:35.502631 7f21ef7fe6c0 Delete type=0 #86
2025/11/21-23:35:34.786516 7f21ed7fa6c0 Level-0 table #91: started
2025/11/21-23:35:34.786543 7f21ed7fa6c0 Level-0 table #91: 0 bytes OK
2025/11/21-23:35:34.796797 7f21ed7fa6c0 Delete type=0 #89
2025/11/21-23:35:34.806589 7f21ed7fa6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,7 @@
2025/11/12-23:48:30.674156 7f4781ffb6c0 Recovering log #82
2025/11/12-23:48:30.683495 7f4781ffb6c0 Delete type=3 #80
2025/11/12-23:48:30.683546 7f4781ffb6c0 Delete type=0 #82
2025/11/13-13:58:42.404378 7f4780bff6c0 Level-0 table #87: started
2025/11/13-13:58:42.404437 7f4780bff6c0 Level-0 table #87: 0 bytes OK
2025/11/13-13:58:42.410719 7f4780bff6c0 Delete type=0 #85
2025/11/13-13:58:42.434856 7f4780bff6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

View File

View File

@@ -98,6 +98,23 @@ i.fvtt-cthulhu-eternal {
color: var(--color-critical-failure); color: var(--color-critical-failure);
font-family: var(--font-title); font-family: var(--font-title);
} }
.san-type-buttons {
display: flex;
justify-content: center;
align-items: center;
margin: 10px 0;
button {
margin: 0 2px;
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 0.9);
border: none;
padding: 2px 2px;
cursor: pointer;
transition: background-color 0.3s;
min-width: 6rem;
max-width: 6rem;
}
}
.san-loose-buttons { .san-loose-buttons {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -111,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 {
@@ -124,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;
}
}
}
} }
} }

View File

@@ -478,7 +478,7 @@
} }
.weapons { .weapons {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(1, 1fr);
gap: 4px; gap: 4px;
.weapon { .weapon {
display: flex; display: flex;
@@ -496,10 +496,24 @@
min-width: 1.8rem; min-width: 1.8rem;
max-width: 1.8rem; max-width: 1.8rem;
} }
.damage { .range {
min-width: 6rem; min-width: 6rem;
max-width: 6rem; max-width: 6rem;
} }
.ammo {
min-width: 4rem;
max-width: 4rem;
}
.lethality {
display: flex;
min-width: 3.2rem;
max-width: 3.2rem;
}
.damage {
display: flex;
min-width: 12rem;
max-width: 12rem;
}
.name { .name {
min-width: 10rem; min-width: 10rem;
max-width: 10rem; max-width: 10rem;

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;
}
}
}
} }

View File

@@ -14,6 +14,24 @@
fieldset { fieldset {
margin-top: 8px; margin-top: 8px;
background-color: var(--color-light-1); background-color: var(--color-light-1);
input {
max-width: 5rem;
min-width: 5rem;
}
select {
max-width: 14rem;
min-width: 14rem;
}
input[type="checkbox"] {
max-width: 1.5rem;
min-width: 1.5rem;
}
.flexrow > *:not(:first-child) {
margin-left: 1rem;
}
.damage-distance {
margin-left: 2rem;
}
} }
label { label {

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",
@@ -152,6 +152,18 @@
}, },
"flags": {} "flags": {}
}, },
{
"name": "weapons",
"label": "Weapons",
"system": "fvtt-cthulhu-eternal",
"path": "packs-system/weapons",
"type": "Item",
"ownership": {
"PLAYER": "OBSERVER",
"ASSISTANT": "OWNER"
},
"flags": {}
},
{ {
"name": "rituals", "name": "rituals",
"label": "Rituals", "label": "Rituals",

View File

@@ -8,21 +8,33 @@
{{#if weapon.system.selectiveFireChoice}} {{#if weapon.system.selectiveFireChoice}}
<li>{{weapon.system.selectiveFireChoiceLabel}}</li> <li>{{weapon.system.selectiveFireChoiceLabel}}</li>
{{/if}} {{/if}}
{{#if weapon.system.killRadius}} {{#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)}}
<li>{{localize "CTHULHUETERNAL.Label.armorPiercing"}} : {{weapon.system.armorPiercing}}</li>
{{/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="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>

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}} WP spent</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,88 +37,101 @@
{{/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}}
{{#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"}} : {{modifier}}%</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
{{#if (eq rollType "weapon")}} "CTHULHUETERNAL.Label.criticalSuccess"
{{#if (eq weapon.system.weaponType "rangedfirearm")}} }}
<a class="damage-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollDamage"}}"><i class="fa-solid fa-gun"></i></a>
{{else}}
<a class="damage-roll" 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" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollHealing"}}"><i class="fa-solid fa-heart"></i></a>
{{/if}}
{{/if}}
</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}}
{{#if (eq weapon.system.weaponType "rangedfirearm")}}
<a class="damage-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollDamage"}}"><i class="fa-solid fa-gun"></i></a>
{{else}}
<a class="damage-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollDamage"}}"><i class="fa-solid fa-sword"></i></a>
{{/if}}
{{#if (eq rollType "skill") }}
{{#if rollItem.system.isHealing}}
<a class="healing-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollHealing"}}"><i class="fa-solid fa-heart"></i></a>
{{/if}}
{{/if}}
</li> </li>
{{/if}} {{/if}}
{{/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
{{#if (eq rollType "skill") }} "CTHULHUETERNAL.Label.criticalFailure"
{{#if rollItem.system.isHealing}} }}
<a class="healing-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollHealing"}}"><i class="fa-solid fa-heart"></i></a>
{{/if}}
{{/if}}
</li> </li>
{{else}} {{else}}
<li class="result-failure"> <li class="result-failure">
{{localize "CTHULHUETERNAL.Label.failure"}} {{localize "CTHULHUETERNAL.Label.failure"}}
{{#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}} {{/if}}
<!-- {{#if isAttackRoll}}
{{#if defenseRoll}}
<li>{{localize "CTHULHUETERNAL.Label.defenseRoll"}}</li>
{{#if attackSuccess}}
<li class="result-success">{{localize
"CTHULHUETERNAL.Label.attackSuccess"
}}</li>
{{else}}
<li class="result-failure">{{localize
"CTHULHUETERNAL.Label.attackFailure"
}}</li>
{{/if}}
{{else}}
{{#if targetId}}
<li class="orange-warning">{{localize
"CTHULHUETERNAL.Label.noDefenseRoll"
}}</li>
{{else}}
<li class="orange-warning">{{localize
"CTHULHUETERNAL.Label.noTarget"
}}</li>
{{/if}}
{{/if}}
{{/if}} -->
</ul> </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}}
@@ -121,4 +142,86 @@
{{{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 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>

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>

View File

@@ -1,18 +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 weapon.system.killRadius}} {{#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 ammoUsed}} {{#if (gt weapon.system.armorPiercing 0)}}
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}: {{ammoUsed}} / {{weapon.system.ammo.value}}</li> <li>{{localize "CTHULHUETERNAL.Label.armorPiercing"}}
:
{{weapon.system.armorPiercing}}</li>
{{/if}} {{/if}}
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.damageMessage"}}: <strong>{{rollResult}}</strong></li> {{#if (gt weapon.system.penetration 0)}}
<li>{{localize "CTHULHUETERNAL.Label.penetration"}}
:
{{weapon.system.penetration}}</li>
{{/if}}
{{#if ammoUsed}}
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}:
{{ammoUsed}}
/
{{weapon.system.ammo.value}}</li>
{{/if}}
<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>

View File

@@ -0,0 +1,8 @@
<div class="{{cssClass}}">
<div class="chat-san-request">
<ul>
<li><strong>{{localize "CTHULHUETERNAL.Label.SANLossNone"}}</strong></li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,8 @@
<div class="{{cssClass}}">
<div class="chat-san-request">
<ul>
<li><strong>{{localize "CTHULHUETERNAL.Label.SANLossUnnatural"}}</strong></li>
</ul>
</div>
</div>

View File

@@ -1,7 +1,11 @@
<div class="{{cssClass}}"> <div class="{{cssClass}}">
<div class="chat-san-request"> <div class="chat-san-request">
<ul> <ul>
<li><strong>{{localize "CTHULHUETERNAL.Label.SANTest"}}</strong></li> {{#if rollData.isSuccess}}
<li><strong>{{localize "CTHULHUETERNAL.Label.SANTestSuccess"}}</strong></li>
{{else}}
<li><strong>{{localize "CTHULHUETERNAL.Label.SANTestFailure"}}</strong></li>
{{/if}}
<li class="san-loose-buttons"> <li class="san-loose-buttons">
<button class="san-loose" data-san-value="0">0</button> <button class="san-loose" data-san-value="0">0</button>

View File

@@ -16,11 +16,64 @@
{{item.name}} {{item.name}}
</div> </div>
<div class="damage" data-tooltip="{{localize 'CTHULHUETERNAL.Tooltip.rollDamage'}}">
{{#if (eq system.lethality 0)}}
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" /> <img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<a class="damage rollable" data-item-id="{{item.id}}" data-action="roll" data-roll-type="damage"
{{#if item.system.hasDamageDistance}}
{{#each item.system.damageDistance as |damageDistance|}}
{{#if (gt damageDistance.distance 0)}}
<a class="rollable" data-item-id="{{item.id}}" data-action="roll" data-roll-type="damage"
data-roll-value="{{damageDistance.damage}}" >
<span class="damage-distance">{{damageDistance.distance}}:{{damageDistance.damage}}&nbsp;&nbsp;</span>
</a>
{{/if}}
{{/each}}
{{else}}
<a class="rollable" data-item-id="{{item.id}}" data-action="roll" data-roll-type="damage"
data-roll-value="{{item.system.damage}}" > data-roll-value="{{item.system.damage}}" >
{{localize "CTHULHUETERNAL.Label.damageShort"}} : {{item.system.damage}}
{{item.system.damage}}</a> </a>
{{/if}}
{{else}}
N/A
{{/if}}
</div>
{{#if (gt system.baseRange 0)}}
<span class="range" data-tooltip="CTHULHUETERNAL.Label.baseRange">{{item.system.baseRange}} {{item.system.rangeUnit}}</span>
{{else}}
<span class="range">{{localize "CTHULHUETERNAL.Label.melee"}}</span>
{{/if}}
{{#if (gt system.lethality 0)}}
<a class="lethality rollable" data-item-id="{{item.id}}" data-action="roll" data-roll-type="damage"
data-tooltip="CTHULHUETERNAL.Label.Lethality" >
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
{{item.system.lethality}}%
</a>
{{else}}
<span class="lethality" data-tooltip="CTHULHUETERNAL.Label.Lethality">-</span>
{{/if}}
{{#if (gt system.killRadius 0)}}
<span class="lethality" data-tooltip="CTHULHUETERNAL.Label.killRadius" >{{item.system.killRadius}}</span>
{{else}}
<span class="lethality" data-tooltip="CTHULHUETERNAL.Label.killRadius">-</span>
{{/if}}
{{#if (gt system.armorPiercing 0)}}
<span class="lethality" data-tooltip="CTHULHUETERNAL.Label.armorPiercing" >{{item.system.armorPiercing}}</span>
{{else}}
<span class="lethality" data-tooltip="CTHULHUETERNAL.Label.armorPiercing">-</span>
{{/if}}
{{#if (eq system.weaponType "rangedfirearm")}}
<span class="ammo" data-tooltip="CTHULHUETERNAL.Label.Ammo" >{{item.system.ammo.value}}/{{item.system.ammo.max}}</span>
{{else}}
<span class="ammo" data-tooltip="CTHULHUETERNAL.Label.Ammo">N/A</span>
{{/if}}
<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>

View File

@@ -9,40 +9,187 @@
{{#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}}
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Storage"}} : {{rollItem.storage}} <input type="checkbox" data-action="selectStorage" {{checked rollItem.enableStorage}}></div> ({{mul initialScore 5}}%)</span></div>
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Hand"}}
:
{{rollItem.hand}}
<input
type="checkbox"
data-action="selectHand"
{{checked rollItem.enableHand}}
/></div>
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Stowed"}}
:
{{rollItem.stowed}}
<input
type="checkbox"
data-action="selectStowed"
{{checked rollItem.enableStowed}}
/></div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.Storage"}}
:
{{rollItem.storage}}
<input
type="checkbox"
data-action="selectStorage"
{{checked rollItem.enableStorage}}
/>
</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> <div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Weapon"}}
{{#if weapon.system.hasSelectiveFire}} :
<div class="dialog-skill">Selective Fire : {{weapon.name}}</div>
<select name="selectiveFireChoice" class="roll-skill-modifier">
{{selectOptions choiceSelectiveFire localize=true selected=selectiveFireChoice nameAttr="id" labelAttr="label"}} {{#if targetName}}
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Target"}}
:
{{targetName}}</div>
{{/if}}
{{#if (eq weapon.system.weaponType "melee")}}
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetMove"}}
<select name="meleeTargetMoveChoice" class="roll-skill-modifier">
{{selectOptions
choiceMeleeTargetMove
localize=true
selected=meleeTargetMoveChoice
valueAttr="id"
labelAttr="label"
}}
</select> </select>
</div> </div>
{{/if}} {{/if}}
{{#if isRangedWeapon}}
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.rangedRange"}}
<select name="rangedRangeChoice" class="roll-skill-modifier">
{{selectOptions
choiceRangedRange
localize=true
selected=rangedRangeChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetMove"}}
<select name="rangedTargetMoveChoice" class="roll-skill-modifier">
{{selectOptions
choiceRangedTargetMove
localize=true
selected=rangedTargetMoveChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.aimingLastRound"}}
<input
type="checkbox"
class="aimingLastRound"
name="aimingLastRound"
/>
</div>
{{#if weapon.system.hasSight}}
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.aimingWithSight"}}
<input
type="checkbox"
class="aimingWithSight"
name="aimingWithSight"
/>
</div>
{{/if}}
{{/if}}
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.visibility"}}
<select name="visibilityChoice" class="roll-skill-modifier">
{{selectOptions
choiceVisibility
localize=true
selected=visibilityChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.attackerState"}}
<select name="attackerStateChoice" class="roll-skill-modifier">
{{selectOptions
choiceAttackerState
localize=true
selected=attackerStateChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetSize"}}
<select name="targetSizeChoice" class="roll-skill-modifier">
{{selectOptions
choiceTargetSize
localize=true
selected=targetSizeChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
{{#if weapon.system.hasSelectiveFire}}
<div class="dialog-skill">Selective Fire :
<select name="selectiveFireChoice" class="roll-skill-modifier">
{{selectOptions
choiceSelectiveFire
localize=true
selected=selectiveFireChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
{{/if}}
{{/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>
@@ -55,7 +202,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>

View File

@@ -12,32 +12,60 @@
{{/if}} {{/if}}
{{formField systemFields.state value=system.state localize=true}} {{formField systemFields.state value=system.state localize=true}}
{{formField systemFields.isStunning value=system.isStunning localize=true}}
<div class="flexrow">
{{formField systemFields.hasDirectSkill value=system.hasDirectSkill }} {{formField systemFields.hasDirectSkill value=system.hasDirectSkill }}
{{#if system.hasDirectSkill}} {{#if system.hasDirectSkill}}
{{formField systemFields.directSkillValue value=system.directSkillValue }} {{formField systemFields.directSkillValue value=system.directSkillValue }}
{{/if}} {{/if}}
</div>
{{formField systemFields.applyDamageBonus value=system.applyDamageBonus}} <div class="flexrow">
{{formField systemFields.hasDamageDistance value=system.hasDamageDistance localize=true}}
</div>
{{#if system.hasDamageDistance}}
{{#each system.damageDistance as |damageDistance idx|}}
<div class="flexrow">
<label class="damage-distance">Distance</label><input type="number" name="system.damageDistance.{{idx}}.distance" value="{{damageDistance.distance}}" min="0" />
<label>Damage</label><input type="text" name="system.damageDistance.{{idx}}.damage" value="{{damageDistance.damage}}" />
</div>
{{/each}}
{{else}}
<div class="flexrow">
{{formField systemFields.damage value=system.damage}} {{formField systemFields.damage value=system.damage}}
{{formField systemFields.applyDamageBonus value=system.applyDamageBonus}}
</div>
{{#if isRanged}} {{#if isRanged}}
<div class="flexrow">
{{formField systemFields.baseRange value=system.baseRange}} {{formField systemFields.baseRange value=system.baseRange}}
{{formField systemFields.rangeUnit value=system.rangeUnit localize=true}} {{formField systemFields.rangeUnit value=system.rangeUnit localize=true}}
</div>
{{/if}}
{{/if}} {{/if}}
{{#if isFireArm}} {{#if isFireArm}}
<div class="flexrow">
{{formField systemFields.hasSelectiveFire value=system.hasSelectiveFire}} {{formField systemFields.hasSelectiveFire value=system.hasSelectiveFire}}
{{formField systemFields.hasSight value=system.hasSight}}
</div>
<div class="flexrow">
{{formField systemFields.ammo.fields.value value=system.ammo.value}} {{formField systemFields.ammo.fields.value value=system.ammo.value}}
{{formField systemFields.ammo.fields.max value=system.ammo.max}} {{formField systemFields.ammo.fields.max value=system.ammo.max}}
</div>
{{/if}} {{/if}}
<div class="flexrow">
{{formField systemFields.lethality value=system.lethality}} {{formField systemFields.lethality value=system.lethality}}
{{formField systemFields.killRadius value=system.killRadius}} {{formField systemFields.killRadius value=system.killRadius}}
</div>
<div class="flexrow">
{{formField systemFields.armorPiercing value=system.armorPiercing}} {{formField systemFields.armorPiercing value=system.armorPiercing}}
{{formField systemFields.resourceLevel value=system.resourceLevel}} {{formField systemFields.resourceLevel value=system.resourceLevel}}
</div>
</fieldset> </fieldset>
<fieldset> <fieldset>