Compare commits

...

18 Commits
12.0.7 ... main

Author SHA1 Message Date
ff89b62133 Fix LethalFantasy stuff 2025-02-13 23:23:32 +01:00
b3eb908f05 Add rituals and tomes and creatures 2025-02-07 18:06:08 +01:00
2ac0f53c4f Add rituals and tomes 2025-02-07 08:34:57 +01:00
270471f137 Ready for next release 2025-02-06 22:37:57 +01:00
da9d0e41a5 Fix - Auto addk unarmed fight on new actor 2025-02-06 22:35:07 +01:00
42f25aa186 Fix - Fix gear icon 2025-02-06 22:23:26 +01:00
5f9057db37 Fix - Auto use damage bonus for melee/unarmed 2025-02-06 22:21:56 +01:00
82731c2d40 Fix - Auto allocate current era 2025-02-06 21:54:59 +01:00
a75a3bf157 Fix - Display cured/uncured for mental disorder 2025-02-06 21:35:22 +01:00
e6e6de35df Fix - Display cured/uncured for mental disorder 2025-02-06 21:10:15 +01:00
7341580a7b Fix - Auto set bond values 2025-02-06 20:55:14 +01:00
75a81e589b Sync with wfrp 4, v8.4.0 2025-02-01 21:17:58 +01:00
74f237c30f Sync with wfrp 4, v8.4.0 2025-02-01 21:17:42 +01:00
440e5c0b66 Sync with wfrp 4, v8.4.0 2025-02-01 21:17:27 +01:00
554220a812 Sync with wfrp 4, v8.4.0 2025-02-01 21:10:28 +01:00
e7ea8138c9 Enhance fonts/sizing/colors 2025-01-28 14:22:11 +01:00
6a18f7fa29 Additionnal cleanup 2025-01-28 07:20:29 +01:00
7070d4c1da Sync bol for module releas 2025-01-25 22:28:16 +01:00
70 changed files with 3930 additions and 453 deletions

@ -27,7 +27,7 @@ jobs:
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/system.json
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-cthulhu-eternal.zip
# Create a zip file with all files required by the module to add to the release

Binary file not shown.

BIN
assets/fonts/FeFCrm2.ttf Normal file

Binary file not shown.

BIN
assets/fonts/FeGPrm2.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><filter id="shadow-1" height="300%" width="300%" x="-100%" y="-100%"><feFlood flood-color="rgba(255, 255, 255, 1)" result="flood"></feFlood><feComposite in="flood" in2="SourceGraphic" operator="atop" result="composite"></feComposite><feGaussianBlur in="composite" stdDeviation="15" result="blur"></feGaussianBlur><feOffset dx="0" dy="0" result="offset"></feOffset><feComposite in="SourceGraphic" in2="offset" operator="over"></feComposite></filter></defs><g class="" style="" transform="translate(0,0)"><path d="M227.227 21.777c-1.845 0-3.704.05-5.567.157-15.314.875-30.76 5.305-39.494 10.863l-.008 73.15c2.884-.094 5.777-.147 8.676-.142 23.382.036 47.104 3.286 68.47 9.513l.01-87.507c-7.034-3.518-19.178-6.03-32.087-6.033zm80.74 9.16c-11.925.15-23.077 2.364-29.967 5.596l-.008 77.602v7.658c38.486 15.67 64.814 42.48 58.735 78.764l-.96 5.73-5.562 1.674c-17.45 5.253-34.872 9.703-52.225 13.335V246.53c25.562-.704 51.327-2.687 77.145-6.098l.02-197.928c-8.284-5.563-23.508-10.243-38.842-11.328-2.792-.198-5.584-.273-8.336-.238zM143.223 46.294c-1.176-.015-2.374-.01-3.588.02-4.175.1-8.533.468-12.903 1.152-15.67 2.454-31.477 8.565-40.406 15.402l-.01 72.955c18.808-15.81 46.704-25.143 77.15-28.54l.007-57.966c-4.82-1.752-12.018-2.916-20.25-3.023zm258.394 3.46c-10.804.117-20.722 1.93-27.043 4.655l-.02 183.182c25.074-4.02 50.16-9.412 75.122-16.358l1.99-158.447c-8.352-5.9-23.648-11.025-39.05-12.553-3.698-.366-7.398-.517-11-.478zm-222.775 74.202c-53.72.702-101.407 20.365-97.887 66.6 15.836-3.918 30.84-5.893 44.94-6.1 34.84-.51 64.213 9.704 87.318 27.613 34.608-3.11 69.852-10 105.412-20.314.14-41.287-74.098-68.657-139.783-67.8zm-48.877 78.65c-1.296-.003-2.603.012-3.92.045-17.256.436-36.45 4.03-57.566 11.037 5.79 53.808 26.325 106.41 58.5 143.346 6.226 7.15 12.856 13.712 19.875 19.615 29.303 9.282 69.26 12.917 110.534 12.14 3.777-55.805-8.717-108.357-36.193-142.74-21.265-26.61-51.064-43.39-91.232-43.444zm129.326 22.282c-9.358 1.637-18.69 3.016-27.995 4.15 1.54 1.74 3.043 3.52 4.502 5.346 3.146 3.937 6.094 8.062 8.873 12.334 9.916.144 19.868.125 29.857-.106H259.29v-21.723zm191.817 15.343c-65.406 17.826-131.462 25.41-195.85 25.315 16.998 35.144 23.828 78.093 21.013 122.6 42.482-2.08 85.03-8.23 118.187-15.983 26.693-32.78 47.37-77.118 56.65-131.932zM400.51 389.9c-38.334 9.145-87.95 16.056-136.873 17.454-47.67 1.36-94.336-2.228-129.448-15.262l-.01 78.93c27.187 12.568 76.414 20.205 127.318 20.298 51.224.094 104.214-7.173 139-20.773l.012-80.647z" fill="#a7de9a" fill-opacity="1" filter="url(#shadow-1)"></path></g></svg>

After

(image error) Size: 2.6 KiB

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><filter id="shadow-1" height="300%" width="300%" x="-100%" y="-100%"><feFlood flood-color="rgba(255, 255, 255, 1)" result="flood"></feFlood><feComposite in="flood" in2="SourceGraphic" operator="atop" result="composite"></feComposite><feGaussianBlur in="composite" stdDeviation="15" result="blur"></feGaussianBlur><feOffset dx="0" dy="0" result="offset"></feOffset><feComposite in="SourceGraphic" in2="offset" operator="over"></feComposite></filter></defs><g class="" style="" transform="translate(0,0)"><path d="M245.813 23.188c-1.228-.006-2.455.027-3.657.093-10.103.56-19.646 3.682-30.156 11.25l20.72 196.782c-8.394 2.127-16.676 4.47-24.814 7.094L137.72 57.812c-7.032-1.706-17.442-.3-27.126 4.626-10.248 5.213-19.034 13.84-22.813 22.937L155.03 261.5c-7.414 4.345-14.59 9.137-21.5 14.47l-74.343-94.25c-16.34.698-34.965 14.455-37.562 32.655C28.89 222.693 93.978 297.77 126 357.405c10.3 19.184 29.543 50.725 39.188 70.064 5.83 11.693 16.004 24.238 27.843 32.342 11.84 8.104 24.7 11.82 37.907 8.282l112.907-30.22c5.493-1.47 9.196-5.39 13.22-11.937 4.02-6.545 7.535-15.137 12.905-23 20.61-30.185 50.432-76.085 115.186-112.062-2.696-15.053-7.405-24.57-12.72-29.563-6.03-5.667-13.198-7.372-23.686-5.843-18.062 2.63-43.498 17.063-69.594 36.874-1.68 1.39-3.318 2.802-4.937 4.22l-7-61.252 42.5-155.718c-4.478-7.355-13.806-13.258-24.845-15.97-10.874-2.67-22.506-1.698-30.28 1.595l-38.75 149.874c-9.365 1.58-18.732 3.17-28.064 4.812L273.69 27.5c-10.057-2.52-19.284-4.272-27.875-4.313zM234.343 255l30.157 56.625 54.406-33.906-33.78 54.186L341.562 362l-64.157-2.188 2.188 64.032-30.03-56.344-54.283 33.813 33.97-54.438-56.53-30.125 63.78 2.156L234.344 255z" fill="#a7de9a" fill-opacity="1" filter="url(#shadow-1)"></path></g></svg>

After

(image error) Size: 1.8 KiB

@ -0,0 +1 @@
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><filter id="shadow-1" height="300%" width="300%" x="-100%" y="-100%"><feFlood flood-color="rgba(255, 255, 255, 1)" result="flood"></feFlood><feComposite in="flood" in2="SourceGraphic" operator="atop" result="composite"></feComposite><feGaussianBlur in="composite" stdDeviation="15" result="blur"></feGaussianBlur><feOffset dx="0" dy="0" result="offset"></feOffset><feComposite in="SourceGraphic" in2="offset" operator="over"></feComposite></filter></defs><g class="" style="" transform="translate(0,0)"><path d="M102.5 26.03l90.03 345.75 289.22 23.25-90.063-345.75L102.5 26.03zm-18.906 1.564c-30.466 11.873-55.68 53.098-49.75 75.312l3.25 11.78c.667-1.76 1.36-3.522 2.093-5.28C49.097 85.7 65.748 62.64 89.564 50.5l-5.97-22.906zm10.844 41.593c-16.657 10.012-29.92 28.077-38 47.407-5.247 12.55-8.038 25.63-8.75 36.53L112.5 388.407c.294-.55.572-1.106.875-1.656 10.603-19.252 27.823-37.695 51.125-48.47L94.437 69.19zm74.874 287.594c-17.677 9.078-31.145 23.717-39.562 39-4.464 8.107-7.27 16.364-8.688 23.75l11.688 42.408 1.625.125c-3.84-27.548 11.352-60.504 41.25-81.094l-6.313-24.19zm26.344 34c-32.567 17.27-46.51 52.44-41.844 72.94l289.844 24.5c-5.34-7.79-8.673-17.947-8.594-28.5l-22.406-9L459 443.436l-13.5-12.875c5.604-6.917 13.707-13.05 24.813-17.687L195.656 390.78z" fill="#a7de9a" fill-opacity="1" filter="url(#shadow-1)"></path></g></svg>

After

(image error) Size: 1.4 KiB

Binary file not shown.

After

(image error) Size: 77 KiB

File diff suppressed because it is too large Load Diff

