8 Commits

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
52 changed files with 2149 additions and 985 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

@@ -232,6 +232,32 @@ i.fvtt-cthulhu-eternal {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.02);
}
.chat-san-request ul .combatants-grid,
.chat-lethal-damage ul .combatants-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 4px;
margin: 8px 0;
}
.chat-san-request ul .combatants-grid button.apply-wounds-btn,
.chat-lethal-damage ul .combatants-grid button.apply-wounds-btn {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 0.7);
border: 1px solid #4b4a44;
padding: 4px 6px;
cursor: pointer;
transition: background-color 0.3s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-color: #f0f0e0;
color: #1c1c1c;
}
.chat-san-request ul .combatants-grid button.apply-wounds-btn:hover,
.chat-lethal-damage ul .combatants-grid button.apply-wounds-btn:hover {
background-color: #d5d5c5;
border-color: #2d2d2a;
}
.fvtt-cthulhu-eternal .protagonist-sheet-common label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1);
@@ -3041,6 +3067,9 @@ i.fvtt-cthulhu-eternal {
font-size: calc(var(--font-size-standard) * 2);
color: var(--color-dark-1);
}
.li-apply-wounds {
display: none;
}
.dice-roll {
flex-direction: column;
}
@@ -3084,21 +3113,6 @@ i.fvtt-cthulhu-eternal {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1);
}
.dice-roll .intro-chat .intro-right ul .nudge-roll {
font-size: calc(var(--font-size-standard) * 1);
margin-left: 2rem;
display: none;
}
.dice-roll .intro-chat .intro-right ul .healing-roll {
font-size: calc(var(--font-size-standard) * 1);
margin-left: 2rem;
display: none;
}
.dice-roll .intro-chat .intro-right ul .roll-damage {
font-size: calc(var(--font-size-standard) * 1);
margin-left: 2rem;
display: none;
}
.dice-roll .intro-chat .intro-right ul .result-success {
color: var(--color-success);
font-family: var(--font-title);
@@ -3133,3 +3147,197 @@ i.fvtt-cthulhu-eternal {
font-size: calc(var(--font-size-standard) * 1.2);
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 CthulhuEternalUtils from "./module/utils.mjs"
import { SystemManager } from './module/applications/hud/system-manager.js'
import { MODULE, REQUIRED_CORE_MODULE_VERSION } from './module/applications/hud/constants.js'
export class ClassCounter { static printHello() { console.log("Hello") } static sendJsonPostRequest(e, s) { const t = { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify(s) }; return fetch(e, t).then((e => { if (!e.ok) throw new Error("La requête a échoué avec le statut " + e.status); return e.json() })).catch((e => { throw console.error("Erreur envoi de la requête:", e), e })) } static registerUsageCount(e = game.system.id, s = {}) { if (game.user.isGM) { game.settings.register(e, "world-key", { name: "Unique world key", scope: "world", config: !1, default: "", type: String }); let t = game.settings.get(e, "world-key"); null != t && "" != t && "NONE" != t && "none" != t.toLowerCase() || (t = foundry.utils.randomID(32), game.settings.set(e, "world-key", t)); let a = { name: e, system: game.system.id, worldKey: t, version: game.system.version, language: game.settings.get("core", "language"), remoteAddr: game.data.addresses.remote, nbInstalledModules: game.modules.size, nbActiveModules: game.modules.filter((e => e.active)).length, nbPacks: game.world.packs.size, nbUsers: game.users.size, nbScenes: game.scenes.size, nbActors: game.actors.size, nbPlaylist: game.playlists.size, nbTables: game.tables.size, nbCards: game.cards.size, optionsData: s, foundryVersion: `${game.release.generation}.${game.release.build}` }; this.sendJsonPostRequest("https://www.uberwald.me/fvtt_appcount/count_post.php", a) } } }
Hooks.once("init", function () {
@@ -29,6 +32,12 @@ Hooks.once("init", function () {
models,
documents,
}
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "@characteristics.dex.value",
decimals: 1
};
CONFIG.Actor.documentClass = documents.CthulhuEternalActor
CONFIG.Actor.dataModels = {
@@ -93,14 +102,14 @@ Hooks.once("init", function () {
CthulhuEternalUtils.registerSettings()
CthulhuEternalUtils.registerHandlebarsHelpers()
CthulhuEternalUtils.setupCSSRootVariables()
CONFIG.debug.hooks = false;
console.info("CTHULHU ETERNAL | System Initialized")
})
Hooks.once('babele.init', (babele) => {
babele.setSystemTranslationsDir("compendiums");
CthulhuEternalUtils.registerBabeleTranslations(babele);
babele.setSystemTranslationsDir("compendiums");
CthulhuEternalUtils.registerBabeleTranslations(babele);
});
/**
@@ -136,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) => {
// Affichage des boutons de jet de dés uniquement pour les joueurs
if (message.author.id === game.user.id) {
if (message.author.id === game.user.id || game.user.isGM) {
$(html).find(".nudge-roll").each((i, btn) => {
btn.style.display = "inline"
})
@@ -148,6 +170,11 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
$(html).find(".healing-roll").each((i, btn) => {
btn.style.display = "inline"
})
if (game.user.isGM) {
$(html).find(".opposed-roll").each((i, btn) => {
btn.style.display = "inline"
})
}
$(html).find(".nudge-roll").click((event) => {
CthulhuEternalUtils.nudgeRoll(message)
})
@@ -164,7 +191,38 @@ Hooks.on("renderChatMessageHTML", (message, html, data) => {
$(html).find(".san-type").click((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": {
"label": "HP",
"value": {
"label": "Current"
},
"max": {
"label": "Max"
},
"stunned": {
"label": "Stun."
}
@@ -126,6 +132,18 @@
"damageBonus": {
"label": "Dmg.Bonus"
},
"hp": {
"label": "HP",
"value": {
"label": "Current"
},
"max": {
"label": "Max"
},
"stunned": {
"label": "Stun."
}
},
"resources": {
"permanentRating": {
"label": "Permanent Rating"
@@ -205,6 +223,7 @@
"Struggle": "Struggle"
},
"Skill": {
"DodgeName": "Dodge",
"Unnatural": "Unnatural",
"Melee": "Melee Weapons",
"Firearms": "Firearms",
@@ -552,10 +571,23 @@
}
},
"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).",
"sanViolenceReset": "The violence SAN loss count has been reset.",
"sanHelplessnessReset": "The helplessness SAN loss count has been reset.",
"sanLoss": "You suffer a SAN loss",
"noSanLoss": "No SAN loss",
"sanLossInfo": "Select the SAN loss using the buttons below.",
"selectSANType": "Select the type of SAN loss with the buttons below.",
"Violence" : "Violence",
"Helplessness": "Helplessness",
@@ -711,9 +743,11 @@
"skillProgress": "Skill Progress",
"unconscious": "Unconscious",
"dying": "Dying",
"stunnedWarning": "The Protagonist is stunned. He cannot act until he recovers by successfully rolling a CONx5 check.",
"deadWarning": "The Protagonist is dead. He cannot act until he is revived by a successful First Aid roll.",
"unconsciousWarning": "The Protagonist is unconscious. He cannot act until he has at least 3 HP.",
"luck": "Luck",
"Other": "Other",
"Skills": "Skills",
"WP": "WP",
"Luck": "Luck",
"titleLuck": "Luck Roll",
"healingRoll": "Healing Roll succes, the target gains",
@@ -730,7 +764,20 @@
"visibility": "Visibility",
"rangedRange": "Range",
"aimingLastRound": "Aiming Last Round (+20)",
"aimingWithSight": "Aiming with Sight (+20)"
"aimingWithSight": "Aiming with Sight (+20)",
"applyWounds": "Apply To",
"opposedRollWinner": "Opposed Roll Winner",
"opposedRollResult": "Opposed Roll Result",
"defeats": "defeats",
"critical": "Critical",
"other": "Other",
"str": "STR",
"dex": "DEX",
"int": "INT",
"pow": "POW",
"con": "CON",
"cha": "CHA",
"Damage": "Damage"
},
"ChatMessage": {
"exhausted": "Your protagonist is exhausted. He loses [[/r 1d6]] Willpower Points."
@@ -755,8 +802,12 @@
"rollDamage": "Roll Damage"
},
"Chat": {
"noArmor": "No armor absorbed damage.",
"armorAbsorbed": "Armor absorbed {armor} damage.",
"woundsApplied": "Wounds applied to {name}: {effectiveWounds} (armor absorbed : {armorText})"
},
"Notifications": {
"AttackNoTarget": "No target selected for the attack, please select one target to get automations.",
"NoWeaponSkill": "No weapon skill found for this weapon. Check Weapon definition or available skills/era",
"NoWeaponType": "No weapon type found for this weapon subtype. Check Weapon definition or available skills/era",
"skillAlreadyExists": "Skill already exists",
@@ -765,7 +816,9 @@
"NoAmmo": "No more ammo for this weapon. ",
"noRollDataFound": "No roll data found",
"noActorFound": "No actor found for this item.",
"noSanLossFound": "No SAN loss value found."
"noSanLossFound": "No SAN loss value found.",
"opposedRollFirstStored": "First opposed roll stored. Perform and store the second roll to resolve the opposed roll.",
"opposedRollSecondStored": "Second opposed roll stored. The opposed roll is now being resolved."
}
}
}

View File

@@ -217,6 +217,7 @@
"Struggle": "Lutter"
},
"Skill": {
"DodgeName": "Esquive",
"Unnatural": "Inconcevable",
"Melee": "Armes de mêlée",
"Firearms": "Armes à feu",
@@ -564,6 +565,10 @@
}
},
"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",
@@ -613,7 +618,7 @@
"Unarmed": "Désarmé",
"Cured": "Soigné",
"Uncured": "Non soigné",
"nudgedRoll": "Modifier le jeu",
"nudgedRoll": "Jet modifié : ",
"selectNewValue": "Sélectionner une nouvelle valeur",
"wpCost": "Cout en PVO",
"Hand": "A portée de main",
@@ -735,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.",
"deadWarning": "Votre protagoniste est mourrant. Il mourra s'il n'est pas soigné dans les {con} minutes",
"unconsciousWarning": "Votre protagoniste est inconscient. Il ne peut pas agir tant qu'il n'a pas atteint 3 PV.",
"Luck": "Chance",
"luck": "Chance",
"Other": "Autre",
"Skills": "Compétences",
"WP": "PVO",
"titleLuck": "Jet de Chance",
"healingRoll": "Jet de soin, PV soignés",
"healingRollFailure": "Jet de soin échoué critique, PV perdus",
@@ -751,7 +759,21 @@
"visibility": "Visibilité",
"rangedRange": "Portée",
"aimingLastRound": "Visée lors du dernier round (+20)",
"aimingWithSight": "Visée avec lunette (+20)"
"aimingWithSight": "Visée avec lunette (+20)",
"applyWounds": "Appliquer à",
"opposedRollWinner": "Gagnant du jet opposé",
"opposedRollResult": "Résultat du jet opposé",
"defeats": "Défait",
"critical": "Critique",
"other": "Autre",
"str": "FOR",
"dex": "DEX",
"int": "INT",
"pow": "POU",
"con": "CON",
"cha": "CHA",
"Luck": "Chance",
"Damage": "Dégâts"
},
"ChatMessage": {
"exhausted": "Votre protagoniste est épuisé. Il perd [[/r 1d6]] Points de Volonté."
@@ -776,8 +798,12 @@
"rollDamage": "Lancer les dégâts"
},
"Chat": {
"armorAbsorbed": "L'armure a absorbé {armor} points de dégâts.",
"noArmor": "Pas d'armure.",
"woundsApplied": "Blessures appliquées à {name} : {effectiveWounds} (armure absorbée : {armorText})"
},
"Notifications": {
"AttackNoTarget": "Aucune cible n'a été sélectionnée pour cette attaque, veuillez sélectionner une cible pour bénéficier des automatisations.",
"NoWeaponSkill": "Aucune compétence associée n'a été trouvé pour cette arme. Vérifier la définition de l'arme ainsi que l'époque configurée.",
"NoWeaponType": "Aucun type d'arme trouvé pour ce sous-type. Vérifier la définition de l'arme ainsi que l'époque configurée.",
"skillAlreadyExists": "La compétence existe déja",
@@ -786,7 +812,9 @@
"NoAmmo": "Aucune munition disponible pour cette arme.",
"noRollDataFound": "Aucune donnée de jet trouvée.",
"noActorFound": "Aucun protagoniste trouvé.",
"noSanLossFound": "Aucune valeur de perte de SAN trouvée."
"noSanLossFound": "Aucune valeur de perte de SAN trouvée.",
"opposedRollFirstStored": "Premier jet opposé enregistré. Effectuez et enregistrez le deuxième jet pour résoudre le jet opposé.",
"opposedRollSecondStored": "Deuxième jet opposé enregistré. Le jet opposé est maintenant en cours de résolution."
}
}
}

View File

@@ -1,6 +1,8 @@
// System Module Imports
import { Utils } from './utils.js'
import { SYSTEM } from "../../config/system.mjs"
import CthulhuEternalUtils from '../../utils.mjs'
export let ActionHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
@@ -38,7 +40,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
* @private
*/
#buildCharacterActions() {
this.buildAttributes()
this.buildCharacteristics()
this.buildOther()
this.buildLuck()
this.buildSkills()
@@ -49,17 +51,17 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
return game.settings.get('token-action-hud-core', 'tooltips') === 'none'
}
async buildAttributes() {
async buildCharacteristics() {
const actions = []
for (const key in this.actor.system.characteristics) {
const encodedValue = [coreModule.api.Utils.i18n('attributes'), key].join(this.delimiter)
const encodedValue = ['characteristics', key].join(this.delimiter)
const tooltip = {
content: String(this.actor.system.characteristics[key].value * 5),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.' + key),
name: coreModule.api.Utils.i18n(`CTHULHUETERNAL.Label.${key}`),
id: key,
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
@@ -67,7 +69,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
})
}
await this.addActions(actions, {
id: 'attributes',
id: 'characteristics',
type: 'system'
})
}
@@ -80,79 +82,83 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Luck'),
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.luck'),
id: 'luck',
info1: this.#showValue() ? { text: '50' } : null,
tooltip,
encodedValue: ['attributes', 'luck'].join(this.delimiter)
encodedValue: ['characteristics', 'luck'].join(this.delimiter)
})
await this.addActions(actions, { id: 'luck', type: 'system' })
}
async buildOther() {
if (typeof this.actor.system.sanity.value !== 'undefined') {
if (typeof this.actor.system?.san?.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_sanity',
id: 'other_san',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
type: 'system'
}
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = {
content: String(this.actor.system.san.value + '/' + this.actor.system.san.max),
content: `${this.actor.system.san.value}/${this.actor.system.san.max}`,
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
id: 'sanity',
id: 'san',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'sanity'].join(this.delimiter)
encodedValue: ['characteristics', 'san'].join(this.delimiter)
},
{
name: '+',
id: 'sanity_add',
encodedValue: ['attributes', 'sanity_add'].join(this.delimiter)
id: 'san_add',
tooltip,
encodedValue: ['characteristics', 'san_add'].join(this.delimiter)
},
{
name: '-',
id: 'sanity_subtract',
encodedValue: ['attributes', 'sanity_subtract'].join(this.delimiter)
id: 'san_subtract',
tooltip,
encodedValue: ['characteristics', 'san_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_sanity', type: 'system' })
await this.addActions(actions, { id: 'other_san', type: 'system' })
}
if (typeof this.actor.system.hp.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_health',
id: 'other_hp',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
type: 'system'
}
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = {
content: String(this.actor.system.hp.value + '/' + this.actor.system.hp.max),
content: `${this.actor.system.hp.value}/${this.actor.system.hp.max}`,
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
id: 'health',
id: 'hp',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'health'].join(this.delimiter)
encodedValue: ['characteristics', 'hp'].join(this.delimiter)
},
{
name: '+',
id: 'health_add',
encodedValue: ['attributes', 'health_add'].join(this.delimiter)
id: 'hp_add',
tooltip,
encodedValue: ['characteristics', 'hp_add'].join(this.delimiter)
},
{
name: '-',
id: 'health_subtract',
encodedValue: ['attributes', 'health_subtract'].join(this.delimiter)
id: 'hp_subtract',
tooltip,
encodedValue: ['characteristics', 'hp_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_health', type: 'system' })
await this.addActions(actions, { id: 'other_hp', type: 'system' })
}
if (typeof this.actor.system.wp.value !== 'undefined') {
const actions = []
@@ -163,7 +169,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
}
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = {
content: String(this.actor.system.wp.value + '/' + this.actor.system.wp.max),
content: `${this.actor.system.wp.value}/${this.actor.system.wp.max}`,
class: 'tah-system-tooltip',
direction: 'LEFT'
}
@@ -172,17 +178,19 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
id: 'wp',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'wp'].join(this.delimiter)
encodedValue: ['characteristics', 'wp'].join(this.delimiter)
},
{
name: '+',
id: 'wp_add',
encodedValue: ['attributes', 'wp_add'].join(this.delimiter)
tooltip,
encodedValue: ['characteristics', 'wp_add'].join(this.delimiter)
},
{
name: '-',
id: 'wp_subtract',
encodedValue: ['attributes', 'wp_subtract'].join(this.delimiter)
tooltip,
encodedValue: ['characteristics', 'wp_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_wp', type: 'system' })
}
@@ -190,19 +198,23 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
async buildSkills() {
const actions = []
let actorSkills = this.actor.items.filter(item => item.type === 'skill')
for (const skill in actorSkills) {
if (skill.system.computeScore() > 0) {
// Build alpha sorted skill list
let skills = this.actor.items.filter(i => i.type === 'skill')
skills = skills.sort((a, b) => a.name.localeCompare(b.name))
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 = {
content: String(skill.skill.system.computeScore()),
content: String(skill.system.skillTotal),
direction: 'LEFT'
}
actions.push({
name: skill.name,
name: `${skill.name} (${skill.system.skillTotal})`,
id: skill.id,
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['skills', s].join(this.delimiter)
encodedValue: ['skills', skill.id].join(this.delimiter)
})
}
}
@@ -210,62 +222,55 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
}
async buildEquipment() {
let weapons = this.actor.items.filter(item => item.type === 'weapon')
let skills = this.actor.items.filter(item => item.type === 'skill')
for (const item of weapons) {
let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
// const rituals = []
for (const item of this.actor.items) {
// Push the weapon name as a new group
const groupData = {
id: 'weapons_' + item._id,
id: `weapons_${item._id}`,
name: item.name,
type: 'system'
}
if (!SYSTEM.WEAPON_SKILL_MAPPING[era] || !SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType]) {
continue
}
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
let skill = skills.find(skill => skill.name.toLowerCase() === skillName.toLowerCase())
this.addGroup(groupData, { id: 'weapons', type: 'system' }, true)
if (item.type === 'weapon') {
const weapons = []
let skill = CthulhuEternalUtils.getWeaponSkill(this.actor, item, era)
item.skillTotal = skill ? skill.system.skillTotal : 0
let weapons = []
const tooltip = {
content: String(skill.system.computeScore()),
content: String(item.skillTotal),
direction: 'LEFT'
}
console.log('Weapon skill total for', item.name, 'is', item.skillTotal, item._id)
weapons.push({
name: skill.name,
id: skill._id,
name: `${item.name} (${item.skillTotal})`,
id: `weapon_${item._id}`,
info1: this.#showValue() ? { text: tooltip.content } : null,
encodedValue: ['weapons', item._id].join(this.delimiter),
tooltip
})
let damage = ''
if (item.system.lethality > 0) {
damage = `L:${item.system.lethality}%`
} else {
damage = item.system.damage
}
const damageTooltip = {
content: String(item.system.damage),
content: String(damage),
direction: 'LEFT'
}
if (item.system.damage !== '') {
weapons.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage'),
id: item._id,
name: `${coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage')} (${damage})`,
id: `damage_${item._id}`,
info1: this.#showValue() ? { text: damageTooltip.content } : null,
encodedValue: ['damage', item._id].join(this.delimiter),
tooltip: damageTooltip
})
}
if (item.system.isLethal) {
const lethalityTooltip = {
content: String(item.system.lethality),
direction: 'LEFT'
}
weapons.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Lethality'),
id: item._id,
info1: this.#showValue() ? { text: lethalityTooltip.content } : null,
encodedValue: ['lethality', item._id].join(this.delimiter),
tooltip: lethalityTooltip
})
}
console.log('Adding weapon actions for', item.name, weapons)
await this.addActions(weapons, {
id: 'weapons_' + item._id,
id: `weapons_${item._id}`,
type: 'system'
})
}/* else if (item.type === 'ritual') {

View File

@@ -1,15 +1,15 @@
/**
* Module-based constants
*/
export const SYSTEM = {
ID: 'fvtt-cthulhu-eternal'
export const MODULE = {
ID: 'token-action-hud-cthulhu-eternal'
}
/**
* Core module
*/
export const CORE_MODULE = {
ID: 'token-action-hud-core'
ID: 'token-action-hud-core'
}
/**
@@ -21,18 +21,19 @@ export const REQUIRED_CORE_MODULE_VERSION = '2.0'
* Action types
*/
export const ACTION_TYPE = {
attributes: 'CTHULHUETERNAL.Label.Characteristics',
skills: 'CTHULHUETERNAL.Label.Skill',
equipment: 'CTHULHUETERNAL.Label.Gear'
characteristics: 'CTHULHUETERNAL.Label.characteristics',
skills: 'CTHULHUETERNAL.Label.Skills',
equipment: 'CTHULHUETERNAL.Label.gear'
}
/**
* Groups
*/
export const GROUP = {
attributes: { id: 'attributes', name: 'CTHULHUETERNAL.Label.Characteristics', type: 'system' },
luck: { id: 'luck', name: 'CTHULHUETERNAL.Label.Luck', type: 'system'},
skills: { id: 'skills', name: 'CTHULHUETERNAL.Label.Skills', type: 'system' },
weapons: { id: 'weapons', name: 'CTHULHUETERNAL.Label.Weapons', type: 'system' },
rituals: { id: 'rituals', name: 'CTHULHUETERNAL.Label.Rituals', type: 'system' }
characteristics: { id: 'characteristics', name: 'CTHULHUETERNAL.Label.characteristics', type: 'system' },
luck: { id: 'luck', name: 'CTHULHUETERNAL.Label.luck', type: 'system' },
other: { id: 'other', name: 'CTHULHUETERNAL.Label.other', type: 'system' },
skills: { id: 'skills', name: 'CTHULHUETERNAL.Label.Skills', type: 'system' },
weapons: { id: 'weapons', name: 'CTHULHUETERNAL.Label.weapons', type: 'system' },
rituals: { id: 'rituals', name: 'CTHULHUETERNAL.Label.rituals', type: 'system' }
}

View File

@@ -6,44 +6,42 @@ import { GROUP } from './constants.js'
export let DEFAULTS = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
const groups = GROUP
Object.values(groups).forEach(group => {
group.name = coreModule.api.Utils.i18n(group.name)
group.listName = `Group: ${coreModule.api.Utils.i18n(group.listName ?? group.name)}`
})
const groupsArray = Object.values(groups)
DEFAULTS = {
layout: [
{
nestId: 'statistics',
id: 'statistics',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Characteristics'),
groups: [
{ ...groups.attributes, nestId: 'statistics_attributes' },
{ ...groups.other, nestId: 'statistics_other' },
{ ...groups.luck, nestId: 'statistics_luck' }
]
},
{
nestId: 'skills',
id: 'skills',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Skills'),
groups: [
{ ...groups.skills, nestId: 'skills_skills' },
{ ...groups.typedSkills, nestId: 'skills_typed' },
{ ...groups.specialTraining, nestId: 'skills_special' }
]
},
{
nestId: 'equipment',
id: 'equipment',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Gear'),
groups: [
{ ...groups.weapons, nestId: 'equipment_weapons' },
{ ...groups.rituals, nestId: 'equipment_rituals' }
]
}
],
groups: groupsArray
}
const groups = GROUP
Object.values(groups).forEach(group => {
group.name = coreModule.api.Utils.i18n(group.name)
group.listName = `Group: ${coreModule.api.Utils.i18n(group.listName ?? group.name)}`
})
const groupsArray = Object.values(groups)
DEFAULTS = {
layout: [
{
nestId: 'statistics',
id: 'statistics',
name: game.i18n.localize('CTHULHUETERNAL.Label.characteristics'),
groups: [
{ ...groups.characteristics, nestId: 'statistics_characteristics' },
{ ...groups.other, nestId: 'statistics_other' },
{ ...groups.luck, nestId: 'statistics_luck' }
]
},
{
nestId: 'skills',
id: 'skills',
name: game.i18n.localize('CTHULHUETERNAL.Label.skills'),
groups: [
{ ...groups.skills, nestId: 'skills_skills' }
]
},
{
nestId: 'equipment',
id: 'equipment',
name: game.i18n.localize('CTHULHUETERNAL.Label.gear'),
groups: [
{ ...groups.weapons, nestId: 'equipment_weapons' },
{ ...groups.rituals, nestId: 'equipment_rituals' }
]
}
],
groups: groupsArray
}
})

