commit 63f6d0271d638b79a3f64ecc89091a4a25f18eaf Author: LeRatierBretonnien Date: Wed Apr 19 18:44:41 2023 +0200 Initial import diff --git a/README.md b/README.md new file mode 100644 index 0000000..e68eb1b --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Système Foundry pour Maléfices (French RPG, Arkhane Asylum Publishing) + +[Vue du système](https://www.lahiette.com/leratierbretonnien/wp-content/uploads/2023/02/malefices_snapshot.webp) + +## EN + +Unofficial system for Maléfices v4 (French version from Arkhane Asylum Publishing). + +This system has been approved by Arkhane Asylum Publishing ( https://arkhane-asylum.fr ), thanks ! +The Tarot assets, as well as other graphical elements has been provide by Arkhane Asylum. + +Books are mandatory to play and are available at : https://arkhane-asylum.fr/en/malefices + +## FR + +Système non-officiel pour le JDR Maléfices, version 4 (Arkhane Asylum Publishing). + +Ce système a été autorisé par le Arkhane Asylum Publishing ( https://arkhane-asylum.fr ), merci à eux ! +Les images du Tarot et autres éléments graphiques ont été fournis par Arkhane Asylum. + +Les livres du jeu sont nécessaires pour jouer, et sont disponibles ici : https://arkhane-asylum.fr/fr/malefices + +# Credits + +Maléfices, le jeu de rôle qui sent le souffre, is a property of Arkhane Asylum Publishing. + +# Developmement + +LeRatierBretonnien + +# Tests, icones et saisie des données + +Dame du Lac, Malik \ No newline at end of file diff --git a/fonts/rivanna.regular.otf b/fonts/rivanna.regular.otf new file mode 100644 index 0000000..513a471 Binary files /dev/null and b/fonts/rivanna.regular.otf differ diff --git a/fonts/rivanna.ttf b/fonts/rivanna.ttf new file mode 100644 index 0000000..a2bd67e Binary files /dev/null and b/fonts/rivanna.ttf differ diff --git a/images/icons/.directory b/images/icons/.directory new file mode 100644 index 0000000..e08811e --- /dev/null +++ b/images/icons/.directory @@ -0,0 +1,6 @@ +[Dolphin] +SortRole=modificationtime +Timestamp=2023,2,26,15,32,34.892 +Version=4 +ViewMode=1 +VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails diff --git a/images/icons/Artiste.webp b/images/icons/Artiste.webp new file mode 100644 index 0000000..8fc7413 Binary files /dev/null and b/images/icons/Artiste.webp differ diff --git a/images/icons/Aventurier.webp b/images/icons/Aventurier.webp new file mode 100644 index 0000000..c0af694 Binary files /dev/null and b/images/icons/Aventurier.webp differ diff --git a/images/icons/Comedien.webp b/images/icons/Comedien.webp new file mode 100644 index 0000000..8e8c306 Binary files /dev/null and b/images/icons/Comedien.webp differ diff --git a/images/icons/Commercant.webp b/images/icons/Commercant.webp new file mode 100644 index 0000000..2c0ebd9 Binary files /dev/null and b/images/icons/Commercant.webp differ diff --git a/images/icons/Ecclesiastique.webp b/images/icons/Ecclesiastique.webp new file mode 100644 index 0000000..1f393da Binary files /dev/null and b/images/icons/Ecclesiastique.webp differ diff --git a/images/icons/Ecrivain.webp b/images/icons/Ecrivain.webp new file mode 100644 index 0000000..94fc7fd Binary files /dev/null and b/images/icons/Ecrivain.webp differ diff --git a/images/icons/Enseignant.webp b/images/icons/Enseignant.webp new file mode 100644 index 0000000..c797c58 Binary files /dev/null and b/images/icons/Enseignant.webp differ diff --git a/images/icons/Ingenieur.webp b/images/icons/Ingenieur.webp new file mode 100644 index 0000000..b16e346 Binary files /dev/null and b/images/icons/Ingenieur.webp differ diff --git a/images/icons/Journaliste.webp b/images/icons/Journaliste.webp new file mode 100644 index 0000000..7a7f78d Binary files /dev/null and b/images/icons/Journaliste.webp differ diff --git a/images/icons/Juriste.webp b/images/icons/Juriste.webp new file mode 100644 index 0000000..5c194c7 Binary files /dev/null and b/images/icons/Juriste.webp differ diff --git a/images/icons/Medecin.webp b/images/icons/Medecin.webp new file mode 100644 index 0000000..a3c9fe9 Binary files /dev/null and b/images/icons/Medecin.webp differ diff --git a/images/icons/Medium.webp b/images/icons/Medium.webp new file mode 100644 index 0000000..5d54eda Binary files /dev/null and b/images/icons/Medium.webp differ diff --git a/images/icons/Militaire.webp b/images/icons/Militaire.webp new file mode 100644 index 0000000..5029428 Binary files /dev/null and b/images/icons/Militaire.webp differ diff --git a/images/icons/Proletaire.webp b/images/icons/Proletaire.webp new file mode 100644 index 0000000..20cc642 Binary files /dev/null and b/images/icons/Proletaire.webp differ diff --git a/images/icons/Rentier.webp b/images/icons/Rentier.webp new file mode 100644 index 0000000..fcee775 Binary files /dev/null and b/images/icons/Rentier.webp differ diff --git a/images/icons/arbalete.webp b/images/icons/arbalete.webp new file mode 100644 index 0000000..49063d6 Binary files /dev/null and b/images/icons/arbalete.webp differ diff --git a/images/icons/arc.webp b/images/icons/arc.webp new file mode 100644 index 0000000..d3431e1 Binary files /dev/null and b/images/icons/arc.webp differ diff --git a/images/icons/archetype.webp b/images/icons/archetype.webp new file mode 100644 index 0000000..0282df7 Binary files /dev/null and b/images/icons/archetype.webp differ diff --git a/images/icons/constitution.webp b/images/icons/constitution.webp new file mode 100644 index 0000000..10b5508 Binary files /dev/null and b/images/icons/constitution.webp differ diff --git a/images/icons/culturegenerale.webp b/images/icons/culturegenerale.webp new file mode 100644 index 0000000..46a5089 Binary files /dev/null and b/images/icons/culturegenerale.webp differ diff --git a/images/icons/epee.webp b/images/icons/epee.webp new file mode 100644 index 0000000..30b8f4d Binary files /dev/null and b/images/icons/epee.webp differ diff --git a/images/icons/equipement.webp b/images/icons/equipement.webp new file mode 100644 index 0000000..05da749 Binary files /dev/null and b/images/icons/equipement.webp differ diff --git a/images/icons/fusil.webp b/images/icons/fusil.webp new file mode 100644 index 0000000..f6bb7de Binary files /dev/null and b/images/icons/fusil.webp differ diff --git a/images/icons/habilite.webp b/images/icons/habilite.webp new file mode 100644 index 0000000..45a8e16 Binary files /dev/null and b/images/icons/habilite.webp differ diff --git a/images/icons/mainsnues.webp b/images/icons/mainsnues.webp new file mode 100644 index 0000000..0483a29 Binary files /dev/null and b/images/icons/mainsnues.webp differ diff --git a/images/icons/perception.webp b/images/icons/perception.webp new file mode 100644 index 0000000..1648443 Binary files /dev/null and b/images/icons/perception.webp differ diff --git a/images/icons/physique.webp b/images/icons/physique.webp new file mode 100644 index 0000000..6d66280 Binary files /dev/null and b/images/icons/physique.webp differ diff --git a/images/icons/rationnalite.webp b/images/icons/rationnalite.webp new file mode 100644 index 0000000..2b5ea21 Binary files /dev/null and b/images/icons/rationnalite.webp differ diff --git a/images/icons/resume.webp b/images/icons/resume.webp new file mode 100644 index 0000000..f1c5cdf Binary files /dev/null and b/images/icons/resume.webp differ diff --git a/images/icons/revolver.webp b/images/icons/revolver.webp new file mode 100644 index 0000000..251dfeb Binary files /dev/null and b/images/icons/revolver.webp differ diff --git a/images/icons/sortilege.webp b/images/icons/sortilege.webp new file mode 100644 index 0000000..8eee378 Binary files /dev/null and b/images/icons/sortilege.webp differ diff --git a/images/icons/spiritualite.webp b/images/icons/spiritualite.webp new file mode 100644 index 0000000..277ebbe Binary files /dev/null and b/images/icons/spiritualite.webp differ diff --git a/images/icons/tarot.webp b/images/icons/tarot.webp new file mode 100644 index 0000000..cc4f1c3 Binary files /dev/null and b/images/icons/tarot.webp differ diff --git a/images/icons/tirage.webp b/images/icons/tirage.webp new file mode 100644 index 0000000..70a58b2 Binary files /dev/null and b/images/icons/tirage.webp differ diff --git a/images/icons/tirer.webp b/images/icons/tirer.webp new file mode 100644 index 0000000..bc7efc6 Binary files /dev/null and b/images/icons/tirer.webp differ diff --git a/images/icons/wisdom.webp b/images/icons/wisdom.webp new file mode 100644 index 0000000..175200e Binary files /dev/null and b/images/icons/wisdom.webp differ diff --git a/images/ui/background_01.webp b/images/ui/background_01.webp new file mode 100644 index 0000000..8c9a9b8 Binary files /dev/null and b/images/ui/background_01.webp differ diff --git a/images/ui/background_01_clear.webp b/images/ui/background_01_clear.webp new file mode 100644 index 0000000..2cae88c Binary files /dev/null and b/images/ui/background_01_clear.webp differ diff --git a/images/ui/logo_pause.webp b/images/ui/logo_pause.webp new file mode 100644 index 0000000..0abedd3 Binary files /dev/null and b/images/ui/logo_pause.webp differ diff --git a/images/ui/separator_01.webp b/images/ui/separator_01.webp new file mode 100644 index 0000000..f3a63ca Binary files /dev/null and b/images/ui/separator_01.webp differ diff --git a/lang/fr.json b/lang/fr.json new file mode 100644 index 0000000..3759df4 --- /dev/null +++ b/lang/fr.json @@ -0,0 +1,13 @@ +{ + "ACTOR": { + "TypePersonnage": "Personnage" + }, + "ITEM": { + "TypeArme": "Arme", + "TypeEquipement": "Equipement", + "TypeTarot": "Tarot", + "TypeElementbio": "Element Biographique", + "TypeArchetype": "Archetype", + "TypeSortilege": "Sortilège" + } +} \ No newline at end of file diff --git a/modules/actors/ecryme-actor-sheet.js b/modules/actors/ecryme-actor-sheet.js new file mode 100644 index 0000000..a152e69 --- /dev/null +++ b/modules/actors/ecryme-actor-sheet.js @@ -0,0 +1,168 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ + +import { EcrymeUtility } from "./common/ecryme-utility.js"; + +/* -------------------------------------------- */ +export class EcrymeActorSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-ecryme", "sheet", "actor"], + template: "systems/fvtt-ecryme/templates/actors/actor-sheet.hbs", + width: 640, + height:680, + tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "skills" }], + dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }], + editScore: true + }); + } + + /* -------------------------------------------- */ + async getData() { + + let formData = { + title: this.title, + id: this.actor.id, + type: this.actor.type, + img: this.actor.img, + name: this.actor.name, + editable: this.isEditable, + cssClass: this.isEditable ? "editable" : "locked", + system: duplicate(this.object.system), + limited: this.object.limited, + armes: duplicate(this.actor.getArmes()), + tarots: duplicate(this.actor.getTarots()), + tarotsCache: duplicate(this.actor.getHiddenTarots()), + archetype: duplicate(this.actor.getArchetype()), + equipements: duplicate(this.actor.getEquipements()), + subActors: duplicate(this.actor.getSubActors()), + phyMalus: this.actor.getPhysiqueMalus(), + elementsbio: this.actor.getElementsBio(), + sorts: this.actor.getSorts(), + description: await TextEditor.enrichHTML(this.object.system.description, { async: true }), + notes: await TextEditor.enrichHTML(this.object.system.notes, { async: true }), + equipementlibre: await TextEditor.enrichHTML(this.object.system.equipementlibre, { async: true }), + options: this.options, + owner: this.document.isOwner, + editScore: this.options.editScore, + isGM: game.user.isGM + } + this.formData = formData; + + console.log("PC : ", formData, this.object); + return formData; + } + + + /* -------------------------------------------- */ + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) return; + + html.bind("keydown", function(e) { // Ignore Enter in actores sheet + if (e.keyCode === 13) return false; + }); + + // Update Inventory Item + html.find('.item-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + const item = this.actor.items.get( itemId ); + item.sheet.render(true); + }); + // Delete Inventory Item + html.find('.item-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item") + EcrymeUtility.confirmDelete(this, li) + }) + html.find('.item-add').click(ev => { + let dataType = $(ev.currentTarget).data("type") + this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true }) + }) + + html.find('.subactor-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let actorId = li.data("actor-id"); + let actor = game.actors.get( actorId ); + actor.sheet.render(true); + }); + + html.find('.subactor-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let actorId = li.data("actor-id"); + this.actor.delSubActor(actorId); + }); + html.find('.quantity-minus').click(event => { + const li = $(event.currentTarget).parents(".item"); + this.actor.incDecQuantity( li.data("item-id"), -1 ); + } ); + html.find('.quantity-plus').click(event => { + const li = $(event.currentTarget).parents(".item"); + this.actor.incDecQuantity( li.data("item-id"), +1 ); + } ); + + html.find('.ammo-minus').click(event => { + const li = $(event.currentTarget).parents(".item") + this.actor.incDecAmmo( li.data("item-id"), -1 ); + } ); + html.find('.ammo-plus').click(event => { + const li = $(event.currentTarget).parents(".item") + this.actor.incDecAmmo( li.data("item-id"), +1 ) + } ); + + html.find('.roll-attribut').click((event) => { + let attrKey = $(event.currentTarget).data("attr-key") + this.actor.rollAttribut(attrKey) + }); + html.find('.roll-arme').click((event) => { + const armeId = $(event.currentTarget).data("arme-id") + this.actor.rollArme(armeId) + }); + + html.find('.lock-unlock-sheet').click((event) => { + this.options.editScore = !this.options.editScore; + this.render(true); + }); + html.find('.item-link a').click((event) => { + const itemId = $(event.currentTarget).data("item-id"); + const item = this.actor.getOwnedItem(itemId); + item.sheet.render(true); + }); + html.find('.item-equip').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + this.actor.equipItem( li.data("item-id") ); + this.render(true); + }); + + html.find('.update-field').change(ev => { + const fieldName = $(ev.currentTarget).data("field-name"); + let value = Number(ev.currentTarget.value); + this.actor.update( { [`${fieldName}`]: value } ); + }); + } + + /* -------------------------------------------- */ + /** @override */ + setPosition(options = {}) { + const position = super.setPosition(options); + const sheetBody = this.element.find(".sheet-body"); + const bodyHeight = position.height - 192; + sheetBody.css("height", bodyHeight); + return position; + } + + /* -------------------------------------------- */ + /** @override */ + _updateObject(event, formData) { + // Update the Actor + return this.object.update(formData); + } +} diff --git a/modules/actors/ecryme-actor.js b/modules/actors/ecryme-actor.js new file mode 100644 index 0000000..db1de74 --- /dev/null +++ b/modules/actors/ecryme-actor.js @@ -0,0 +1,395 @@ +/* -------------------------------------------- */ +import { EcrymeUtility } from "./common/ecryme-utility.js"; +import { EcrymeRollDialog } from "./dialogs/ecryme-roll-dialog.js"; + + +/* -------------------------------------------- */ +/* -------------------------------------------- */ +/** + * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. + * @extends {Actor} + */ +export class EcrymeActor extends Actor { + + /* -------------------------------------------- */ + /** + * Override the create() function to provide additional SoS functionality. + * + * This overrided create() function adds initial items + * Namely: Basic skills, money, + * + * @param {Object} data Barebones actor data which this function adds onto. + * @param {Object} options (Unused) Additional options which customize the creation workflow. + * + */ + + static async create(data, options) { + + // Case of compendium global import + if (data instanceof Array) { + return super.create(data, options); + } + // If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic + if (data.items) { + let actor = super.create(data, options); + return actor; + } + + if (data.type == 'character') { + } + if (data.type == 'npc') { + } + + return super.create(data, options); + } + + /* -------------------------------------------- */ + prepareBaseData() { + } + + /* -------------------------------------------- */ + async prepareData() { + + super.prepareData() + + } + + /* -------------------------------------------- */ + computeHitPoints() { + if (this.type == "character") { + } + } + + /* -------------------------------------------- */ + prepareDerivedData() { + + if (this.type == 'character' || game.user.isGM) { + } + + super.prepareDerivedData(); + } + + /* -------------------------------------------- */ + _preUpdate(changed, options, user) { + + super._preUpdate(changed, options, user); + } + + /*_onUpdateEmbeddedDocuments( embeddedName, ...args ) { + this.rebuildSkills() + super._onUpdateEmbeddedDocuments(embeddedName, ...args) + }*/ + + /* -------------------------------------------- */ + getMoneys() { + let comp = this.items.filter(item => item.type == 'money'); + EcrymeUtility.sortArrayObjectsByName(comp) + return comp; + } + getSorts() { + let comp = this.items.filter(item => item.type == 'sortilege'); + EcrymeUtility.sortArrayObjectsByName(comp) + return comp; + } + getArchetype() { + let comp = duplicate(this.items.find(item => item.type == 'archetype') || {name: "Pas d'archetype"}) + if (comp && comp.system) { + comp.tarot = EcrymeUtility.getTarot(comp.system.lametutelaire) + } + + return comp; + } + /* -------------------------------------------- */ + getElementsBio() { + let comp = duplicate(this.items.filter(item => item.type == 'elementbio') || []) + EcrymeUtility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getTarots() { + let comp = duplicate(this.items.filter(item => item.type == 'tarot' && !item.system.isgm) || []) + EcrymeUtility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getHiddenTarots() { + let comp = duplicate(this.items.filter(item => item.type == 'tarot' && item.system.isgm) || []) + EcrymeUtility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getArmes() { + let comp = duplicate(this.items.filter(item => item.type == 'arme') || []) + EcrymeUtility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getItemById(id) { + let item = this.items.find(item => item.id == id); + if (item) { + item = duplicate(item) + } + return item; + } + + /* -------------------------------------------- */ + async equipItem(itemId) { + let item = this.items.find(item => item.id == itemId) + if (item && item.system) { + if (item.type == "armor") { + let armor = this.items.find(item => item.id != itemId && item.type == "armor" && item.system.equipped) + if (armor) { + ui.notifications.warn("You already have an armor equipped!") + return + } + } + if (item.type == "shield") { + let shield = this.items.find(item => item.id != itemId && item.type == "shield" && item.system.equipped) + if (shield) { + ui.notifications.warn("You already have a shield equipped!") + return + } + } + let update = { _id: item.id, "system.equipped": !item.system.equipped }; + await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity + } + } + + /* -------------------------------------------- */ + compareName(a, b) { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + } + + /* ------------------------------------------- */ + getEquipements() { + return this.items.filter(item => item.type == 'equipement') + } + + /* ------------------------------------------- */ + async buildContainerTree() { + let equipments = duplicate(this.items.filter(item => item.type == "equipment") || []) + for (let equip1 of equipments) { + if (equip1.system.iscontainer) { + equip1.system.contents = [] + equip1.system.contentsEnc = 0 + for (let equip2 of equipments) { + if (equip1._id != equip2.id && equip2.system.containerid == equip1.id) { + equip1.system.contents.push(equip2) + let q = equip2.system.quantity ?? 1 + equip1.system.contentsEnc += q * equip2.system.weight + } + } + } + } + + // Compute whole enc + let enc = 0 + for (let item of equipments) { + //item.data.idrDice = EcrymeUtility.getDiceFromLevel(Number(item.data.idr)) + if (item.system.equipped) { + if (item.system.iscontainer) { + enc += item.system.contentsEnc + } else if (item.system.containerid == "") { + let q = item.system.quantity ?? 1 + enc += q * item.system.weight + } + } + } + for (let item of this.items) { // Process items/shields/armors + if ((item.type == "weapon" || item.type == "shield" || item.type == "armor") && item.system.equipped) { + let q = item.system.quantity ?? 1 + enc += q * item.system.weight + } + } + + // Store local values + this.encCurrent = enc + this.containersTree = equipments.filter(item => item.system.containerid == "") // Returns the root of equipements without container + + } + + /* -------------------------------------------- */ + async equipGear(equipmentId) { + let item = this.items.find(item => item.id == equipmentId); + if (item && item.system) { + let update = { _id: item.id, "system.equipped": !item.system.equipped }; + await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity + } + } + + /* -------------------------------------------- */ + clearInitiative(){ + this.getFlag("world", "initiative", -1) + } + /* -------------------------------------------- */ + getInitiativeScore(combatId, combatantId) { + let init = Math.floor( (this.system.attributs.physique.value+this.system.attributs.habilite.value) / 2) + let subvalue = new Roll("1d20").roll({async: false}) + return init + (subvalue.total / 100) + } + + /* -------------------------------------------- */ + getSubActors() { + let subActors = []; + for (let id of this.system.subactors) { + subActors.push(duplicate(game.actors.get(id))) + } + return subActors; + } + /* -------------------------------------------- */ + async addSubActor(subActorId) { + let subActors = duplicate(this.system.subactors); + subActors.push(subActorId); + await this.update({ 'system.subactors': subActors }); + } + /* -------------------------------------------- */ + async delSubActor(subActorId) { + let newArray = []; + for (let id of this.system.subactors) { + if (id != subActorId) { + newArray.push(id); + } + } + await this.update({ 'system.subactors': newArray }); + } + + /* -------------------------------------------- */ + async deleteAllItemsByType(itemType) { + let items = this.items.filter(item => item.type == itemType); + await this.deleteEmbeddedDocuments('Item', items); + } + + /* -------------------------------------------- */ + async addItemWithoutDuplicate(newItem) { + let item = this.items.find(item => item.type == newItem.type && item.name.toLowerCase() == newItem.name.toLowerCase()) + if (!item) { + await this.createEmbeddedDocuments('Item', [newItem]); + } + } + /* -------------------------------------------- */ + incDecFluide(value) { + let fluide = this.system.fluide + value + this.update( {'system.fluide': fluide} ) + } + incDecDestin(value) { + let destin = this.system.pointdestin + value + this.update( {'system.pointdestin': destin} ) + } + incDecMPMB(value) { + let mpmb = this.system.mpmb + value + this.update( {'system.mpmb': mpmb} ) + } + incDecMPMN(value) { + let mpmn = this.system.mpmn + value + this.update( {'system.mpmn': mpmn} ) + } + /* -------------------------------------------- */ + incDecAttr(attrKey, value) { + let attr = duplicate(this.system.attributs[attrKey]) + attr.value += value + this.update( { [`system.attributs.${attrKey}`]: attr}) + } + /* -------------------------------------------- */ + async incDecQuantity(objetId, incDec = 0) { + let objetQ = this.items.get(objetId) + if (objetQ) { + let newQ = objetQ.system.quantity + incDec + if (newQ >= 0) { + const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantity': newQ }]) // pdates one EmbeddedEntity + } + } + } + /* -------------------------------------------- */ + async incDecAmmo(objetId, incDec = 0) { + let objetQ = this.items.get(objetId) + if (objetQ) { + let newQ = objetQ.system.ammocurrent + incDec; + if (newQ >= 0 && newQ <= objetQ.system.ammomax) { + const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.ammocurrent': newQ }]); // pdates one EmbeddedEntity + } + } + } + + /* -------------------------------------------- */ + getAtttributImage( attrKey) { + return `systems/fvtt-ecryme/images/icons/${attrKey}.webp` + } + + /* -------------------------------------------- */ + incDecDestin( value) { + let newValue = Math.max( this.system.pointdestin + value, 0) + this.update( {'system.pointdestin': newValue}) + } + + /* -------------------------------------------- */ + getCommonRollData() { + + let rollData = EcrymeUtility.getBasicRollData() + rollData.alias = this.name + rollData.actorImg = this.img + rollData.actorId = this.id + rollData.img = this.img + rollData.phyMalus = this.getPhysiqueMalus() + rollData.elementsbio = this.getElementsBio() + rollData.destin = this.system.pointdestin + rollData.isReroll = false + rollData.confrontationDegre = 0 + rollData.confrontationModif = 0 + + console.log("ROLLDATA", rollData) + + return rollData + } + /* -------------------------------------------- */ + getPhysiqueMalus() { + if ( this.system.attributs.constitution.value <= 8) { + return -(9 - this.system.attributs.constitution.value) + } + return 0 + } + + /* -------------------------------------------- */ + rollAttribut(attrKey) { + let attr = this.system.attributs[attrKey] + let rollData = this.getCommonRollData() + rollData.attr = duplicate(attr) + rollData.mode = "attribut" + rollData.title = attr.label + rollData.img = this.getAtttributImage(attrKey) + this.startRoll(rollData) + } + + /* -------------------------------------------- */ + rollArme(weaponId) { + let arme = this.items.get(weaponId) + if (arme) { + arme = duplicate(arme) + let rollData = this.getCommonRollData() + if (arme.system.armetype == "mainsnues" || arme.system.armetype == "epee") { + rollData.attr = { label: "(Physique+Habilité)/2", value: Math.floor( (this.getPhysiqueMalus()+this.system.attributs.physique.value+this.system.attributs.habilite.value) / 2) } + } else { + rollData.attr = duplicate(this.system.attributs.habilite) + } + rollData.mode = "arme" + rollData.arme = arme + rollData.img = arme.img + rollData.title = arme.name + this.startRoll(rollData) + } else { + ui.notifications.warn("Impossible de trouver l'arme concernée ") + } + } + + /* -------------------------------------------- */ + async startRoll(rollData) { + let rollDialog = await EcrymeRollDialog.create(this, rollData) + rollDialog.render(true) + } + +} diff --git a/modules/actors/ecryme-npc-sheet.js b/modules/actors/ecryme-npc-sheet.js new file mode 100644 index 0000000..4542dcb --- /dev/null +++ b/modules/actors/ecryme-npc-sheet.js @@ -0,0 +1,207 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ + +import { EcrymeUtility } from "./common/ecryme-utility.js"; + +/* -------------------------------------------- */ +export class EcrymeNPCSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["Ecryme", "sheet", "actor"], + template: "systems/fvtt-ecryme/templates/npc-sheet.html", + width: 640, + height: 720, + tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }], + dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }], + editScore: true + }); + } + + /* -------------------------------------------- */ + async getData() { + const objectData = this.object.system + let actorData = duplicate(objectData) + + let formData = { + title: this.title, + id: this.actor.id, + type: this.actor.type, + img: this.actor.img, + name: this.actor.name, + editable: this.isEditable, + cssClass: this.isEditable ? "editable" : "locked", + data: actorData, + limited: this.object.limited, + skills: this.actor.getSkills( ), + weapons: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getWeapons()) ), + armors: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getArmors())), + shields: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getShields())), + spells: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getLore())), + equipments: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquipmentsOnly()) ), + equippedWeapons: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquippedWeapons()) ), + equippedArmor: this.actor.getEquippedArmor(), + equippedShield: this.actor.getEquippedShield(), + subActors: duplicate(this.actor.getSubActors()), + moneys: duplicate(this.actor.getMoneys()), + encCapacity: this.actor.getEncumbranceCapacity(), + saveRolls: this.actor.getSaveRoll(), + conditions: this.actor.getConditions(), + containersTree: this.actor.containersTree, + encCurrent: this.actor.encCurrent, + options: this.options, + owner: this.document.isOwner, + editScore: this.options.editScore, + isGM: game.user.isGM + } + this.formData = formData; + + console.log("PC : ", formData, this.object); + return formData; + } + + /* -------------------------------------------- */ + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) return; + + html.bind("keydown", function(e) { // Ignore Enter in actores sheet + if (e.keyCode === 13) return false; + }); + + // Update Inventory Item + html.find('.item-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + const item = this.actor.items.get( itemId ); + item.sheet.render(true); + }); + // Delete Inventory Item + html.find('.item-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item") + EcrymeUtility.confirmDelete(this, li) + }) + html.find('.item-add').click(ev => { + let dataType = $(ev.currentTarget).data("type") + this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true }) + }) + + html.find('.equip-activate').click(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + this.actor.equipActivate( itemId) + }); + html.find('.equip-deactivate').click(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + this.actor.equipDeactivate( itemId) + }); + + html.find('.subactor-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let actorId = li.data("actor-id"); + let actor = game.actors.get( actorId ); + actor.sheet.render(true); + }); + + html.find('.subactor-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let actorId = li.data("actor-id"); + this.actor.delSubActor(actorId); + }); + html.find('.quantity-minus').click(event => { + const li = $(event.currentTarget).parents(".item"); + this.actor.incDecQuantity( li.data("item-id"), -1 ); + } ); + html.find('.quantity-plus').click(event => { + const li = $(event.currentTarget).parents(".item"); + this.actor.incDecQuantity( li.data("item-id"), +1 ); + } ); + + html.find('.ammo-minus').click(event => { + const li = $(event.currentTarget).parents(".item") + this.actor.incDecAmmo( li.data("item-id"), -1 ); + } ); + html.find('.ammo-plus').click(event => { + const li = $(event.currentTarget).parents(".item") + this.actor.incDecAmmo( li.data("item-id"), +1 ) + } ); + + html.find('.roll-ability').click((event) => { + const abilityKey = $(event.currentTarget).data("ability-key"); + this.actor.rollAbility(abilityKey); + }); + html.find('.roll-skill').click((event) => { + const li = $(event.currentTarget).parents(".item") + const skillId = li.data("item-id") + this.actor.rollSkill(skillId) + }); + + html.find('.roll-weapon').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const skillId = li.data("item-id") + this.actor.rollWeapon(skillId) + }); + html.find('.roll-armor-die').click((event) => { + this.actor.rollArmorDie() + }); + html.find('.roll-shield-die').click((event) => { + this.actor.rollShieldDie() + }); + html.find('.roll-target-die').click((event) => { + this.actor.rollDefenseRanged() + }); + + html.find('.roll-save').click((event) => { + const saveKey = $(event.currentTarget).data("save-key") + this.actor.rollSave(saveKey) + }); + + + html.find('.lock-unlock-sheet').click((event) => { + this.options.editScore = !this.options.editScore; + this.render(true); + }); + html.find('.item-link a').click((event) => { + const itemId = $(event.currentTarget).data("item-id"); + const item = this.actor.getOwnedItem(itemId); + item.sheet.render(true); + }); + html.find('.item-equip').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + this.actor.equipItem( li.data("item-id") ); + this.render(true); + }); + + html.find('.update-field').change(ev => { + const fieldName = $(ev.currentTarget).data("field-name"); + let value = Number(ev.currentTarget.value); + this.actor.update( { [`${fieldName}`]: value } ); + }); + + } + + /* -------------------------------------------- */ + /** @override */ + setPosition(options = {}) { + const position = super.setPosition(options); + const sheetBody = this.element.find(".sheet-body"); + const bodyHeight = position.height - 192; + sheetBody.css("height", bodyHeight); + return position; + } + + /* -------------------------------------------- */ + /** @override */ + _updateObject(event, formData) { + // Update the Actor + return this.object.update(formData); + } +} diff --git a/modules/app/ecryme-combat.js b/modules/app/ecryme-combat.js new file mode 100644 index 0000000..7f2df10 --- /dev/null +++ b/modules/app/ecryme-combat.js @@ -0,0 +1,40 @@ +import { EcrymeUtility } from "./common/ecryme-utility.js"; + +/* -------------------------------------------- */ +export class EcrymeCombat extends Combat { + + /* -------------------------------------------- */ + async rollInitiative(ids, formula = undefined, messageOptions = {} ) { + ids = typeof ids === "string" ? [ids] : ids; + for (let cId = 0; cId < ids.length; cId++) { + const c = this.combatants.get(ids[cId]); + let id = c._id || c.id; + let initBonus = c.actor ? c.actor.getInitiativeScore( this.id, id ) : -1; + await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: initBonus } ]); + } + + return this; + } + + /* -------------------------------------------- */ + _onUpdate(changed, options, userId) { + } + + /* -------------------------------------------- */ + static async checkTurnPosition() { + while (game.combat.turn > 0) { + await game.combat.previousTurn() + } + } + + /* -------------------------------------------- */ + _onDelete() { + let combatants = this.combatants.contents + for (let c of combatants) { + let actor = game.actors.get(c.actorId) + actor.clearInitiative() + } + super._onDelete() + } + +} diff --git a/modules/app/ecryme-commands.js b/modules/app/ecryme-commands.js new file mode 100644 index 0000000..ee3cbe9 --- /dev/null +++ b/modules/app/ecryme-commands.js @@ -0,0 +1,145 @@ +/* -------------------------------------------- */ + +import { EcrymeUtility } from "./common/ecryme-utility.js"; +import { EcrymeCharacterSummary } from "./app/ecryme-summary-app.js" + +/* -------------------------------------------- */ +export class EcrymeCommands { + + static init() { + if (!game.system.ecryme.commands) { + const commands = new EcrymeCommands(); + commands.registerCommand({ path: ["/tirage"], func: (content, msg, params) => EcrymeCommands.createTirage(msg), descr: "Tirage des tarots" }); + commands.registerCommand({ path: ["/carte"], func: (content, msg, params) => EcrymeCommands.tirerCarte(msg), descr: "Tirer une carte" }); + commands.registerCommand({ path: ["/resume"], func: (content, msg, params) => EcrymeCharacterSummary.displayPCSummary(), descr: "Affiche la liste des PJs!" }); + game.system.ecryme.commands = commands; + } + } + constructor() { + this.commandsTable = {} + } + + /* -------------------------------------------- */ + registerCommand(command) { + this._addCommand(this.commandsTable, command.path, '', command); + } + + /* -------------------------------------------- */ + _addCommand(targetTable, path, fullPath, command) { + if (!this._validateCommand(targetTable, path, command)) { + return; + } + const term = path[0]; + fullPath = fullPath + term + ' ' + if (path.length == 1) { + command.descr = `${fullPath}: ${command.descr}`; + targetTable[term] = command; + } + else { + if (!targetTable[term]) { + targetTable[term] = { subTable: {} }; + } + this._addCommand(targetTable[term].subTable, path.slice(1), fullPath, command) + } + } + + /* -------------------------------------------- */ + _validateCommand(targetTable, path, command) { + if (path.length > 0 && path[0] && command.descr && (path.length != 1 || targetTable[path[0]] == undefined)) { + return true; + } + console.warn("crucibleCommands._validateCommand failed ", targetTable, path, command); + return false; + } + + + /* -------------------------------------------- */ + /* Manage chat commands */ + processChatCommand(commandLine, content = '', msg = {}) { + // Setup new message's visibility + let rollMode = game.settings.get("core", "rollMode"); + if (["gmroll", "blindroll"].includes(rollMode)) msg["whisper"] = ChatMessage.getWhisperRecipients("GM"); + if (rollMode === "blindroll") msg["blind"] = true; + msg["type"] = 0; + + let command = commandLine[0].toLowerCase(); + let params = commandLine.slice(1); + + return this.process(command, params, content, msg); + } + + /* -------------------------------------------- */ + process(command, params, content, msg) { + return this._processCommand(this.commandsTable, command, params, content, msg); + } + + /* -------------------------------------------- */ + _processCommand(commandsTable, name, params, content = '', msg = {}, path = "") { + console.log("===> Processing command") + let command = commandsTable[name]; + path = path + name + " "; + if (command && command.subTable) { + if (params[0]) { + return this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path) + } + else { + this.help(msg, command.subTable); + return true; + } + } + if (command && command.func) { + const result = command.func(content, msg, params); + if (result == false) { + CrucibleCommands._chatAnswer(msg, command.descr); + } + return true; + } + return false; + } + + /* -------------------------------------------- */ + static _chatAnswer(msg, content) { + msg.whisper = [game.user.id]; + msg.content = content; + ChatMessage.create(msg); + } + + /* --------------------------------------------- */ + static async createTirage(msg) { + if (game.user.isGM) { + let tirageData = { + state: 'select-player', + nbCard: 0, + maxPlayerCard: 4, + maxSecretCard: 1, + cards: [], + players: duplicate(game.users), + secretCards: [], + deck: EcrymeUtility.getTarots() + } + for (let i = 0; i < 4; i++) { + tirageData.cards.push({ name: "???", img: "systems/fvtt-ecryme/images/tarots/background.webp" }) + } + tirageData.secretCards.push({ name: "???", img: "systems/fvtt-ecryme/images/tarots/background.webp" }) + + let tirageDialog = await EcrymeTirageTarotDialog.create(this, tirageData) + tirageDialog.render(true) + } + } + /* --------------------------------------------- */ + static async tirerCarte(msg) { + let deck = EcrymeUtility.getTarots() + let index = Math.round(Math.random() * (deck.length-1)) + let selectedCard = deck[index] + selectedCard.system.ispositif = true + if ( selectedCard.system.isdualside) { // Cas des cartes pouvant avoir 2 sens + selectedCard.system.ispositif = (Math.random() > 0.5) + } + selectedCard.system.isgm = false + selectedCard.value = (selectedCard.system.ispositif)? selectedCard.system.numericvalueup : selectedCard.system.numericvaluedown + EcrymeUtility.createChatMessage(game.user.name, "", { + content: await renderTemplate(`systems/fvtt-ecryme/templates/chat/display-tarot-card.hbs`, selectedCard) + }) + } + +} \ No newline at end of file diff --git a/modules/app/ecryme-hotbar.js b/modules/app/ecryme-hotbar.js new file mode 100644 index 0000000..04d756a --- /dev/null +++ b/modules/app/ecryme-hotbar.js @@ -0,0 +1,86 @@ + +export class EcrymeHotbar { + + /** + * Create a macro when dropping an entity on the hotbar + * Item - open roll dialog for item + * Actor - open actor sheet + * Journal - open journal sheet + */ + static init( ) { + + Hooks.on("hotbarDrop", async (bar, documentData, slot) => { + // Create item macro if rollable item - weapon, spell, prayer, trait, or skill + if (documentData.type == "Item") { + console.log("Drop done !!!", bar, documentData, slot) + let item = documentData.data + let command = `game.system.Ecryme.EcrymeHotbar.rollMacro("${item.name}", "${item.type}");` + let macro = game.macros.contents.find(m => (m.name === item.name) && (m.command === command)) + if (!macro) { + macro = await Macro.create({ + name: item.name, + type: "script", + img: item.img, + command: command + }, { displaySheet: false }) + } + game.user.assignHotbarMacro(macro, slot); + } + // Create a macro to open the actor sheet of the actor dropped on the hotbar + else if (documentData.type == "Actor") { + let actor = game.actors.get(documentData.id); + let command = `game.actors.get("${documentData.id}").sheet.render(true)` + let macro = game.macros.contents.find(m => (m.name === actor.name) && (m.command === command)); + if (!macro) { + macro = await Macro.create({ + name: actor.data.name, + type: "script", + img: actor.data.img, + command: command + }, { displaySheet: false }) + game.user.assignHotbarMacro(macro, slot); + } + } + // Create a macro to open the journal sheet of the journal dropped on the hotbar + else if (documentData.type == "JournalEntry") { + let journal = game.journal.get(documentData.id); + let command = `game.journal.get("${documentData.id}").sheet.render(true)` + let macro = game.macros.contents.find(m => (m.name === journal.name) && (m.command === command)); + if (!macro) { + macro = await Macro.create({ + name: journal.data.name, + type: "script", + img: "", + command: command + }, { displaySheet: false }) + game.user.assignHotbarMacro(macro, slot); + } + } + return false; + }); + } + + /** Roll macro */ + static rollMacro(itemName, itemType, bypassData) { + const speaker = ChatMessage.getSpeaker() + let actor + if (speaker.token) actor = game.actors.tokens[speaker.token] + if (!actor) actor = game.actors.get(speaker.actor) + if (!actor) { + return ui.notifications.warn(`Select your actor to run the macro`) + } + + let item = actor.items.find(it => it.name === itemName && it.type == itemType) + if (!item ) { + return ui.notifications.warn(`Unable to find the item of the macro in the current actor`) + } + // Trigger the item roll + if (item.type === "weapon") { + return actor.rollWeapon( item.id) + } + if (item.type === "skill") { + return actor.rollSkill( item.id) + } + } + +} diff --git a/modules/app/ecryme-summary-app.js b/modules/app/ecryme-summary-app.js new file mode 100644 index 0000000..35611ad --- /dev/null +++ b/modules/app/ecryme-summary-app.js @@ -0,0 +1,134 @@ +/* -------------------------------------------- */ +import { EcrymeUtility } from "./common/ecryme-utility.js"; + +/* -------------------------------------------- */ +export class EcrymeCharacterSummary extends Application { + + /* -------------------------------------------- */ + static displayPCSummary() { + if (game.user.isGM) { + game.system.ecryme.charSummary.render(true) + } else { + ui.notifications.info("Commande /tirage réservée au MJ !") + } + } + + /* -------------------------------------------- */ + updatePCSummary() { + if (this.rendered) { + this.render(true) + } + } + + /* -------------------------------------------- */ + static createSummaryPos() { + return { top: 200, left: 200 }; + } + + /* -------------------------------------------- */ + static ready() { + if (!game.user.isGM) { // Uniquement si GM + return + } + let charSummary = new EcrymeCharacterSummary() + game.system.ecryme.charSummary = charSummary + } + + /* -------------------------------------------- */ + constructor() { + super(); + //game.settings.set("world", "character-summary-data", {npcList: [], x:0, y:0}) + this.settings = game.settings.get("world", "character-summary-data") + } + + /* -------------------------------------------- */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + template: "systems/fvtt-ecryme/templates/dialogs/character-summary.hbs", + popOut: true, + resizable: true, + dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }], + classes: ["bol", "dialog"], width: 920, height: 'fit-content' + }) + } + + /* -------------------------------------------- */ + getData() { + let formData = super.getData(); + + formData.pcs = game.actors.filter(ac => ac.type == "personnage" && ac.hasPlayerOwner) + formData.npcs = [] + let newList = [] + let toUpdate = false + for (let actorId of this.settings.npcList) { + let actor = game.actors.get(actorId) + if (actor) { + formData.npcs.push(actor) + newList.push(actorId) + } else { + toUpdate = true + } + } + formData.config = game.system.ecryme.config + + if (toUpdate) { + this.settings.npcList = newList + //console.log("Going to update ...", this.settings) + game.settings.set("world", "character-summary-data", this.settings) + } + + return formData + } + + /* -------------------------------------------- */ + updateNPC() { + game.settings.set("world", "character-summary-data", game.system.ecryme.charSummary.settings) + game.system.ecryme.charSummary.close() + setTimeout(function () { game.system.ecryme.charSummary.render(true) }, 500) + } + + /* -------------------------------------------- */ + async _onDrop(event) { + //console.log("Dragged data are : ", dragData) + let data = event.dataTransfer.getData('text/plain') + let dataItem = JSON.parse(data) + let actor = fromUuidSync(dataItem.uuid) + if (actor) { + game.system.ecryme.charSummary.settings.npcList.push(actor.id) + game.system.ecryme.charSummary.updateNPC() + + } else { + ui.notifications.warn("Pas d'acteur trouvé") + } + } + + /* -------------------------------------------- */ + /** @override */ + async activateListeners(html) { + super.activateListeners(html); + + html.find('.actor-open').click((event) => { + const li = $(event.currentTarget).parents(".item") + const actor = game.actors.get(li.data("actor-id")) + actor.sheet.render(true) + }) + + html.find('.summary-roll').click((event) => { + const li = $(event.currentTarget).parents(".item") + const actor = game.actors.get(li.data("actor-id")) + let type = $(event.currentTarget).data("type") + let key = $(event.currentTarget).data("key") + actor.rollAttribut(key) + }) + + html.find('.actor-delete').click(event => { + const li = $(event.currentTarget).parents(".item"); + let actorId = li.data("actor-id") + let newList = game.system.ecryme.charSummary.settings.npcList.filter(id => id != actorId) + game.system.ecryme.charSummary.settings.npcList = newList + game.system.ecryme.charSummary.updateNPC() + }) + + } + +} \ No newline at end of file diff --git a/modules/common/ecryme-config.js b/modules/common/ecryme-config.js new file mode 100644 index 0000000..5202b52 --- /dev/null +++ b/modules/common/ecryme-config.js @@ -0,0 +1,3 @@ + +export const ECRYME_CONFIG = { +} \ No newline at end of file diff --git a/modules/common/ecryme-utility.js b/modules/common/ecryme-utility.js new file mode 100644 index 0000000..d84fdf2 --- /dev/null +++ b/modules/common/ecryme-utility.js @@ -0,0 +1,567 @@ +/* -------------------------------------------- */ +import { EcrymeCommands } from "./ecryme-commands.js"; + + +/* -------------------------------------------- */ +export class EcrymeUtility { + + /* -------------------------------------------- */ + static async init() { + Hooks.on('renderChatLog', (log, html, data) => EcrymeUtility.chatListeners(html)); + + this.rollDataStore = {} + this.defenderStore = {} + + EcrymeCommands.init(); + } + + /* -------------------------------------------- */ + static async ready() { + + Handlebars.registerHelper('count', function (list) { + return list.length; + }) + Handlebars.registerHelper('includes', function (array, val) { + return array.includes(val); + }) + Handlebars.registerHelper('upper', function (text) { + return text.toUpperCase(); + }) + Handlebars.registerHelper('lower', function (text) { + return text.toLowerCase() + }) + Handlebars.registerHelper('upperFirst', function (text) { + if (typeof text !== 'string') return text + return text.charAt(0).toUpperCase() + text.slice(1) + }) + Handlebars.registerHelper('notEmpty', function (list) { + return list.length > 0; + }) + Handlebars.registerHelper('mul', function (a, b) { + return parseInt(a) * parseInt(b); + }) + Handlebars.registerHelper('add', function (a, b) { + return parseInt(a) + parseInt(b); + }) + + game.settings.register("world", "character-summary-data", { + name: "character-summary-data", + scope: "world", + config: false, + default: { npcList: [], x: 200, y: 200 }, + type: Object + }) + + const tarots = await EcrymeUtility.loadCompendium("fvtt-ecryme.ecryme-tarots") + this.tarots = tarots.map(i => i.toObject()) + + } + + /*-------------------------------------------- */ + static upperFirst(text) { + if (typeof text !== 'string') return text + return text.charAt(0).toUpperCase() + text.slice(1) + } + + /*-------------------------------------------- */ + static getTarots() { + return duplicate(this.tarots) + } + static getTarot(tId) { + return this.tarots.find(t => t._id == tId) + } + + /* -------------------------------------------- */ + static async loadCompendiumData(compendium) { + const pack = game.packs.get(compendium) + return await pack?.getDocuments() ?? [] + } + + /* -------------------------------------------- */ + static async loadCompendium(compendium, filter = item => true) { + let compendiumData = await EcrymeUtility.loadCompendiumData(compendium) + return compendiumData.filter(filter) + } + + /* -------------------------------------------- */ + static getActorFromRollData(rollData) { + let actor = game.actors.get(rollData.actorId) + if (rollData.tokenId) { + let token = canvas.tokens.placeables.find(t => t.id == rollData.tokenId) + if (token) { + actor = token.actor + } + } + return actor + } + /* -------------------------------------------- */ + static drawDeckCard(msgId) { + if (game.user.isGM) { + game.system.ecryme.currentTirage.addCard(msgId) + } else { + game.socket.emit( "system.fvtt-ecryme", {name: "msg-draw-card", data: {msgId: msgId}}) + } + } + + /* -------------------------------------------- */ + static async chatListeners(html) { + + html.on("click", '.roll-destin', event => { + let messageId = EcrymeUtility.findChatMessageId(event.currentTarget) + let message = game.messages.get(messageId) + let rollData = message.getFlag("world", "rolldata") + let actor = this.getActorFromRollData(rollData) + actor.incDecDestin(-1) + rollData.isReroll = true + this.rollEcryme(rollData) + }) + html.on("click", '.draw-tarot-card', event => { + let messageId = EcrymeUtility.findChatMessageId(event.currentTarget) + this.drawDeckCard(messageId) + }) + + } + + /* -------------------------------------------- */ + static async preloadHandlebarsTemplates() { + + const templatePaths = [ + 'systems/fvtt-ecryme/templates/actors/editor-notes-gm.hbs', + 'systems/fvtt-ecryme/templates/items/partial-item-nav.hbs', + 'systems/fvtt-ecryme/templates/items/partial-item-description.hbs' + ] + return loadTemplates(templatePaths); + } + + /* -------------------------------------------- */ + static removeChatMessageId(messageId) { + if (messageId) { + game.messages.get(messageId)?.delete(); + } + } + + static findChatMessageId(current) { + return EcrymeUtility.getChatMessageId(EcrymeUtility.findChatMessage(current)); + } + + static getChatMessageId(node) { + return node?.attributes.getNamedItem('data-message-id')?.value; + } + + static findChatMessage(current) { + return EcrymeUtility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id')); + } + + static findNodeMatching(current, predicate) { + if (current) { + if (predicate(current)) { + return current; + } + return EcrymeUtility.findNodeMatching(current.parentElement, predicate); + } + return undefined; + } + + + /* -------------------------------------------- */ + static createDirectOptionList(min, max) { + let options = {}; + for (let i = min; i <= max; i++) { + options[`${i}`] = `${i}`; + } + return options; + } + + /* -------------------------------------------- */ + static buildListOptions(min, max) { + let options = "" + for (let i = min; i <= max; i++) { + options += `` + } + return options; + } + + /* -------------------------------------------- */ + static getTarget() { + if (game.user.targets) { + for (let target of game.user.targets) { + return target + } + } + return undefined + } + + /* -------------------------------------------- */ + static updateRollData(rollData) { + + let id = rollData.rollId + let oldRollData = this.rollDataStore[id] || {} + let newRollData = mergeObject(oldRollData, rollData) + this.rollDataStore[id] = newRollData + } + + /* -------------------------------------------- */ + static async onSocketMesssage(msg) { + console.log("SOCKET MESSAGE", msg.name) + if (msg.name == "msg-draw-card") { + if ( game.user.isGM && game.system.ecryme.currentTirage) { + game.system.ecryme.currentTirage.addCard(msg.data.msgId) + } + } + } + + /* -------------------------------------------- */ + static async searchItem(dataItem) { + let item + if (dataItem.pack) { + let id = dataItem.id || dataItem._id + let items = await this.loadCompendium(dataItem.pack, item => item.id == id) + item = items[0] || undefined + } else { + item = game.items.get(dataItem.id) + } + return item + } + + /* -------------------------------------------- */ + static chatDataSetup(content, modeOverride, isRoll = false, forceWhisper) { + let chatData = { + user: game.user.id, + rollMode: modeOverride || game.settings.get("core", "rollMode"), + content: content + }; + + if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id); + if (chatData.rollMode === "blindroll") chatData["blind"] = true; + else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user]; + + if (forceWhisper) { // Final force ! + chatData["speaker"] = ChatMessage.getSpeaker(); + chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper); + } + + return chatData; + } + + /* -------------------------------------------- */ + static async showDiceSoNice(roll, rollMode) { + if (game.modules.get("dice-so-nice")?.active) { + if (game.dice3d) { + let whisper = null; + let blind = false; + rollMode = rollMode ?? game.settings.get("core", "rollMode"); + switch (rollMode) { + case "blindroll": //GM only + blind = true; + case "gmroll": //GM + rolling player + whisper = this.getUsers(user => user.isGM); + break; + case "roll": //everybody + whisper = this.getUsers(user => user.active); + break; + case "selfroll": + whisper = [game.user.id]; + break; + } + await game.dice3d.showForRoll(roll, game.user, true, whisper, blind); + } + } + } + + /* -------------------------------------------- */ + static processSpecialCard(actor, rollData) { + if (rollData.selectedCard.name.toLowerCase().includes("archange")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("archange")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "gmroll", { + content: `Conséquence supplémentaire !
L'Archange : ${actor.name} gagne 1 point de Spiritualité.` }) + actor.incDecAttr("spiritualite", 1) + } + } + if (rollData.selectedCard.name.toLowerCase().includes("vicaire")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("vicaire")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "blindroll", { + content: `Conséquence supplémentaire !
Le Vicaire : ${actor.name} vient de gagner 1 point en Pratique de la Magie Blanche (MPMB, secret).` }) + actor.incDecMPMB(1) + } + } + if (rollData.selectedCard.name.toLowerCase().includes("chance")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("chance")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "gmroll", { + content: `Conséquence supplémentaire !
La Chance : ${actor.name} a gagné 1 point de Destin.` }) + actor.incDecDestin(1) + } + } + if (rollData.selectedCard.name.toLowerCase().includes("mort")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("mort")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "gmroll", { + content: `Conséquence supplémentaire !
La Mort : ${actor.name} est pétrifié par la peur.` }) + actor.incDecDestin(1) + } + } + if (rollData.selectedCard.name.toLowerCase().includes("diable")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("diable")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "gmroll", { + content: `Conséquence supplémentaire !
Le Diable : ${actor.name} gagne 1 point de Rationnalité.` }) + actor.incDecAttr("rationnalite", 1) + } + } + if (rollData.selectedCard.name.toLowerCase().includes("lune noire")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("lune noire")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "blindroll", { + content: `Conséquence supplémentaire !
La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).` }) + actor.incDecFluide(1) + } + } + if (rollData.selectedCard.name.toLowerCase().includes("grand livre")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("grand livre")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "blindroll", { + content: `Conséquence supplémentaire !
La Lune Noire : ${actor.name} vient de gagner 1 point de Fluide (secret).` }) + actor.incDecFluide(1) + } + } + if (rollData.selectedCard.name.toLowerCase().includes("sorcier")) { + let actorCard = actor.items.find( c => c.type =="tarot" && c.name.toLowerCase().includes("sorcier")) + if (actorCard) { + EcrymeUtility.createChatMessage(actor.name, "blindroll", { + content: `Conséquence supplémentaire !
Le Vicaire : ${actor.name} vient de gagner 1 point en Pratique de la Magie Noire (MPMN, secret).` }) + actor.incDecMPMN(1) + } + } + + } + + /* -------------------------------------------- */ + static computeResults(rollData) { + rollData.isSuccess = false + if (rollData.total <= rollData.target) { + rollData.isSuccess = true + } + if (rollData.total == 1) { + rollData.isSuccess = true + rollData.isCritical = true + } + if (rollData.total == 20) { + rollData.isSuccess = false + rollData.isFumble = true + } + if (rollData.total <= Math.floor(rollData.target / 3)) { + rollData.isPart = true + } + } + /* -------------------------------------------- */ + static async tirageConfrontationEcryme(rollData) { + let actor = game.actors.get(rollData.actorId) + + rollData.target = rollData.attr.value - rollData.confrontationDegre + rollData.confrontationModif + + let deck = this.getTarots() + let index = Math.round(Math.random() * (deck.length-1)) + let selectedCard = deck[index] + selectedCard.system.ispositif = (Math.random() > 0.5) + selectedCard.value = (selectedCard.system.ispositif)? selectedCard.system.numericvalueup : selectedCard.system.numericvaluedown + rollData.total = selectedCard.value + rollData.selectedCard = selectedCard + await EcrymeUtility.createChatMessage(actor.name, "gmroll", { + content: await renderTemplate(`systems/fvtt-ecryme/templates/chat/display-tarot-card.hbs`, selectedCard) + }) + + this.computeResults(rollData) + + if (rollData.isSuccess) { + rollData.gainAttr = Math.ceil(rollData.confrontationDegre/2) + ((rollData.isCritical ) ? 1 : 0) + actor.incDecAttr(rollData.attr.abbrev, rollData.gainAttr ) + } else { + rollData.gainAttr = rollData.confrontationDegre + actor.incDecAttr(rollData.attr.abbrev, -rollData.gainAttr ) + } + + await EcrymeUtility.createChatMessage(actor.name, "gmroll", { + content: await renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-confrontation-result.hbs`, rollData) + }) + this.processSpecialCard(actor, rollData) + } + /* -------------------------------------------- */ + static async rollEcryme(rollData) { + + let actor = game.actors.get(rollData.actorId) + + // Build the dice formula + let diceFormula = "1d20" + rollData.target = rollData.attr.value + rollData.bonusMalusPerso + rollData.bonusMalusSituation + rollData.bonusMalusDef + rollData.bonusMalusPortee + if (rollData.attr.abbrev == "physique") { + rollData.target += rollData.phyMalus + } + rollData.diceFormula = diceFormula + + // Performs roll + console.log("Roll formula", diceFormula) + let myRoll = new Roll(diceFormula).roll({ async: false }) + await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) + rollData.roll = myRoll + rollData.total = myRoll.total + + this.computeResults(rollData) + + let msg = await this.createChatWithRollMode(rollData.alias, { + content: await renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-generic-result.hbs`, rollData) + }) + msg.setFlag("world", "rolldata", rollData) + if (rollData.mode == "initiative") { + actor.setFlag("world", "initiative", myRoll.total) + } + + console.log("Rolldata result", rollData) + } + + /* -------------------------------------------- */ + static sortArrayObjectsByName(myArray) { + myArray.sort((a, b) => { + let fa = a.name.toLowerCase(); + let fb = b.name.toLowerCase(); + if (fa < fb) { + return -1; + } + if (fa > fb) { + return 1; + } + return 0; + }) + } + + /* -------------------------------------------- */ + static getUsers(filter) { + return game.users.filter(filter).map(user => user.id); + } + /* -------------------------------------------- */ + static getWhisperRecipients(rollMode, name) { + switch (rollMode) { + case "blindroll": return this.getUsers(user => user.isGM); + case "gmroll": return this.getWhisperRecipientsAndGMs(name); + case "useronly": return this.getWhisperRecipientsOnly(name); + case "selfroll": return [game.user.id]; + } + return undefined; + } + /* -------------------------------------------- */ + static getWhisperRecipientsOnly(name) { + let recep1 = ChatMessage.getWhisperRecipients(name) || []; + return recep1 + } + /* -------------------------------------------- */ + static getWhisperRecipientsAndGMs(name) { + let recep1 = ChatMessage.getWhisperRecipients(name) || []; + return recep1.concat(ChatMessage.getWhisperRecipients('GM')); + } + + /* -------------------------------------------- */ + static blindMessageToGM(chatOptions) { + let chatGM = duplicate(chatOptions); + chatGM.whisper = this.getUsers(user => user.isGM); + chatGM.content = "Blinde message of " + game.user.name + "
" + chatOptions.content; + console.log("blindMessageToGM", chatGM); + game.socket.emit("system.fvtt-ecryme", { msg: "msg_gm_chat_message", data: chatGM }); + } + + + /* -------------------------------------------- */ + static split3Columns(data) { + + let array = [[], [], []]; + if (data == undefined) return array; + + let col = 0; + for (let key in data) { + let keyword = data[key]; + keyword.key = key; // Self-reference + array[col].push(keyword); + col++; + if (col == 3) col = 0; + } + return array; + } + + /* -------------------------------------------- */ + static async createChatMessage(name, rollMode, chatOptions) { + switch (rollMode) { + case "blindroll": // GM only + if (!game.user.isGM) { + this.blindMessageToGM(chatOptions); + + chatOptions.whisper = [game.user.id]; + chatOptions.content = "Message only to the GM"; + } + else { + chatOptions.whisper = this.getUsers(user => user.isGM); + } + break; + default: + chatOptions.whisper = this.getWhisperRecipients(rollMode, name); + break; + } + chatOptions.alias = chatOptions.alias || name; + return await ChatMessage.create(chatOptions); + } + + /* -------------------------------------------- */ + static getBasicRollData() { + let rollData = { + rollId: randomID(16), + bonusMalusPerso: 0, + bonusMalusSituation: 0, + bonusMalusDef: 0, + bonusMalusPortee: 0, + rollMode: game.settings.get("core", "rollMode") + } + EcrymeUtility.updateWithTarget(rollData) + return rollData + } + + /* -------------------------------------------- */ + static updateWithTarget(rollData) { + let target = EcrymeUtility.getTarget() + if (target) { + rollData.defenderTokenId = target.id + } + } + + /* -------------------------------------------- */ + static async createChatWithRollMode(name, chatOptions) { + return await this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions) + } + + /* -------------------------------------------- */ + static async confirmDelete(actorSheet, li) { + let itemId = li.data("item-id"); + let msgTxt = "

Are you sure to remove this Item ?"; + let buttons = { + delete: { + icon: '', + label: "Yes, remove it", + callback: () => { + actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]); + li.slideUp(200, () => actorSheet.render(false)); + } + }, + cancel: { + icon: '', + label: "Cancel" + } + } + msgTxt += "

"; + let d = new Dialog({ + title: "Confirm removal", + content: msgTxt, + buttons: buttons, + default: "cancel" + }); + d.render(true); + } + +} \ No newline at end of file diff --git a/modules/dialogs/ecryme-roll-dialog.js b/modules/dialogs/ecryme-roll-dialog.js new file mode 100644 index 0000000..828f41d --- /dev/null +++ b/modules/dialogs/ecryme-roll-dialog.js @@ -0,0 +1,92 @@ +import { EcrymeUtility } from "./ecryme-utility.js"; + +export class EcrymeRollDialog extends Dialog { + + /* -------------------------------------------- */ + static async create(actor, rollData) { + + let options = { classes: ["EcrymeDialog"], width: 540, height: 'fit-content', 'z-index': 99999 } + let html + if (rollData.attr && rollData.attr.iscard) { + html = await renderTemplate('systems/fvtt-ecryme/templates/dialogs/confrontation-dialog.hbs', rollData); + } else { + html = await renderTemplate('systems/fvtt-ecryme/templates/dialogs/roll-dialog-generic.hbs', rollData); + } + + return new EcrymeRollDialog(actor, rollData, html, options); + } + + /* -------------------------------------------- */ + constructor(actor, rollData, html, options, close = undefined) { + let isCard = rollData.attr && rollData.attr.iscard + let conf = { + title: (isCard) ? "Jet" : "Tirage", + content: html, + buttons: { + roll: { + icon: '', + label: (isCard) ? "Tirer une carte" : "Lancer le dé", + callback: () => { this.roll() } + }, + cancel: { + icon: '', + label: "Annuler", + callback: () => { this.close() } + } + }, + close: close + } + + super(conf, options); + + this.actor = actor; + this.rollData = rollData; + } + + /* -------------------------------------------- */ + roll() { + let isCard = this.rollData.attr && this.rollData.attr.iscard + if (isCard) { + EcrymeUtility.tirageConfrontationEcryme(this.rollData) + } else { + EcrymeUtility.rollEcryme(this.rollData) + } + } + + /* -------------------------------------------- */ + async refreshDialog() { + const content = await renderTemplate("systems/fvtt-ecryme/templates/dialogs/roll-dialog-generic.hbs", this.rollData) + this.data.content = content + this.render(true) + } + + /* -------------------------------------------- */ + activateListeners(html) { + super.activateListeners(html); + + var dialog = this; + function onLoad() { + } + $(function () { onLoad(); }); + + html.find('#bonusMalusSituation').change((event) => { + this.rollData.bonusMalusSituation = Number(event.currentTarget.value) + }) + html.find('#bonusMalusPerso').change((event) => { + this.rollData.bonusMalusPerso = Number(event.currentTarget.value) + }) + html.find('#bonusMalusDef').change((event) => { + this.rollData.bonusMalusDef = Number(event.currentTarget.value) + }) + html.find('#bonusMalusPortee').change((event) => { + this.rollData.bonusMalusPortee = Number(event.currentTarget.value) + }) + html.find('#confrontationDegre').change((event) => { + this.rollData.confrontationDegre = Number(event.currentTarget.value) + }) + html.find('#confrontationModif').change((event) => { + this.rollData.confrontationModif = Number(event.currentTarget.value) + }) + + } +} \ No newline at end of file diff --git a/modules/ecryme-main.js b/modules/ecryme-main.js new file mode 100644 index 0000000..ee3472d --- /dev/null +++ b/modules/ecryme-main.js @@ -0,0 +1,146 @@ +/** + * Ecryme system + * Author: Uberwald + * Software License: Prop + */ + +/* -------------------------------------------- */ + +/* -------------------------------------------- */ +// Import Modules +import { EcrymeActor } from "./actors/ecryme-actor.js"; +import { EcrymeItemSheet } from "./items/ecryme-item-sheet.js"; +import { EcrymeActorSheet } from "./actors/ecryme-actor-sheet.js"; +import { EcrymeNPCSheet } from "./actors/ecryme-npc-sheet.js"; +import { EcrymeUtility } from "./common/ecryme-utility.js"; +import { EcrymeCombat } from "./app/ecryme-combat.js"; +import { EcrymeItem } from "./items/ecryme-item.js"; +import { EcrymeHotbar } from "./app/ecryme-hotbar.js" +import { EcrymeCharacterSummary } from "./app/ecryme-summary-app.js" +import { MALEFICES_CONFIG } from "./common/ecryme-config.js" + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ + +/************************************************************************************/ +Hooks.once("init", async function () { + + console.log(`Initializing Ecryme RPG`); + + game.system.ecryme = { + config: ECRYME_CONFIG, + EcrymeHotbar + } + + /* -------------------------------------------- */ + // preload handlebars templates + EcrymeUtility.preloadHandlebarsTemplates(); + + /* -------------------------------------------- */ + // Set an initiative formula for the system + CONFIG.Combat.initiative = { + formula: "1d6", + decimals: 1 + }; + + /* -------------------------------------------- */ + game.socket.on("system.fvtt-ecryme", data => { + EcrymeUtility.onSocketMesssage(data) + }); + + /* -------------------------------------------- */ + // Define custom Entity classes + CONFIG.Combat.documentClass = EcrymeCombat + CONFIG.Actor.documentClass = EcrymeActor + CONFIG.Item.documentClass = EcrymeItem + + /* -------------------------------------------- */ + // Register sheet application classes + Actors.unregisterSheet("core", ActorSheet); + Actors.registerSheet("fvtt-ecryme", EcrymeActorSheet, { types: ["personnage"], makeDefault: true }); + Actors.registerSheet("fvtt-ecryme", EcrymeNPCSheet, { types: ["pnj"], makeDefault: false }); + + Items.unregisterSheet("core", ItemSheet); + Items.registerSheet("fvtt-ecryme", EcrymeItemSheet, { makeDefault: true }); + + EcrymeUtility.init() + +}); + +/* -------------------------------------------- */ +function welcomeMessage() { + if (game.user.isGM) { + ChatMessage.create({ + user: game.user.id, + whisper: [game.user.id], + content: `
+ Bienvenu dans Ecryme, le JDR qui sent le souffre ! +

Le Livre de Base de Maléfices v4 est nécessaire pour jouer : https://arkhane-asylum.fr/en/ecryme/

+

Maléfices et un jeu de rôle publié par Arkhane Asylum Publishing, tout les droits leur appartiennent.

+

Système développé par LeRatierBretonnien avec l'aide de la Dame du Lac et Malik, support sur le Discord FR de Foundry.

+

Commandes : /tirage pour le tirage des tarots, /carte pour tirer une simple carte et /resume pour le résumé des PJs (MJ seulement)` }); + } +} +/* -------------------------------------------- */ +// Register world usage statistics +function registerUsageCount(registerKey) { + if (game.user.isGM) { + game.settings.register(registerKey, "world-key", { + name: "Unique world key", + scope: "world", + config: false, + default: "", + type: String + }); + + let worldKey = game.settings.get(registerKey, "world-key") + if (worldKey == undefined || worldKey == "") { + worldKey = randomID(32) + game.settings.set(registerKey, "world-key", worldKey) + } + // Simple API counter + let regURL = `https://www.uberwald.me/fvtt_appcount/count.php?name="${registerKey}"&worldKey="${worldKey}"&version="${game.release.generation}.${game.release.build}"&system="${game.system.id}"&systemversion="${game.system.version}"` + //$.ajaxSetup({ + //headers: { 'Access-Control-Allow-Origin': '*' } + //}) + $.ajax(regURL) + } +} + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ +Hooks.once("ready", function () { + + // User warning + if (!game.user.isGM && game.user.character == undefined) { + ui.notifications.info("Attention ! Aucun personnage relié au joueur !"); + ChatMessage.create({ + content: "WARNING Le joueur " + game.user.name + " n'est pas relié à un personnage !", + user: game.user._id + }); + } + + registerUsageCount('fvtt-ecryme') + welcomeMessage(); + EcrymeUtility.ready() + EcrymeCharacterSummary.ready() + +}) + + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ +Hooks.on("chatMessage", (html, content, msg) => { + if (content[0] == '/') { + let regExp = /(\S+)/g; + let commands = content.match(regExp); + if (game.system.ecryme.commands.processChatCommand(commands, content, msg)) { + return false; + } + } + return true; +}); + diff --git a/modules/items/ecryme-item-sheet.js b/modules/items/ecryme-item-sheet.js new file mode 100644 index 0000000..d60a79c --- /dev/null +++ b/modules/items/ecryme-item-sheet.js @@ -0,0 +1,186 @@ +import { EcrymeUtility } from "./common/ecryme-utility.js"; + +/** + * Extend the basic ItemSheet with some very simple modifications + * @extends {ItemSheet} + */ +export class EcrymeItemSheet extends ItemSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-ecryme", "sheet", "item"], + template: "systems/fvtt-ecryme/templates/item-sheet.hbs", + dragDrop: [{ dragSelector: null, dropSelector: null }], + width: 620, + height: 580, + tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }] + }); + } + + /* -------------------------------------------- */ + _getHeaderButtons() { + let buttons = super._getHeaderButtons(); + // Add "Post to chat" button + // We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry! + buttons.unshift( + { + class: "post", + icon: "fas fa-comment", + onclick: ev => { } + }) + return buttons + } + + /* -------------------------------------------- */ + /** @override */ + setPosition(options = {}) { + const position = super.setPosition(options); + const sheetBody = this.element.find(".sheet-body"); + const bodyHeight = position.height - 192; + sheetBody.css("height", bodyHeight); + if (this.item.type.includes('weapon')) { + position.width = 640; + } + return position; + } + + /* -------------------------------------------- */ + async getData() { + + let formData = { + title: this.title, + id: this.id, + type: this.object.type, + img: this.object.img, + name: this.object.name, + editable: this.isEditable, + cssClass: this.isEditable ? "editable" : "locked", + system: duplicate(this.object.system), + config: duplicate(game.system.ecryme.config), + limited: this.object.limited, + options: this.options, + owner: this.document.isOwner, + description: await TextEditor.enrichHTML(this.object.system.description, { async: true }), + notes: await TextEditor.enrichHTML(this.object.system.notes, { async: true }), + isGM: game.user.isGM + } + + if ( this.object.type == "archetype") { + formData.tarots = EcrymeUtility.getTarots() + } + + this.options.editable = !(this.object.origin == "embeddedItem"); + console.log("ITEM DATA", formData, this); + return formData; + } + + + /* -------------------------------------------- */ + _getHeaderButtons() { + let buttons = super._getHeaderButtons(); + buttons.unshift({ + class: "post", + icon: "fas fa-comment", + onclick: ev => this.postItem() + }); + return buttons + } + + /* -------------------------------------------- */ + postItem() { + let chatData = duplicate(this.item) + if (this.actor) { + chatData.actor = { id: this.actor.id }; + } + // Don't post any image for the item (which would leave a large gap) if the default image is used + if (chatData.img.includes("/blank.png")) { + chatData.img = null; + } + // JSON object for easy creation + chatData.jsondata = JSON.stringify( + { + compendium: "postedItem", + payload: chatData, + }); + + renderTemplate('systems/Ecryme/templates/post-item.html', chatData).then(html => { + let chatOptions = EcrymeUtility.chatDataSetup(html); + ChatMessage.create(chatOptions) + }); + } + + /* -------------------------------------------- */ + async viewSubitem(ev) { + let levelIndex = Number($(ev.currentTarget).parents(".item").data("level-index")) + let choiceIndex = Number($(ev.currentTarget).parents(".item").data("choice-index")) + let featureId = $(ev.currentTarget).parents(".item").data("feature-id") + + let itemData = this.object.system.levels[levelIndex].choices[choiceIndex].features[featureId] + + if (itemData.name != 'None') { + let item = await Item.create(itemData, { temporary: true }); + item.system.origin = "embeddedItem"; + new EcrymeItemSheet(item).render(true); + } + } + + /* -------------------------------------------- */ + async deleteSubitem(ev) { + let field = $(ev.currentTarget).data('type'); + let idx = Number($(ev.currentTarget).data('index')); + let oldArray = this.object.system[field]; + let itemData = this.object.system[field][idx]; + if (itemData.name != 'None') { + let newArray = []; + for (var i = 0; i < oldArray.length; i++) { + if (i != idx) { + newArray.push(oldArray[i]); + } + } + this.object.update({ [`system.${field}`]: newArray }); + } + } + + /* -------------------------------------------- */ + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) return; + + + // Update Inventory Item + html.find('.item-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + const item = this.object.options.actor.getOwnedItem(li.data("item-id")); + item.sheet.render(true); + }); + + html.find('.delete-subitem').click(ev => { + this.deleteSubitem(ev); + }); + + // Update Inventory Item + html.find('.item-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + let itemType = li.data("item-type"); + }); + + } + + /* -------------------------------------------- */ + get template() { + let type = this.item.type; + return `systems/fvtt-ecryme/templates/items/item-${type}-sheet.hbs` + } + + /* -------------------------------------------- */ + /** @override */ + _updateObject(event, formData) { + return this.object.update(formData) + } +} \ No newline at end of file diff --git a/modules/items/ecryme-item.js b/modules/items/ecryme-item.js new file mode 100644 index 0000000..2973e53 --- /dev/null +++ b/modules/items/ecryme-item.js @@ -0,0 +1,25 @@ +import { MaleficesUtility } from "./malefices-utility.js"; + +export const defaultItemImg = { + arme: "systems/fvtt-malefices/images/icons/arme.webp", + equipement: "systems/fvtt-malefices/images/icons/equipement.webp", + elementbio: "systems/fvtt-malefices/images/icons/wisdom.webp", + archetype: "systems/fvtt-malefices/images/icons/archetype.webp", + tarot: "systems/fvtt-malefices/images/icons/tarot.webp", + sortilege: "systems/fvtt-malefices/images/icons/sortilege.webp", +} + +/** + * Extend the basic ItemSheet with some very simple modifications + * @extends {ItemSheet} + */ +export class MaleficesItem extends Item { + + constructor(data, context) { + if (!data.img) { + data.img = defaultItemImg[data.type]; + } + super(data, context); + } + +} diff --git a/styles/ecryme.css b/styles/ecryme.css new file mode 100644 index 0000000..045fd20 --- /dev/null +++ b/styles/ecryme.css @@ -0,0 +1,1327 @@ + /* ==================== (A) Fonts ==================== */ + @font-face { + font-family: "Rivanna"; + src: url('../fonts/rivanna.ttf') format("truetype"); + } + + :root { + /* =================== 1. ACTOR SHEET FONT STYLES =========== */ + --window-header-title-font-size: 1.3rem; + --window-header-title-font-weight: normal; + --window-header-title-color: #f5f5f5; + + --major-button-font-size: 1.05rem; + --major-button-font-weight: normal; + --major-button-color: #dadada; + + --tab-header-font-size: 1.0rem; + --tab-header-font-weight: 700; + --tab-header-color: #403f3e; + --tab-header-color-active: #4a0404; + + --actor-input-font-size: 0.8rem; + --actor-input-font-weight: 500; + --actor-input-color: black; + + --actor-label-font-size: 0.8rem; + --actor-label-font-weight: 700; + --actor-label-color: #464331c4; + + /* =================== 2. DEBUGGING HIGHLIGHTERS ============ */ + --debug-background-color-red: #ff000054; + --debug-background-color-blue: #1d00ff54; + --debug-background-color-green: #54ff0054; + + --debug-box-shadow-red: inset 0 0 2px red; + --debug-box-shadow-blue: inset 0 0 2px blue; + --debug-box-shadow-green: inset 0 0 2px green; + } + +/*@import url("https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap");*/ +/* Global styles & Font */ +.window-app { + text-align: justify; + font-size: 16px; + letter-spacing: 1px; +} + +/* Fonts */ +.sheet header.sheet-header h1 input, .window-app .window-header, #actors .directory-list, #navigation #scene-list .scene.nav-item { + font-size: 1.0rem; +} /* For title, sidebar character and scene */ +.sheet nav.sheet-tabs { + font-size: 0.8rem; +} /* For nav and title */ +.window-app input, .fvtt-malefices .item-form, .sheet header.sheet-header .flex-group-center.flex-compteurs, .sheet header.sheet-header .flex-group-center.flex-fatigue, select, button, .item-checkbox, #sidebar, #players, #navigation #nav-toggle { + font-size: 0.8rem; +} + +.window-header{ + background: rgba(0,0,0,0.75); +} + +.window-app.sheet .window-content { + margin: 0; + padding: 0; +} +.strong-text{ + font-weight: bold; +} + +.tabs .item.active, .blessures-list li ul li:first-child:hover, a:hover { + text-shadow: 1px 0px 0px #ff6600; +} + +.rollable:hover, .rollable:focus { + color: #000; + text-shadow: 0 0 10px red; + cursor: pointer; +} + +input:hover, select:hover { + border-width: 4px; + border-color: rgb(85, 65, 130); +} + +input:disabled { + color:#1c2058; +} +select:disabled { + color:#1c2058; +} +table {border: 1px solid #7a7971;} + +.grid, .grid-2col { + display: grid; + grid-column: span 2 / span 2; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + margin: 10px 0; + padding: 0; +} + +.grid-3col { + grid-column: span 3 / span 3; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.grid-4col { + grid-column: span 4 / span 4; + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.grid-5col { + grid-column: span 5 / span 5; + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.grid-6col { + grid-column: span 5 / span 5; + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.grid-7col { + grid-column: span 7 / span 7; + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + +.grid-8col { + grid-column: span 8 / span 8; + grid-template-columns: repeat(8, minmax(0, 1fr)); +} + +.grid-9col { + grid-column: span 9 / span 9; + grid-template-columns: repeat(9, minmax(0, 1fr)); +} + +.grid-10col { + grid-column: span 10 / span 10; + grid-template-columns: repeat(10, minmax(0, 1fr)); +} + +.grid-11col { + grid-column: span 11 / span 11; + grid-template-columns: repeat(11, minmax(0, 1fr)); +} + +.grid-12col { + grid-column: span 12 / span 12; + grid-template-columns: repeat(12, minmax(0, 1fr)); +} + +.flex-group-center, +.flex-group-left, +.flex-group-right { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + text-align: center; + padding: 5px; +} + +.flex-group-left { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + text-align: left; +} + +.flex-group-right { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + text-align: right; +} + +.flex-center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; +} + +.table-create-actor { + font-size: 0.8rem; +} + +.flex-between { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.flex-shrink { + flex: 'flex-shrink' ; +} + +/* Styles limited to sheets */ +.fvtt-malefices .sheet-header { + -webkit-box-flex: 0; + -ms-flex: 0 0 210px; + flex: 0 0 210px; + overflow: hidden; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + margin-bottom: 10px; +} + +.fvtt-malefices .sheet-header .profile-img { + -webkit-box-flex: 0; + -ms-flex: 0 0 128px; + flex: 0 0 128px; + width: 128px; + height: auto; + max-height:128px; + margin-top: 0px; + margin-right: 10px; + object-fit: cover; + object-position: 50% 0; + border-width: 0px; +} +.profile-img-container { + margin-right: 0.2rem; + max-width: 140px; + width: 140px; +} + +.button-img { + vertical-align: baseline; + width: 8%; + height: 8%; + max-height: 48px; + border-width: 0px; + border: 1px solid rgba(0, 0, 0, 0); +} + +.button-img:hover { + color: rgba(255, 255, 128, 0.7); + border: 1px solid rgba(255, 128, 0, 0.8); + cursor: pointer; +} + +.button-effect-img { + vertical-align: baseline; + width: 16px; + max-height: 16px; + height: 16; + border-width: 0; +} + +.small-button-container { + height: 16px; + width: 16px; + border: 0; + vertical-align: bottom; +} + +.fvtt-malefices .sheet-header .header-fields { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.fvtt-malefices .sheet-header h1.charname { + height: 50px; + padding: 0px; + margin: 5px 0; + border-bottom: 0; +} + +.fvtt-malefices .sheet-header h1.charname input { + font-family: Rivanna; + font-size: 3rem; + width: 100%; + height: 100%; + margin: 0; +} + +.fvtt-malefices .sheet-tabs { + -webkit-box-flex: 0; + -ms-flex: 0; + flex: 0; + font-family: Rivanna; + font-size: 2.2rem; +} + +.fvtt-malefices .sheet-body, +.fvtt-malefices .sheet-body .tab, +.fvtt-malefices .sheet-body .tab .editor { + height: 100%; + font-size: 0.8rem; +} + +.editor { + border: 2; + height: 100%; + padding: 0 3px; +} + +.medium-editor { + border: 2; + height: 240px; + padding: 0 3px; +} + +.small-editor { + border: 2; + height: 120px; + padding: 0 3px; +} + +.fvtt-malefices .tox .tox-editor-container { + background: #fff; +} + +.fvtt-malefices .tox .tox-edit-area { + padding: 0 8px; +} + +.fvtt-malefices .resource-label { + font-weight: bold; + text-transform: uppercase; +} + +.fvtt-malefices .tabs { + height: 40px; + border-top: 1px solid #AAA; + border-bottom: 1px solid #AAA; + color: #000000; +} + +.fvtt-malefices .tabs .item { + line-height: 40px; + font-weight: bold; +} + +.fvtt-malefices .tabs .item.active { + text-decoration: underline; + text-shadow: none; +} + +.fvtt-malefices .items-list { + list-style: none; + margin: 1px 0; + padding: 0; + overflow-y: auto; +} + +.fvtt-malefices .items-list .item-header { + font-weight: bold; +} + +.fvtt-malefices .items-list .item { + height: 30px; + line-height: 24px; + padding: 1px 0; + border-bottom: 1px solid #BBB; +} + +.fvtt-malefices .items-list .item .item-image { + -webkit-box-flex: 0; + -ms-flex: 0 0 24px; + flex: 0 0 24px; + margin-right: 5px; +} + +.fvtt-malefices .items-list .item img { + display: block; +} + +.fvtt-malefices .items-list .item-name { + margin: 0; +} + +.fvtt-malefices .items-list .item-controls { + -webkit-box-flex: 0; + -ms-flex: 0 0 86px; + flex: 0 0 86px; + text-align: right; +} + +li.folder > .folder-header h3 { + color: rgba(19, 18, 18, 0.95); +} + +/* ======================================== */ +/* Sheet */ +.window-app.sheet .window-content .sheet-header{ + color: rgba(19, 18, 18, 0.95); + background: url("../images/ui/background_01_clear.webp"); + /*background: #494e6b;*/ +} + +input[type="text"], select[type="text"] { + background:white; + color: #494e6b; +} + +select { + background:white; + color: #494e6b; +} +/* background: #011d33 url("../images/ui/fond1.webp") repeat left top;*/ +/*color: rgba(168, 139, 139, 0.5);*/ +.window-app.sheet .window-content .sheet-header select[type="text"], .window-app.sheet .window-content .sheet-header input[type="text"], .window-app.sheet .window-content .sheet-header input[type="number"], .window-app.sheet .window-content .sheet-body input[type="text"], .window-app.sheet .window-content .sheet-body input[type="number"], .window-app.sheet .window-content .sheet-body select[type="text"] { + color: rgba(19, 18, 18, 0.95); + /*color: #494e6b;*/ +} + +.window-app.sheet .window-content .sheet-header input[type="password"], .window-app.sheet .window-content .sheet-header input[type="date"], .window-app.sheet .window-content .sheet-header input[type="time"] { + color: rgba(19, 18, 18, 0.95); + background: url("../images/ui/background_01_clear.webp"); + border: 1 none; + margin-bottom: 0.25rem; + margin-left: 2px; +} + +.window-app.sheet .window-content .sheet-body input[type="password"], .window-app.sheet .window-content .sheet-body input[type="date"], .window-app.sheet .window-content .sheet-body input[type="time"] { + color: rgba(19, 18, 18, 0.95); + background: url("../images/ui/background_01_clear.webp"); + border: 1 none; + margin-bottom: 0.25rem; + margin-left: 2px; +} + +.window-app.sheet .window-content .sheet-body select, .window-app.sheet .window-content .sheet-header select { + color: rgba(19, 18, 18, 0.95); + background: #fff; + border: 1 none; + margin-bottom: 0.25rem; + margin-left: 2px; +} + +.window-app .window-content, .window-app.sheet .window-content .sheet-body{ + font-size: 0.8rem; + /*background: url("../images/ui/pc_sheet_bg.webp") repeat left top;*/ + background: url("../images/ui/background_01_clear.webp"); + color: rgba(19, 18, 18, 0.95); +} + +/* background: rgba(245,245,240,0.6) url("../images/ui/sheet_background.webp") left top;*/ + +section.sheet-body{padding: 0.25rem 0.5rem;} + +.sheet header.sheet-header .profile-img { + object-fit: cover; + object-position: 50% 0; + margin: 0.5rem 0 0.5rem 0.5rem; + padding: 0; +} + +.sheet nav.sheet-tabs { + font-size: 1.2rem; + font-weight: bold; + height: 3rem; + flex: 0 0 3rem; + margin: 0; + padding: 0 0 0 0.25rem; + text-align: center; + text-transform: uppercase; + line-height: 1.5rem; + border-top: 0 none; + border-bottom: 0 none; + background-color:#252525; + color:beige; +} + +/* background: rgb(245,245,240) url("../images/ui/fond4.webp") repeat left top;*/ + +nav.sheet-tabs .item { + position: relative; + padding: 0 0.25rem; +} + +nav.sheet-tabs .item:after { + content: ""; + position: absolute; + top: 0; + right: 0; + height: 2rem; + width: 1px; + border-right: 1px dashed rgba(52, 52, 52, 0.25); +} + +.sheet .tab[data-tab] { + padding: 0; +} + +section.sheet-body:after { + content: ""; + display: block; + clear: both; +} + +.sheet header.sheet-header .flex-compteurs {text-align: right;} +.sheet header.sheet-header .resource-content {width: 2rem;} + +.select-diff { + display: inline-block; + text-align: left; + width: 50px; +} + +.window-app.sheet .window-content .tooltip:hover .tooltiptext { + top: 2rem; + left: 2rem; + margin: 0; + padding: 0.25rem; +} + +.window-app.sheet .window-content .carac-value, .window-app.sheet .window-content .competence-xp { + margin: 0.05rem; + flex-basis: 3rem; + text-align: center; +} + +/* ======================================== */ +/* Global UI elements */ + +/* ======================================== */ + +h1, h2, h3, h4 { + font-weight: bold; +} + +ul, ol { + margin: 0; + padding: 0; +} +ul, li { + list-style-type: none; +} + +.sheet li { + margin: 0.010rem; + padding: 0.25rem; +} +.header-fields li { + margin: 0; + padding: 0; +} + +.alterne-list > .list-item:hover { + background: rgba(100, 100, 50, 0.25); +} +.alterne-list > .list-item:nth-child(even) { + background: rgba(80, 60, 0, 0.10); +} +.alterne-list > .list-item:nth-child(odd) { + background: rgb(160, 130, 100, 0.05); +} + +.specialisation-label { + font-size: 0.8rem; +} + +.carac-label, +.attr-label { + font-weight: bold; +} + +.list-item { + margin: 0.125rem; + /*box-shadow: inset 0px 0px 1px #00000096; + border-radius: 0.25rem;*/ + padding: 0.125rem; + flex: 1 1 5rem; + display: flex !important; + color: rgba(19, 18, 18, 0.95); +} +.list-item-shadow { + background:rgba(87, 60, 32, 0.35); + flex-grow: 0; + flex-wrap: nowrap; + justify-content: flex-start; +} +.list-item-shadow2 { + background:rgba(87, 60, 32, 0.25); + flex-grow: 0; + flex-wrap: nowrap; + justify-content: flex-start; +} +.item-display-show { + display: block; +} +.item-display-hide { + display: none; +} +.item-quantite { + margin-left: 0.5rem; +} +.list-item-margin1 { + margin-left: 1rem; +} +.list-item-margin2 { + margin-left: 2rem; +} +.list-item-margin3 { + margin-left: 3rem; +} +.list-item-margin4 { + margin-left: 4rem; +} + +.sheet-competence-img { + width: 24px; + max-width: 24px; + height: 24px; + max-height: 24px; + flex-grow: 0; + margin-right: 0.25rem; +} +.competence-column { + flex-direction: column; + align-content: flex-start; + justify-content: flex-start; + flex-grow: 0; + flex-basis: 1; +} +.competence-header { + align-content: flex-start; + justify-content: flex-start; + font-weight: bold; + flex-grow: 0; +} + +.description-label { + flex-grow: 2; + margin-left: 4px; +} +.status-header-label { + margin-left: 2px; +} +.roll-dialog-label { + margin: 4px 0; + min-width: 96px; +} +.short-label { + flex-grow: 1; +} +.keyword-label { + font-size: 0.85rem; +} + +.item-sheet-label { + flex-grow: 1; +} + +.item-text-long-line { + flex-grow: 3; +} + +.score-label { + flex-grow: 2; + align-content: center; +} + +.attribut-value, +.carac-value { + flex-grow: 0; + flex-basis: 64px; + margin-right: 4px; + margin-left: 4px; +} +.sante-value, +.competence-value { + flex-grow: 0; + flex-basis: 2rem; + margin-right: 0.25rem; + margin-left: 0.25rem; +} +.description-value { + flex-grow: 0; + flex-basis: 4rem; + margin-right: 0.25rem; + margin-left: 0.25rem; +} +.small-label { + margin-top: 5px; +} +.padd-right { + margin-right: 8px; +} +.padd-left { + margin-left: 8px; +} +.stack-left { + align-items:center; + flex-shrink: 1; + flex-grow: 0; +} +.packed-left { + white-space: nowrap; + flex-grow: 0; +} + +.input-numeric-short { + width: 40px; + max-width: 40px; + flex-grow: 0; + flex-shrink: 0; + flex-basis: 40px; + margin-right: 0.25rem; + margin-left: 0.25rem; +} + +.abilities-table { + align-content: flex-start; +} + +/* ======================================== */ +.tokenhudext { + display: flex; + flex: 0 !important; + font-weight: 600; +} +.tokenhudext.left { + justify-content: flex-start; + flex-direction: column; + position: absolute; + top: 2.75rem; + right: 4rem; +} +.tokenhudext.right { + justify-content: flex-start; + flex-direction: column; + position: absolute; + top: 2.75rem; + left: 4rem; +} +.control-icon.tokenhudicon { + width: fit-content; + height: fit-content; + min-width: 6rem; + flex-basis: auto; + padding: 0; + line-height: 1rem; + margin: 0.25rem; +} +.control-icon.tokenhudicon.right { + margin-left: 8px; +} +#token-hud .status-effects.active{ + z-index: 2; +} +.token-sheet .window-content .flexcol .sheet-tabs { + font-size: 0.8rem; +} + +/* ======================================== */ +.item-checkbox { + height: 25px; + border: 1px solid #736953a6; + border-left: none; + font-weight: 500; + font-size: 1rem; + color: black; + padding-top: 5px; + margin-right: 0px; + width: 45px; + position: relative; + left: 0px; + text-align: center; +} + +.skill-label { + font-size: 0.7rem; +} +.skill-good-checkbox { + max-height: 10px; + max-width: 10px; +} + +.flex-actions-bar { + flex-grow: 2; +} + +/* ======================================== */ +/* Sidebar CSS */ +#sidebar { + font-size: 1rem; + /*background-position: 100%;*/ + background-color:#f5f5f5; + background-position: 0px 35px; + background-repeat: no-repeat; + background-image: url("../images/ui/background_01_clear.webp"); + color: rgba(19, 18, 18, 0.95); +} + +/* background: rgb(105,85,65) url("../images/ui/texture_feuille_perso_onglets.webp") no-repeat right bottom;*/ + +#sidebar.collapsed { + height: 470px !important; +} + +#sidebar-tabs > .collapsed, #chat-controls .chat-control-icon { + color: rgba(19, 18, 18, 0.95); + text-shadow: 1px 1px 0 rgba(0,0,0,0.75); +} + +.sidebar-tab .directory-list .entity { + border-top: 1px dashed rgba(0,0,0,0.25); + border-bottom: 0 none; + padding: 0.25rem 0; +} + +.sidebar-tab .directory-list .entity:hover { + background: rgba(0,0,0,0.05); + cursor: pointer; +} +.chat-message-header { + background: rgba(220,220,210,0.5); + font-size: 1.1rem; + height: 48px; + text-align: center; + vertical-align: middle; + display: flex; + align-items: center; +} + +.chat-message .message-header .flavor-text, .chat-message .message-header .whisper-to { + font-size: 0.9rem; +} +.chat-result-text, +.chat-actor-name { + font-weight: bold; + font-family: Rivanna; + font-size: 1.2rem; + padding: 4px; +} +.chat-result-success { + color:darkgreen; +} +.chat-result-failure { + color:darkred; +} +.chat-img { + width: 64px; + height: 64px; +} + +.roll-dialog-header { + height: 52px; +} + +.actor-icon { + float: left; + width: 48px; + height: 48px; + padding: 2px 6px 2px 2px; +} + +.padding-dice { + padding-top: .2rem; + padding-bottom: .2rem; +} + +.dice-image { + box-sizing: border-box; + border: none; + border-radius: 0; + max-width: 100%; +} + +.dice-image-reroll { + background-color:rgba(115, 224, 115, 0.25); + border-color: #011d33; + box-sizing: border-box; + border: 1px; + border-radius: 0%; + max-width: 100%; +} + +.chat-dice { + width: 15%; + height: 15%; + font-size: 15px; + padding: 10px; + padding-bottom: 20px; + padding-top: .2rem; + padding-bottom: .2rem; +} + +.div-center { + align-self: center; +} + +.chat-message { + background: rgba(220,220,210,0.5); + font-size: 0.9rem; +} + +.chat-message.whisper { + background: rgba(220,220,210,0.75); + border: 2px solid #545469; +} + +.chat-message .chat-icon { + border: 0; + padding: 2px 6px 2px 2px; + float: left; + width: 64px; + height: 64px; +} + +.ability-icon { + border: 0; + padding: 2px 2px 2px 2px; + max-width:32px; + max-height:32px; + width: auto; + height: auto; +} +.small-ability-icon { + border: 0; + padding: 2px 2px 2px 2px; + max-width:16px; + max-height:16px; + width: auto; + height: auto; +} +.combat-icon { + border: 0; + padding: 2px 2px 2px 2px; + max-width:24px; + max-height:24px; + width: auto; + height: auto; +} + +#sidebar-tabs { + flex: 0 0 32px; + box-sizing: border-box; + margin: 0 0 5px; + border-bottom: 1px solid rgba(0,0,0,0); + box-shadow: inset 0 0 2rem rgba(0,0,0,0.5); +} + +#sidebar-tabs > .item.active { + border: 1px solid rgba(114,98,72,1); + background: rgba(30, 25, 20, 0.75); + box-shadow: 0 0 6px inset rgba(114,98,72,1); +} + +#sidebar #sidebar-tabs i{ + display: inline-block; + background-position:center; + background-size:cover; + text-shadow: 1px 1px 0 rgba(0,0,0,0.75); + +} + +/*--------------------------------------------------------------------------*/ +/* Control, Tool, hotbar & navigation */ + +#controls .scene-control, #controls .control-tool { + box-shadow: 0 0 3px #000; + margin: 0 0 8px; + border-radius: 0; + background: rgba(30, 25, 20, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; +} + +#controls .scene-control.active, #controls .control-tool.active, #controls .scene-control:hover, #controls .control-tool:hover { + background: rgba(72, 46, 28, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; + box-shadow: 0 0 3px #ff6400; +} + +#hotbar #action-bar #macro-list { + border: 1px solid rgba(72, 46, 28, 1); + box-shadow: 2px 2px 5px #000000; +} + +#hotbar #action-bar .macro { + border-image: url(img/ui/bg_control.jpg) 21 repeat; + border-image-slice: 6 6 6 6 fill; + border-image-width: 6px 6px 6px 6px; + border-image-outset: 0px 0px 0px 0px; + border-radius: 0px; +} + +#hotbar .bar-controls { + background: rgba(30, 25, 20, 1); + border: 1px solid rgba(72, 46, 28, 1); +} + +#players { + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; + background: rgba(30, 25, 20, 1); +} + +#navigation #scene-list .scene.nav-item.active { + background: rgba(72, 46, 28, 1); +} + +#navigation #scene-list .scene.nav-item { + background: rgba(30, 25, 20, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; +} + +#navigation #scene-list .scene.view, #navigation #scene-list .scene.context { + background: rgba(72, 46, 28, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; + box-shadow: 0 0 3px #ff6400; +} + +#navigation #nav-toggle { + background: rgba(30, 25, 20, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; +} + +/* Tooltip container */ +.tooltip { + position: relative; + display: inline-block; + /*border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ +} + +/* Tooltip text */ +.tooltip .tooltiptext { + text-align: left; + background: rgba(231, 229, 226, 0.9); + width: 150px; + padding: 3px 0; + font-size: 0.9rem; + + /* Position the tooltip text */ + top: 1px; + position: absolute; + z-index: 1; + + /* Fade in tooltip */ + visibility: hidden; + opacity: 0; + transition: opacity 0.3s; +} + + +.tooltip-nobottom { + border-bottom: unset; /* If you want dots under the hoverable text */ +} + +/* Show the tooltip text when you mouse over the tooltip container */ +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.chat-card-button { + box-shadow: inset 0px 1px 0px 0px #a6827e; + background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%); + background-color: #7d5d3b00; + border-radius: 3px; + border: 2px ridge #846109; + display: inline-block; + cursor: pointer; + color: #ffffff; + font-size: 0.8rem; + padding: 4px 12px 0px 12px; + text-decoration: none; + text-shadow: 0px 1px 0px #4d3534; + position: relative; + margin:2px; +} + +.chat-card-button:hover { + background: linear-gradient(to bottom, #800000 5%, #3e0101 100%); + background-color: red; +} +.chat-card-button:active { + position:relative; + top:1px; +} + +.plus-minus-button { + box-shadow: inset 0px 1px 0px 0px #a6827e; + background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%); + background-color: #7d5d3b00; + border-radius: 2px; + border: 1px ridge #846109; + display: inline-block; + cursor: pointer; + color: #ffffff; + margin: 2px 2px 2px 2px; + padding: 2px 2px 2px 2px; + text-decoration: none; + text-shadow: 0px 1px 0px #4d3534; + position: relative; + margin:0px; +} + +.plus-minus-button:hover, +.chat-card-button:hover { + background: linear-gradient(to bottom, #800000 5%, #3e0101 100%); + background-color: red; +} + +.plus-minus-button:active, +.chat-card-button:active { + position:relative; + top:1px; +} + +.plus-minus { + font-size: 0.9rem; + font-weight: bold; +} + +.ul-level1 { + padding-left: 2rem; +} + + +/*************************************************************/ +#pause +{ + font-size: 2rem; +} +#pause > h3 +{ + color: #CCC +} +#pause > img { + content: url(../images/ui/logo_pause.webp); + height: 200px; + width: 200px; + top: -100px; + left: calc(50% - 132px); +} + +#logo { + content : url(../images/ui/logo_pause.webp); + width: 100px; + height: 60px; +} + +.dice-cell { + padding-left: 12px; + padding-right: 12px; + width: 60px; + text-align: center; +} + +.dice-formula, +.dice-total { + height: 54px; + position:relative; +} + +.status-small-label { + font-size: 0.65rem; +} +.no-grow { + flex-grow: 1; + max-width: 32px; +} +.status-col-name { + max-width: 72px; +} +.img-no-border { + max-width: 48px; + max-height: 48px; + border: 0px; +} +.items-title-bg { + margin-top: 6px; + color: rgba(19, 18, 18, 0.95); + +} +.items-title-text { + margin-left: 4px; +} +.lock-icon { + width:16px; + height: 16px; +} +.item-sheet-img { + width: 64px; + height: auto; + border: 0; +} +.item-name-img { + flex-grow:1; + max-width: 2rem; + min-width: 2rem; +} +.item-name-label-header { + flex-grow:2; + max-width: 12rem; + min-width: 12rem; +} +.item-name-label-header-long { + flex-grow:2; + max-width: 14rem; + min-width: 14rem; +} +.item-name-label-header-long2 { + flex-grow:2; + max-width: 24rem; + min-width: 24rem; +} +.item-name-label { + flex-grow:2; + max-width: 10rem; + min-width: 10rem; +} +.item-name-label-long { + margin-top: 4px; + flex-grow:2; + max-width: 10rem; + min-width: 10rem; +} +.item-name-label-short { + flex-grow:1; + max-width: 4rem; + min-width: 4rem; +} +.item-name-label-medium { + margin-top: 4px; + flex-grow:2; + max-width: 6rem; + min-width: 6rem; +} +.item-name-label-long2 { + margin-top: 4px; + flex-grow:2; + max-width: 22rem; + min-width: 22rem; +} +.item-name-label-level2 { + flex-grow:2; + max-width: 9rem; + min-width: 9rem; +} +.item-field-label-short { + flex-grow:1; + max-width: 4rem; + min-width: 4rem; +} +.item-field-label-medium { + flex-grow:1; + max-width: 6rem; + min-width: 6rem; +} +.item-field-skill { + flex-grow:1; + max-width: 6.8rem; + min-width: 6.8rem; +} +.item-field-label-long { + flex-grow:1; + max-width: 10rem; + min-width: 10rem; +} +.item-control-end { + align-self: flex-end; +} +.alternate-list { + margin-top: 4px; + flex-wrap: nowrap; +} +.item-filler { + flex-grow: 6; + flex-shrink: 7; +} +.item-controls-fixed { + min-width:2rem; + max-width: 2rem; +} +.attribute-label { + font-weight: bold; +} +.flexrow-no-expand { + flex-grow: 0; +} +.item-input-small { + max-width: 16px; + max-height: 12px; +} +.flip-tarot { + transform: scaleY(-1); +} +.tarot-fixed-width { + width: 140px; + max-width: 140px; +} +.tarot-title { + text-align: center; + font-weight: bold; +} +.character-summary-rollable { + text-decoration: underline; +} diff --git a/system.json b/system.json new file mode 100644 index 0000000..b459fd6 --- /dev/null +++ b/system.json @@ -0,0 +1,48 @@ +{ + "description": "Ecryme", + "esmodules": [ + "modules/ecryme-main.js" + ], + "gridDistance": 1, + "gridUnits": "u", + "languages": [ + { + "lang": "fr", + "name": "French", + "path": "lang/fr.json", + "flags": {} + }, + { + "lang": "en", + "name": "English", + "path": "lang/en.json", + "flags": {} + } + ], + "authors": [ + { + "name": "Uberwald", + "flags": {} + } + ], + "packs": [ + ], + "license": "LICENSE.txt", + "manifest": "https://www.uberwald.me/gitea/uberwald/fvtt-ecryme/raw/branch/master/system.json", + "compatibility": { + "minimum": "10", + "verified": "10" + }, + "id": "fvtt-ecryme", + "primaryTokenAttribute": "secondary.health", + "secondaryTokenAttribute": "secondary.delirium", + "socket": true, + "styles": [ + "styles/ecryme.css" + ], + "title": "Ecryme, le Jeu de Rôles", + "url": "https://www.uberwald.me/gitea/uberwald/fvtt-ecryme", + "version": "10.0.0", + "download": "https://www.uberwald.me/gitea/uberwald/fvtt-ecryme/archive/fvtt-ecryme-v10.0.0.zip", + "background": "systems/fvtt-ecryme/images/ui/accueil_01.webp" +} \ No newline at end of file diff --git a/template.json b/template.json new file mode 100644 index 0000000..05d5d8b --- /dev/null +++ b/template.json @@ -0,0 +1,146 @@ +{ + "Actor": { + "types": [ + "personnage" + ], + "templates": { + "biodata": { + "biodata": { + "age": 0, + "size": "", + "lieunaissance": "", + "nationalite": "", + "profession": "", + "residence": "", + "milieusocial": "", + "poids": "", + "cheveux": "", + "sexe": "", + "yeux": "", + "enfance": "", + "adulte": "", + "loisirs": "", + "singularite": "", + "politique": "", + "religion": "", + "fantastique": "", + "description": "", + "gmnotes": "" + } + }, + "core": { + "subactors": [], + "lamesdestin": [], + "pointdestin": 1, + "fluide": 5, + "mpmb": 0, + "mpmn": 0, + "attributs": { + "constitution": { + "label": "Constitution", + "abbrev": "constitution", + "value": 0, + "hasmax": true, + "max": 0 + }, + "physique": { + "label": "Aptitudes Physiques", + "abbrev": "physique", + "value": 0, + "max": 0 + }, + "culturegenerale": { + "label": "Culture Générale", + "abbrev": "culturegenerale", + "value": 0, + "max": 0 + }, + "habilite": { + "label": "Habilité", + "abbrev": "habilite", + "value": 0, + "max": 0 + }, + "perception": { + "label": "Perception", + "abbrev": "perception", + "value": 0, + "max": 0 + }, + "spiritualite": { + "label": "Spiritualite", + "abbrev": "spiritualite", + "hasmax": false, + "iscard": true, + "value": 0, + "max": 0 + }, + "rationnalite": { + "label": "Rationnalite", + "abbrev": "rationnalite", + "hasmax": false, + "iscard": true, + "value": 0, + "max": 0 + } + }, + "equipementlibre": "" + }, + "npccore": { + "npctype": "", + "description": "" + } + }, + "personnage": { + "templates": [ + "biodata", + "core" + ] + } + }, + "Item": { + "types": [ + "arme", + "equipement", + "archetype", + "tarot", + "sortilege", + "elementbio" + ], + "templates": {}, + "elementbio": { + "description": "" + }, + "equipement": { + "description": "" + }, + "tarot": { + "tarottype": "", + "numericvalueup": 0, + "numericvaluedown": 0, + "isdualside": false, + "ispositif": true, + "isgm": false, + "description": "" + }, + "archetype": { + "lametutelaire": "", + "description": "" + }, + "sortilege": { + "seuil": 0, + "description": "" + }, + "arme": { + "armetype": "", + "porteecourte": "", + "porteemoyenne": "", + "dommagenormale": 0, + "dommagepart": 0, + "dommagecritique": 0, + "dommagecritiqueKO": false, + "dommagecritiquemort": false, + "description": "" + } + } +} \ No newline at end of file diff --git a/templates/actors/actor-sheet.hbs b/templates/actors/actor-sheet.hbs new file mode 100644 index 0000000..f8065c5 --- /dev/null +++ b/templates/actors/actor-sheet.hbs @@ -0,0 +1,415 @@ +

+ + {{!-- Sheet Header --}} +
+
+
+ +
+ +
+ +
+

+ +
+
    +
  • + + +
  • +
  • + + +
  • +
+
+ +
+
+
+
+ + + + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
+ + {{!-- Skills Tab --}} +
+ + +
+
+
    +
  • + +

    +
    +
  • + {{#each system.attributs as |attr key|}} +
  • + + {{attr.label}} + {{#if attr.iscard}} + + {{else}} + + {{/if}} + + + {{#if attr.hasmax}} + + {{/if}} + {{#if (eq key "physique")}} + {{#if @root.phyMalus}} + ({{@root.phyMalus}}) + {{/if}} + {{/if}} +
  • + {{/each}} + +
  • + Points de Destin + +
  • + + {{#if isGM}} +
  • + Fluide (MJ) + +
  • +
  • + MPMB (MJ) + +
  • +
  • + MPMN (MJ) + +
  • + {{/if}} + +
+
+ +
+
    +
  • + +

    +
    +
  • + + {{#each armes as |arme key|}} +
  • + {{arme.name}} +
  • + {{/each}} +
+ +
    +
  • + +

    +
    +
     
    +
    + +
    +
  • + + {{#each elementsbio as |elem key|}} +
  • + + {{elem.name}} + +
     
    +
    + +
    +
  • + {{/each}} +
+ +
+ +
+ +
+ + {{!-- Equipement Tab --}} +
+ + +

+
+
+ {{editor equipementlibre target="system.equipementlibre" button=true owner=owner editable=editable}} +
+ +
    +
  • + +

    +
    + + + + + + + + + +
    + +
    +
  • + {{#each armes as |arme key|}} +
  • + + {{arme.name}} + + + + +
     
    +
    + +
    +
  • + {{/each}} +
+ +
    +
  • + +

    +
    + + + +
     
    +
    + +
    + +
  • + {{#each equipements as |equip key|}} +
  • + + {{equip.name}} + +
     
    +
    + +
    +
  • + {{/each}} +
+ +
    +
  • + +

    +
    + + + +
     
    +
    + +
    + +
  • + {{#each sorts as |sort key|}} +
  • + + {{sort.name}} + + + +
     
    +
    + +
    +
  • + {{/each}} +
+ +
+ +
+ + {{!-- Biography Tab --}} +
+ +
+
+
    +
  • + + + {{archetype.name}} +
    + +
    +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+
+
    +
  • + + {{archetype.tarot.name}} +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+
+ +
    +
  • + +
  • +
  • + +
  • +
+ +
    +
  • + +

    +
    + + + +
    + +
    +
  • + {{#each tarots as |tarot key|}} +
  • + + {{tarot.name}} + +
     
    + {{#if @root.isGM}} +
    + +
    + {{/if}} +
  • + {{/each}} +
+ + {{#if isGM}} +
    +
  • + +

    +
    + + + +
    + +
    +
  • + {{#each tarotsCache as |tarot key|}} +
  • + + {{tarot.name}} + +
     
    + {{#if @root.isGM}} +
    + +
    + {{/if}} +
  • + {{/each}} +
+ {{/if}} + +
+ +
+
+ +

+
+
+ {{editor description target="system.biodata.description" button=true owner=owner + editable=editable}} +
+
+ +

+
+
+ {{editor notes target="system.biodata.notes" button=true owner=owner editable=editable}} +
+
+ +
+ + +
+ \ No newline at end of file diff --git a/templates/actors/editor-notes-gm.hbs b/templates/actors/editor-notes-gm.hbs new file mode 100644 index 0000000..4c17392 --- /dev/null +++ b/templates/actors/editor-notes-gm.hbs @@ -0,0 +1,6 @@ +{{#if data.isGM}} +

GM Notes :

+
+ {{editor data.gmnotes target="system.gmnotes" button=true owner=owner editable=editable}} +
+{{/if}} diff --git a/templates/chat/chat-confrontation-result.hbs b/templates/chat/chat-confrontation-result.hbs new file mode 100644 index 0000000..b5b949d --- /dev/null +++ b/templates/chat/chat-confrontation-result.hbs @@ -0,0 +1,37 @@ +
+ {{#if actorImg}} + {{alias}} + {{/if}} +

{{alias}}

+
+ +
+ + {{#if img}} +
+ {{name}} +
+ {{/if}} + +
+
+ +
+ +
+ + diff --git a/templates/chat/chat-generic-result.hbs b/templates/chat/chat-generic-result.hbs new file mode 100644 index 0000000..8997207 --- /dev/null +++ b/templates/chat/chat-generic-result.hbs @@ -0,0 +1,76 @@ +
+ {{#if actorImg}} + {{alias}} + {{/if}} +

{{alias}}

+
+ +
+ + {{#if img}} +
+ {{name}} +
+ {{/if}} + +
+
+ +
+ +
+ + diff --git a/templates/chat/display-tarot-card.hbs b/templates/chat/display-tarot-card.hbs new file mode 100644 index 0000000..b01e5ab --- /dev/null +++ b/templates/chat/display-tarot-card.hbs @@ -0,0 +1,12 @@ + +
+ + {{name}} + {{#if system.isdualside}} + {{#if system.ispositif}}Positif{{else}}Négatif{{/if}} + {{/if}} + {{#if value}} + Valeur : {{value}} + {{/if}} +
+ diff --git a/templates/chat/request-tarot-card.hbs b/templates/chat/request-tarot-card.hbs new file mode 100644 index 0000000..3e260f0 --- /dev/null +++ b/templates/chat/request-tarot-card.hbs @@ -0,0 +1,4 @@ +
+ +
+ diff --git a/templates/dialogs/character-summary.hbs b/templates/dialogs/character-summary.hbs new file mode 100644 index 0000000..8dd88c7 --- /dev/null +++ b/templates/dialogs/character-summary.hbs @@ -0,0 +1,86 @@ +
+ +
    + +
  1. +
    Nom
    + {{#each config.attributs as |attr key|}} +
    {{attr}}
    + {{/each}} +
    Destin
    +
    Fluide
    +
    MPMB
    +
    MPMN
    + +
  2. + + {{#each pcs as |pc key|}} +
  3. + + {{#each pc.system.attributs as |attr key|}} + + {{/each}} + + + + + +
  4. + {{/each}} + +
  5. +
    PNJs
    + {{#each config.attributs as |attr key|}} +
    attr
    + {{/each}} +
    Destin
    +
    Fluide
    +
    MPMB
    +
    MPMN
    + +
  6. + + {{#each npcs as |pc key|}} +
  7. + + {{#each pc.system.attributs as |attr key|}} + + {{/each}} + + + + + +
    + +
    +
  8. + {{/each}} + +
+ +
\ No newline at end of file diff --git a/templates/dialogs/confrontation-dialog.hbs b/templates/dialogs/confrontation-dialog.hbs new file mode 100644 index 0000000..9f24310 --- /dev/null +++ b/templates/dialogs/confrontation-dialog.hbs @@ -0,0 +1,45 @@ +
+
+ {{#if img}} + + {{/if}} +

{{title}}

+
+ +
+ +
+ {{attr.label}} : + + {{attr.value}} + +
+ +
+ Degré de la confrontation : + +
+ +
+ Modificateur de confrontation : + +
+ +
+ +
\ No newline at end of file diff --git a/templates/dialogs/roll-dialog-generic.hbs b/templates/dialogs/roll-dialog-generic.hbs new file mode 100644 index 0000000..f735431 --- /dev/null +++ b/templates/dialogs/roll-dialog-generic.hbs @@ -0,0 +1,93 @@ +
+
+ {{#if img}} + + {{/if}} +

{{title}}

+
+ +
+ + {{#if attr}} +
+ {{attr.label}} : + + {{attr.value}} + {{#if (eq attr.abbrev "physique")}} + {{#if phyMalus}} + ({{phyMalus}}) + {{/if}} + {{/if}} + +
+ {{/if}} + +
+ Rappel des élements biographiques : +
    + {{#each elementsbio as |elem key|}} +
  • + {{elem.name}} +
  • + {{/each}} +
+
+ +
+ Bonus/Malus biographique : + +
+ +
+ Bonus/Malus de situation : + +
+ + {{#if arme}} +
+ Défense : + +
+ +
+ Portée : + +
+ {{/if}} + +
+ +
\ No newline at end of file diff --git a/templates/dialogs/tirage-tarot-dialog.hbs b/templates/dialogs/tirage-tarot-dialog.hbs new file mode 100644 index 0000000..ce7473c --- /dev/null +++ b/templates/dialogs/tirage-tarot-dialog.hbs @@ -0,0 +1,72 @@ +
+
+

Joueur : {{user.name}}

+
+ +
+ + {{#if (eq state "select-player")}} +
+ Sélectionnez le joueur pour faire le tirage + +
+ {{/if}} + + {{#if (eq state "attribute-to-actor")}} +
+ Sélectionnez l'acteur pour lui attribuer les tarots + +
+ {{/if}} + +
+

Main du joueur :

+
+ {{#each cards as |card index|}} +
+
+ + {{card.name}} + {{#if card.system.isdualside}} + {{#if card.system.ispositif}}Positif{{else}}Négatif{{/if}} + {{/if}} +
+
+ {{/each}} +
+ +
+

Main secrête :

+
+ {{#each secretCards as |card index|}} +
+
+ + {{card.name}} + {{#if card.system.isdualside}} + {{#if card.system.ispositif}}Positif{{else}}Négatif{{/if}} + {{/if}} +
+
+ {{/each}} +
+ +
+ +
+ +
\ No newline at end of file diff --git a/templates/items/item-archetype-sheet.hbs b/templates/items/item-archetype-sheet.hbs new file mode 100644 index 0000000..665f248 --- /dev/null +++ b/templates/items/item-archetype-sheet.hbs @@ -0,0 +1,37 @@ +
+
+ +
+

+
+
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-description.hbs}} + +
+ +
+
    +
  • + + +
  • + +
+
+
+ +
+
diff --git a/templates/items/item-arme-sheet.hbs b/templates/items/item-arme-sheet.hbs new file mode 100644 index 0000000..62dc172 --- /dev/null +++ b/templates/items/item-arme-sheet.hbs @@ -0,0 +1,73 @@ +
+
+ +
+

+
+
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-description.hbs}} + +
+ +
+
    + +
  • + + +
  • + +
  • + + +
  • +
  • + + +
  • + +
  • + + +
  • + +
  • + + +
  • + +
  • + + + + + + + +
  • + +
  • + + +
  • + + +
+
+
+ +
+
diff --git a/templates/items/item-elementbio-sheet.hbs b/templates/items/item-elementbio-sheet.hbs new file mode 100644 index 0000000..8be4ccc --- /dev/null +++ b/templates/items/item-elementbio-sheet.hbs @@ -0,0 +1,27 @@ +
+
+ +
+

+
+
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-description.hbs}} + +
+ +
+
    + +
+
+
+ +
+
diff --git a/templates/items/item-equipement-sheet.hbs b/templates/items/item-equipement-sheet.hbs new file mode 100644 index 0000000..8be4ccc --- /dev/null +++ b/templates/items/item-equipement-sheet.hbs @@ -0,0 +1,27 @@ +
+
+ +
+

+
+
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-description.hbs}} + +
+ +
+
    + +
+
+
+ +
+
diff --git a/templates/items/item-sortilege-sheet.hbs b/templates/items/item-sortilege-sheet.hbs new file mode 100644 index 0000000..d626657 --- /dev/null +++ b/templates/items/item-sortilege-sheet.hbs @@ -0,0 +1,30 @@ +
+
+ +
+

+
+
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-description.hbs}} + +
+ +
+
    +
  • + + +
  • +
+
+
+ +
+
diff --git a/templates/items/item-tarot-sheet.hbs b/templates/items/item-tarot-sheet.hbs new file mode 100644 index 0000000..446a62d --- /dev/null +++ b/templates/items/item-tarot-sheet.hbs @@ -0,0 +1,68 @@ +
+
+
+

+
+
+ + {{> systems/fvtt-ecryme/templates/items/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
+
+
+ +
+ +
+ {{> systems/fvtt-ecryme/templates/items/partial-item-description.hbs}} + +
+ +
+
    + +
  • + + +
  • + + {{#if isGM}} +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • + {{#if system.isdualside}} +
  • + + +
  • + {{/if}} +
  • + + +
  • + {{/if}} + +
+
+
+
+ +
+
diff --git a/templates/items/partial-item-description.hbs b/templates/items/partial-item-description.hbs new file mode 100644 index 0000000..fafd2b5 --- /dev/null +++ b/templates/items/partial-item-description.hbs @@ -0,0 +1,3 @@ +
+ {{editor description target="system.description" button=true owner=owner editable=editable}} +
diff --git a/templates/items/partial-item-nav.hbs b/templates/items/partial-item-nav.hbs new file mode 100644 index 0000000..95b52cd --- /dev/null +++ b/templates/items/partial-item-nav.hbs @@ -0,0 +1,5 @@ +{{!-- Sheet Tab Navigation --}} +