@ -14,7 +14,7 @@ import * as applications from "./module/applications/_module.mjs"
import { handleSocketEvent } from "./module/socket.mjs"
import CthulhuEternalUtils from "./module/utils.mjs"
export class ClassCounter{static printHello(){console.log("Hello")}static sendJsonPostRequest(e,s){const t={method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(s)};return fetch(e,t).then((e=>{if(!e.ok)throw new Error("La requête a échoué avec le statut "+e.status);return e.json()})).catch((e=>{throw console.error("Erreur envoi de la requête:",e),e}))}static registerUsageCount(e=game.system.id,s={}){if(game.user.isGM){game.settings.register(e,"world-key",{name:"Unique world key",scope:"world",config:!1,default:"",type:String});let t=game.settings.get(e,"world-key");null!=t&&""!=t&&"NONE"!=t&&"none"!=t.toLowerCase()||(t=foundry.utils.randomID(32),game.settings.set(e,"world-key",t));let a={name:e,system:game.system.id,worldKey:t,version:game.system.version,language:game.settings.get("core","language"),remoteAddr:game.data.addresses.remote,nbInstalledModules:game.modules.size,nbActiveModules:game.modules.filter((e=>e.active)).length,nbPacks:game.world.packs.size,nbUsers:game.users.size,nbScenes:game.scenes.size,nbActors:game.actors.size,nbPlaylist:game.playlists.size,nbTables:game.tables.size,nbCards:game.cards.size,optionsData:s,foundryVersion:`${game.release.generation}.${game.release.build}`};this.sendJsonPostRequest("https://www.uberwald.me/fvtt_appcount/count_post.php",a)}}}
export class ClassCounter { static printHello() { console.log("Hello") } static sendJsonPostRequest(e, s) { const t = { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify(s) }; return fetch(e, t).then((e => { if (!e.ok) throw new Error("La requête a échoué avec le statut " + e.status); return e.json() })).catch((e => { throw console.error("Erreur envoi de la requête:", e), e })) } static registerUsageCount(e = game.system.id, s = {}) { if (game.user.isGM) { game.settings.register(e, "world-key", { name: "Unique world key", scope: "world", config: !1, default: "", type: String }); let t = game.settings.get(e, "world-key"); null != t && "" != t && "NONE" != t && "none" != t.toLowerCase() || (t = foundry.utils.randomID(32), game.settings.set(e, "world-key", t)); let a = { name: e, system: game.system.id, worldKey: t, version: game.system.version, language: game.settings.get("core", "language"), remoteAddr: game.data.addresses.remote, nbInstalledModules: game.modules.size, nbActiveModules: game.modules.filter((e => e.active)).length, nbPacks: game.world.packs.size, nbUsers: game.users.size, nbScenes: game.scenes.size, nbActors: game.actors.size, nbPlaylist: game.playlists.size, nbTables: game.tables.size, nbCards: game.cards.size, optionsData: s, foundryVersion: `${game.release.generation}.${game.release.build}` }; this.sendJsonPostRequest("https://www.uberwald.me/fvtt_appcount/count_post.php", a) } } }
Hooks.once("init", function () {
console.info("Cthulhu Eternal RPG | Initializing System")
@ -33,7 +33,8 @@ Hooks.once("init", function () {
CONFIG.Actor.documentClass = documents.CthulhuEternalActor
CONFIG.Actor.dataModels = {
protagonist: models.CthulhuEternalProtagonist,
vehicle: models.CthulhuEternalVehicle
vehicle: models.CthulhuEternalVehicle,
creature: models.CthulhuEternalCreature
}
CONFIG.Item.documentClass = documents.CthulhuEternalItem
@ -47,13 +48,16 @@ Hooks.once("init", function () {
bond: models.CthulhuEternalBond,
arcane: models.CthulhuEternalArcane,
gear: models.CthulhuEternalGear,
archetype: models.CthulhuEternalArchetype
archetype: models.CthulhuEternalArchetype,
ritual: models.CthulhuEternalRitual,
tome: models.CthulhuEternalTome
}
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet)
Actors.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalProtagonistSheet, { types: ["protagonist"], makeDefault: true })
Actors.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalVehicleSheet, { types: ["vehicle"], makeDefault: true })
Actors.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalCreatureSheet, { types: ["creature"], makeDefault: true })
Items.unregisterSheet("core", ItemSheet)
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalSkillSheet, { types: ["skill"], makeDefault: true })
@ -66,6 +70,8 @@ Hooks.once("init", function () {
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalBondSheet, { types: ["bond"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalGearSheet, { types: ["gear"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalArchetypeSheet, { types: ["archetype"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalRitualSheet, { types: ["ritual"], makeDefault: true })
Items.registerSheet("fvtt-cthulhu-eternal", applications.CthulhuEternalTomeSheet, { types: ["tome"], makeDefault: true })
// Other Document Configuration
CONFIG.ChatMessage.documentClass = documents.CthulhuEternalChatMessage
@ -91,6 +97,7 @@ Hooks.once("init", function () {
console.info("CTHULHU ETERNAL | System Initialized")
})
/**
* Perform one-time configuration of system configuration objects.
*/
@ -114,31 +121,20 @@ Hooks.once("ready", function () {
})
Hooks.on("renderChatMessage", (message, html, data) => {
const typeMessage = data.message.flags.CthulhuEternal?.typeMessage
// Message de demande de jet de dés
if (typeMessage === "askRoll") {
// Affichage des boutons de jet de dés uniquement pour les joueurs
if (game.user.isGM) {
html.find(".ask-roll-dice").each((i, btn) => {
btn.style.display = "none"
if (message.author.id === game.user.id) {
html.find(".nudge-roll").each((i, btn) => {
btn.style.display = "inline"
})
} else {
html.find(".ask-roll-dice").click((event) => {
const btn = $(event.currentTarget)
const type = btn.data("type")
const value = btn.data("value")
const avantage = btn.data("avantage") ?? "="
const character = game.user.character
if (type === SYSTEM.ROLL_TYPE.RESOURCE) character.rollResource(value)
else if (type === SYSTEM.ROLL_TYPE.SAVE) character.rollSave(value, avantage)
html.find(".nudge-roll").click((event) => {
CthulhuEternalUtils.nudgeRoll(message)
})
}
}
})
// Dice-so-nice Ready
Hooks.once("diceSoNiceReady", (dice3d) => {
configureDiceSoNice(dice3d)
//configureDiceSoNice(dice3d)
})
/**

@ -2,7 +2,8 @@
"TYPES": {
"Actor": {
"protagonist": "Protagonist",
"vehicle": "Vehicle"
"vehicle": "Vehicle",
"creature": "Creature"
},
"Item": {
"skill": "Skill",
@ -14,7 +15,9 @@
"mentaldisorder": "Mental Disorder",
"bond": "Bond" ,
"arcane": "Arcane",
"archetype": "Archetype"
"archetype": "Archetype",
"ritual": "Ritual",
"tome": "Tome"
}
},
"CTHULHUETERNAL": {
@ -37,6 +40,86 @@
},
"Protagonist": {
"FIELDS": {
"damageBonus": {
"label": "Dmg.Bonus"
},
"resources": {
"permanentRating": {
"label": "Permanent Rating"
},
"hand": {
"label": "Hand"
},
"stowed": {
"label": "Stowed"
},
"storage": {
"label": "Storage"
}
},
"biodata": {
"feature": {
"label": "Feature"
},
"adaptedToViolence": {
"label": "Adapted to violence"
},
"adaptedToHelplessness": {
"label": "Adapted to helplessness"
},
"harshness": {
"label": "Harshness"
},
"age": {
"label": "Age"
},
"gender": {
"label": "Gender"
},
"hair": {
"label": "Hair"
},
"eyes": {
"label": "Eyes"
},
"height": {
"label": "Height"
},
"home": {
"label": "Home"
},
"birthplace": {
"label": "Birthplace"
},
"label": "Biodata"
},
"characteristics:": {
"str": {
"label": "Strength"
},
"dex": {
"label": "Dexterity"
},
"int": {
"label": "Intelligence"
},
"pow": {
"label": "Power"
},
"con": {
"label": "Constitution"
},
"char": {
"label": "Charisma"
}
}
}
},
"Creature": {
"FIELDS": {
"damageBonus": {
"label": "Dmg.Bonus"
},
"resources": {
"permanentRating": {
"label": "Permanent Rating"
@ -183,6 +266,12 @@
"riflecarabine": "Rifle/Carabine"
},
"FIELDS": {
"hasDirectSkill": {
"label": "Has direct skill"
},
"directSkillValue": {
"label": "Direct skill value"
},
"state": {
"label": "State"
},
@ -324,7 +413,85 @@
"harsh": "Harsh",
"veryHarsh": "Very Harsh"
},
"Tome": {
"FIELDS": {
"language": {
"label": "Language"
},
"settings": {
"label": "Settings"
},
"studyTime": {
"label": "Study Time"
},
"sanLoss": {
"label": "SAN Loss"
},
"unnaturalSkill": {
"label": "Unnatural Skill"
},
"rituals": {
"label": "Rituals"
},
"minimumEra": {
"label": "Minimum Era"
},
"otherBenefits": {
"label": "Other Benefits"
},
"creationDate": {
"label": "Creation Date"
},
"description": {
"label": "Description"
}
},
"Label": {
"tomeDetails": "Tome Details"
},
"Button": {
"addRitual": "Add Ritual"
}
},
"Ritual": {
"Simple": "Simple",
"Complex": "Complex",
"Elaborate": "Elaborate",
"FIELDS": {
"ritualType": {
"label": "Type"
},
"studyTime": {
"label": "Study time"
},
"studySAN": {
"label": "Study SAN"
},
"activationTime": {
"label": "Activation time"
},
"activationSAN": {
"label": "Activation SAN"
},
"activationWP": {
"label": "Activation WP"
},
"description": {
"label": "Description"
}
}
},
"Label": {
"creature": "Creature",
"Rituals": "Rituals",
"Tomes": "Tomes",
"otherBenefits": "Other Benefits",
"Unarmed": "Unarmed",
"Cured": "Cured",
"Uncured": "Uncured",
"nudgedRoll": "Nudged Roll",
"selectNewValue": "Select the new value",
"wpCost": "WP Cost",
"Hand": "Hand",
"Stowed": "Stowed",
"Storage": "Storage",
@ -424,7 +591,9 @@
"newGear": "New Gear",
"newArcane": "New Arcane",
"newArchetype": "New Archetype",
"newSkill": "New Skill"
"newSkill": "New Skill",
"newTome": "New Tome",
"newRitual": "New Ritual"
},
"ChatMessage": {
"exhausted": "Your protagonist is exhausted. He loses [[/r 1d6]] Willpower Points."
@ -437,7 +606,10 @@
},
"Roll": {
"skill": "Skill",
"roll": "Roll"
"roll": "Roll",
"applyNudge": "Apply",
"cancel": "Cancel",
"nudgeRoll": "Nudge Roll"
},
"Tooltip": {
"sanBP": ">5 SAN lost in one roll, temporary insanity. If SAN less reaches BP = a Disorder unconscious Breaking and AND reset BP.",
@ -445,10 +617,11 @@
},
"Chat": {
},
"Notitications": {
"Notifications": {
"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"
"skillAlreadyExists": "Skill already exists",
"WrongEra": "The era of the item does not match the ear of the system"
}
}
}

@ -9,4 +9,7 @@ export { default as CthulhuEternalMentalDisorderSheet } from "./sheets/mentaldis
export { default as CthulhuEternalGearSheet } from "./sheets/gear-sheet.mjs"
export { default as CthulhuEternalMotivationSheet } from "./sheets/motivation-sheet.mjs"
export { default as CthulhuEternalArchetypeSheet } from "./sheets/archetype-sheet.mjs"
export { default as CthulhuEternalRitualSheet } from "./sheets/ritual-sheet.mjs"
export { default as CthulhuEternalVehicleSheet } from "./sheets/vehicle-sheet.mjs"
export { default as CthulhuEternalCreatureSheet } from "./sheets/creature-sheet.mjs"
export { default as CthulhuEternalTomeSheet } from "./sheets/tome-sheet.mjs"

@ -0,0 +1,294 @@
// System Module Imports
import { Utils } from './utils.js'
import { SYSTEM } from "../../config/system.mjs"
export let ActionHandler = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Extends Token Action HUD Core's ActionHandler class and builds system-defined actions for the HUD
*/
ActionHandler = class ActionHandler extends coreModule.api.ActionHandler {
/**
* Build system actions
* Called by Token Action HUD Core
* @override
* @param {array} groupIds
*/
async buildSystemActions(groupIds) {
// Set actor and token variables
this.actors = (!this.actor) ? this._getActors() : [this.actor]
this.actorType = this.actor?.type
// Set items variable
if (this.actor) {
let items = this.actor.items
items = coreModule.api.Utils.sortItemsByName(items)
this.items = items
}
if (this.actorType !== 'vehicle') {
this.#buildCharacterActions()
} else if (!this.actor) {
this.#buildMultipleTokenActions()
}
}
/**
* Build character actions
* @private
*/
#buildCharacterActions() {
this.buildAttributes()
this.buildOther()
this.buildLuck()
this.buildSkills()
this.buildEquipment()
}
#showValue() {
return game.settings.get('token-action-hud-core', 'tooltips') === 'none'
}
async buildAttributes() {
const actions = []
for (const key in this.actor.system.characteristics) {
const encodedValue = [coreModule.api.Utils.i18n('attributes'), 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),
id: key,
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue
})
}
await this.addActions(actions, {
id: 'attributes',
type: 'system'
})
}
async buildLuck() {
const actions = []
const tooltip = {
content: '50',
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Luck'),
id: 'luck',
info1: this.#showValue() ? { text: '50' } : null,
tooltip,
encodedValue: ['attributes', 'luck'].join(this.delimiter)
})
await this.addActions(actions, { id: 'luck', type: 'system' })
}
async buildOther() {
if (typeof this.actor.system.sanity.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_sanity',
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),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.SAN'),
id: 'sanity',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'sanity'].join(this.delimiter)
},
{
name: '+',
id: 'sanity_add',
encodedValue: ['attributes', 'sanity_add'].join(this.delimiter)
},
{
name: '-',
id: 'sanity_subtract',
encodedValue: ['attributes', 'sanity_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_sanity', type: 'system' })
}
if (typeof this.actor.system.hp.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_health',
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),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.HP'),
id: 'health',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'health'].join(this.delimiter)
},
{
name: '+',
id: 'health_add',
encodedValue: ['attributes', 'health_add'].join(this.delimiter)
},
{
name: '-',
id: 'health_subtract',
encodedValue: ['attributes', 'health_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_health', type: 'system' })
}
if (typeof this.actor.system.wp.value !== 'undefined') {
const actions = []
const groupData = {
id: 'other_wp',
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.WP'),
type: 'system'
}
this.addGroup(groupData, { id: 'other', type: 'system' }, true)
const tooltip = {
content: String(this.actor.system.wp.value + '/' + this.actor.system.wp.max),
class: 'tah-system-tooltip',
direction: 'LEFT'
}
actions.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.WP'),
id: 'wp',
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['attributes', 'wp'].join(this.delimiter)
},
{
name: '+',
id: 'wp_add',
encodedValue: ['attributes', 'wp_add'].join(this.delimiter)
},
{
name: '-',
id: 'wp_subtract',
encodedValue: ['attributes', 'wp_subtract'].join(this.delimiter)
})
await this.addActions(actions, { id: 'other_wp', type: 'system' })
}
}
async buildSkills() {
const actions = []
let actorSkills = this.actor.items.filter(item => item.type === 'skill')
for (const skill in actorSkills) {
if (skill.system.computeScore() > 0) {
const tooltip = {
content: String(skill.skill.system.computeScore()),
direction: 'LEFT'
}
actions.push({
name: skill.name,
id: skill.id,
info1: this.#showValue() ? { text: tooltip.content } : null,
tooltip,
encodedValue: ['skills', s].join(this.delimiter)
})
}
}
await this.addActions(actions, { id: 'skills', type: 'system' })
}
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) {
// Push the weapon name as a new group
const groupData = {
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 = []
const tooltip = {
content: String(skill.system.computeScore()),
direction: 'LEFT'
}
weapons.push({
name: skill.name,
id: skill._id,
info1: this.#showValue() ? { text: tooltip.content } : null,
encodedValue: ['weapons', item._id].join(this.delimiter),
tooltip
})
const damageTooltip = {
content: String(item.system.damage),
direction: 'LEFT'
}
if (item.system.damage !== '') {
weapons.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Damage'),
id: item._id,
info1: this.#showValue() ? { text: damageTooltip.content } : null,
encodedValue: ['damage', item._id].join(this.delimiter),
tooltip: damageTooltip
})
}
if (item.system.isLethal) {
const lethalityTooltip = {
content: String(item.system.lethality),
direction: 'LEFT'
}
weapons.push({
name: coreModule.api.Utils.i18n('CTHULHUETERNAL.Label.Lethality'),
id: item._id,
info1: this.#showValue() ? { text: lethalityTooltip.content } : null,
encodedValue: ['lethality', item._id].join(this.delimiter),
tooltip: lethalityTooltip
})
}
await this.addActions(weapons, {
id: 'weapons_' + item._id,
type: 'system'
})
}/* else if (item.type === 'ritual') {
rituals.push({
name: item.name,
id: item._id,
encodedValue: ['rituals', item.name].join(this.delimiter)
})
} */
/* await this.addActions(rituals, {
id: 'rituals',
type: 'system'
}) */
}
}
/**
* Build multiple token actions
* @private
* @returns {object}
*/
#buildMultipleTokenActions() {
}
}
})