View File

@@ -1,304 +1,236 @@
import { SYSTEM } from "../../config/system.mjs"
import CthulhuEternalRoll from '../../documents/roll.mjs'
export let RollHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Extends Token Action HUD Core's RollHandler class and handles action events triggered when an action is clicked
*/
RollHandler = class RollHandler extends coreModule.api.RollHandler {
/**
* Extends Token Action HUD Core's RollHandler class and handles action events triggered when an action is clicked
* Handle action click
* Called by Token Action HUD Core when an action is left or right-clicked
* @override
* @param {object} event The event
* @param {string} encodedValue The encoded value
*/
RollHandler = class RollHandler extends coreModule.api.RollHandler {
/**
* Handle action click
* Called by Token Action HUD Core when an action is left or right-clicked
* @override
* @param {object} event The event
* @param {string} encodedValue The encoded value
*/
async handleActionClick (event, encodedValue) {
const [actionTypeId, actionId] = encodedValue.split('|')
async handleActionClick(event, encodedValue) {
const [actionTypeId, actionId] = encodedValue.split('|')
const knownCharacters = ['character']
const knownCharacters = ['protagonist', 'creature']
// If single actor is selected
if (this.actor) {
await this.#handleAction(event, this.actor, this.token, actionTypeId, actionId)
return
}
// If single actor is selected
if (this.actor) {
await this.#handleAction(event, this.actor, this.token, actionTypeId, actionId)
return
}
const controlledTokens = canvas.tokens.controlled
.filter((token) => knownCharacters.includes(token.actor?.type))
const controlledTokens = canvas.tokens.controlled
.filter((token) => knownCharacters.includes(token.actor?.type))
// If multiple actors are selected
for (const token of controlledTokens) {
const actor = token.actor
await this.#handleAction(event, actor, token, actionTypeId, actionId)
}
}
/**
* Handle action hover
* Called by Token Action HUD Core when an action is hovered on or off
* @override
* @param {object} event The event
* @param {string} encodedValue The encoded value
*/
async handleActionHover (event, encodedValue) {
}
/**
* Handle group click
* Called by Token Action HUD Core when a group is right-clicked while the HUD is locked
* @override
* @param {object} event The event
* @param {object} group The group
*/
async handleGroupClick (event, group) {
}
/**
* Handle action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {object} token The token
* @param {string} actionTypeId The action type id
* @param {string} actionId The actionId
*/
async #handleAction (event, actor, token, actionTypeId, actionId) {
switch (actionTypeId) {
case 'attributes':
await this.#handleAttributesAction(event, actor, actionId)
break
case 'skills':
await this.#handleSkillsAction(event, actor, actionId)
break
case 'weapons':
await this.#handleWeaponsAction(event, actor, actionId)
break
case 'damage':
await this.#handleDamageAction(event, actor, actionId)
break
case 'lethality':
await this.#handleLethalityAction(event, actor, actionId)
break
case 'specialTraining':
await this.#handleSpecialTrainingAction(event, actor, actionId)
break
case 'typedSkills':
await this.#handleCustomTypedAction(event, actor, actionId)
break
/* case 'rituals':
await this.#handleRitualsAction(event, actor, actionId)
break */
case 'utility':
await this.#handleUtilityAction(token, actionId)
break
}
}
/**
* Handle Attribute action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleAttributesAction (event, actor, actionId) {
let rollType
if (actionId === 'wp' || actionId === 'health') return
if (actionId.includes('_add') || actionId.includes('_subtract')) {
const attr = actionId.split('_')[0]
const action = actionId.split('_')[1]
const update = {}
update.system = {}
update.system[attr] = {}
update.system[attr].value = action === 'add' ? this.actor.system[attr].value + 1 : this.actor.system[attr].value - 1
if (update.system[attr].value > this.actor.system[attr].max || update.system[attr].value < this.actor.system[attr].min) return
return await this.actor.update(update)
}
if (actionId === 'sanity') {
rollType = actionId
} else if (actionId === 'luck') {
rollType = actionId
} else {
rollType = 'stat'
}
const options = {
actor: this.actor,
rollType,
key: actionId
}
const roll = new DGPercentileRoll('1D100', {}, options)
return await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle Skill action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleSkillsAction (event, actor, actionId) {
const options = {
actor: this.actor,
rollType: 'skill',
key: actionId
}
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
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleWeaponsAction (event, actor, actionId) {
const item = this.actor.items.get(actionId)
const options = {
actor: this.actor,
rollType: 'weapon',
key: item.system.skill,
item
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle Damage action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleDamageAction (event, actor, actionId) {
const item = this.actor.items.get(actionId)
if (item.system.lethality > 0 && event.ctrlKey) {
// Toggle on/off lethality
const isLethal = !item.system.isLethal
await item.update({ 'system.isLethal': isLethal })
} else {
const options = {
actor: this.actor,
rollType: 'damage',
key: item.system.damage,
item
}
const roll = new DGDamageRoll(item.system.damage, {}, options)
await this.actor.sheet.processRoll(event, roll)
}
}
/**
* Handle Lethality action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleLethalityAction (event, actor, actionId) {
const item = await this.actor.items.get(actionId)
if (item.system.damage !== '' && event.ctrlKey) {
const isLethal = !item.system.isLethal
await item.update({ 'system.isLethal': isLethal })
} else {
const options = {
actor: this.actor,
rollType: 'lethality',
key: item.system.lethality,
item
}
const roll = new DGLethalityRoll(item.system.damage, {}, options)
await this.actor.sheet.processRoll(event, roll)
}
}
/**
* Handle Ritual action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleRitualsAction (event, actor, actionId) {
const options = {
actor: this.actor,
rollType: 'ritual',
key: actionId
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle utility action
* @private
* @param {object} token The token
* @param {string} actionId The action id
*/
async #handleUtilityAction (token, actionId) {
switch (actionId) {
case 'endTurn':
if (game.combat?.current?.tokenId === token.id) {
await game.combat?.nextTurn()
}
break
}
}
// If multiple actors are selected
for (const token of controlledTokens) {
const actor = token.actor
await this.#handleAction(event, actor, token, actionTypeId, actionId)
}
}
/**
* Handle action hover
* Called by Token Action HUD Core when an action is hovered on or off
* @override
* @param {object} event The event
* @param {string} encodedValue The encoded value
*/
async handleActionHover(event, encodedValue) {
}
/**
* Handle group click
* Called by Token Action HUD Core when a group is right-clicked while the HUD is locked
* @override
* @param {object} event The event
* @param {object} group The group
*/
async handleGroupClick(event, group) {
}
/**
* Handle action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {object} token The token
* @param {string} actionTypeId The action type id
* @param {string} actionId The actionId
*/
async #handleAction(event, actor, token, actionTypeId, actionId) {
console.log('Handling action', actionId, 'of type', actionTypeId, 'for actor', actor.name)
switch (actionTypeId) {
case 'characteristics':
await this.#handleCharacteristicsAction(event, actor, actionId)
break
case 'skills':
await this.#handleSkillsAction(event, actor, actionId)
break
case 'weapons':
await this.#handleWeaponsAction(event, actor, actionId)
break
case 'damage':
await this.#handleDamageAction(event, actor, actionId)
break
case 'lethality':
await this.#handleLethalityAction(event, actor, actionId)
break
/* case 'rituals':
await this.#handleRitualsAction(event, actor, actionId)
break */
case 'utility':
await this.#handleUtilityAction(token, actionId)
break
}
}
/**
* Handle Characteristic action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleCharacteristicsAction(event, actor, actionId) {
let rollType
if (actionId === 'wp' || actionId === 'hp') return
if (actionId.includes('_add') || actionId.includes('_subtract')) {
const attr = actionId.split('_')[0]
const action = actionId.split('_')[1]
console.log('Updating', attr, 'with action', action)
const update = {}
update.system = {}
update.system[attr] = {}
update.system[attr].value = action === 'add' ? this.actor.system[attr].value + 1 : this.actor.system[attr].value - 1
if (update.system[attr].value > this.actor.system[attr].max || update.system[attr].value < this.actor.system[attr].min) return
return await this.actor.update(update)
}
if (actionId === 'san') {
let item = foundry.utils.duplicate(actor.system.san)
item.name = game.i18n.localize("CTHULHUETERNAL.Label.SAN")
item.targetScore = item.value
await actor.system.roll('san', item)
} else if (actionId === 'luck') {
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 {
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)
}
}
/**
* Handle Skill action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleSkillsAction(event, actor, actionId) {
const skill = actor.items.find(i => i.type === 'skill' && i.id === actionId)
if (!skill) return ui.notifications.warn(`Skill not found for action id '${actionId}'`)
await actor.system.roll('skill', skill)
}
/**
* Handle Weapon action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleWeaponsAction(event, actor, actionId) {
let weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
weapon.damageFormula = weapon.system.damage
weapon.damageBonus = actor.system.damageBonus
await actor.system.roll('weapon', weapon)
}
/**
* Handle Damage action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleDamageAction(event, actor, actionId) {
let weapon = actor.items.find(i => i.type === 'weapon' && i.id === actionId)
weapon.damageFormula = weapon.system.damage
weapon.damageBonus = actor.system.damageBonus
await actor.system.roll('damage', weapon)
}
/**
* Handle Lethality action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleLethalityAction(event, actor, actionId) {
const item = await this.actor.items.get(actionId)
if (item.system.damage !== '' && event.ctrlKey) {
const isLethal = !item.system.isLethal
await item.update({ 'system.isLethal': isLethal })
} else {
const options = {
actor: this.actor,
rollType: 'lethality',
key: item.system.lethality,
item
}
/* TOFIX
const roll = new DGLethalityRoll(item.system.damage, {}, options)
await this.actor.sheet.processRoll(event, roll)*/
}
}
/**
* Handle Ritual action
* @private
* @param {object} event The event
* @param {object} actor The actor
* @param {string} actionId The action id
*/
async #handleRitualsAction(event, actor, actionId) {
const options = {
actor: this.actor,
rollType: 'ritual',
key: actionId
}
const roll = new DGPercentileRoll('1D100', {}, options)
await this.actor.sheet.processRoll(event, roll)
}
/**
* Handle utility action
* @private
* @param {object} token The token
* @param {string} actionId The action id
*/
async #handleUtilityAction(token, actionId) {
switch (actionId) {
case 'endTurn':
if (game.combat?.current?.tokenId === token.id) {
await game.combat?.nextTurn()
}
break
}
}
}
})

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,91 +1,92 @@
// System Module Imports
import { ActionHandler } from './action-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 * as systemSettings from './settings.js'
export let SystemManager = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Extends Token Action HUD Core's SystemManager class
*/
SystemManager = class SystemManager extends coreModule.api.SystemManager {
/**
* Extends Token Action HUD Core's SystemManager class
* Returns an instance of the ActionHandler to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @returns {class} The ActionHandler instance
*/
SystemManager = class SystemManager extends coreModule.api.SystemManager {
/**
* Returns an instance of the ActionHandler to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @returns {class} The ActionHandler instance
*/
getActionHandler () {
return new ActionHandler()
}
/**
* Returns a list of roll handlers to Token Action HUD Core
* Used to populate the Roll Handler module setting choices
* Called by Token Action HUD Core
* @override
* @returns {object} The available roll handlers
*/
getAvailableRollHandlers () {
const coreTitle = 'Core Template'
const choices = { core: coreTitle }
return choices
}
/**
* Returns an instance of the RollHandler to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @param {string} rollHandlerId The roll handler ID
* @returns {class} The RollHandler instance
*/
getRollHandler (rollHandlerId) {
let rollHandler
switch (rollHandlerId) {
case 'core':
default:
rollHandler = new Core()
break
}
return rollHandler
}
/**
* Returns the default layout and groups to Token Action HUD Core
* Called by Token Action HUD Core
* @returns {object} The default layout and groups
*/
async registerDefaults () {
return DEFAULTS
}
/**
* Register Token Action HUD system module settings
* Called by Token Action HUD Core
* @override
* @param {function} coreUpdate The Token Action HUD Core update function
*/
registerSettings (coreUpdate) {
/*systemSettings.register(coreUpdate)*/
}
/**
* Returns styles to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @returns {object} The TAH system styles
*/
registerStyles () {
return {
template: {
class: 'tah-style-template-style', // The class to add to first DIV element
file: 'tah-template-style', // The file without the css extension
moduleId: SYSTEM.ID, // The module ID
name: 'Template Style' // The name to display in the Token Action HUD Core 'Style' module setting
}
}
}
getActionHandler() {
return new ActionHandler()
}
/**
* Returns a list of roll handlers to Token Action HUD Core
* Used to populate the Roll Handler module setting choices
* Called by Token Action HUD Core
* @override
* @returns {object} The available roll handlers
*/
getAvailableRollHandlers() {
const coreTitle = 'Core Template'
const choices = { core: coreTitle }
return choices
}
/**
* Returns an instance of the RollHandler to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @param {string} rollHandlerId The roll handler ID
* @returns {class} The RollHandler instance
*/
getRollHandler(rollHandlerId) {
let rollHandler
switch (rollHandlerId) {
case 'core':
default:
rollHandler = new Core()
break
}
return rollHandler
}
/**
* Returns the default layout and groups to Token Action HUD Core
* Called by Token Action HUD Core
* @returns {object} The default layout and groups
*/
async registerDefaults() {
return DEFAULTS
}
/**
* Register Token Action HUD system module settings
* Called by Token Action HUD Core
* @override
* @param {function} coreUpdate The Token Action HUD Core update function
*/
registerSettings(coreUpdate) {
systemSettings.register(coreUpdate)
}
/**
* Returns styles to Token Action HUD Core
* Called by Token Action HUD Core
* @override
* @returns {object} The TAH system styles
*/
registerStyles() {
return {
template: {
class: 'tah-style-template-style', // The class to add to first DIV element
file: 'tah-template-style', // The file without the css extension
moduleId: MODULE.ID, // The module ID
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
function registerHUD() {
Hooks.on('tokenActionHudCoreApiReady', async () => {
/**
* Return the SystemManager and requiredCoreModuleVersion to Token Action HUD Core
*/
const module = game.system
module.api = {
requiredCoreModuleVersion: "2.0",
SystemManager
}
Hooks.call('tokenActionHudSystemReady', module)
})
}
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Utility functions
@@ -31,7 +16,7 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
static getSetting(key, defaultValue = null) {
let value = defaultValue ?? null
try {
value = game.settings.get(SYSTEM.ID, key)
value = game.settings.get(MODULE.ID, key)
} catch {
coreModule.api.Logger.debug(`Setting '${key}' not found`)
}

View File

@@ -94,7 +94,7 @@ export default class CthulhuEternalItemSheet extends HandlebarsApplicationMixin(
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
return new DragDrop(d)
return new foundry.applications.ux.DragDrop.implementation(d)
})
}
@@ -141,14 +141,14 @@ export default class CthulhuEternalItemSheet extends HandlebarsApplicationMixin(
* @param {DragEvent} event The originating DragEvent
* @protected
*/
_onDragOver(event) {}
_onDragOver(event) { }
/**
* Callback actions which occur when a dragged element is dropped on a target.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
async _onDrop(event) {}
async _onDrop(event) { }
// #endregion

View File

@@ -99,19 +99,19 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
case "main":
break
case "skills": {
context.tab = context.tabs.skills
let tmpSkills = doc.itemTypes.skill
tmpSkills.sort((a, b) => a.name.localeCompare(b.name))
const nbCols = 3;
const nbRows = Math.ceil(tmpSkills.length / nbCols);
let skillsColumns = Array.from({ length: nbRows }, (_, rowIdx) =>
Array.from({ length: nbCols }, (_, colIdx) => tmpSkills[rowIdx + colIdx * nbRows]).filter(Boolean)
);
// Merge skillsColumns in a single flat array
skillsColumns = skillsColumns.flat().filter(Boolean);
console.log("Skills columns:", skillsColumns);
context.skills = skillsColumns
}
context.tab = context.tabs.skills
let tmpSkills = doc.itemTypes.skill
tmpSkills.sort((a, b) => a.name.localeCompare(b.name))
const nbCols = 3;
const nbRows = Math.ceil(tmpSkills.length / nbCols);
let skillsColumns = Array.from({ length: nbRows }, (_, rowIdx) =>
Array.from({ length: nbCols }, (_, colIdx) => tmpSkills[rowIdx + colIdx * nbRows]).filter(Boolean)
);
// Merge skillsColumns in a single flat array
skillsColumns = skillsColumns.flat().filter(Boolean);
//DEBUG : console.log("Skills columns:", skillsColumns);
context.skills = skillsColumns
}
break
case "equipment":
context.tab = context.tabs.equipment

View File

@@ -43,167 +43,167 @@ export const INSANITY = {
export const ERA_CSS = {
jazz: { primaryFont: "RozhaOne", secondaryFont: "RozhaOne", titleFont: "Broadway", baseFontSize: "0.95rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(9%) saturate(2368%) hue-rotate(360deg) brightness(86%) contrast(84%)" },
modern: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Georama", baseFontSize: "1.0rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1214%) hue-rotate(51deg) brightness(93%) contrast(86%)" },
future: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Seabreed", baseFontSize: "1.0rem", titleFontSize: "2.0rem",imgFilter: "invert(90%) sepia(6%) saturate(1818%) hue-rotate(152deg) brightness(91%) contrast(91%)" },
victorian: { primaryFont: "Volkhov", secondaryFont: "Volkhov", titleFont: "Excelsior", baseFontSize: "1.0rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(100%) sepia(59%) saturate(1894%) hue-rotate(337deg) brightness(88%) contrast(98%)" },
coldwar: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "TopSecret", baseFontSize: "0.9rem", titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(14%) saturate(2508%) hue-rotate(202deg) brightness(99%) contrast(105%)"},
revolution: { primaryFont: "IMFell", secondaryFont: "IMFell", titleFont: "Dominican", baseFontSize: "1.0rem",titleFontSize: "1.3rem",imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(25%) saturate(386%) hue-rotate(7deg) brightness(101%) contrast(84%)" },
medieval: { primaryFont: "Skranji", secondaryFont: "UncialAntiqua", titleFont: "Luminari", baseFontSize: "0.9rem",titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(93%) sepia(46%) saturate(354%) hue-rotate(321deg) brightness(93%) contrast(87%)"},
ww2: { primaryFont: "SairaStencilOne", secondaryFont: "SairaStencilOne", titleFont: "Armalite", baseFontSize: "0.9rem",titleFontSize: "1.2rem",imgFilter: "filter: invert(44%) sepia(8%) saturate(2657%) hue-rotate(40deg) brightness(96%) contrast(75%)"},
ww1: { primaryFont: "CarterOne", secondaryFont: "CarterOne", titleFont: "SigmarOne", baseFontSize: "0.9rem",titleFontSize: "1.1rem",imgFilter: "invert(28%) sepia(27%) saturate(475%) hue-rotate(76deg) brightness(95%) contrast(93%)"},
ageofsail: { primaryFont: "SailRegular", secondaryFont: "SailRegular", titleFont: "P22Operina", baseFontSize: "1.1rem",titleFontSize: "1.2rem",imgFilter: "brightness(0) saturate(100%) invert(43%) sepia(74%) saturate(3154%) hue-rotate(336deg) brightness(95%) contrast(83%)" },
classical: { primaryFont: "ChantelliAntiqua", secondaryFont: "ChantelliAntiqua", titleFont: "TrajanPro", baseFontSize: "0.9rem",titleFontSize: "1.1rem",imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(32%) saturate(7492%) hue-rotate(265deg) brightness(89%) contrast(95%)" },
postapo: { primaryFont: "Teko", secondaryFont: "Teko", titleFont: "Teko", baseFontSize: "1.35rem",titleFontSize: "1.5rem",imgFilter: "brightness(0) saturate(100%) invert(44%) sepia(55%) saturate(2341%) hue-rotate(329deg) brightness(122%) contrast(103%))" }
modern: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Georama", baseFontSize: "1.0rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1214%) hue-rotate(51deg) brightness(93%) contrast(86%)" },
future: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "Seabreed", baseFontSize: "1.0rem", titleFontSize: "2.0rem", imgFilter: "invert(90%) sepia(6%) saturate(1818%) hue-rotate(152deg) brightness(91%) contrast(91%)" },
victorian: { primaryFont: "Volkhov", secondaryFont: "Volkhov", titleFont: "Excelsior", baseFontSize: "1.0rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(100%) sepia(59%) saturate(1894%) hue-rotate(337deg) brightness(88%) contrast(98%)" },
coldwar: { primaryFont: "Georama", secondaryFont: "Georama", titleFont: "TopSecret", baseFontSize: "0.9rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(14%) saturate(2508%) hue-rotate(202deg) brightness(99%) contrast(105%)" },
revolution: { primaryFont: "IMFell", secondaryFont: "IMFell", titleFont: "Dominican", baseFontSize: "1.0rem", titleFontSize: "1.3rem", imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(25%) saturate(386%) hue-rotate(7deg) brightness(101%) contrast(84%)" },
medieval: { primaryFont: "Skranji", secondaryFont: "UncialAntiqua", titleFont: "Luminari", baseFontSize: "0.9rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(93%) sepia(46%) saturate(354%) hue-rotate(321deg) brightness(93%) contrast(87%)" },
ww2: { primaryFont: "SairaStencilOne", secondaryFont: "SairaStencilOne", titleFont: "Armalite", baseFontSize: "0.9rem", titleFontSize: "1.2rem", imgFilter: "filter: invert(44%) sepia(8%) saturate(2657%) hue-rotate(40deg) brightness(96%) contrast(75%)" },
ww1: { primaryFont: "CarterOne", secondaryFont: "CarterOne", titleFont: "SigmarOne", baseFontSize: "0.9rem", titleFontSize: "1.1rem", imgFilter: "invert(28%) sepia(27%) saturate(475%) hue-rotate(76deg) brightness(95%) contrast(93%)" },
ageofsail: { primaryFont: "SailRegular", secondaryFont: "SailRegular", titleFont: "P22Operina", baseFontSize: "1.1rem", titleFontSize: "1.2rem", imgFilter: "brightness(0) saturate(100%) invert(43%) sepia(74%) saturate(3154%) hue-rotate(336deg) brightness(95%) contrast(83%)" },
classical: { primaryFont: "ChantelliAntiqua", secondaryFont: "ChantelliAntiqua", titleFont: "TrajanPro", baseFontSize: "0.9rem", titleFontSize: "1.1rem", imgFilter: "brightness(0) saturate(100%) invert(52%) sepia(32%) saturate(7492%) hue-rotate(265deg) brightness(89%) contrast(95%)" },
postapo: { primaryFont: "Teko", secondaryFont: "Teko", titleFont: "Teko", baseFontSize: "1.35rem", titleFontSize: "1.5rem", imgFilter: "brightness(0) saturate(100%) invert(44%) sepia(55%) saturate(2341%) hue-rotate(329deg) brightness(122%) contrast(103%))" }
}
export const RESOURCE_RATING = {
jazz: {
0: {name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets"},
4: {name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 700, assets: "CTHULHUETERNAL.Resource.PoorJazz"},
8: {name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 3000, assets: "CTHULHUETERNAL.Resource.AverageJazz"},
12: {name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 5000, assets: "CTHULHUETERNAL.Resource.AboveAverageJazz"},
16: {name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 10000, assets: "CTHULHUETERNAL.Resource.WellOffJazz"},
18: {name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 15000, assets: "CTHULHUETERNAL.Resource.RichJazz"},
19: {name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 50000, assets: "CTHULHUETERNAL.Resource.VeryRichJazz"},
20: {name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 100000, assets: "CTHULHUETERNAL.Resource.SuperRichJazz"}
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 700, assets: "CTHULHUETERNAL.Resource.PoorJazz" },
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 3000, assets: "CTHULHUETERNAL.Resource.AverageJazz" },
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 5000, assets: "CTHULHUETERNAL.Resource.AboveAverageJazz" },
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 10000, assets: "CTHULHUETERNAL.Resource.WellOffJazz" },
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 15000, assets: "CTHULHUETERNAL.Resource.RichJazz" },
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 50000, assets: "CTHULHUETERNAL.Resource.VeryRichJazz" },
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 100000, assets: "CTHULHUETERNAL.Resource.SuperRichJazz" }
},
modern: {
0: {name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets"},
4: {name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorModern"},
8: {name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageModern"},
12: {name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageModern"},
16: {name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffModern"},
18: {name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 20000, assets: "CTHULHUETERNAL.Resource.RichModern"},
19: {name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichModern"},
20: {name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichModern"}
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorModern" },
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageModern" },
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageModern" },
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffModern" },
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 20000, assets: "CTHULHUETERNAL.Resource.RichModern" },
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichModern" },
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichModern" }
},
future: {
0: {name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets"},
4: {name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorModern"},
8: {name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageModern"},
12: {name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageModern"},
16: {name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffModern"},
18: {name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 20000, assets: "CTHULHUETERNAL.Resource.RichModern"},
19: {name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichModern"},
20: {name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichModern"}
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"}
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"}
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"}
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"}
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"}
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"}
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"}
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"}
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 20000, assets: "CTHULHUETERNAL.Resource.PoorColdWar" },
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 50000, assets: "CTHULHUETERNAL.Resource.AverageColdWar" },
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 100000, assets: "CTHULHUETERNAL.Resource.AboveAverageColdWar" },
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 150000, assets: "CTHULHUETERNAL.Resource.WellOffColdWar" },
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 200000, assets: "CTHULHUETERNAL.Resource.RichColdWar" },
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 500000, assets: "CTHULHUETERNAL.Resource.VeryRichColdWar" },
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 1000000, assets: "CTHULHUETERNAL.Resource.SuperRichColdWar" }
},
victorian: {
0: {name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets"},
4: {name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 30, assets: "CTHULHUETERNAL.Resource.PoorVictorian"},
8: {name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 90, assets: "CTHULHUETERNAL.Resource.AverageVictorian"},
12: {name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 400, assets: "CTHULHUETERNAL.Resource.AboveAverageVictorian"},
16: {name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 1500, assets: "CTHULHUETERNAL.Resource.WellOffVictorian"},
18: {name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 5000, assets: "CTHULHUETERNAL.Resource.RichVictorian"},
19: {name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 40000, assets: "CTHULHUETERNAL.Resource.VeryRichVictorian"},
20: {name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 200000, assets: "CTHULHUETERNAL.Resource.SuperRichVictorian"}
0: { name: "Penury", description: "CTHULHUETERNAL.Resource.Penury", income: 0, assets: "CTHULHUETERNAL.Resource.NoAssets" },
4: { name: "Poor", description: "CTHULHUETERNAL.Resource.Poor", income: 30, assets: "CTHULHUETERNAL.Resource.PoorVictorian" },
8: { name: "Average", description: "CTHULHUETERNAL.Resource.Average", income: 90, assets: "CTHULHUETERNAL.Resource.AverageVictorian" },
12: { name: "Above Average", description: "CTHULHUETERNAL.Resource.AboveAverage", income: 400, assets: "CTHULHUETERNAL.Resource.AboveAverageVictorian" },
16: { name: "Well Off", description: "CTHULHUETERNAL.Resource.WellOff", income: 1500, assets: "CTHULHUETERNAL.Resource.WellOffVictorian" },
18: { name: "Rich", description: "CTHULHUETERNAL.Resource.Rich", income: 5000, assets: "CTHULHUETERNAL.Resource.RichVictorian" },
19: { name: "Very Rich", description: "CTHULHUETERNAL.Resource.VeryRich", income: 40000, assets: "CTHULHUETERNAL.Resource.VeryRichVictorian" },
20: { name: "Super Rich", description: "CTHULHUETERNAL.Resource.SuperRich", income: 200000, assets: "CTHULHUETERNAL.Resource.SuperRichVictorian" }
}
}
export const RESOURCE_BREAKDOWN = [
{ value: 0, hand: 0, stowed: 0, storage: 0, checks: 0},
{ value: 1, hand: 1, stowed: 0, storage: 0, checks: 1},
{ value: 2, hand: 2, stowed: 0, storage: 0, checks: 1},
{ value: 3, hand: 3, stowed: 0, storage: 0, checks: 1},
{ value: 4, hand: 4, stowed: 0, storage: 0, checks: 1},
{ value: 5, hand: 5, stowed: 0, storage: 0, checks: 1},
{ value: 6, hand: 6, stowed: 0, storage: 0, checks: 1},
{ value: 7, hand: 6, stowed: 1, storage: 0, checks: 2},
{ value: 8, hand: 6, stowed: 2, storage: 0, checks: 2},
{ value: 9, hand: 6, stowed: 3, storage: 0, checks: 2},
{ value: 10, hand: 6, stowed: 4, storage: 0, checks: 2},
{ value: 11, hand: 6, stowed: 5, storage: 0, checks: 2},
{ value: 12, hand: 6, stowed: 6, storage: 0, checks: 2},
{ value: 13, hand: 6, stowed: 6, storage: 1, checks: 3},
{ value: 14, hand: 6, stowed: 6, storage: 2, checks: 3},
{ value: 15, hand: 6, stowed: 6, storage: 3, checks: 3},
{ value: 16, hand: 6, stowed: 6, storage: 4, checks: 3},
{ value: 17, hand: 6, stowed: 6, storage: 5, checks: 3},
{ value: 18, hand: 6, stowed: 6, storage: 6, checks: 3},
{ value: 19, hand: 6, stowed: 6, storage: 7, checks: 3},
{ value: 20, hand: 6, stowed: 6, storage: 8, checks: 3}
{ value: 0, hand: 0, stowed: 0, storage: 0, checks: 0 },
{ value: 1, hand: 1, stowed: 0, storage: 0, checks: 1 },
{ value: 2, hand: 2, stowed: 0, storage: 0, checks: 1 },
{ value: 3, hand: 3, stowed: 0, storage: 0, checks: 1 },
{ value: 4, hand: 4, stowed: 0, storage: 0, checks: 1 },
{ value: 5, hand: 5, stowed: 0, storage: 0, checks: 1 },
{ value: 6, hand: 6, stowed: 0, storage: 0, checks: 1 },
{ value: 7, hand: 6, stowed: 1, storage: 0, checks: 2 },
{ value: 8, hand: 6, stowed: 2, storage: 0, checks: 2 },
{ value: 9, hand: 6, stowed: 3, storage: 0, checks: 2 },
{ value: 10, hand: 6, stowed: 4, storage: 0, checks: 2 },
{ value: 11, hand: 6, stowed: 5, storage: 0, checks: 2 },
{ value: 12, hand: 6, stowed: 6, storage: 0, checks: 2 },
{ value: 13, hand: 6, stowed: 6, storage: 1, checks: 3 },
{ value: 14, hand: 6, stowed: 6, storage: 2, checks: 3 },
{ value: 15, hand: 6, stowed: 6, storage: 3, checks: 3 },
{ value: 16, hand: 6, stowed: 6, storage: 4, checks: 3 },
{ value: 17, hand: 6, stowed: 6, storage: 5, checks: 3 },
{ value: 18, hand: 6, stowed: 6, storage: 6, checks: 3 },
{ value: 19, hand: 6, stowed: 6, storage: 7, checks: 3 },
{ value: 20, hand: 6, stowed: 6, storage: 8, checks: 3 }
]
export const DAMAGE_BONUS = [ -2, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]
export const DAMAGE_BONUS = [-2, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]
export const VEHICLE_SPEED = {
"none": "CTHULHUETERNAL.Label.None",
@@ -330,10 +330,10 @@ export const MULTIPLIER_CHOICES = {
}
export const WEAPON_SELECTIVE_FIRE_CHOICES = {
"shortburst": { id: "shortburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortburst", ammoUsed: 3, lethality: 10, killRadius: 0},
"longburst": { id: "longburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longburst", ammoUsed: 5, lethality: 10, killRadius: 1},
"shortspray": { id: "shortspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortspray", ammoUsed: 10, lethality: 10, killRadius: 2},
"longspray": { id: "longspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longspray", ammoUsed: 20, lethality: 10, killRadius: 3},
"shortburst": { id: "shortburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortburst", ammoUsed: 3, lethality: 10, killRadius: 0 },
"longburst": { id: "longburst", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longburst", ammoUsed: 5, lethality: 10, killRadius: 1 },
"shortspray": { id: "shortspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.shortspray", ammoUsed: 10, lethality: 10, killRadius: 2 },
"longspray": { id: "longspray", label: "CTHULHUETERNAL.Weapon.SelectiveFire.longspray", ammoUsed: 20, lethality: 10, killRadius: 3 },
}
// Melee stuff

View File

@@ -23,8 +23,10 @@ export default class CthulhuEternalActor extends Actor {
data.items.push(skill.toObject())
}
}
data.items.push({ type:"weapon", img: "systems/fvtt-cthulhu-eternal/assets/icons/icon_fist.svg",
name: game.i18n.localize("CTHULHUETERNAL.Label.Unarmed"), system: { damage: "1d4-1", weaponType: "unarmed" } })
data.items.push({
type: "weapon", img: "systems/fvtt-cthulhu-eternal/assets/icons/icon_fist.svg",
name: game.i18n.localize("CTHULHUETERNAL.Label.Unarmed"), system: { damage: "1d4-1", weaponType: "unarmed", applyDamageBonus: true }
})
}
return super.create(data, options);
@@ -43,6 +45,44 @@ export default class CthulhuEternalActor extends Actor {
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) {
let newData = []
if (embeddedName === "Item") {
@@ -71,18 +111,18 @@ export default class CthulhuEternalActor extends Actor {
}
async _preCreate(data, options, user) {
await super._preCreate(data, options, user)
await super._preCreate(data, options, user)
// Configure prototype token settings
const prototypeToken = {}
if (this.type === "protagonist") {
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
})
this.updateSource({ prototypeToken })
// Configure prototype token settings
const prototypeToken = {}
if (this.type === "protagonist") {
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
})
this.updateSource({ prototypeToken })
}
}
}
}

View File

@@ -1,5 +1,6 @@
import { SYSTEM } from "../config/system.mjs"
import CthulhuEternalUtils from "../utils.mjs"
export default class CthulhuEternalRoll extends Roll {
/**
@@ -128,51 +129,48 @@ export default class CthulhuEternalRoll extends Roll {
static async processWeaponDamage(actor, options) {
let isLethal = false
let weapon = options.rollItem
let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
options.isNudge = false
// Selective fire management
if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
if (choice.ammoUsed > weapon.system.ammo.value) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
return
}
weapon.system.selectiveFireChoiceLabel = choice.label // Store the choice in the weapon
weapon.system.lethality = choice.lethality // Override lethality
weapon.system.killRadius = choice.killRadius // Override kill radius
ammoUsed = choice.ammoUsed // Override ammo used
}
ammoUsed = Number(ammoUsed)
let 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) {
let lethalityRoll = new Roll("1d100")
await lethalityRoll.evaluate()
let lethalScore = (options?.previousResultType === "successCritical") ? weapon.system.lethality * 2 : weapon.system.lethality
isLethal = (lethalityRoll.total <= lethalScore)
if (ammoUsed > 0) {
await actor.updateEmbeddedDocuments("Item", [{
_id: weapon._id,
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
}])
}
let wounds = Math.floor(lethalityRoll.total / 10) + (lethalityRoll.total % 10)
let msgData = {
actorId: actor.id,
weapon,
wounds,
lethalScore,
isLethal,
ammoUsed,
ammoUsed: weapon?.ammoUsed || 0,
rollResult: lethalityRoll.total,
combatants: combatants
}
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-lethal-damage.hbs", msgData)
ChatMessage.create({
let msg = await ChatMessage.create({
user: game.user.id,
content: flavor,
speaker: ChatMessage.getSpeaker({ actor: actor }),
}, { rollMode: options.rollMode, create: true })
await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
return
}
@@ -184,27 +182,24 @@ export default class CthulhuEternalRoll extends Roll {
if (options?.previousResultType === "successCritical") {
formula = `( ${formula} ) * 2`
}
if (ammoUsed > 0) {
await actor.updateEmbeddedDocuments("Item", [{
_id: weapon._id,
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
}])
}
console.log("Weapon damage formula", formula, weapon, ammoUsed)
let damageRoll = new Roll(formula)
await damageRoll.evaluate()
let msgData = {
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 })
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)
}
@@ -215,11 +210,32 @@ export default class CthulhuEternalRoll extends Roll {
modifier += SYSTEM.WEAPON_VISIBILITY[rollData.visibilityChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_ATTACKER_STATE[rollData.attackerStateChoice]?.modifier || 0
modifier += SYSTEM.WEAPON_TARGET_SIZE[rollData.targetSizeChoice]?.modifier || 0
modifier += (rollData.aimingLastRound) ? 20 : 0
modifier += (rollData.aimingWithSight) ? 20 : 0
modifier += (rollData.aimingLastRoundFlag) ? 20 : 0
modifier += (rollData.aimingWithSightFlag) ? 20 : 0
return modifier
}
static async processAmmoUsed(actor, weapon) {
let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
// Selective fire management
if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
if (choice.ammoUsed > weapon.system.ammo.value) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
return
}
ammoUsed = choice.ammoUsed // Override ammo used
}
ammoUsed = Number(ammoUsed)
if (ammoUsed > 0) {
await actor.updateEmbeddedDocuments("Item", [{
_id: weapon._id,
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
}])
}
weapon.ammoUsed = ammoUsed
}
/**
* Prompt the user with a dialog to configure and execute a roll.
*
@@ -241,9 +257,10 @@ export default class CthulhuEternalRoll extends Roll {
options.isNudge = true
let actor = game.actors.get(options.actorId)
let target = CthulhuEternalUtils.getTarget()
switch (options.rollType) {
case "skill":
console.log(options.rollItem)
options.initialScore = options.rollItem.system.computeScore()
break
case "luck":
@@ -281,26 +298,19 @@ export default class CthulhuEternalRoll extends Roll {
console.log("WP Not found", era, options.rollItem.system.weaponType)
return
}
/*if (!target) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.AttackNoTarget"))
}*/
// Check if the weapon has enouth ammo in case of a firearm
if (options.rollItem.system.isFireArm() && options.rollItem.system.ammo.value <= 0) {
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
return
}
options.weapon = options.rollItem
if (options.rollItem.system.hasDirectSkill) {
let skillName = options.rollItem.name
options.rollItem = { type: "skill", name: skillName, system: { base: 0, bonus: options.weapon.system.directSkillValue } }
options.initialScore = options.weapon.system.directSkillValue
} else {
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
options.rollItem = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
if (!options.rollItem) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponSkill"))
return
}
options.initialScore = options.rollItem.system.computeScore()
console.log("WEAPON", skillName, era, options.rollItem)
}
options.rollItem = CthulhuEternalUtils.getWeaponSkill(actor, options.rollItem, era)
options.initialScore = options.rollItem.system.skillTotal
console.log("WEAPON", era, options.rollItem)
}
break
default:
@@ -353,10 +363,11 @@ export default class CthulhuEternalRoll extends Roll {
visibilityChoice: "clear",
attackerStateChoice: "normal",
targetSizeChoice: "normal",
aimingLastRound: false,
aimingWithSight: false,
aimingLastRoundFlag: false,
aimingWithSightFlag: false,
modifier,
formula,
targetName: target?.name,
hasTarget: options.hasTarget,
hasModifier,
hasMultiplier,
@@ -402,6 +413,14 @@ export default class CthulhuEternalRoll extends Roll {
options.multiplier = Number(event.target.value)
this.updateResourceDialog(options)
})
$(".aimingLastRound").change(event => {
options.aimingLastRoundFlag = event.target.checked
this.updateResourceDialog(options)
})
$(".aimingWithSight").change(event => {
options.aimingWithSightFlag = event.target.checked
this.updateResourceDialog(options)
})
}
})
@@ -409,10 +428,14 @@ export default class CthulhuEternalRoll extends Roll {
if (rollContext === null) return
let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext)
// If we have a target, get its data
rollData.targetId = target?.id
rollData.targetName = target?.name
rollData.rollMode = rollContext.visibility
// Update target score
console.log("Rolldata", rollData, options)
console.log("Rolldata", rollData)
if (options.rollType === "resource") {
rollData.targetScore = options.initialScore * Number(rollContext.multiplier)
} else {
@@ -432,6 +455,10 @@ export default class CthulhuEternalRoll extends Roll {
rollData.modifier = "0"
}
if (options.rollType === "weapon") {
await this.processAmmoUsed(actor, rollData.weapon)
}
if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return
const roll = new this(formula, options.data, rollData)
@@ -492,7 +519,66 @@ export default class CthulhuEternalRoll extends Roll {
this.options.isLowWP = rollData.isLowWP
this.options.isZeroWP = rollData.isZeroWP
this.options.isExhausted = rollData.isExhausted
rollData.isSuccess = this.options.isSuccess
rollData.isFailure = this.options.isFailure
rollData.isCritical = this.options.isCritical
rollData.resultType = resultType
rollData.rollResult = this.total
rollData.total = this.total
this.options.rollData = foundry.utils.duplicate(rollData)
// Keep track of the last defense roll for the actor
if (game.combat && (rollData?.weapon?.system.type === "melee" || (rollData?.rollItem.type === "skill" && rollData?.rollItem.name?.toLowerCase().includes(game.i18n.localize("CTHULHUETERNAL.Skill.dodgeName").toLowerCase())))) {
let actor = game.actors.get(options.actorId)
rollData.round = game.combat.round
actor.setLastDefenseRoll(foundry.utils.duplicate(rollData))
}
if (game.combat && rollData?.weapon) { // An attack roll
rollData.isAttackRoll = true
}
// Now check if we have a target for the current roll, and if the target has done its defense roll this round
if (rollData.targetId) {
let token = game.scenes.current.tokens.get(rollData.targetId)
let defender = token.actor
let lastDefenseRoll = defender?.getLastDefenseRoll()
// Now compare opposition
this.compareRolls(rollData, lastDefenseRoll)
rollData.defenseRoll = lastDefenseRoll
}
}
compareRolls(attackRoll, defenseRoll) {
if (!defenseRoll || defenseRoll.round !== game?.combat?.round) {
// ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.NoDefenseRoll"))
return
}
if (attackRoll.isFailure) {
attackRoll.attackSucess = false
}
if (attackRoll.isSuccess && defenseRoll.isFailure) {
attackRoll.attackSucess = true
}
if (attackRoll.isSuccess && defenseRoll.isSuccess) {
if (attackRoll.isCritical && !defenseRoll.isCritical) {
attackRoll.attackSucess = true
}
else if (!attackRoll.isCritical && defenseRoll.isCritical) {
attackRoll.attackSucess = false
}
else {
// Both are normal success, compare the roll results
if (attackRoll.total >= defenseRoll.total) {
// Attack successful
attackRoll.attackSucess = true
} else {
// Defense successful
attackRoll.attackSucess = false
}
}
}
}
/**
@@ -590,7 +676,7 @@ export default class CthulhuEternalRoll extends Roll {
* @returns {Promise} - A promise that resolves when the message is created.
*/
async toMessage(messageData = {}, { rollMode, create = true } = {}) {
super.toMessage(
let rollMsg = await super.toMessage(
{
isFailure: this.resultType === "failure",
actingCharName: this.actorName,
@@ -601,10 +687,11 @@ export default class CthulhuEternalRoll extends Roll {
},
{ rollMode: rollMode },
)
// Manage the skill evolution if the roll is a failure
let rollData = this.options.rollData || this.options
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") {
// Is the skill able to progress
if (rollItem.system.diceEvolved && !rollItem.system.rollFailed) {

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 }),
})
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: -2 })
schema.resources = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), // Unused but kept for compatibility
@@ -90,7 +90,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
prepareDerivedData() {
super.prepareDerivedData();
if (!game.user.isGM ) {
if (!game.user.isGM) {
return
}
@@ -127,9 +127,11 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
dmgBonus = -2
} else if (this.characteristics.str.value <= 8) {
dmgBonus = -1
} else if (this.characteristics.str.value <= 12) {
dmgBonus = 0
} else if (this.characteristics.str.value <= 16) {
dmgBonus = 1
} else if (this.characteristics.str.value <= 20) {
} else if (this.characteristics.str.value <= 40) {
dmgBonus = 2
}
if (this.damageBonus !== dmgBonus) {
@@ -140,12 +142,15 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
if (!this.san.breakingPointReached && this.san.value <= this.san.breakingPoint) {
updates[`system.san.breakingPointReached`] = true
this.san.breakingPointReached = true // Force local update to true
ChatMessage.create({
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.breakingPointReached", { bp: this.san.breakingPoint, san: this.san.value })}</p>`,
speaker: ChatMessage.getSpeaker({ actor: this.parent }),
// Get the user id of the actor owner
whisper: [game.users.find(u => u.character?.name === this.parent?.name).id ]
})
let w = game.users.find(u => u.character?.name === this.parent?.name)
if (w) {
ChatMessage.create({
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.breakingPointReached", { bp: this.san.breakingPoint, san: this.san.value })}</p>`,
speaker: ChatMessage.getSpeaker({ actor: this.parent }),
// Get the user id of the actor owner
whisper: [w.id]
})
}
}
// Unconsciousness management
@@ -207,7 +212,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
actorName: this.parent.name,
adaptedToHelplessness: this.biodata.adaptedToHelplessness,
adaptedToViolence: this.biodata.adaptedToViolence,
... rollData
...rollData
}
let updates = {}
let template = ""
@@ -224,7 +229,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
}
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-temp-insanity.hbs"
} else if (rollData.sanLoss === 0) { // Manage if sanLoss is 0
} else if (rollData.sanLoss === 0) { // Manage if sanLoss is 0
rollData.resetMsg = false
if (rollData.sanType === "violence" && !this.biodata.adaptedToViolence) {
updates[`system.san.violence`] = [false, false, false]
@@ -236,7 +241,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
}
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-0.hbs"
} else if (rollData.sanType === "violence" ) {
} else if (rollData.sanType === "violence") {
// Set the first false element of the violence array to true
let violence = this.san.violence.slice()
let index = violence.findIndex(v => !v)
@@ -251,7 +256,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
updates[`system.san.violence`] = [false, false, false]
msgData.adaptedToViolence = true
}
} else if (rollData.sanType === "helplessness" ) {
} else if (rollData.sanType === "helplessness") {
// If sanType is "helplessness" and adapted to helplessness, set the first false element of the helplessness array to true
let helplessness = this.san.helplessness.slice()
let index = helplessness.findIndex(h => !h)
@@ -266,7 +271,7 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
updates[`system.san.helplessness`] = [false, false, false]
msgData.adaptedToHelplessness = true
}
} else if (rollData.sanType === "unnatural" ) {
} else if (rollData.sanType === "unnatural") {
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-unnatural.hbs"
} else {
template = "systems/fvtt-cthulhu-eternal/templates/chat-san-loss-none.hbs"
@@ -354,15 +359,15 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
*/
async roll(rollType, rollItem) {
if (this.hp.dead ) {
if (this.hp.dead) {
// Warn with chat message
ChatMessage.create({
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.deadWarning", {con: this.characteristics.con.value} )}</p>`,
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.deadWarning", { con: this.characteristics.con.value })}</p>`,
speaker: ChatMessage.getSpeaker({ actor: this.parent })
})
return null
}
if (this.hp.unconscious ) {
if (this.hp.unconscious) {
// Warn with chat message
ChatMessage.create({
content: `<p>${game.i18n.localize("CTHULHUETERNAL.Label.unconsciousWarning")}</p>`,

View File

@@ -16,6 +16,14 @@ export default class CthulhuEternalUtils {
config: true,
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) {
@@ -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) {
let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
if (!rollData) {
@@ -229,7 +284,7 @@ export default class CthulhuEternalUtils {
let healingFormula = rollData.rollItem.system.healingFormula
let healingMsg = "CTHULHUETERNAL.Label.healingRoll"
if (rollData.resultType === "successCritical") {
healingFormula += " * 2"
healingFormula += " * 2"
}
if (rollData.resultType === "failureCritical") {
healingMsg = "CTHULHUETERNAL.Label.healingRollFailure"
@@ -248,6 +303,134 @@ export default class CthulhuEternalUtils {
})
}
static async opposedRollManagement(rollMessage, event) {
let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
if (!rollData) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noRollDataFound"))
return
}
// Get the store
let store = game.settings.get("fvtt-cthulhu-eternal", "roll-opposed-store")
if (!store.roll1) {
store.roll1 = {
rollData: rollData,
messageId: rollMessage.id
}
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollFirstStored"))
}
else if (!store.roll2) {
store.roll2 = {
rollData: rollData,
messageId: rollMessage.id
}
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollSecondStored"))
// Now perform the opposed roll resolution
await this.resolveOpposedRolls(store.roll1, store.roll2)
// Clear the store
store.roll1 = null
store.roll2 = null
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
}
else {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollStoreFull"))
}
}
static async resolveOpposedRolls(roll1, roll2) {
// Get actors
let actor1 = game.actors.get(roll1.rollData.actorId)
let actor2 = game.actors.get(roll2.rollData.actorId)
if (!actor1 || !actor2) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noActorFound"))
return
}
// Determine winner
let winner = null
let loser = null
// If there critical success/failure, apply them first (remark : this d100 results)
roll1.rollData.rollCompare = roll1.rollData.rollResult
roll2.rollData.rollCompare = roll2.rollData.rollResult
if (roll1.rollData.resultType === "successCritical") {
roll1.rollData.rollCompare = -roll1.rollData.rollResult
}
if (roll2.rollData.resultType === "failureCritical") {
roll2.rollData.rollCompare = 100 + roll2.rollData.rollResult
}
if (roll2.rollData.resultType === "successCritical") {
roll2.rollData.rollCompare = -roll2.rollData.rollResult
}
if (roll1.rollData.resultType === "failureCritical") {
roll1.rollData.rollCompare = roll1.rollData.rollResult * 2
}
if (roll1.rollData.isSuccess && roll2.rollData.isFailure) {
winner = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
loser = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
}
else if (roll2.rollData.isSuccess && roll1.rollData.isFailure) {
winner = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
loser = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
}
else if (roll1.rollData.rollCompare < roll2.rollData.rollCompare) {
winner = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
loser = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
}
else {
winner = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
loser = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
}
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}`)
@@ -268,11 +451,11 @@ export default class CthulhuEternalUtils {
}
static async registerBabeleTranslations(babele) {
babele.registerConverters( {
'translateRangeUnit': (originalValue) => {
babele.registerConverters({
'translateRangeUnit': (originalValue) => {
return CthulhuEternalUtils.translateRangeUnit(originalValue)
},
'translateRange' : (originalValue) => {
'translateRange': (originalValue) => {
return CthulhuEternalUtils.translateRange(originalValue)
}
})
@@ -280,11 +463,19 @@ export default class CthulhuEternalUtils {
static async damageRoll(rollMessage, formula = null) {
let rollData = rollMessage.rolls[0]?.options?.rollData
let actor = game.actors.get(rollData.actorId)
if (!rollData) {
rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
}
if (!rollData) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noRollDataFound"))
return
}
let actor = game.actors.get(rollData.actorId)
if (!actor) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noActorFound"))
return
}
console.log("Damage roll data", rollData)
rollData.weapon.resultType = rollData.resultType // Keep the result type from the roll message
rollData.weapon.selectiveFireChoice = rollData.selectiveFireChoice // Keep the selected fire choice from the roll message
@@ -336,8 +527,8 @@ export default class CthulhuEternalUtils {
rejectClose: false, // Click on Close button will not launch an error
render: (event, dialog) => {
$(".nudged-score-select").change(event => {
dialogContext.nudgedValue = Number(event.target.value)+1
dialogContext.wpCost = Math.ceil(Math.abs(rollMessage.rolls[0].total - dialogContext.nudgedValue) / 5)
dialogContext.nudgedValue = Number(event.target.value) + 1
dialogContext.wpCost = Math.ceil(Math.abs(rollMessage.rolls[0].total - dialogContext.nudgedValue) / 5)
$("#nudged-wp-cost").val(dialogContext.wpCost)
})
}
@@ -357,6 +548,10 @@ export default class CthulhuEternalUtils {
roll.toMessage()
actor.system.modifyWP(-dialogContext.wpCost)
// Delete the initial roll message
await rollMessage.delete()
}
static setupCSSRootVariables() {
@@ -375,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")`);
}
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-000230
MANIFEST-000273

View File

@@ -1,7 +1,7 @@
2025/07/29-18:28:11.485462 7fbfdeffd6c0 Recovering log #227
2025/07/29-18:28:11.542963 7fbfdeffd6c0 Delete type=3 #225
2025/07/29-18:28:11.543020 7fbfdeffd6c0 Delete type=0 #227
2025/07/29-18:28:25.939648 7fbd3ffff6c0 Level-0 table #233: started
2025/07/29-18:28:25.939685 7fbd3ffff6c0 Level-0 table #233: 0 bytes OK
2025/07/29-18:28:25.996353 7fbd3ffff6c0 Delete type=0 #231
2025/07/29-18:28:25.996608 7fbd3ffff6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)
2025/11/21-23:23:35.521610 7f21eeffd6c0 Recovering log #271
2025/11/21-23:23:35.612353 7f21eeffd6c0 Delete type=3 #269
2025/11/21-23:23:35.612430 7f21eeffd6c0 Delete type=0 #271
2025/11/21-23:35:34.796894 7f21ed7fa6c0 Level-0 table #276: started
2025/11/21-23:35:34.796917 7f21ed7fa6c0 Level-0 table #276: 0 bytes OK
2025/11/21-23:35:34.806397 7f21ed7fa6c0 Delete type=0 #274
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,11 +1,7 @@
2025/07/29-17:56:07.304105 7fbfddffb6c0 Delete type=3 #1
2025/07/29-18:20:36.901079 7fbd3ffff6c0 Level-0 table #228: started
2025/07/29-18:20:36.901119 7fbd3ffff6c0 Level-0 table #228: 0 bytes OK
2025/07/29-18:20:36.907068 7fbd3ffff6c0 Delete type=0 #226
2025/07/29-18:20:36.913035 7fbd3ffff6c0 Manual compaction at level-0 from '!items!4oyPRBWPBWAChrJP' @ 72057594037927935 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at '!items!zVFfp3o0G0Zg3Ia4' @ 52 : 1
2025/07/29-18:20:36.913051 7fbd3ffff6c0 Compacting 1@0 + 0@1 files
2025/07/29-18:20:36.917150 7fbd3ffff6c0 Generated table #229@0: 26 keys, 60964 bytes
2025/07/29-18:20:36.917172 7fbd3ffff6c0 Compacted 1@0 + 0@1 files => 60964 bytes
2025/07/29-18:20:36.923988 7fbd3ffff6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/07/29-18:20:36.924086 7fbd3ffff6c0 Delete type=2 #60
2025/07/29-18:20:36.941818 7fbd3ffff6c0 Manual compaction at level-0 from '!items!zVFfp3o0G0Zg3Ia4' @ 52 : 1 .. '!items!zVFfp3o0G0Zg3Ia4' @ 0 : 0; will stop at (end)
2025/11/12-23:48:30.686758 7f4781ffb6c0 Recovering log #267
2025/11/12-23:48:30.697449 7f4781ffb6c0 Delete type=3 #265
2025/11/12-23:48:30.697507 7f4781ffb6c0 Delete type=0 #267
2025/11/13-13:58:42.410831 7f4780bff6c0 Level-0 table #272: started
2025/11/13-13:58:42.410865 7f4780bff6c0 Level-0 table #272: 0 bytes OK
2025/11/13-13:58:42.417362 7f4780bff6c0 Delete type=0 #270
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

@@ -1 +1 @@
MANIFEST-000399
MANIFEST-000442

View File

@@ -1,7 +1,7 @@
2025/07/29-18:28:11.326583 7fbfdd7fa6c0 Recovering log #396
2025/07/29-18:28:11.422544 7fbfdd7fa6c0 Delete type=3 #394
2025/07/29-18:28:11.422600 7fbfdd7fa6c0 Delete type=0 #396
2025/07/29-18:28:25.859630 7fbd3ffff6c0 Level-0 table #402: started
2025/07/29-18:28:25.859675 7fbd3ffff6c0 Level-0 table #402: 0 bytes OK
2025/07/29-18:28:25.902834 7fbd3ffff6c0 Delete type=0 #400
2025/07/29-18:28:25.996583 7fbd3ffff6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)
2025/11/21-23:23:35.280848 7f21edffb6c0 Recovering log #440
2025/11/21-23:23:35.368467 7f21edffb6c0 Delete type=3 #438
2025/11/21-23:23:35.368552 7f21edffb6c0 Delete type=0 #440
2025/11/21-23:35:34.777225 7f21ed7fa6c0 Level-0 table #445: started
2025/11/21-23:35:34.777275 7f21ed7fa6c0 Level-0 table #445: 0 bytes OK
2025/11/21-23:35:34.786357 7f21ed7fa6c0 Delete type=0 #443
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,11 +1,7 @@
2025/07/29-17:56:07.265693 7fbfddffb6c0 Delete type=3 #1
2025/07/29-18:20:36.941849 7fbd3ffff6c0 Level-0 table #397: started
2025/07/29-18:20:36.941895 7fbd3ffff6c0 Level-0 table #397: 0 bytes OK
2025/07/29-18:20:36.948373 7fbd3ffff6c0 Delete type=0 #395
2025/07/29-18:20:36.960564 7fbd3ffff6c0 Manual compaction at level-0 from '!folders!5PrT9QmN1cFPzDFP' @ 72057594037927935 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at '!items!zvoUByzWSWZ87fxA' @ 1281 : 1
2025/07/29-18:20:36.960577 7fbd3ffff6c0 Compacting 1@0 + 0@1 files
2025/07/29-18:20:36.968003 7fbd3ffff6c0 Generated table #398@0: 556 keys, 320457 bytes
2025/07/29-18:20:36.968044 7fbd3ffff6c0 Compacted 1@0 + 0@1 files => 320457 bytes
2025/07/29-18:20:36.974076 7fbd3ffff6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/07/29-18:20:36.974209 7fbd3ffff6c0 Delete type=2 #277
2025/07/29-18:20:36.980862 7fbd3ffff6c0 Manual compaction at level-0 from '!items!zvoUByzWSWZ87fxA' @ 1281 : 1 .. '!items!zvoUByzWSWZ87fxA' @ 0 : 0; will stop at (end)
2025/11/12-23:48:30.659183 7f47817fa6c0 Recovering log #436
2025/11/12-23:48:30.670042 7f47817fa6c0 Delete type=3 #434
2025/11/12-23:48:30.670124 7f47817fa6c0 Delete type=0 #436
2025/11/13-13:58:42.417482 7f4780bff6c0 Level-0 table #441: started
2025/11/13-13:58:42.417509 7f4780bff6c0 Level-0 table #441: 0 bytes OK
2025/11/13-13:58:42.423396 7f4780bff6c0 Delete type=0 #439
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

View File

@@ -1 +1 @@
MANIFEST-000045
MANIFEST-000088

View File

@@ -1,7 +1,7 @@
2025/07/29-18:28:11.426069 7fbfde7fc6c0 Recovering log #42
2025/07/29-18:28:11.482529 7fbfde7fc6c0 Delete type=3 #40
2025/07/29-18:28:11.482582 7fbfde7fc6c0 Delete type=0 #42
2025/07/29-18:28:25.996691 7fbd3ffff6c0 Level-0 table #48: started
2025/07/29-18:28:25.996717 7fbd3ffff6c0 Level-0 table #48: 0 bytes OK
2025/07/29-18:28:26.032508 7fbd3ffff6c0 Delete type=0 #46
2025/07/29-18:28:26.140428 7fbd3ffff6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
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

@@ -1,11 +1,7 @@
2025/07/29-17:56:07.286370 7fbfdeffd6c0 Delete type=3 #1
2025/07/29-18:20:36.907148 7fbd3ffff6c0 Level-0 table #43: started
2025/07/29-18:20:36.907174 7fbd3ffff6c0 Level-0 table #43: 0 bytes OK
2025/07/29-18:20:36.912933 7fbd3ffff6c0 Delete type=0 #41
2025/07/29-18:20:36.924240 7fbd3ffff6c0 Manual compaction at level-0 from '!folders!0DI3T2jve3nsmsfZ' @ 72057594037927935 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at '!items!zyxA9DhO36t5OBDv' @ 55 : 1
2025/07/29-18:20:36.924252 7fbd3ffff6c0 Compacting 1@0 + 0@1 files
2025/07/29-18:20:36.928859 7fbd3ffff6c0 Generated table #44@0: 362 keys, 93592 bytes
2025/07/29-18:20:36.928870 7fbd3ffff6c0 Compacted 1@0 + 0@1 files => 93592 bytes
2025/07/29-18:20:36.935549 7fbd3ffff6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2025/07/29-18:20:36.935675 7fbd3ffff6c0 Delete type=2 #35
2025/07/29-18:20:36.941835 7fbd3ffff6c0 Manual compaction at level-0 from '!items!zyxA9DhO36t5OBDv' @ 55 : 1 .. '!items!zyxA9DhO36t5OBDv' @ 0 : 0; will stop at (end)
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.

Binary file not shown.

View File

View File

@@ -11,8 +11,8 @@
@import "weapon.less";
@import "armor.less";
@import "motivation.less";
@import "mentaldisorder.less";
@import "bond.less";
@import "mentaldisorder.less";
@import "bond.less";
@import "chat.less";
@import "gear.less";
@import "arcane.less";
@@ -21,4 +21,4 @@
@import "tome.less";
}
@import "roll.less";
@import "roll.less";

View File

@@ -111,8 +111,8 @@ i.fvtt-cthulhu-eternal {
padding: 2px 2px;
cursor: pointer;
transition: background-color 0.3s;
min-width: 6.0rem;
max-width: 6.0rem;
min-width: 6rem;
max-width: 6rem;
}
}
.san-loose-buttons {
@@ -128,8 +128,8 @@ i.fvtt-cthulhu-eternal {
padding: 2px 2px;
cursor: pointer;
transition: background-color 0.3s;
min-width: 3.0rem;
max-width: 3.0rem;
min-width: 3rem;
max-width: 3rem;
}
}
.result-non-lethal {
@@ -141,5 +141,28 @@ i.fvtt-cthulhu-eternal {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.02);
}
.combatants-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 4px;
margin: 8px 0;
button.apply-wounds-btn {
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 0.7);
border: 1px solid #4b4a44;
padding: 4px 6px;
cursor: pointer;
transition: background-color 0.3s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-color: #f0f0e0;
color: #1c1c1c;
&:hover {
background-color: #d5d5c5;
border-color: #2d2d2a;
}
}
}
}
}