@ -0,0 +1,38 @@
/**
* Module-based constants
*/
export const SYSTEM = {
ID: 'fvtt-cthulhu-eternal'
}
/**
* Core module
*/
export const CORE_MODULE = {
ID: 'token-action-hud-core'
}
/**
* Core module version required by the system module
*/
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'
}
/**
* 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' }
}

@ -0,0 +1,49 @@
import { GROUP } from './constants.js'
/**
* Default layout and groups
*/
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
}
})

@ -0,0 +1,304 @@
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 {
/**
* 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('|')
const knownCharacters = ['character']
// 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))
// 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
}
}
}
})

@ -0,0 +1,91 @@
// System Module Imports
import { ActionHandler } from './action-handler.js'
import { RollHandler as Core } from './roll-handler.js'
import { SYSTEM } from './constants.js'
import { DEFAULTS } from './defaults.js'
export let SystemManager = null
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
/**
* Extends Token Action HUD Core's SystemManager class
*/
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
}
}
}
}
})

@ -0,0 +1,55 @@
import { SYSTEM } 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
*/
Utils = class Utils {
/**
* Get setting
* @param {string} key The key
* @param {string=null} defaultValue The default value
* @returns {string} The setting value
*/
static getSetting(key, defaultValue = null) {
let value = defaultValue ?? null
try {
value = game.settings.get(SYSTEM.ID, key)
} catch {
coreModule.api.Logger.debug(`Setting '${key}' not found`)
}
return value
}
/**
* Set setting
* @param {string} key The key
* @param {string} value The value
*/
static async setSetting(key, value) {
try {
value = await game.settings.set(MODULE.ID, key, value)
coreModule.api.Logger.debug(`Setting '${key}' set to '${value}'`)
} catch {
coreModule.api.Logger.debug(`Setting '${key}' not found`)
}
}
}
})

@ -96,7 +96,6 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
drop: this._canDragDrop.bind(this),
}
d.callbacks = {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
}
@ -133,70 +132,6 @@ export default class CthulhuEternalActorSheet extends HandlebarsApplicationMixin
return true //this.isEditable && this.document.isOwner
}
/**
* Callback actions which occur at the beginning of a drag start workflow.
* @param {DragEvent} event The originating DragEvent
* @protected
*/
_onDragStart(event) {
if ("link" in event.target.dataset) return
const el = event.currentTarget.closest('[data-drag="true"]')
const dragType = el.dataset.dragType
let dragData = {}
let target
switch (dragType) {
case "save":
target = event.currentTarget.querySelector("input")
dragData = {
actorId: this.document.id,
type: "roll",
rollType: target.dataset.rollType,
rollTarget: target.dataset.rollTarget,
value: target.value,
}
break
case "resource":
target = event.currentTarget.querySelector("select")
dragData = {
actorId: this.document.id,
type: "roll",
rollType: target.dataset.rollType,
rollTarget: target.dataset.rollTarget,
value: target.value,
}
break
case "damage":
dragData = {
actorId: this.document.id,
type: "rollDamage",
rollType: el.dataset.dragType,
rollTarget: el.dataset.itemId,
}
break
case "attack":
dragData = {
actorId: this.document.id,
type: "rollAttack",
rollValue: el.dataset.rollValue,
rollTarget: el.dataset.rollTarget,
}
break
default:
// Handle other cases or do nothing
break
}
// Extract the data you need
if (!dragData) return
// Set data transfer
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Callback actions which occur when a dragged element is over a drop target.
* @param {DragEvent} event The originating DragEvent

@ -0,0 +1,175 @@
import CthulhuEternalActorSheet from "./base-actor-sheet.mjs"
export default class CthulhuEternalCreatureSheet extends CthulhuEternalActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["creature"],
position: {
width: 860,
height: 620,
},
window: {
contentClasses: ["creature-content"],
},
actions: {
createArmor: CthulhuEternalCreatureSheet.#onCreateArmor,
createWeapon: CthulhuEternalCreatureSheet.#onCreateWeapon,
createSkill: CthulhuEternalCreatureSheet.#onCreateSkill,
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-cthulhu-eternal/templates/creature-main.hbs",
},
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
skills: {
template: "systems/fvtt-cthulhu-eternal/templates/creature-skills.hbs",
},
biography: {
template: "systems/fvtt-cthulhu-eternal/templates/creature-biography.hbs",
},
}
/** @override */
tabGroups = {
sheet: "skills",
}
/**
* Prepare an array of form header tabs.
* @returns {Record<string, Partial<ApplicationTab>>}
*/
#getTabs() {
const tabs = {
skills: { id: "skills", group: "sheet", icon: "fa-solid fa-shapes", label: "CTHULHUETERNAL.Label.skills" },
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "CTHULHUETERNAL.Label.biography" },
}
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] === v.id
v.cssClass = v.active ? "active" : ""
}
return tabs
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.tabs = this.#getTabs()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
context.enrichedNotes = await TextEditor.enrichHTML(this.document.system.notes, { async: true })
context.tooltipsCharacteristic = {
str: game.i18n.localize("CTHULHUETERNAL.Characteristic.Str"),
dex: game.i18n.localize("CTHULHUETERNAL.Characteristic.Dex"),
con: game.i18n.localize("CTHULHUETERNAL.Characteristic.Con"),
int: game.i18n.localize("CTHULHUETERNAL.Characteristic.Int"),
pow: game.i18n.localize("CTHULHUETERNAL.Characteristic.Pow"),
cha: game.i18n.localize("CTHULHUETERNAL.Characteristic.Cha")
}
return context
}
/** @override */
async _preparePartContext(partId, context) {
const doc = this.document
switch (partId) {
case "main":
break
case "skills":
context.tab = context.tabs.skills
context.skills = doc.itemTypes.skill
context.skills.sort((a, b) => a.name.localeCompare(b.name))
context.weapons = doc.itemTypes.weapon
context.weapons.sort((a, b) => a.name.localeCompare(b.name))
context.armors = doc.itemTypes.armor
context.armors.sort((a, b) => a.name.localeCompare(b.name))
break
case "biography":
context.tab = context.tabs.biography
context.enrichedDescription = await TextEditor.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await TextEditor.enrichHTML(doc.system.notes, { async: true })
break
}
return context
}
/**
* Creates a new attack item directly from the sheet and embeds it into the document.
* @param {Event} event The initiating click event.
* @param {HTMLElement} target The current target of the event listener.
*/
static #onCreateWeapon(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newWeapon"), type: "weapon" }])
}
static #onCreateArmor(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newArmor"), type: "armor" }])
}
static #onCreateSkill(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newSkill"), type: "skill" }])
}
/**
* Handles the roll action triggered by user interaction.
*
* @param {PointerEvent} event The event object representing the user interaction.
* @param {HTMLElement} target The target element that triggered the roll.
*
* @returns {Promise<void>} A promise that resolves when the roll action is complete.
*
* @throws {Error} Throws an error if the roll type is not recognized.
*
* @description This method checks the current mode (edit or not) and determines the type of roll
* (save, resource, or damage) based on the target element's data attributes. It retrieves the
* corresponding value from the document's system and performs the roll.
*/
async _onRoll(event, target) {
const rollType = $(event.currentTarget).data("roll-type")
let item
let li
// Debug : console.log(">>>>", event, target, rollType)
// Deprecated : if (this.isEditMode) return
switch (rollType) {
case "char":
let charId = $(event.currentTarget).data("char-id")
item = foundry.utils.duplicate(this.actor.system.characteristics[charId])
item.name = game.i18n.localize(`CTHULHUETERNAL.Label.${charId}Long`)
item.targetScore = item.value * 5
break
case "skill":
li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
break
case "weapon":
case "damage":
li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
item.damageBonus = this.actor.system.damageBonus
break
default:
throw new Error(`Unknown roll type ${rollType}`)
}
await this.document.system.roll(rollType, item)
}
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
const data = TextEditor.getDragEventData(event)
// Handle different data types
switch (data.type) {
case "Item":
const item = await fromUuid(data.uuid)
return super._onDropItem(item)
}
}
}

@ -20,7 +20,9 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
createInjury: CthulhuEternalProtagonistSheet.#onCreateInjury,
createMentalDisorder: CthulhuEternalProtagonistSheet.#onCreateMentalDisorder,
createMotivation: CthulhuEternalProtagonistSheet.#onCreateMotivation,
createSkill: CthulhuEternalProtagonistSheet.#onCreateSkill
createSkill: CthulhuEternalProtagonistSheet.#onCreateSkill,
createRitual: CthulhuEternalProtagonistSheet.#onCreateRitual,
createTome: CthulhuEternalProtagonistSheet.#onCreateTome,
},
}
@ -108,6 +110,10 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
context.armors.sort((a, b) => a.name.localeCompare(b.name))
context.gears = doc.itemTypes.gear
context.gears.sort((a, b) => a.name.localeCompare(b.name))
context.rituals = doc.itemTypes.ritual
context.rituals.sort((a, b) => a.name.localeCompare(b.name))
context.tomes = doc.itemTypes.tome
context.tomes.sort((a, b) => a.name.localeCompare(b.name))
break
case "status":
context.tab = context.tabs.status
@ -115,13 +121,13 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
context.injuries.sort((a, b) => a.name.localeCompare(b.name))
context.mentaldisorders = doc.itemTypes.mentaldisorder
context.mentaldisorders.sort((a, b) => a.name.localeCompare(b.name))
context.motivations = doc.itemTypes.motivation
context.motivations.sort((a, b) => a.name.localeCompare(b.name))
context.bonds = doc.itemTypes.bond
context.bonds.sort((a, b) => a.name.localeCompare(b.name))
break
case "biography":
context.tab = context.tabs.biography
context.motivations = doc.itemTypes.motivation
context.motivations.sort((a, b) => a.name.localeCompare(b.name))
context.enrichedDescription = await TextEditor.enrichHTML(doc.system.description, { async: true })
context.enrichedNotes = await TextEditor.enrichHTML(doc.system.notes, { async: true })
break
@ -170,6 +176,14 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newSkill"), type: "skill" }])
}
static #onCreateRitual(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newRitual"), type: "ritual" }])
}
static #onCreateTome(event, target) {
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("CTHULHUETERNAL.Label.newTome"), type: "tome" }])
}
/**
* Handles the roll action triggered by user interaction.
*
@ -210,6 +224,7 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
case "damage":
li = $(event.currentTarget).parents(".item");
item = this.actor.items.get(li.data("item-id"));
item.damageBonus = this.actor.system.damageBonus
break
case "san":
item = foundry.utils.duplicate(this.actor.system.san)
@ -234,5 +249,4 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
}
}
// #endregion
}

@ -0,0 +1,28 @@
import CthulhuEternalItemSheet from "./base-item-sheet.mjs"
export default class CthulhuEternalRitualSheet extends CthulhuEternalItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ritual"],
position: {
width: 600,
},
window: {
contentClasses: ["ritual-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-cthulhu-eternal/templates/ritual.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
return context
}
}

@ -0,0 +1,28 @@
import CthulhuEternalItemSheet from "./base-item-sheet.mjs"
export default class CthulhuEternalTomeSheet extends CthulhuEternalItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["tome"],
position: {
width: 600,
},
window: {
contentClasses: ["tome-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-cthulhu-eternal/templates/tome.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedDescription = await TextEditor.enrichHTML(this.document.system.description, { async: true })
return context
}
}

@ -1,6 +1,6 @@
import LethalFantasyItemSheet from "./base-item-sheet.mjs"
import CthulhuEternalItemSheet from "./base-item-sheet.mjs"
export default class LethalFantasyWeaponSheet extends LethalFantasyItemSheet {
export default class CthulhuEternalWeaponSheet extends CthulhuEternalItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["weapon"],

@ -42,18 +42,18 @@ export const INSANITY = {
}
export const ERA_CSS = {
jazz: { primaryFont: "RozhaOne", secondaryFont: "RozhaOne", titleFont: "Broadway", 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", imgFilter: "brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1214%) hue-rotate(51deg) brightness(93%) contrast(86%)" },
future: { primaryFont: "Megrim", secondaryFont: "Megrim", titleFont: "Seabreed", imgFilter: "brightness(0) saturate(100%) invert(83%) sepia(30%) saturate(588%) hue-rotate(168deg) brightness(105%) contrast(103%)" },
victorian: { primaryFont: "Volkhov", secondaryFont: "Volkhov", titleFont: "Excelsior", imgFilter: "brightness(0) saturate(100%) invert(100%) sepia(59%) saturate(1894%) hue-rotate(337deg) brightness(88%) contrast(98%)" },
coldwar: { primaryFont: "BebasNeue", secondaryFont: "BebasNeue", titleFont: "TopSecret", 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", imgFilter: "brightness(0) saturate(100%) invert(81%) sepia(25%) saturate(386%) hue-rotate(7deg) brightness(101%) contrast(84%)" },
medieval: { primaryFont: "UncialAntiqua", secondaryFont: "UncialAntiqua", titleFont: "Luminari", 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", imgFilter: "brightness(0) saturate(100%) invert(95%) sepia(9%) saturate(1471%) hue-rotate(342deg) brightness(103%) contrast(107%)"},
ww1: { primaryFont: "CarterOne", secondaryFont: "CarterOne", titleFont: "SigmarOne", imgFilter: "brightness(0) saturate(100%) invert(90%) sepia(38%) saturate(341%) hue-rotate(21deg) brightness(105%) contrast(105%)"},
ageofsail: { primaryFont: "Tangerine", secondaryFont: "Tangerine", titleFont: "P22Operina", imgFilter: "brightness(0) saturate(100%) invert(43%) sepia(74%) saturate(3154%) hue-rotate(336deg) brightness(95%) contrast(83%)" },
classical: { primaryFont: "SpectralSC", secondaryFont: "SpectralSC", titleFont: "TrajanPro", 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", imgFilter: "brightness(0) saturate(100%) invert(44%) sepia(55%) saturate(2341%) hue-rotate(329deg) brightness(122%) contrast(103%))" }
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: "1.0rem", 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: "UncialAntiqua", 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.25rem",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 = {
@ -113,6 +113,8 @@ export const RESOURCE_BREAKDOWN = [
{ 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 VEHICLE_SPEED = {
"none": "CTHULHUETERNAL.Label.None",
"slow": "CTHULHUETERNAL.Label.Slow",
@ -140,6 +142,13 @@ export const WEAPON_SKILL_MAPPING = {
"rangedfirearm": "CTHULHUETERNAL.Skill.Firearms",
"unarmed": "CTHULHUETERNAL.Skill.UnarmedCombat"
},
postapo: {
"melee": "CTHULHUETERNAL.Skill.Melee",
"rangedprimitive": "CTHULHUETERNAL.Skill.Firearms",
"rangedthrown": "CTHULHUETERNAL.Skill.Athletics",
"rangedfirearm": "CTHULHUETERNAL.Skill.Firearms",
"unarmed": "CTHULHUETERNAL.Skill.UnarmedCombat"
},
jazz: {
"melee": "CTHULHUETERNAL.Skill.Melee",
"rangedprimitive": "CTHULHUETERNAL.Skill.Firearms",
@ -211,9 +220,9 @@ export const WEAPON_SKILL_MAPPING = {
}
export const MODIFIER_CHOICES = {
"-10": "-10",
"-20": "-20",
"-40": "-40",
"-20": "-20",
"-10": "-10",
"+0": "+0",
"+10": "+10",
"+20": "+20",
@ -229,6 +238,12 @@ export const MULTIPLIER_CHOICES = {
"5": "5"
}
export const RITUAL_TYPES = {
"simple": "CTHULHUETERNAL.Ritual.Simple",
"complex": "CTHULHUETERNAL.Ritual.Complex",
"elaborate": "CTHULHUETERNAL.Ritual.Elaborate"
}
/**
* Include all constant definitions within the SYSTEM global export
* @type {Object}
@ -251,5 +266,7 @@ export const SYSTEM = {
VEHICLE_SPEED,
MODIFIER_CHOICES,
MULTIPLIER_CHOICES,
ASCII
ASCII,
DAMAGE_BONUS,
RITUAL_TYPES
}

@ -23,6 +23,8 @@ 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" } })
}
return super.create(data, options);
@ -52,6 +54,13 @@ export default class CthulhuEternalActor extends Actor {
continue
}
}
if (i.type === "bond") {
if (i.system.bondType === "individual") {
i.system.value = this.system.characteristics.cha.value
} else {
i.system.value = Math.floor(this.system.resources.permanentRating / 2)
}
}
newData.push(i)
}
return super.createEmbeddedDocuments(embeddedName, newData, operation)

@ -1,7 +1,7 @@
export const defaultItemImg = {
weapon: "systems/fvtt-cthulhu-eternal/assets/icons/icon_weapon.svg",
armor: "systems/fvtt-cthulhu-eternal/assets/icons/icon_armor.svg",
gear: "systems/fvtt-cthulhu-eternal/assets/icons/icon_gear.svg",
gear: "systems/fvtt-cthulhu-eternal/assets/icons/icon_equipment.svg",
skill: "systems/fvtt-cthulhu-eternal/assets/icons/icon_skill.svg",
archetype: "systems/fvtt-cthulhu-eternal/assets/icons/icon_archetype.svg",
bond: "systems/fvtt-cthulhu-eternal/assets/icons/icon_bond.svg",
@ -9,6 +9,8 @@ export const defaultItemImg = {
arcane: "systems/fvtt-cthulhu-eternal/assets/icons/icon_arcane.svg",
injury: "systems/fvtt-cthulhu-eternal/assets/icons/icon_injury.svg",
motivation: "systems/fvtt-cthulhu-eternal/assets/icons/icon_motivation.svg",
ritual: "systems/fvtt-cthulhu-eternal/assets/icons/icon_ritual.svg",
tome: "systems/fvtt-cthulhu-eternal/assets/icons/icon_tome.svg",
}
export default class CthulhuEternalItem extends Item {

@ -1,5 +1,6 @@
import { SYSTEM } from "../config/system.mjs"
export default class CthulhuEternalRoll extends Roll {
/**
* The HTML template path used to render dice checks of this type
@ -79,6 +80,14 @@ export default class CthulhuEternalRoll extends Roll {
return this.options.isExhausted
}
get isNudgedRoll() {
return this.options.isNudgedRoll
}
get wpCost() {
return this.options.wpCost
}
static updateResourceDialog(options) {
let rating = 0
if (options.rollItem.enableHand) {
@ -114,6 +123,7 @@ export default class CthulhuEternalRoll extends Roll {
let formula = "1d100"
let hasModifier = true
let hasMultiplier = false
options.isNudge = true
switch (options.rollType) {
case "skill":
@ -123,6 +133,7 @@ export default class CthulhuEternalRoll extends Roll {
case "san":
case "char":
options.initialScore = options.rollItem.targetScore
options.isNudge = (options.rollType !== "san")
break
case "resource":
hasModifier = false
@ -133,15 +144,20 @@ export default class CthulhuEternalRoll extends Roll {
options.rollItem.enableHand = true
options.rollItem.enableStowed = true
options.rollItem.enableStorage = true
options.isNudge = false
break
case "damage":
let formula = options.rollItem.system.damage
if ( options.rollItem.system.weaponType === "melee" || options.rollItem.system.weaponType === "unarmed") {
formula += ` + ${options.rollItem.damageBonus}`
}
let damageRoll = new Roll(formula)
await damageRoll.evaluate()
await damageRoll.toMessage({
flavor: `${options.rollItem.name} - Damage Roll`
});
let isLethal = false
options.isNudge = false
if (options.rollItem.system.lethality > 0) {
let lethalityRoll = new Roll("1d100")
await lethalityRoll.evaluate()
@ -153,13 +169,24 @@ export default class CthulhuEternalRoll extends Roll {
return
case "weapon":
let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
if (!SYSTEM.WEAPON_SKILL_MAPPING[era] || !SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType]) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponType"))
if (era !== options.rollItem.system.settings) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.WrongEra"))
console.log("WP Wrong Era", era, options.rollItem.system.weaponType)
return
}
if (!SYSTEM.WEAPON_SKILL_MAPPING[era] || !SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType]) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponType"))
console.log("WP Not found", era, options.rollItem.system.weaponType)
return
}
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])
let actor = game.actors.get(options.actorId)
options.weapon = options.rollItem
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"))
@ -167,6 +194,7 @@ export default class CthulhuEternalRoll extends Roll {
}
options.initialScore = options.rollItem.system.computeScore()
console.log("WEAPON", skillName, era, options.rollItem)
}
break
default:
options.initialScore = 50
@ -259,8 +287,8 @@ export default class CthulhuEternalRoll extends Roll {
rollData.rollMode = rollContext.visibility
// Update target score
console.log(rollData)
if (options.rollType === "resource" ) {
console.log("Rolldata", rollData, options)
if (options.rollType === "resource") {
rollData.targetScore = options.initialScore * Number(rollContext.multiplier)
} else {
rollData.targetScore = Math.min(Math.max(options.initialScore + Number(rollData.modifier), 0), 100)
@ -273,45 +301,51 @@ export default class CthulhuEternalRoll extends Roll {
rollData.targetScore = Math.min(Math.max(rollData.targetScore, 0), 100)
}
/**
* A hook event that fires before the roll is made.
*/
if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return
const roll = new this(formula, options.data, rollData)
await roll.evaluate()
roll.displayRollResult(roll, options, rollData)
if (Hooks.call("fvtt-cthulhu-eternal.Roll", options, rollData, roll) === false) return
return roll
}
displayRollResult(formula, options, rollData) {
// Compute the result quality
let resultType = "failure"
let dec = Math.floor(roll.total / 10)
let unit = roll.total - (dec * 10)
if (roll.total <= rollData.targetScore) {
let dec = Math.floor(this.total / 10)
let unit = this.total - (dec * 10)
if (this.total <= rollData.targetScore) {
resultType = "success"
// Detect if decimal == unit in the dire total result
if (dec === unit || roll.total === 1) {
if (dec === unit || this.total === 1) {
resultType = "successCritical"
}
} else {
// Detect if decimal == unit in the dire total result
if (dec === unit || roll.total === 100) {
if (dec === unit || this.total === 100) {
resultType = "failureCritical"
}
}
roll.options.resultType = resultType
roll.options.isSuccess = resultType === "success" || resultType === "successCritical"
roll.options.isFailure = resultType === "failure" || resultType === "failureCritical"
roll.options.isCritical = resultType === "successCritical" || resultType === "failureCritical"
roll.options.isLowWP = rollData.isLowWP
roll.options.isZeroWP = rollData.isZeroWP
roll.options.isExhausted = rollData.isExhausted
/**
* A hook event that fires after the roll has been made.
*/
if (Hooks.call("fvtt-cthulhu-eternal.Roll", options, rollData, roll) === false) return
return roll
this.options.resultType = resultType
if (this.options.isNudgedRoll) {
this.options.isSuccess = resultType === "success" || resultType === "successCritical"
this.options.isFailure = resultType === "failure" || resultType === "failureCritical"
this.options.isCritical = false
} else {
this.options.isSuccess = resultType === "success" || resultType === "successCritical"
this.options.isFailure = resultType === "failure" || resultType === "failureCritical"
this.options.isCritical = resultType === "successCritical" || resultType === "failureCritical"
}
this.options.isLowWP = rollData.isLowWP
this.options.isZeroWP = rollData.isZeroWP
this.options.isExhausted = rollData.isExhausted
this.options.rollData = foundry.utils.duplicate(rollData)
}
/**
@ -387,9 +421,8 @@ export default class CthulhuEternalRoll extends Roll {
cardData.isLowWP = this.isLowWP
cardData.isZeroWP = this.isZeroWP
cardData.isExhausted = this.isExhausted
console.log(cardData)
cardData.isNudgedRoll = this.isNudgedRoll
cardData.wpCost = this.wpCost
cardData.cssClass = cardData.css.join(" ")
cardData.tooltip = isPrivate ? "" : await this.getTooltip()

@ -9,5 +9,8 @@ export { default as CthulhuEternalBond } from "./bond.mjs"
export { default as CthulhuEternalGear } from "./gear.mjs"
export { default as CthulhuEternalMotivation } from "./motivation.mjs"
export { default as CthulhuEternalArchetype } from "./archetype.mjs"
export { default as CthulhuEternalRitual } from "./ritual.mjs"
export { default as CthulhuEternalVehicle } from "./vehicle.mjs"
export { default as CthulhuEternalCreature } from "./creature.mjs"
export { default as CthulhuEternalTome } from "./tome.mjs"

@ -5,7 +5,8 @@ export default class CthulhuEternalArchetype extends foundry.abstract.TypeDataMo
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.description = new fields.HTMLField({
required: false,
@ -19,4 +20,5 @@ export default class CthulhuEternalArchetype extends foundry.abstract.TypeDataMo
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Archetype"]
}

@ -6,7 +6,10 @@ export default class CthulhuEternalArmor extends foundry.abstract.TypeDataModel
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.protection = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })

@ -0,0 +1,80 @@
import { SYSTEM } from "../config/system.mjs"
import CthulhuEternalRoll from "../documents/roll.mjs"
export default class CthulhuEternalCreature extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
// Carac
const characteristicField = (label) => {
const schema = {
value: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
feature: new fields.StringField({ required: true, nullable: false, initial: "" })
}
return new fields.SchemaField(schema, { label })
}
schema.characteristics = new fields.SchemaField(
Object.values(SYSTEM.CHARACTERISTICS).reduce((obj, characteristic) => {
obj[characteristic.id] = characteristicField(characteristic.label)
return obj
}, {}),
)
schema.wp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
exhausted: new fields.BooleanField({ required: true, initial: false })
})
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 })
})
schema.movement = new fields.StringField({ required: true, initial: "" })
schema.sanLoss = new fields.StringField({ required: true, initial: "1/1D6" })
schema.armor = new fields.StringField({ required: true, initial: "" })
schema.size = new fields.StringField({ required: true, initial: "medium", choices: SYSTEM.CREATURE_SIZE })
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Creature"]
/** */
/**
* Rolls a dice for a character.
* @param {("save"|"resource|damage")} rollType The type of the roll.
* @param {number} rollItem The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
*/
async roll(rollType, rollItem) {
let opponentTarget
const hasTarget = opponentTarget !== undefined
let roll = await CthulhuEternalRoll.prompt({
rollType,
rollItem,
isLowWP: false,
isZeroWP: false,
isExhausted: false,
actorId: this.parent.id,
actorName: this.parent.name,
actorImage: this.parent.img,
hasTarget,
target: opponentTarget
})
if (!roll) return null
await roll.toMessage({}, { rollMode: roll.options.rollMode })
}
}

@ -7,7 +7,10 @@ export default class CthulhuEternalGHear extends foundry.abstract.TypeDataModel
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })
schema.state = new fields.StringField({ required: true, initial: "pristine", choices: SYSTEM.EQUIPMENT_STATES })