View File

@@ -53,6 +53,10 @@
color: var(--color-dark-1);
}
.li-apply-wounds {
display: none;
}
&.dice-roll {
flex-direction: column;
@@ -70,7 +74,7 @@
border: 0px;
}
.intro-chat {
color:var(--color-dark-1);
color: var(--color-dark-1);
border-radius: 20px;
display: flex;
flex-direction: row;
@@ -91,22 +95,7 @@
li {
margin: 0 10px;
font-family: var(--font-primary);
font-size: calc(var(--font-size-standard) * 1.0);
}
.nudge-roll {
font-size: calc(var(--font-size-standard) * 1.0);
margin-left: 2rem;
display: none;
}
.healing-roll {
font-size: calc(var(--font-size-standard) * 1.0);
margin-left: 2rem;
display: none;
}
.roll-damage {
font-size: calc(var(--font-size-standard) * 1.0);
margin-left: 2rem;
display: none;
font-size: calc(var(--font-size-standard) * 1);
}
.result-success {
color: var(--color-success);
@@ -145,4 +134,227 @@
font-size: calc(var(--font-size-standard) * 1.2);
text-shadow: 0 0 10px var(--color-shadow-primary);
}
.chat-actions {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
padding: 0.5rem;
margin-top: 0.5rem;
border-top: 1px solid var(--color-border-light-primary);
background: rgba(0, 0, 0, 0.05);
border-radius: 0 0 5px 5px;
justify-content: center;
.chat-action-button {
display: inline-flex !important;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
padding: 0 !important;
margin: 0 !important;
background: var(--color-dark-6);
color: var(--color-light-1);
border: 1px solid var(--color-border-light-primary);
border-radius: 3px;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
line-height: 1;
&:hover {
background: var(--color-dark-5);
border-color: var(--color-border-dark);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
transform: translateY(-1px);
}
i {
font-size: 1rem !important;
line-height: 1;
display: block;
margin: 0;
}
}
.nudge-roll,
.damage-roll,
.healing-roll,
.opposed-roll {
display: none;
}
}
}
.opposed-roll-result {
padding: 0.5rem;
background: rgba(0, 0, 0, 0.05);
border-radius: 5px;
font-family: var(--font-primary);
.opposed-header {
text-align: center;
margin-bottom: 0.3rem;
padding-bottom: 0.2rem;
border-bottom: 1px solid var(--color-border-light-primary);
h3 {
margin: 0;
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1);
color: var(--color-dark-1);
}
}
.opposed-content {
display: flex;
flex-direction: column;
gap: 0.15rem;
margin-bottom: 0.3rem;
}
.opposed-winner,
.opposed-loser {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.3rem 0.4rem;
border-radius: 5px;
}
.opposed-winner {
background: rgba(34, 139, 34, 0.1);
border: 2px solid var(--color-success);
}
.opposed-loser {
background: rgba(220, 20, 60, 0.1);
border: 2px solid var(--color-failure);
}
.character-info {
display: flex;
align-items: center;
gap: 0.5rem;
img {
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid var(--color-border-light-primary);
}
.character-name {
display: flex;
flex-direction: column;
gap: 0.15rem;
strong {
font-size: calc(var(--font-size-standard) * 0.75);
text-transform: uppercase;
color: var(--color-dark-2);
}
.winner-name {
font-size: calc(var(--font-size-standard) * 0.95);
font-weight: bold;
color: var(--color-success);
}
.loser-name {
font-size: calc(var(--font-size-standard) * 0.95);
font-weight: bold;
color: var(--color-failure);
}
}
}
.roll-result {
display: flex;
align-items: center;
gap: 0.3rem;
.roll-value {
font-size: calc(var(--font-size-standard) * 1.2);
font-weight: bold;
font-family: var(--font-title);
}
.critical-badge {
padding: 0.15rem 0.3rem;
border-radius: 3px;
font-size: calc(var(--font-size-standard) * 0.7);
font-weight: bold;
text-transform: uppercase;
background: var(--color-critical-success);
color: white;
}
}
.winner-result .roll-value {
color: var(--color-success);
}
.loser-result .roll-value {
color: var(--color-failure);
}
.versus-separator {
display: flex;
align-items: center;
justify-content: center;
gap: 0.3rem;
padding: 0;
font-size: calc(var(--font-size-standard) * 0.85);
font-weight: bold;
color: var(--color-dark-2);
i {
font-size: calc(var(--font-size-standard) * 0.9);
}
}
.chat-actions {
display: flex;
justify-content: center;
gap: 0.375rem;
padding: 0.5rem;
margin-top: 0.3rem;
border-top: 1px solid var(--color-border-light-primary);
background: rgba(0, 0, 0, 0.05);
border-radius: 0 0 5px 5px;
.chat-action-button {
display: inline-flex !important;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
padding: 0 !important;
margin: 0 !important;
background: var(--color-dark-6);
color: var(--color-light-1);
border: 1px solid var(--color-border-light-primary);
border-radius: 3px;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
line-height: 1;
&:hover {
background: var(--color-dark-5);
border-color: var(--color-border-dark);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
transform: translateY(-1px);
}
i {
font-size: 1rem !important;
line-height: 1;
display: block;
margin: 0;
}
}
}
}