@ -165,7 +165,6 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
}
}
isLowWP() {
return this.wp.value <= 2
}
@ -178,6 +177,17 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
return this.wp.exhausted
}
modifyWP(value) {
let updates = {}
let wp = Math.max(Math.min(this.wp.value + value, this.wp.max), 0)
if ( this.wp.value !== wp) {
updates[`system.wp.value`] = wp
}
if (Object.keys(updates).length > 0) {
this.parent.update(updates)
}
}
setBP() {
let updates = {}
let bp = Math.max(this.san.value - this.characteristics.pow.value, 0)

24
module/models/ritual.mjs Normal file

@ -0,0 +1,24 @@
import { SYSTEM } from "../config/system.mjs"
export default class CthulhuEternalRitual extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.ritualType = new fields.StringField({ required: true, initial: "simple", choices: SYSTEM.RITUAL_TYPES })
schema.studyTime = new fields.StringField({ required: true, initial: "X days", textSearch: true })
schema.studySAN = new fields.StringField({ required: true, initial: "1d4", textSearch: true })
schema.activationTime = new fields.StringField({ required: true, initial: "X turns", textSearch: true })
schema.activationWP = new fields.StringField({ required: true, initial: "1d4", textSearch: true })
schema.activationSAN = new fields.StringField({ required: true, initial: "1d6", textSearch: true })
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema
}
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Ritual"]
}

@ -6,7 +6,9 @@ export default class CthulhuEternalSkill extends foundry.abstract.TypeDataModel
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.base = new fields.StringField({ required: true, initial: "0" })
schema.bonus = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })

64
module/models/tome.mjs Normal file

@ -0,0 +1,64 @@
import { SYSTEM } from "../config/system.mjs";
export default class CthulhuEternalTome extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.minimumEra = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.creationDate = new fields.StringField({
required: true,
initial: "",
textSearch: true
});
// Language field
schema.language = new fields.StringField({
required: true,
initial: "Latin",
textSearch: true
});
// studyTime field
schema.studyTime = new fields.StringField({
required: true,
initial: "X days",
textSearch: true
});
// SAN loss field
schema.sanLoss = new fields.StringField({
required: true,
initial: "1d4",
textSearch: true
});
// Unnatural skill field
schema.unnaturalSkill = new fields.StringField({
required: true,
initial: "1d4",
textSearch: true
});
schema.rituals = new fields.StringField({
required: true,
initial: "",
textSearch: true
});
schema.otherBenefits = new fields.StringField({
required: true,
initial: "",
textSearch: true
});
schema.description = new fields.HTMLField({ required: true, textSearch: true })
return schema;
}
/** @override */
static LOCALIZATION_PREFIXES = ["CTHULHUETERNAL.Tome"];
}

@ -7,7 +7,8 @@ export default class CthulhuEternalVehicle extends foundry.abstract.TypeDataMode
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.hp = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),

@ -1,15 +1,20 @@
import { SYSTEM } from "../config/system.mjs"
export default class LethalFantasySkill extends foundry.abstract.TypeDataModel {
export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
const requiredInteger = { required: true, nullable: false, integer: true }
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.settings = new fields.StringField({ required: true, initial: "modern", choices: SYSTEM.AVAILABLE_SETTINGS })
let setting = game.settings.get("fvtt-cthulhu-eternal", "settings-era") || "modern"
schema.settings = new fields.StringField({ required: true, initial: setting, choices: SYSTEM.AVAILABLE_SETTINGS })
schema.weaponType = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.WEAPON_TYPE })
schema.hasDirectSkill = new fields.BooleanField({ required: true, initial: false })
schema.directSkillValue = new fields.NumberField({ required: true, initial: 0, min: 0, max:99 })
schema.damage = new fields.StringField({required: true, initial: "1d6"})
schema.baseRange = new fields.StringField({required: true, initial: ""})
schema.rangeUnit = new fields.StringField({ required: true, initial: "yard", choices: SYSTEM.WEAPON_RANGE_UNIT })

@ -1,4 +1,8 @@
import CthulhuEternalRoll from "./documents/roll.mjs"
import { SystemManager } from './applications/hud/system-manager.js'
import { SYSTEM } from "./config/system.mjs"
export default class CthulhuEternalUtils {
static registerSettings() {
@ -177,12 +181,82 @@ export default class CthulhuEternalUtils {
}
static async nudgeRoll(rollMessage) {
let dialogContext = rollMessage.rolls[0]?.options
let actor = game.actors.get(dialogContext.actorId)
dialogContext.wpValue = actor.system.wp.value
dialogContext.rollResultIndex = rollMessage.rolls[0].total - 1
dialogContext.minValue = Math.max(rollMessage.rolls[0].total - (dialogContext.wpValue * 5), 1)
dialogContext.maxValue = Math.min(rollMessage.rolls[0].total + (dialogContext.wpValue * 5), 100)
dialogContext.wpCost = 0
// Build options table for the select operator between minValue and maxValue
dialogContext.nudgeOptions = Array.from({ length: dialogContext.maxValue - dialogContext.minValue + 1 }, (_, i) => dialogContext.minValue + i)
console.log(dialogContext)
const content = await renderTemplate("systems/fvtt-cthulhu-eternal/templates/nudge-dialog.hbs", dialogContext)
const title = game.i18n.localize("CTHULHUETERNAL.Roll.nudgeRoll")
const rollContext = await foundry.applications.api.DialogV2.wait({
window: { title: title },
classes: ["fvtt-cthulhu-eternal"],
content,
buttons: [
{
action: "apply",
label: game.i18n.localize("CTHULHUETERNAL.Roll.applyNudge"),
callback: (event, button, dialog) => {
const output = Array.from(button.form.elements).reduce((obj, input) => {
if (input.name) obj[input.name] = input.value
return obj
}, {})
return output
},
},
{
action: "cancel",
label: game.i18n.localize("CTHULHUETERNAL.Roll.cancel"),
callback: (event, button, dialog) => { }
}
],
actions: {
},
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)
$("#nudged-wp-cost").val(dialogContext.wpCost)
})
}
})
// If the user cancels the dialog, exit
if (rollContext === null || dialogContext.wpCost === 0) {
return
}
const roll = new CthulhuEternalRoll(String(dialogContext.nudgedValue))
await roll.evaluate()
roll.options = dialogContext
roll.options.isNudgedRoll = true
roll.options.isNudge = false
roll.displayRollResult(roll, dialogContext, dialogContext.rollData)
roll.toMessage()
actor.system.modifyWP(-dialogContext.wpCost)
}
static setupCSSRootVariables() {
const era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
let eraCSS = SYSTEM.ERA_CSS[era];
if (!eraCSS) eraCSS = SYSTEM.ERA_CSS["jazz"];
document.documentElement.style.setProperty('--font-size-standard', eraCSS.baseFontSize);
document.documentElement.style.setProperty('--font-size-title', eraCSS.titleFontSize);
document.documentElement.style.setProperty('--font-size-result', eraCSS.titleFontSize);
document.documentElement.style.setProperty('--font-primary', eraCSS.primaryFont);
document.documentElement.style.setProperty('--font-secondary', eraCSS.secondaryFont);
document.documentElement.style.setProperty('--font-title', eraCSS.titleFont);

@ -1 +1 @@
MANIFEST-000076
MANIFEST-000140

@ -1,8 +1,8 @@
2025/01/25-20:17:02.992814 7f697a7fc6c0 Recovering log #74
2025/01/25-20:17:03.002900 7f697a7fc6c0 Delete type=3 #72
2025/01/25-20:17:03.002968 7f697a7fc6c0 Delete type=0 #74
2025/01/25-20:19:31.386247 7f6978bff6c0 Level-0 table #79: started
2025/01/25-20:19:31.386288 7f6978bff6c0 Level-0 table #79: 0 bytes OK
2025/01/25-20:19:31.417028 7f6978bff6c0 Delete type=0 #77
2025/01/25-20:19:31.593890 7f6978bff6c0 Manual compaction at level-0 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)
2025/01/25-20:19:31.593942 7f6978bff6c0 Manual compaction at level-1 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)
2025/02/09-18:56:19.542927 7f46adffb6c0 Recovering log #138
2025/02/09-18:56:19.597959 7f46adffb6c0 Delete type=3 #136
2025/02/09-18:56:19.598032 7f46adffb6c0 Delete type=0 #138
2025/02/09-19:16:45.192949 7f46abbff6c0 Level-0 table #143: started
2025/02/09-19:16:45.192979 7f46abbff6c0 Level-0 table #143: 0 bytes OK
2025/02/09-19:16:45.199734 7f46abbff6c0 Delete type=0 #141
2025/02/09-19:16:45.210582 7f46abbff6c0 Manual compaction at level-0 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)
2025/02/09-19:16:45.220994 7f46abbff6c0 Manual compaction at level-1 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)