View File

@@ -6,7 +6,7 @@
"download": "#{DOWNLOAD}#",
"url": "https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal",
"license": "LICENSE",
"version": "13.0.1",
"version": "13.0.2",
"authors": [
{
"name": "Uberwald",

View File

@@ -2,7 +2,7 @@
<div class="chat-lethal-damage">
<ul>
<li><strong>{{weapon.name}} : {{localize "CTHULHUETERNAL.Label.lethalityRoll"}}</strong></li>
<li>{{localize "CTHULHUETERNAL.Label.result"}} :{{rollResult}} ({{lethalScore}})</li>
<li>{{localize "CTHULHUETERNAL.Label.result"}} :{{rollResult}} ({{lethalScore}})</li>
{{#if weapon.system.selectiveFireChoice}}
@@ -21,12 +21,20 @@
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}: {{ammoUsed}} / {{weapon.system.ammo.value}}</li>
{{/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}}
<li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityLethal"}}</li>
<li class="result-lethal">{{localize "CTHULHUETERNAL.Label.lethalityWounded"}}</li>
{{else}}
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.lethalityNotLethal"}}</li>
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.lethalityNotWounded"}}: <strong>{{wounds}}</strong></li>
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.lethalityNotWounded"}}: <strong>{{wounds}}</strong>
</li>
{{/if}}
</ul>

View File

@@ -6,126 +6,222 @@
</div>
<div class="intro-right">
<ul>
<li><strong>{{actingCharName}}</strong></li>
{{#if (eq rollType "char")}}
<li><strong>{{localize "CTHULHUETERNAL.Label.charRoll"}}</strong></li>
<li><strong>{{localize "CTHULHUETERNAL.Label.charRoll"}}</strong></li>
{{/if}}
{{#if (eq rollType "skill")}}
<li><strong>{{localize "CTHULHUETERNAL.Label.skillRoll"}}</strong></li>
<li><strong>{{localize
"CTHULHUETERNAL.Label.skillRoll"
}}</strong></li>
{{/if}}
{{#if isNudgedRoll}}
<li><strong>{{localize "CTHULHUETERNAL.Label.nudgedRoll"}} : {{wpCost}} {{localize "CTHULHUETERNAL.Label.WPSpent"}}</strong></li>
<li><strong>{{localize "CTHULHUETERNAL.Label.nudgedRoll"}}
:
{{wpCost}}
{{localize "CTHULHUETERNAL.Label.WPSpent"}}</strong></li>
{{/if}}
{{#if weapon}}
<li><strong>{{localize "CTHULHUETERNAL.Label.Weapon"}} : {{weapon.name}}</strong></li>
<li><strong>{{localize "CTHULHUETERNAL.Label.Weapon"}}
:
{{weapon.name}}</strong></li>
{{/if}}
{{#if (eq rollType "resource")}}
<li><strong>{{rollItem.name}} : {{initialScore}}</strong></li>
<li><strong>{{rollItem.name}} : {{initialScore}}</strong></li>
{{else}}
<li><strong>{{rollItem.name}} : {{initialScore}}%</strong></li>
<li><strong>{{rollItem.name}} : {{initialScore}}%</strong></li>
{{/if}}
{{#if isZeroWP}}
<li class="red-warning">{{localize "CTHULHUETERNAL.Label.ZeroWP"}}</li>
<li class="red-warning">{{localize
"CTHULHUETERNAL.Label.ZeroWP"
}}</li>
{{else}}
{{#if isLowWP}}
<li class="orange-warning">{{localize "CTHULHUETERNAL.Label.LowWP"}} : -20%</li>
{{/if}}
{{#if isLowWP}}
<li class="orange-warning">{{localize "CTHULHUETERNAL.Label.LowWP"}}
: -20%</li>
{{/if}}
{{/if}}
{{#if isExhausted}}
<li class="orange-warning">{{localize "CTHULHUETERNAL.Label.Exhausted"}} : -20%</li>
<li class="orange-warning">{{localize
"CTHULHUETERNAL.Label.Exhausted"
}}
: -20%</li>
{{/if}}
{{#if (eq rollType "resource")}}
<li>{{localize "CTHULHUETERNAL.Label.multiplier"}} : {{multiplier}}</li>
{{#if (eq rollType "resource")}}
<li>{{localize "CTHULHUETERNAL.Label.multiplier"}}
:
{{multiplier}}</li>
{{else}}
<li>{{localize "CTHULHUETERNAL.Label.modifier"}} : {{totalModifier}}%</li>
<li>{{localize "CTHULHUETERNAL.Label.modifier"}}
:
{{totalModifier}}%</li>
{{/if}}
<li>{{localize "CTHULHUETERNAL.Label.targetScore"}} : {{targetScore}}%</li>
<li>{{localize "CTHULHUETERNAL.Label.targetScore"}}
:
{{targetScore}}%</li>
{{#if isSuccess}}
{{#if isCritical}}
<li class="result-critical-success">{{localize "CTHULHUETERNAL.Label.criticalSuccess"}}
<li class="result-critical-success">{{localize
"CTHULHUETERNAL.Label.criticalSuccess"
}}
</li>
{{else}}
<li class="result-success">
{{localize "CTHULHUETERNAL.Label.success"}}
{{#if isNudge}}
<a class="nudge-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollNudge"}}"><i class="fa-solid fa-circle-sort-down"></i></a>
{{/if}}
</li>
<li class="result-success">
{{localize "CTHULHUETERNAL.Label.success"}}
</li>
{{/if}}
{{#if (eq rollType "weapon")}}
<li>
{{#if (eq weapon.system.weaponType "rangedfirearm")}}
{{#if weapon.system.hasDamageDistance}}
{{#each weapon.system.damageDistance as |damageDistance|}}
{{#if (gt damageDistance.distance 0)}}
<a class="damage-roll" data-item-id="{{weapon.id}}" data-action="roll" data-roll-type="damage"
data-roll-value="{{damageDistance.damage}}" >
<i class="fa-solid fa-gun"></i>
<span class="damage-distance">{{damageDistance.distance}}:{{damageDistance.damage}}&nbsp;&nbsp;</span>
</a>
{{/if}}
{{/each}}
{{else}}
<a class="damage-roll" data-roll-value="{{weapon.system.damage}}" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollDamage"}}"><i class="fa-solid fa-gun"></i></a>
{{/if}}
{{else}}
<a class="damage-roll" data-roll-value="{{weapon.system.damage}}" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollDamage"}}"><i class="fa-solid fa-sword"></i></a>
{{/if}}
</li>
{{/if}}
{{#if (eq rollType "skill") }}
{{#if rollItem.system.isHealing}}
<li>
<a class="healing-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollHealing"}}"><i class="fa-solid fa-heart"></i></a>
</li>
{{/if}}
{{/if}}
</li>
{{/if}}
{{#if isFailure}}
{{#if isCritical}}
<li class="result-critical-failure">{{localize "CTHULHUETERNAL.Label.criticalFailure"}}
<li class="result-critical-failure">{{localize
"CTHULHUETERNAL.Label.criticalFailure"
}}
</li>
{{else}}
<li class="result-failure">
{{localize "CTHULHUETERNAL.Label.failure"}}
</li>
<li class="result-failure">
{{localize "CTHULHUETERNAL.Label.failure"}}
</li>
{{/if}}
{{#if isNudge}}
<li>
<a class="nudge-roll" data-tooltip="{{localize "CTHULHUETERNAL.Label.rollNudge"}}"><i class="fa-solid fa-circle-sort-down"></i></a>
</li>
{{/if}}
{{/if}}
<!-- {{#if isAttackRoll}}
{{#if defenseRoll}}
<li>{{localize "CTHULHUETERNAL.Label.defenseRoll"}}</li>
{{#if attackSuccess}}
<li class="result-success">{{localize
"CTHULHUETERNAL.Label.attackSuccess"
}}</li>
{{else}}
<li class="result-failure">{{localize
"CTHULHUETERNAL.Label.attackFailure"
}}</li>
{{/if}}
{{else}}
{{#if targetId}}
<li class="orange-warning">{{localize
"CTHULHUETERNAL.Label.noDefenseRoll"
}}</li>
{{else}}
<li class="orange-warning">{{localize
"CTHULHUETERNAL.Label.noTarget"
}}</li>
{{/if}}
{{/if}}
{{/if}} -->
</ul>
</div>
</div>
{{#if isDamage}}
<div>
{{#if (and isGM hasTarget)}}
{{{localize "CTHULHUETERNAL.Roll.displayArmor" targetName=targetName targetArmor=targetArmor
realDamage=realDamage}}}
{{/if}}
</div>
<div>
{{#if (and isGM hasTarget)}}
{{{localize
"CTHULHUETERNAL.Roll.displayArmor"
targetName=targetName
targetArmor=targetArmor
realDamage=realDamage
}}}
{{/if}}
</div>
{{/if}}
{{#unless isPrivate}}
<div class="dice-result">
<h4 class="dice-total">{{total}}</h4>
<div class="dice-formula">{{formula}}</div>
{{{tooltip}}}
</div>
<div class="dice-result">
<h4 class="dice-total">{{total}}</h4>
<div class="dice-formula">{{formula}}</div>
{{{tooltip}}}
</div>
{{/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>

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,22 +1,59 @@
<div class="{{cssClass}}">
<div class="chat-lethal-damage">
<ul>
<li><strong>{{weapon.name}} : {{localize "CTHULHUETERNAL.Label.damageRoll"}}</strong></li>
<li>{{localize "CTHULHUETERNAL.Label.result"}} :{{rollResult}} ({{formula}})</li>
<li><strong>{{weapon.name}}
:
{{localize "CTHULHUETERNAL.Label.damageRoll"}}</strong></li>
<li>{{localize "CTHULHUETERNAL.Label.result"}}
:{{rollResult}}
({{formula}})</li>
{{#if (gt weapon.system.killRadius 0)}}
<li>{{localize "CTHULHUETERNAL.Label.killRadius"}} : {{weapon.system.killRadius}} {{weapon.system.rangeUnit}}</li>
<li>{{localize "CTHULHUETERNAL.Label.killRadiusInfo"}}</li>
<li>{{localize "CTHULHUETERNAL.Label.killRadius"}}
:
{{weapon.system.killRadius}}
{{weapon.system.rangeUnit}}</li>
<li>{{localize "CTHULHUETERNAL.Label.killRadiusInfo"}}</li>
{{/if}}
{{#if (gt weapon.system.armorPiercing 0)}}
<li>{{localize "CTHULHUETERNAL.Label.armorPiercing"}} : {{weapon.system.armorPiercing}}</li>
<li>{{localize "CTHULHUETERNAL.Label.armorPiercing"}}
:
{{weapon.system.armorPiercing}}</li>
{{/if}}
{{#if (gt weapon.system.penetration 0)}}
<li>{{localize "CTHULHUETERNAL.Label.penetration"}}
:
{{weapon.system.penetration}}</li>
{{/if}}
{{#if ammoUsed}}
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}: {{ammoUsed}} / {{weapon.system.ammo.value}}</li>
<li>{{localize "CTHULHUETERNAL.Label.ammoUsed"}}:
{{ammoUsed}}
/
{{weapon.system.ammo.value}}</li>
{{/if}}
<li class="result-non-lethal">{{localize "CTHULHUETERNAL.Label.damageMessage"}}: <strong>{{rollResult}}</strong></li>
<li class="li-apply-wounds">
<div>{{localize "CTHULHUETERNAL.Label.applyWounds"}}</div>
<div class="combatants-grid">
{{#each combatants}}
<button
class="apply-wounds-btn"
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>

View File

@@ -1,33 +1,70 @@
<div class="fvtt-cthulhu-eternal-roll-dialog">
<fieldSet>
{{#if (eq rollType "skill")}}
<legend>{{localize "CTHULHUETERNAL.Label.skill"}}</legend>
<legend>{{localize "CTHULHUETERNAL.Label.skill"}}</legend>
{{/if}}
{{#if (eq rollType "char")}}
<legend>{{localize "CTHULHUETERNAL.Label.characteristic"}}</legend>
<legend>{{localize "CTHULHUETERNAL.Label.characteristic"}}</legend>
{{/if}}
{{#if (eq rollType "resource")}}
<legend>{{localize "CTHULHUETERNAL.Label.resourceRating"}}</legend>
<div class="dialog-skill">{{rollItem.name}} : <span class="resource-score">{{initialScore}} ({{mul initialScore 5}}%)</span></div>
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Hand"}} : {{rollItem.hand}} <input type="checkbox" data-action="selectHand" {{checked rollItem.enableHand}}></div>
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Stowed"}} : {{rollItem.stowed}} <input type="checkbox" data-action="selectStowed" {{checked rollItem.enableStowed}}></div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.Storage"}} : {{rollItem.storage}}
<input type="checkbox" data-action="selectStorage" {{checked rollItem.enableStorage}}>
</div>
<legend>{{localize "CTHULHUETERNAL.Label.resourceRating"}}</legend>
<div class="dialog-skill">{{rollItem.name}}
:
<span class="resource-score">{{initialScore}}
({{mul initialScore 5}}%)</span></div>
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Hand"}}
:
{{rollItem.hand}}
<input
type="checkbox"
data-action="selectHand"
{{checked rollItem.enableHand}}
/></div>
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Stowed"}}
:
{{rollItem.stowed}}
<input
type="checkbox"
data-action="selectStowed"
{{checked rollItem.enableStowed}}
/></div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.Storage"}}
:
{{rollItem.storage}}
<input
type="checkbox"
data-action="selectStorage"
{{checked rollItem.enableStorage}}
/>
</div>
{{else}}
<div class="dialog-skill">{{rollItem.name}} : {{initialScore}}%</div>
<div class="dialog-skill">{{rollItem.name}} : {{initialScore}}%</div>
{{/if}}
{{#if weapon}}
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Weapon"}} : {{weapon.name}}</div>
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Weapon"}}
:
{{weapon.name}}</div>
{{#if targetName}}
<div class="dialog-skill">{{localize "CTHULHUETERNAL.Label.Target"}}
:
{{targetName}}</div>
{{/if}}
{{#if (eq weapon.system.weaponType "melee")}}
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetMove"}}
<select name="meleeTargetMoveChoice" class="roll-skill-modifier">
{{selectOptions choiceMeleeTargetMove localize=true selected=meleeTargetMoveChoice valueAttr="id" labelAttr="label"}}
{{selectOptions
choiceMeleeTargetMove
localize=true
selected=meleeTargetMoveChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
{{/if}}
@@ -35,25 +72,45 @@
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.rangedRange"}}
<select name="rangedRangeChoice" class="roll-skill-modifier">
{{selectOptions choiceRangedRange localize=true selected=rangedRangeChoice valueAttr="id" labelAttr="label"}}
{{selectOptions
choiceRangedRange
localize=true
selected=rangedRangeChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetMove"}}
<select name="rangedTargetMoveChoice" class="roll-skill-modifier">
{{selectOptions choiceRangedTargetMove localize=true selected=rangedTargetMoveChoice valueAttr="id" labelAttr="label"}}
{{selectOptions
choiceRangedTargetMove
localize=true
selected=rangedTargetMoveChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.aimingLastRound"}}
<input type="checkbox" name="aimingLastRound">
<input
type="checkbox"
class="aimingLastRound"
name="aimingLastRound"
/>
</div>
{{#if weapon.system.hasSight}}
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.aimingWithSight"}}
<input type="checkbox" name="aimingWithSight">
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.aimingWithSight"}}
<input
type="checkbox"
class="aimingWithSight"
name="aimingWithSight"
/>
</div>
{{/if}}
{{/if}}
@@ -61,63 +118,97 @@
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.visibility"}}
<select name="visibilityChoice" class="roll-skill-modifier">
{{selectOptions choiceVisibility localize=true selected=visibilityChoice valueAttr="id" labelAttr="label"}}
{{selectOptions
choiceVisibility
localize=true
selected=visibilityChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.attackerState"}}
<select name="attackerStateChoice" class="roll-skill-modifier">
{{selectOptions choiceAttackerState localize=true selected=attackerStateChoice valueAttr="id" labelAttr="label"}}
{{selectOptions
choiceAttackerState
localize=true
selected=attackerStateChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
<div class="dialog-skill">
{{localize "CTHULHUETERNAL.Label.targetSize"}}
<select name="targetSizeChoice" class="roll-skill-modifier">
{{selectOptions choiceTargetSize localize=true selected=targetSizeChoice valueAttr="id" labelAttr="label"}}
{{selectOptions
choiceTargetSize
localize=true
selected=targetSizeChoice
valueAttr="id"
labelAttr="label"
}}
</select>
</div>
{{#if weapon.system.hasSelectiveFire}}
<div class="dialog-skill">Selective Fire :
<select name="selectiveFireChoice" class="roll-skill-modifier">
{{selectOptions choiceSelectiveFire localize=true selected=selectiveFireChoice valueAttr="id" labelAttr="label"}}
</select>
</div>
<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 isZeroWP}}
<div class="dialog-skill red-warning">{{localize "CTHULHUETERNAL.Label.ZeroWP"}}</div>
<div class="dialog-skill red-warning">{{localize
"CTHULHUETERNAL.Label.ZeroWP"
}}</div>
{{else}}
{{#if isLowWP}}
<div class="dialog-skill orange-warning">{{localize "CTHULHUETERNAL.Label.LowWP"}} : -20%</div>
<div class="dialog-skill orange-warning">{{localize
"CTHULHUETERNAL.Label.LowWP"
}}
: -20%</div>
{{/if}}
{{/if}}
{{#if isExhausted}}
<div class="dialog-skill orange-warning">{{localize "CTHULHUETERNAL.Label.Exhausted"}} : -20%</div>
<div class="dialog-skill orange-warning">{{localize
"CTHULHUETERNAL.Label.Exhausted"
}}
: -20%</div>
{{/if}}
</fieldSet>
{{#if hasModifier}}
<fieldSet class="dialog-modifier">
<legend>{{localize "CTHULHUETERNAL.Label.modifier"}}</legend>
<select name="modifier" class="roll-skill-modifier">
{{selectOptions choiceModifier selected=modifier}}
</select>
</fieldSet>
<fieldSet class="dialog-modifier">
<legend>{{localize "CTHULHUETERNAL.Label.modifier"}}</legend>
<select name="modifier" class="roll-skill-modifier">
{{selectOptions choiceModifier selected=modifier}}
</select>
</fieldSet>
{{/if}}
{{#if hasMultiplier}}
<fieldSet class="dialog-modifier">
<legend>{{localize "CTHULHUETERNAL.Label.multiplier"}}</legend>
<select name="multiplier" class="roll-skill-modifier roll-skill-multiplier">
{{selectOptions choiceMultiplier selected=multiplier}}
</select>
</fieldSet>
<fieldSet class="dialog-modifier">
<legend>{{localize "CTHULHUETERNAL.Label.multiplier"}}</legend>
<select
name="multiplier"
class="roll-skill-modifier roll-skill-multiplier"
>
{{selectOptions choiceMultiplier selected=multiplier}}
</select>
</fieldSet>
{{/if}}
<fieldSet>