@ -1,8 +1,8 @@
2025/01/25-20:09:49.585129 7f697a7fc6c0 Recovering log #70
2025/01/25-20:09:49.662901 7f697a7fc6c0 Delete type=3 #68
2025/01/25-20:09:49.662960 7f697a7fc6c0 Delete type=0 #70
2025/01/25-20:11:19.050068 7f6978bff6c0 Level-0 table #75: started
2025/01/25-20:11:19.050103 7f6978bff6c0 Level-0 table #75: 0 bytes OK
2025/01/25-20:11:19.078755 7f6978bff6c0 Delete type=0 #73
2025/01/25-20:11:19.078917 7f6978bff6c0 Manual compaction at level-0 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)
2025/01/25-20:11:19.078946 7f6978bff6c0 Manual compaction at level-1 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)
2025/02/09-18:43:17.595623 7f46ac7f86c0 Recovering log #134
2025/02/09-18:43:17.606503 7f46ac7f86c0 Delete type=3 #132
2025/02/09-18:43:17.606590 7f46ac7f86c0 Delete type=0 #134
2025/02/09-18:55:41.771958 7f46abbff6c0 Level-0 table #139: started
2025/02/09-18:55:41.772001 7f46abbff6c0 Level-0 table #139: 0 bytes OK
2025/02/09-18:55:41.779302 7f46abbff6c0 Delete type=0 #137
2025/02/09-18:55:41.793230 7f46abbff6c0 Manual compaction at level-0 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)
2025/02/09-18:55:41.793270 7f46abbff6c0 Manual compaction at level-1 from '!folders!DD8331Hda4rhvEf9' @ 72057594037927935 : 1 .. '!items!zplzTG30QXHURusr' @ 0 : 0; will stop at (end)

Binary file not shown.

644
styles/creature.less Normal file

@ -0,0 +1,644 @@
.creature-content {
.sheet-common();
.creature-sheet-common();
overflow: scroll;
}
.sheet-tabs {
background-color: var(--color-light-1);
}
.creature-main {
background-color: var(--color-light-1);
display: flex;
.creature-pc {
display: flex;
gap: 4px;
flex: 1;
.creature-left {
min-width: 180px;
display: flex;
flex-direction: column;
.creature-left-image {
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 8px;
.creature-img {
height: 140px;
width: auto;
border: none;
}
}
.creature-hp {
gap: 2px;
align-items: center;
input {
flex: none;
width: 2rem;
margin-left: 4px;
}
.damage-bonus {
font-size: calc(var(--font-size-standard) * 0.8);
}
.hp-separator {
font-size: calc(var(--font-size-standard) * 1.2);
display: flex;
align-items: center;
justify-content: center;
}
}
.creature-dv,
.creature-dmax {
.form-fields {
flex: none;
}
}
.creature-dmax-edit {
input {
display: flex;
width: 60px;
font-size: calc(var(--font-size-standard) * 1.4);
align-items: center;
justify-content: center;
padding: 0 5px 0 5px;
text-align: center;
}
}
}
.creature-right {
display: flex;
flex-direction: column;
gap: 5px;
.creature-name {
display: flex;
input {
font-family: var(--font-title);
font-size: var(--font-size-title);
width: 400px;
}
}
.san {
align-content: flex-start;
input {
min-width: 2.2rem;
max-width: 2.2rem;
margin-bottom: 4px;
}
select {
min-width: 6rem;
max-width: 6rem;
}
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
font-size: 0.9rem;
}
.button {
min-width: 4rem;
max-width: 4rem;
}
.san-checkbox {
min-width: 1rem;
max-width: 1rem;
}
.label-short-field {
font-size: 0.9rem;
max-width: 3rem;
min-width: 3rem;
flex-grow: 1;
}
.label-recovery {
margin-left: 4px;
}
.label-field {
font-size: 0.9rem;
max-width: 6rem;
min-width: 6rem;
flex-grow: 1;
}
.label-bp {
flex-grow: 1;
max-width: 3rem;
min-width: 3rem;
margin-left: 4px;
}
.label-insanity {
flex-grow: 1;
margin-left: 4px;
max-width: 8rem;
min-width: 8rem;
}
.spacing {
margin-left: 4px;
}
.d100 {
flex: 0;
}
}
.willpower {
input {
min-width: 2.4rem;
max-width: 2.4rem;
}
input[type="checkbox"] {
min-width: 1rem;
max-width: 1rem;
}
.label-field {
flex-grow: 1;
margin-left: 4px;
max-width: 5rem;
min-width: 5rem;
font-size: 0.9rem;
}
.checkbox {
flex-grow: 0;
min-width: 1rem;
max-width: 1rem;
}
}
label {
min-width: 120px;
}
}
}
.creature-pc-play {
min-width: 500px;
}
.creature-pc-edit {
min-width: 650px;
}
.creature-characteristics {
background-color: var(--color-light-1);
display: flex;
flex-direction: column;
gap: 5px;
flex: 1;
.creature-characteristic {
display: flex;
align-items: center;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.rollable {
min-width: 3rem;
max-width: 3rem;
}
.char-text {
margin-left: 0.5rem;
}
.d100 {
flex: 0;
max-width: 0.6rem;
}
.form-group {
flex: 0;
padding-left: 5px;
.form-fields {
font-size: 1.1rem;
flex: none;
width: 40px;
}
}
}
}
.creature-characteristic-play {
min-width: 225px;
}
.creature-characteristic-edit {
min-width: 400px;
}
}
.creature-biography {
background-color: var(--color-light-1);
prose-mirror.inactive {
min-height: 40px;
}
prose-mirror.active {
min-height: 150px;
}
.field-label {
margin-left: 8px;
}
.adapted {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
label {
min-width: 20rem;
}
}
.resources {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
label {
min-width: 8rem;
}
}
.features,
.biodata {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
label {
min-width: 3rem;
}
.feature {
display: flex;
align-items: center;
gap: 4px;
min-width: 18rem;
max-width: 18rem;
}
}
}
.tab.creature-skills {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
legend {
a {
font-size: calc(var(--font-size-standard) * 1.4);
padding-left: 5px;
}
}
.armors {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
.armor {
display: flex;
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.protection {
min-width: 5rem;
max-width: 5rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.weapons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.weapon {
display: flex;
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.damage {
min-width: 6rem;
max-width: 6rem;
}
.name {
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.skills {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
.skill {
display: flex;
align-items: center;
gap: 4px;
margin-left: 4px;
min-width: 12.3rem;
max-width: 12.3rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.score {
min-width: 1.2rem;
max-width: 1.2rem;
}
.name {
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
}
.tab.creature-status {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
legend {
a {
font-size: calc(var(--font-size-standard) * 1.4);
padding-left: 5px;
}
}
.bonds {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.bond {
display: flex;
align-items: center;
gap: 4px;
min-width: 18rem;
max-width: 18rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.type {
min-width: 6rem;
max-width: 6rem;
}
.level {
min-width: 2rem;
max-width: 2rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.motivations {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.motivation {
display: flex;
align-items: center;
gap: 4px;
min-width: 14rem;
max-width: 14rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.mentaldisorders {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.mentaldisorder {
display: flex;
align-items: center;
gap: 4px;
min-width: 18rem;
max-width: 18rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
}
.cured {
min-width: 5rem;
max-width: 5rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.injuries {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.injury {
display: flex;
align-items: center;
gap: 4px;
min-width: 16rem;
max-width: 16rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
}
.tab.creature-equipment {
background-color: var(--color-light-1);
display: grid;
grid-template-columns: 1fr;
legend {
a {
font-size: calc(var(--font-size-standard) * 1.4);
padding-left: 5px;
}
}
.gears {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
.gear {
display: flex;
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.rituals {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.ritual {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.tomes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.tome {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
prose-mirror.inactive {
min-height: 40px;
}
prose-mirror.active {
min-height: 150px;
}
}

@ -44,9 +44,19 @@
src: url("../assets/fonts/Volkhov-Regular.ttf") format("truetype");
}
@font-face {
font-family: "ChantelliAntiqua";
src: url("../assets/fonts/Chantelli_Antiqua.ttf") format("truetype");
}
@font-face {
font-family: "IMFell";
src: url("../assets/fonts/IMFell.ttf") format("truetype");
src: url("../assets/fonts/IMFeDPrm28P.ttf") format("truetype");
}
@font-face {
font-family: "SailRegular";
src: url("../assets/fonts/Sail-Regular.ttf") format("truetype");
}
@font-face {

@ -5,6 +5,7 @@
@import "mixins.less";
@import "protagonist.less";
@import "vehicle.less";
@import "creature.less";
@import "skill.less";
@import "injury.less";
@import "weapon.less";
@ -16,6 +17,8 @@
@import "gear.less";
@import "arcane.less";
@import "archetype.less";
@import "ritual.less";
@import "tome.less";
}
@import "roll.less";

@ -1,13 +1,13 @@
:root {
--font-size-standard: 0.9rem;
--font-size-result: 1.4rem;
--background-image-base: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)),
url("../assets/ui/jazz_background_main.webp");
/*--background-image-base: url("../assets/ui/jazzage_background_main.webp");*/
--font-primary: "RozhaOne";
--font-secondary: "RozhaOne";
--font-title: "Broadway";
--logo-standard: url("../assets/logos/reanimated-ce-logo.webp");
--color-success: rgb(18, 233, 18);
--color-success: rgb(15, 122, 15);
--color-failure: darkred;
--color-warning: darkorange;
--color-critical-success: rgb(21, 39, 204);

@ -68,6 +68,13 @@
}
}
.creature-sheet-common {
label {
font-family: var(--font-secondary);
font-size: calc(var(--font-size-standard) * 1.0);
}
}
.item-sheet-common {
.form-fields {
padding-top: 4px;

@ -34,6 +34,25 @@
}
}
.protagonist-hp {
gap: 2px;
align-items: center;
input {
flex: none;
width: 2rem;
margin-left: 4px;
}
.damage-bonus {
font-size: calc(var(--font-size-standard) * 0.8);
}
.hp-separator {
font-size: calc(var(--font-size-standard) * 1.2);
display: flex;
align-items: center;
justify-content: center;
}
}
.protagonist-dv,
.protagonist-dmax {
.form-fields {
@ -62,7 +81,7 @@
display: flex;
input {
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.4);
font-size: var(--font-size-title);
width: 400px;
}
}
@ -146,50 +165,10 @@
min-width: 1rem;
max-width: 1rem;
}
}
.protagonist-infos {
display: flex;
flex-direction: column;
gap: 4px;
label {
min-width: 120px;
}
.protagonist-hp {
display: flex;
gap: 2px;
align-items: center;
.protagonist-hp-value {
.form-fields input {
flex: none;
width: 50px;
margin-left: 4px;
font-size: calc(var(--font-size-standard) * 1.4);
}
}
.protagonist-hp-max {
clear: both;
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 3px 0;
align-items: center;
input {
width: 50px;
text-align: center;
font-size: calc(var(--font-size-standard) * 1.4);
}
}
.hp-separator {
font-size: calc(var(--font-size-standard) * 1.2);
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
@ -223,6 +202,10 @@
.char-text {
margin-left: 0.5rem;
}
.d100 {
flex: 0;
max-width: 0.6rem;
}
.form-group {
flex: 0;
padding-left: 5px;
@ -280,7 +263,7 @@
grid-template-columns: repeat(2, 1fr);
gap: 8px;
label {
min-width: 3.0rem;
min-width: 3rem;
}
.feature {
display: flex;
@ -290,32 +273,6 @@
max-width: 18rem;
}
}
.motivations {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.motivation {
display: flex;
align-items: center;
gap: 4px;
min-width: 14rem;
max-width: 14rem;
.controls {
min-width: 2rem;
max-width: 2rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
}
.item-img {
width: 32px;
height: 32px;
margin: 4px 0 0 0;
}
}
}
}
.tab.protagonist-skills {
@ -336,20 +293,26 @@
display: flex;
align-items: center;
gap: 4px;
min-width: 10rem;
max-width: 10rem;
margin-left: 4px;
min-width: 12.3rem;
max-width: 12.3rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.score {
min-width: 1.2rem;
max-width: 1.2rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
@ -370,6 +333,7 @@
padding-left: 5px;
}
}
.bonds {
display: grid;
grid-template-columns: repeat(2, 1fr);
@ -378,19 +342,20 @@
display: flex;
align-items: center;
gap: 4px;
min-width: 16rem;
max-width: 16rem;
min-width: 18rem;
max-width: 18rem;
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.type {
min-width: 5rem;
max-width: 5rem;
min-width: 6rem;
max-width: 6rem;
}
.level {
min-width: 2rem;
@ -403,23 +368,25 @@
}
}
}
.mentaldisorders {
.motivations {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.mentaldisorder {
.motivation {
display: flex;
align-items: center;
gap: 4px;
min-width: 16rem;
max-width: 16rem;
.controls {
min-width: 2rem;
max-width: 2rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 12rem;
max-width: 12rem;
}
.item-img {
width: 24px;
@ -428,6 +395,38 @@
}
}
}
.mentaldisorders {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.mentaldisorder {
display: flex;
align-items: center;
gap: 4px;
min-width: 18rem;
max-width: 18rem;
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
max-width: 14rem;
}
.cured {
min-width: 5rem;
max-width: 5rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.injuries {
display: grid;
grid-template-columns: repeat(2, 1fr);
@ -439,8 +438,9 @@
min-width: 16rem;
max-width: 16rem;
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 14rem;
@ -481,8 +481,9 @@
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.damage {
min-width: 6rem;
@ -508,15 +509,16 @@
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13srem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.protection {
min-width: 5rem;
@ -533,6 +535,7 @@
}
}
}
.gears {
display: grid;
grid-template-columns: repeat(3, 1fr);
@ -542,23 +545,84 @@
align-items: center;
gap: 4px;
min-width: 13rem;
max-width: 13srem;
max-width: 13rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
min-width: 2rem;
max-width: 2rem;
}
.damage {
min-width: 5rem;
max-width: 5rem;
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 8rem;
max-width: 8rem;
min-width: 10rem;
max-width: 10rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.rituals {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.ritual {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;
height: 24px;
margin: 4px 0 0 0;
}
}
}
.tomes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px;
.tome {
display: flex;
align-items: center;
gap: 4px;
min-width: 20rem;
max-width: 20rem;
.rollable:hover,
.rollable:focus {
text-shadow: 0 0 8px var(--color-shadow-primary);
cursor: pointer;
}
.controls {
font-size: 0.7rem;
min-width: 1.8rem;
max-width: 1.8rem;
}
.name {
min-width: 17rem;
max-width: 17rem;
}
.item-img {
width: 24px;

22
styles/ritual.less Normal file

@ -0,0 +1,22 @@
.ritual-content {
.sheet-common();
.item-sheet-common();
fieldset {
margin-top: 8px;
background-color: var(--color-light-1);
}
.header {
background-color: var(--color-light-1);
display: flex;
img {
width: 50px;
height: 50px;
}
}
label {
flex: 10%;
}
}

@ -31,7 +31,7 @@
border: none;
background-color: rgba(0, 0, 0, 0.1);
color: var(--color-dark-2);
width: 60px;
width: 4rem;
text-align: center;
}
}
@ -93,25 +93,30 @@
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: 4rem;
display: none;
}
.result-success {
color: var(--color-success);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
.result-critical-success {
color: var(--color-critical-success);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
.result-failure {
color: var(--color-failure);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
.result-critical-failure {
color: var(--color-critical-failure);
font-family: var(--font-title);
font-size: calc(var(--font-size-standard) * 1.2);
font-size: var(--font-size-result);
}
}
.introText {

22
styles/tome.less Normal file

@ -0,0 +1,22 @@
.tome-content {
.sheet-common();
.item-sheet-common();
fieldset {
margin-top: 8px;
background-color: var(--color-light-1);
}
.header {
background-color: var(--color-light-1);
display: flex;
img {
width: 50px;
height: 50px;
}
}
label {
flex: 10%;
}
}

@ -6,7 +6,7 @@
"download": "#{DOWNLOAD}#",
"url": "https://www.uberwald.me/gitea/public/fvtt-cthulhu-eternal",
"license": "LICENSE",
"version": "12.0.4",
"version": "12.0.5",
"authors": [
{
"name": "Uberwald",
@ -35,7 +35,8 @@
"documentTypes": {
"Actor": {
"protagonist": { "htmlFields": ["description", "notes"] },
"vehicle": { "htmlFields": ["description", "notes"] }
"vehicle": { "htmlFields": ["description", "notes"] },
"creature": { "htmlFields": ["description", "notes"] }
},
"Item": {
"skill": { "htmlFields": ["description"] },
@ -47,7 +48,9 @@
"motivation": { "htmlFields": ["description"] },
"arcane": { "htmlFields": ["description"] },
"gear": { "htmlFields": ["description"] },
"archetype": { "htmlFields": ["description"] }
"archetype": { "htmlFields": ["description"] },
"ritual": { "htmlFields": ["description"] },
"tome": { "htmlFields": ["description"] }
}
},
"packs": [

@ -14,6 +14,10 @@
<li><strong>{{localize "CTHULHUETERNAL.Label.skillRoll"}}</strong></li>
{{/if}}
{{#if isNudgedRoll}}
<li><strong>{{localize "CTHULHUETERNAL.Label.nudgedRoll"}} : {{wpCost}} WP spent</strong></li>
{{/if}}
{{#if weapon}}
<li><strong>Weapon : {{weapon.name}}</strong></li>
{{/if}}
@ -47,14 +51,24 @@
{{#if isCritical}}
<li class="result-critical-success">{{localize "CTHULHUETERNAL.Label.criticalSuccess"}}</li>
{{else}}
<li class="result-success">{{localize "CTHULHUETERNAL.Label.success"}}</li>
<li class="result-success">
{{localize "CTHULHUETERNAL.Label.success"}}
{{#if isNudge}}
<a class="nudge-roll"><i class="fa-solid fa-circle-sort-down"></i></a>
{{/if}}
</li>
{{/if}}
{{/if}}
{{#if isFailure}}
{{#if isCritical}}
<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"}}
{{#if isNudge}}
<a class="nudge-roll"><i class="fa-solid fa-circle-sort-down"></i></a>
{{/if}}
</li>
{{/if}}
{{/if}}
</ul>

@ -0,0 +1,12 @@
<section class="tab creature-{{tab.id}} {{tab.cssClass}}" data-tab="{{tab.id}}" data-group="{{tab.group}}">
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.notes"}}</legend>
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</fieldset>
</section>

@ -0,0 +1,95 @@
<section class="creature-main creature-main-{{ifThen isPlayMode 'play' 'edit'}}">
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.creature"}}</legend>
<div class="creature-pc creature-pc-{{ifThen isPlayMode 'play' 'edit'}}">
<div class="creature-left">
<div class="creature-left-image">
<img class="creature-img" src="{{actor.img}}" data-edit="img" data-action="editImage"
data-tooltip="{{actor.name}}" />
</div>
</div>
<div class="creature-right">
<div class="creature-name">
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
<a class="control" data-action="toggleSheet" data-tooltip="CTHULHUETERNAL.ToggleSheet"
data-tooltip-direction="UP">
<i class="fa-solid fa-user-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
</a>
</div>
<fieldset class="creature-hp">
<legend>{{localize "CTHULHUETERNAL.Label.HP"}}</legend>
<div class="flexrow">
{{formField systemFields.hp.fields.value value=system.hp.value}}
<span class="hp-separator">/</span>
{{formField systemFields.hp.fields.max value=system.hp.max }}
</div>
<div class="flexrow ">
{{formField systemFields.damageBonus value=system.damageBonus classes="damage-bonus"}}
</div>
</fieldset>
<fieldset class="willpower">
<legend>{{localize "CTHULHUETERNAL.Label.willpower"}}</legend>
<div class="flexrow">
<label class="label-field">{{localize "CTHULHUETERNAL.Label.current"}}</label>
{{formInput systemFields.wp.fields.value value=system.wp.value}}
<label class="label-field">{{localize "CTHULHUETERNAL.Label.max"}}</label>
{{formInput systemFields.wp.fields.max value=system.wp.max rootId=partId }}
<label class="label-field">{{localize "CTHULHUETERNAL.Label.exhausted"}}</label>
{{formInput systemFields.wp.fields.exhausted value=system.wp.exhausted classes="checkbox"}}
</div>
</fieldset>
</div>
</div>
</fieldset>
<fieldset class="creature-characteristics creature-characteristics-{{ifThen isPlayMode 'play' 'edit'}}">
<legend>{{localize "CTHULHUETERNAL.Label.characteristics"}}</legend>
<div class="creature-characteristic" >
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<label class="rollable" data-roll-type="char" data-char-id="str" data-tooltip="{{system.characteristics.str.feature}}">{{localize
"CTHULHUETERNAL.Label.strShort"}}</label>
{{formField systemFields.characteristics.fields.str.fields.value value=system.characteristics.str.value
rootId=partId disabled=isPlayMode }}
<label class="char-text">{{mul system.characteristics.str.value 5}}</label>
</div>
<div class="creature-characteristic">
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<label class="rollable" data-roll-type="char" data-char-id="dex" data-tooltip="{{system.characteristics.dex.feature}}">{{localize
"CTHULHUETERNAL.Label.dexShort"}}</label>
{{formField systemFields.characteristics.fields.dex.fields.value value=system.characteristics.dex.value
rootId=partId disabled=isPlayMode }}
<label class="char-text">{{mul system.characteristics.dex.value 5}}</label>
</div>
<div class="creature-characteristic" >
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<label class="rollable" data-roll-type="char" data-char-id="con" data-tooltip="{{system.characteristics.con.feature}}">{{localize
"CTHULHUETERNAL.Label.conShort"}}</label>
{{formField systemFields.characteristics.fields.con.fields.value value=system.characteristics.con.value
rootId=partId disabled=isPlayMode }}
<label class="char-text">{{mul system.characteristics.con.value 5}}</label>
</div>
<div class="creature-characteristic" >
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<label class="rollable" data-roll-type="char" data-char-id="int" data-tooltip="{{system.characteristics.int.feature}}">{{localize
"CTHULHUETERNAL.Label.intShort"}}</label>
{{formField systemFields.characteristics.fields.int.fields.value value=system.characteristics.int.value
rootId=partId disabled=isPlayMode }}
<label class="char-text">{{mul system.characteristics.int.value 5}}</label>
</div>
<div class="creature-characteristic" >
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<label class="rollable" data-roll-type="char" data-char-id="pow" data-tooltip="{{system.characteristics.pow.feature}}">{{localize
"CTHULHUETERNAL.Label.powShort"}}</label>
{{formField systemFields.characteristics.fields.pow.fields.value value=system.characteristics.pow.value
rootId=partId disabled=isPlayMode }}
<label class="char-text">{{mul system.characteristics.pow.value 5}}</label>
</div>
</fieldset>
</section>

@ -0,0 +1,57 @@
<section class="tab creature-{{tab.id}} {{tab.cssClass}}" data-tab="{{tab.id}}" data-group="{{tab.group}}">
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.weapons"}}{{#if isEditMode}}
<a class="action" data-tooltip="{{localize " CTHULHUETERNAL.Tooltip.addWeapon"}}" data-tooltip-direction="UP"><i
class="fas fa-plus" data-action="createWeapon"></i></a>{{/if}}
</legend>
<div class="weapons">
{{#each weapons as |item|}}
{{!log 'weapon' this}}
<div class="weapon item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-drag="true">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<div class="name rollable" data-roll-type="weapon" data-tooltip="{{{item.system.description}}}">
{{item.name}}
</div>
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<a class="damage rollable" data-item-id="{{item.id}}" data-action="roll" data-roll-type="damage"
data-roll-value="{{item.system.damage}}">
{{localize "CTHULHUETERNAL.Label.damageShort"}} :
{{item.system.damage}}</a>
<div class="controls">
<a data-tooltip="{{localize 'CTHULHUETERNAL.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'CTHULHUETERNAL.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
<fieldset>
<legend data-tooltip="{{localize "CTHULHUETERNAL.Tooltip.skills"}}" data-tooltip-direction="UP">{{localize "CTHULHUETERNAL.Label.skills"}}</legend>
<div class="skills">
{{#each skills as |item|}}
<div class="skill item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<div class="name rollable" data-roll-type="skill" data-tooltip="{{{item.description}}}" data-tooltip-direction="UP">{{item.name}}
</div>
<div class="score" >
{{item.system.skillTotal}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'CTHULHUETERNAL.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'CTHULHUETERNAL.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
</section>

@ -1,39 +0,0 @@
<table class="tenebris-table">
<thead>
<tr>
<th>{{localize "TENEBRIS.Manager.player"}}</th>
<th>{{localize "TENEBRIS.Manager.character"}}</th>
<th>{{localize "TENEBRIS.Manager.pv"}}</th>
<td data-action="saveAll" data-save="rob">{{localize "TENEBRIS.Manager.rob"}}</td>
<td data-action="saveAll" data-save="dex">{{localize "TENEBRIS.Manager.dex"}}</td>
<td data-action="saveAll" data-save="int">{{localize "TENEBRIS.Manager.int"}}</td>
<td data-action="saveAll" data-save="per">{{localize "TENEBRIS.Manager.per"}}</td>
<td data-action="saveAll" data-save="vol">{{localize "TENEBRIS.Manager.vol"}}</td>
<td data-action="resourceAll" data-resource="san">{{localize "TENEBRIS.Manager.san"}}</td>
<td data-action="resourceAll" data-resource="oeil">{{localize "TENEBRIS.Manager.oeil"}}</td>
<td data-action="resourceAll" data-resource="verbe">{{localize "TENEBRIS.Manager.verbe"}}</td>
<td data-action="resourceAll" data-resource="bourse">{{localize "TENEBRIS.Manager.bourse"}}</td>
<td data-action="resourceAll" data-resource="magie">{{localize "TENEBRIS.Manager.magie"}}</td>
</tr>
</thead>
<tbody>
{{#each players as |player|}}
{{!log "player" this}}
<tr class="player" data-user-id="{{player.id}}" data-character-name="{{player.character.name}}" data-character-id="{{player.character.id}}">
<td>{{player.name}}</td>
<td data-action="openSheet" data-character-id="{{player.character.id}}">{{player.character.name}}</td>
<td>{{player.character.system.pv.value}}</td>
<td data-action="saveOne" data-save="rob">{{player.character.system.caracteristiques.rob.valeur}}</td>
<td data-action="saveOne" data-save="dex">{{player.character.system.caracteristiques.dex.valeur}}</td>
<td data-action="saveOne" data-save="int">{{player.character.system.caracteristiques.int.valeur}}</td>
<td data-action="saveOne" data-save="per">{{player.character.system.caracteristiques.per.valeur}}</td>
<td data-action="saveOne" data-save="vol">{{player.character.system.caracteristiques.vol.valeur}}</td>
<td data-action="resourceOne" data-resource="san">{{player.character.system.ressources.san.valeur}}</td>
<td data-action="resourceOne" data-resource="oeil">{{player.character.system.ressources.oeil.valeur}}</td>
<td data-action="resourceOne" data-resource="verbe">{{player.character.system.ressources.verbe.valeur}}</td>
<td data-action="resourceOne" data-resource="bourse">{{player.character.system.ressources.bourse.valeur}}</td>
<td data-action="resourceOne" data-resource="magie">{{player.character.system.ressources.magie.valeur}}</td>
</tr>
{{/each}}
</tbody>
</table>

@ -0,0 +1,18 @@
<div class="fvtt-cthulhu-eternal-roll-dialog">
<fieldSet class="dialog-modifier">
<legend>{{localize "CTHULHUETERNAL.Label.selectNewValue"}}</legend>
<select name="modifiedValue" class="nudged-score-select">
{{selectOptions nudgeOptions selected=rollResultIndex}}
</select>
</fieldSet>
<fieldSet>
<legend>{{localize "CTHULHUETERNAL.Label.wpCost"}}</legend>
<div>
Willpower points cost :
<input class="text" value="0" id="nudged-wp-cost" disabled >
</div>
</fieldSet>
</div>

@ -81,4 +81,55 @@
</div>
</fieldset>
{{#if (count tomes)}}
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.Tomes"}}{{#if isEditMode}}
<a class="action" data-tooltip="{{localize "CTHULHUETERNAL.Tooltip.addTome"}}" data-tooltip-direction="UP"><i
class="fas fa-plus" data-action="createTome"></i></a>{{/if}}
</legend>
<div class="tomes">
{{#each tomes as |item|}}
<div class="tome" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{{item.system.description}}}">
{{item.name}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'CTHULHUETERNAL.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'CTHULHUETERNAL.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
{{/if}}
{{#if (count rituals)}}
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.Rituals"}}{{#if isEditMode}}
<a class="action" data-tooltip="{{localize "CTHULHUETERNAL.Tooltip.addRitual"}}" data-tooltip-direction="UP"><i
class="fas fa-plus" data-action="createRitual"></i></a>{{/if}}
</legend>
<div class="rituals">
{{#each rituals as |item|}}
<div class="ritual" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="name" data-tooltip="{{{item.system.description}}}">
{{item.name}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'CTHULHUETERNAL.Edit'}}" data-action="edit" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'CTHULHUETERNAL.Delete'}}" data-action="delete" data-item-id="{{item.id}}"
data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</fieldset>
{{/if}}
</section>

@ -9,13 +9,16 @@
<img class="protagonist-img" src="{{actor.img}}" data-edit="img" data-action="editImage"
data-tooltip="{{actor.name}}" />
</div>
<fieldset>
<fieldset class="protagonist-hp">
<legend>{{localize "CTHULHUETERNAL.Label.HP"}}</legend>
<div class="flexrow">
{{formField systemFields.hp.fields.value value=system.hp.value}}
/
<span class="hp-separator">/</span>
{{formField systemFields.hp.fields.max value=system.hp.max rootId=partId disabled=true}}
</div>
<div class="flexrow ">
{{formField systemFields.damageBonus value=system.damageBonus classes="damage-bonus"}}
</div>
</fieldset>
</div>
@ -32,8 +35,9 @@
<fieldset class="san">
<legend>{{localize "CTHULHUETERNAL.Label.SAN"}}</legend>
<div class="flexrow">
<img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
<label class="label-field rollable" data-roll-type="san" >{{localize "CTHULHUETERNAL.Label.current"}}</label>
<label class="label-field rollable" data-roll-type="san" ><img src="systems/fvtt-cthulhu-eternal/assets/ui/d100.svg" class="d100" />
{{localize "CTHULHUETERNAL.Label.current"}}</label>
{{formInput systemFields.san.fields.value value=system.san.value}}
<span class="label-field label-recovery">{{localize "CTHULHUETERNAL.Label.recovery"}}</span>

@ -12,7 +12,7 @@
{{item.name}}
</div>
<div class="type">
{{item.system.bondType}}
{{upperFirst item.system.bondType}}
</div>
<div class="level">
{{item.system.value}}
@ -56,6 +56,13 @@
<div class="name" data-tooltip="{{{item.system.description}}}">
{{item.name}}
</div>
<div class="cured">
{{#if item.system.cured}}
{{localize 'CTHULHUETERNAL.Label.Cured'}}
{{else}}
{{localize 'CTHULHUETERNAL.Label.Uncured'}}
{{/if}}
</div>
<div class="controls">
<a data-tooltip="{{localize 'CTHULHUETERNAL.Edit'}}" data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
<a data-tooltip="{{localize 'CTHULHUETERNAL.Delete'}}" data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>

23
templates/ritual.hbs Normal file

@ -0,0 +1,23 @@
<section>
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<fieldset>
{{formField systemFields.ritualType value=system.ritualType localize=true}}
{{formField systemFields.studyTime value=system.studyTime}}
{{formField systemFields.studySAN value=system.studySAN}}
{{formField systemFields.activationTime value=system.activationTime}}
{{formField systemFields.activationSAN value=system.activationSAN}}
{{formField systemFields.activationWP value=system.activationWP}}
</fieldset>
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.description"}}</legend>
{{formInput systemFields.description enriched=description value=system.description name="system.description" toggled=true}}
</fieldset>
</section>

@ -1,4 +1,3 @@
{{log "roll-dialog" this}}
<div class="fvtt-cthulhu-eternal-roll-dialog">
<fieldSet>
{{#if (eq rollType "skill")}}

42
templates/tome.hbs Normal file

@ -0,0 +1,42 @@
<section>
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<fieldset>
<div class="form-group">
{{formField systemFields.language value=system.language }}
</div>
<div class="form-group">
{{formField systemFields.minimumEra value=system.minimumEra localize=true }}
</div>
<div class="form-group">
{{formField systemFields.creationDate value=system.creationDate }}
</div>
<div class="form-group">
{{formField systemFields.studyTime value=system.studyTime }}
</div>
<div class="form-group">
{{formField systemFields.sanLoss value=system.sanLoss }}
</div>
<div class="form-group">
{{formField systemFields.unnaturalSkill value=system.unnaturalSkill }}
</div>
<div class="form-group">
<label>{{localize "CTHULHUETERNAL.Label.Rituals"}} </label>
<textarea class="form-control" rows="3" name="system.rituals" data-tooltip="{{localize "CTHULHUETERNAL.Label.Rituals"}}">{{system.rituals}}</textarea>
</div>
<div class="form-group">
<label>{{localize "CTHULHUETERNAL.Label.otherBenefits"}} </label>
<textarea class="form-control" rows="3" name="system.otherBenefits" data-tooltip="{{localize "CTHULHUETERNAL.Label.otherBenefits"}}">{{system.otherBenefits}}</textarea>
</div>
</fieldset>
<fieldset>
<legend>{{localize "CTHULHUETERNAL.Label.description"}}</legend>
{{formInput systemFields.description enriched=description value=system.description name="system.description" toggled=true}}
</fieldset>
</section>

@ -13,6 +13,11 @@
{{formField systemFields.state value=system.state localize=true}}
{{formField systemFields.hasDirectSkill value=system.hasDirectSkill }}
{{#if system.hasDirectSkill}}
{{formField systemFields.directSkillValue value=system.directSkillValue }}
{{/if}}
{{formField systemFields.damage value=system.damage}}
{{formField systemFields.baseRange value=system.baseRange}}
{{formField systemFields.rangeUnit value=system.rangeUnit localize=true}}