commit 64ce2fcbb90f6d494369b30d8e946a6c99ed3aa0 Author: LeRatierBretonnien Date: Wed Feb 1 14:28:08 2023 +0100 Initial import diff --git a/images/ui/D12_Black.png b/images/ui/D12_Black.png new file mode 100644 index 0000000..f4ab136 Binary files /dev/null and b/images/ui/D12_Black.png differ diff --git a/images/ui/D12_Black.webp b/images/ui/D12_Black.webp new file mode 100644 index 0000000..39e2b92 Binary files /dev/null and b/images/ui/D12_Black.webp differ diff --git a/images/ui/D12_White.png b/images/ui/D12_White.png new file mode 100644 index 0000000..7f10632 Binary files /dev/null and b/images/ui/D12_White.png differ diff --git a/images/ui/D12_White.webp b/images/ui/D12_White.webp new file mode 100644 index 0000000..9d4cea0 Binary files /dev/null and b/images/ui/D12_White.webp differ diff --git a/images/ui/logo.png b/images/ui/logo.png new file mode 100644 index 0000000..26172aa Binary files /dev/null and b/images/ui/logo.png differ diff --git a/images/ui/logo.webp b/images/ui/logo.webp new file mode 100644 index 0000000..2600f53 Binary files /dev/null and b/images/ui/logo.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/lang/fr.json b/lang/fr.json new file mode 100644 index 0000000..08a01c1 --- /dev/null +++ b/lang/fr.json @@ -0,0 +1,22 @@ +{ + "ACTOR": { + "TypeCharacter": "Character", + "TypeNpc": "NPC" + }, + "ITEM": { + "TypeWeapon": "Weapon", + "TypeShield": "Shield", + "TypeArmor": "Armor", + "TypeSpell": "Spell", + "TypeModule": "Module", + "TypeMoney": "Money", + "TypeEquipment": "Equipment", + "TypeAction": "Action", + "TypeFreeaction": "Free Action", + "TypeReaction": "Reaction", + "TypeStance": "Stance", + "TypeTrait": "Trait", + "TypeCondition": "Condition", + "TypeCraftingskill": "Crafting Skill" + } +} \ No newline at end of file diff --git a/modules/malefices-actor-sheet.js b/modules/malefices-actor-sheet.js new file mode 100644 index 0000000..a513070 --- /dev/null +++ b/modules/malefices-actor-sheet.js @@ -0,0 +1,163 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ + +import { MaleficesUtility } from "./malefices-utility.js"; + +/* -------------------------------------------- */ +export class MaleficesActorSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-malefices", "sheet", "actor"], + template: "systems/fvtt-malefices/templates/actors/actor-sheet.hbs", + width: 960, + height: 720, + 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()), + equipements: duplicate(this.actor.getEquipements()), + subActors: duplicate(this.actor.getSubActors()), + focusData: this.actor.computeFinalFocusData(), + 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") + MaleficesUtility.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") + let skillKey = $(event.currentTarget).data("skill-key") + this.actor.rollSkill(attrKey, skillKey) + }); + html.find('.roll-arme').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const weponId = li.data("item-id") + this.actor.rollWeapon(weponId) + }); + + 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/malefices-actor.js b/modules/malefices-actor.js new file mode 100644 index 0000000..8a49809 --- /dev/null +++ b/modules/malefices-actor.js @@ -0,0 +1,327 @@ +/* -------------------------------------------- */ +import { MaleficesUtility } from "./malefices-utility.js"; +import { MaleficesRollDialog } from "./malefices-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 MaleficesActor 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'); + MaleficesUtility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getArmes() { + let comp = duplicate(this.items.filter(item => item.type == 'arme') || []) + MaleficesUtility.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 = MaleficesUtility.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) { + if (this.type == 'character') { + let init = this.getFlag("world", "initiative" ) + console.log("INIT", init) + if (!init || init == -1) { + ChatMessage.create( { content: "Roll your initiative for this combat"} ) + } + return init + } + return -1; + } + + /* -------------------------------------------- */ + 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]); + } + } + + /* -------------------------------------------- */ + 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 + } + } + } + /* -------------------------------------------- */ + getCommonRollData() { + + let rollData = MaleficesUtility.getBasicRollData() + rollData.alias = this.name + rollData.actorImg = this.img + rollData.actorId = this.id + rollData.img = this.img + + console.log("ROLLDATA", rollData) + + return rollData + } + + /* -------------------------------------------- */ + rollAtribut(attrKey, skillKey) { + let attr = this.system.attributes[attrKey] + let skill = attr.skills[skillKey] + if (skill) { + skill = duplicate(skill) + skill.name = MaleficesUtility.upperFirst(skillKey) + skill.attr = duplicate(attr) + let rollData = this.getCommonRollData() + rollData.mode = "skill" + rollMode.skillKey = skillKey + rollData.skill = skill + rollData.title = "Roll Skill " + skill.name + rollData.img = skill.img + this.startRoll(rollData) + } + } + + /* -------------------------------------------- */ + rollArme(weaponId) { + let weapon = this.items.get(weaponId) + if (weapon) { + weapon = duplicate(weapon) + this.prepareWeapon(weapon) + let rollData = this.getCommonRollData() + rollData.modifier = this.system.bonus[weapon.system.weapontype] + rollData.mode = "weapon" + rollData.weapon = weapon + rollData.img = weapon.img + this.startRoll(rollData) + } else { + ui.notifications.warn("Unable to find the relevant weapon ") + } + } + + /* -------------------------------------------- */ + async startRoll(rollData) { + this.syncRoll(rollData) + let rollDialog = await MaleficesRollDialog.create(this, rollData) + rollDialog.render(true) + } + +} diff --git a/modules/malefices-combat.js b/modules/malefices-combat.js new file mode 100644 index 0000000..b1e09ce --- /dev/null +++ b/modules/malefices-combat.js @@ -0,0 +1,40 @@ +import { MaleficesUtility } from "./malefices-utility.js"; + +/* -------------------------------------------- */ +export class MaleficesCombat 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/malefices-commands.js b/modules/malefices-commands.js new file mode 100644 index 0000000..5a511bf --- /dev/null +++ b/modules/malefices-commands.js @@ -0,0 +1,117 @@ +/* -------------------------------------------- */ + +import { MaleficesUtility } from "./malefices-utility.js"; +import { MaleficesRollDialog } from "./malefices-roll-dialog.js"; + +/* -------------------------------------------- */ +export class MaleficesCommands { + + static init() { + if (!game.system.Malefices.commands) { + const MaleficesCommands = new MaleficesCommands(); + //crucibleCommands.registerCommand({ path: ["/char"], func: (content, msg, params) => crucibleCommands.createChar(msg), descr: "Create a new character" }); + //crucibleCommands.registerCommand({ path: ["/pool"], func: (content, msg, params) => crucibleCommands.poolRoll(msg), descr: "Generic Roll Window" }); + game.system.Malefices.commands = MaleficesCommands; + } + } + 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); + } + + /* -------------------------------------------- */ + async poolRoll( msg) { + let rollData = MaleficesUtility.getBasicRollData() + rollData.alias = "Dice Pool Roll", + rollData.mode = "generic" + rollData.title = `Dice Pool Roll`; + + let rollDialog = await MaleficesRollDialog.create( this, rollData); + rollDialog.render( true ); + } + +} \ No newline at end of file diff --git a/modules/malefices-config.js b/modules/malefices-config.js new file mode 100644 index 0000000..3c7cabd --- /dev/null +++ b/modules/malefices-config.js @@ -0,0 +1,15 @@ + +export const MALEFICES_CONFIG = { + + armeTypes : { + "fusilchasse": "Fusil de Chasse", + "fusilguerre": "Fusil de Guerre", + "pistoletgros": "Pistolet (gros calibre)", + "pistoletmoyen": "Pistolet (moyen calibre)", + "pistoletpetit": "Pistolet (petit calibre)", + "arbalete": "Arbalète", + "arc": "Arc", + "epee": "Epée, sabre, javelot, etc", + "mainsnues": "Mains Nues" + }, +} \ No newline at end of file diff --git a/modules/malefices-hotbar.js b/modules/malefices-hotbar.js new file mode 100644 index 0000000..5a71078 --- /dev/null +++ b/modules/malefices-hotbar.js @@ -0,0 +1,86 @@ + +export class MaleficesHotbar { + + /** + * 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.Malefices.MaleficesHotbar.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/malefices-item-sheet.js b/modules/malefices-item-sheet.js new file mode 100644 index 0000000..f5c41fe --- /dev/null +++ b/modules/malefices-item-sheet.js @@ -0,0 +1,181 @@ +import { MaleficesUtility } from "./malefices-utility.js"; + +/** + * Extend the basic ItemSheet with some very simple modifications + * @extends {ItemSheet} + */ +export class MaleficesItemSheet extends ItemSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-malefices", "sheet", "item"], + template: "systems/fvtt-malefices/templates/item-sheet.hbs", + dragDrop: [{ dragSelector: null, dropSelector: null }], + width: 620, + height: 480, + 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.malefices), + limited: this.object.limited, + options: this.options, + owner: this.document.isOwner, + description: await TextEditor.enrichHTML(this.object.system.description, { async: true }), + isGM: game.user.isGM + } + + 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/Malefices/templates/post-item.html', chatData).then(html => { + let chatOptions = MaleficesUtility.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 MaleficesItemSheet(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-malefices/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/malefices-item.js b/modules/malefices-item.js new file mode 100644 index 0000000..32ac83e --- /dev/null +++ b/modules/malefices-item.js @@ -0,0 +1,21 @@ +import { MaleficesUtility } from "./malefices-utility.js"; + +export const defaultItemImg = { + //skill: "systems/fvtt-malefices/images/icons/skill1.webp", + arme: "systems/fvtt-malefices/images/icones/arme.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/modules/malefices-main.js b/modules/malefices-main.js new file mode 100644 index 0000000..14a3484 --- /dev/null +++ b/modules/malefices-main.js @@ -0,0 +1,117 @@ +/** + * Malefices system + * Author: Uberwald + * Software License: Prop + */ + +/* -------------------------------------------- */ + +/* -------------------------------------------- */ +// Import Modules +import { MaleficesActor } from "./malefices-actor.js"; +import { MaleficesItemSheet } from "./malefices-item-sheet.js"; +import { MaleficesActorSheet } from "./malefices-actor-sheet.js"; +import { MaleficesNPCSheet } from "./malefices-npc-sheet.js"; +import { MaleficesUtility } from "./malefices-utility.js"; +import { MaleficesCombat } from "./malefices-combat.js"; +import { MaleficesItem } from "./malefices-item.js"; +import { MaleficesHotbar } from "./malefices-hotbar.js" +import { MALEFICES_CONFIG } from "./malefices-config.js" + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ + +/************************************************************************************/ +Hooks.once("init", async function () { + + console.log(`Initializing Malefices RPG`); + + game.system.malefices = { + config: MALEFICES_CONFIG, + MaleficesHotbar + } + + /* -------------------------------------------- */ + // preload handlebars templates + MaleficesUtility.preloadHandlebarsTemplates(); + + /* -------------------------------------------- */ + // Set an initiative formula for the system + CONFIG.Combat.initiative = { + formula: "1d6", + decimals: 1 + }; + + /* -------------------------------------------- */ + game.socket.on("system.fvtt-malefices", data => { + MaleficesUtility.onSocketMesssage(data) + }); + + /* -------------------------------------------- */ + // Define custom Entity classes + CONFIG.Combat.documentClass = MaleficesCombat + CONFIG.Actor.documentClass = MaleficesActor + CONFIG.Item.documentClass = MaleficesItem + + /* -------------------------------------------- */ + // Register sheet application classes + Actors.unregisterSheet("core", ActorSheet); + Actors.registerSheet("fvtt-malefices", MaleficesActorSheet, { types: ["personnage"], makeDefault: true }); + Actors.registerSheet("fvtt-malefices", MaleficesNPCSheet, { types: ["pnj"], makeDefault: false }); + + Items.unregisterSheet("core", ItemSheet); + Items.registerSheet("fvtt-malefices", MaleficesItemSheet, { makeDefault: true }); + + MaleficesUtility.init() +}); + +/* -------------------------------------------- */ +function welcomeMessage() { + ChatMessage.create({ + user: game.user.id, + whisper: [game.user.id], + content: `
+ Bienvenu dans Malefices, le JDR qui sent le souffre ! + ` }); +} + +/* -------------------------------------------- */ +/* 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 + }); + } + + // CSS patch for v9 + if (game.version) { + let sidebar = document.getElementById("sidebar"); + sidebar.style.width = "min-content"; + } + + welcomeMessage(); + MaleficesUtility.ready() + MaleficesUtility.init() +}) + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ +Hooks.on("chatMessage", (html, content, msg) => { + if (content[0] == '/') { + let regExp = /(\S+)/g; + let commands = content.match(regExp); + if (game.system.Malefices.commands.processChatCommand(commands, content, msg)) { + return false; + } + } + return true; +}); + diff --git a/modules/malefices-npc-sheet.js b/modules/malefices-npc-sheet.js new file mode 100644 index 0000000..8c0718f --- /dev/null +++ b/modules/malefices-npc-sheet.js @@ -0,0 +1,207 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ + +import { MaleficesUtility } from "./malefices-utility.js"; + +/* -------------------------------------------- */ +export class MaleficesNPCSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["Malefices", "sheet", "actor"], + template: "systems/fvtt-malefices/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") + MaleficesUtility.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/malefices-roll-dialog.js b/modules/malefices-roll-dialog.js new file mode 100644 index 0000000..aa22b57 --- /dev/null +++ b/modules/malefices-roll-dialog.js @@ -0,0 +1,69 @@ +import { MaleficesUtility } from "./malefices-utility.js"; + +export class MaleficesRollDialog extends Dialog { + + /* -------------------------------------------- */ + static async create(actor, rollData) { + + let options = { classes: ["MaleficesDialog"], width: 540, height: 'fit-content', 'z-index': 99999 }; + let html = await renderTemplate('systems/fvtt-malefices/templates/dialogs/roll-dialog-generic.hbs', rollData); + + return new MaleficesRollDialog(actor, rollData, html, options); + } + + /* -------------------------------------------- */ + constructor(actor, rollData, html, options, close = undefined) { + let conf = { + title: (rollData.mode == "skill") ? "Skill" : "Attribute", + content: html, + buttons: { + roll: { + icon: '', + label: "Roll !", + callback: () => { this.roll() } + }, + cancel: { + icon: '', + label: "Cancel", + callback: () => { this.close() } + } + }, + close: close + } + + super(conf, options); + + this.actor = actor; + this.rollData = rollData; + } + + /* -------------------------------------------- */ + roll() { + MaleficesUtility.rollMalefices(this.rollData) + } + + /* -------------------------------------------- */ + async refreshDialog() { + const content = await renderTemplate("systems/fvtt-malefices/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('#bonusMalusRoll').change((event) => { + this.rollData.bonusMalusRoll = event.currentTarget.value + }) + html.find('#targetCheck').change((event) => { + this.rollData.targetCheck = event.currentTarget.value + }) + + } +} \ No newline at end of file diff --git a/modules/malefices-utility.js b/modules/malefices-utility.js new file mode 100644 index 0000000..3668e6b --- /dev/null +++ b/modules/malefices-utility.js @@ -0,0 +1,701 @@ +/* -------------------------------------------- */ +import { MaleficesCombat } from "./malefices-combat.js"; +import { MaleficesCommands } from "./malefices-commands.js"; + + +/* -------------------------------------------- */ +export class MaleficesUtility { + + + /* -------------------------------------------- */ + static async init() { + Hooks.on('renderChatLog', (log, html, data) => MaleficesUtility.chatListeners(html)); + /*Hooks.on("dropCanvasData", (canvas, data) => { + MaleficesUtility.dropItemOnToken(canvas, data) + });*/ + + this.rollDataStore = {} + this.defenderStore = {} + + MaleficesCommands.init(); + + 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); + }) + } + + /*-------------------------------------------- */ + static upperFirst(text) { + if (typeof text !== 'string') return text + return text.charAt(0).toUpperCase() + text.slice(1) + } + + /*-------------------------------------------- */ + static getSkills() { + return duplicate(this.skills) + } + /*-------------------------------------------- */ + static getWeaponSkills() { + return duplicate(this.weaponSkills) + } + /*-------------------------------------------- */ + static getShieldSkills() { + return duplicate(this.shieldSkills) + } + + /* -------------------------------------------- */ + static isModuleItemAllowed(type) { + return __ALLOWED_MODULE_TYPES[type] + } + + /* -------------------------------------------- */ + static buildBonusList() { + let bonusList = [] + for (let key in game.system.model.Actor.character.bonus) { + let bonuses = game.system.model.Actor.character.bonus[key] + for (let bonus in bonuses) { + bonusList.push(key + "." + bonus) + } + } + for (let key in game.system.model.Actor.character.attributes) { + let attrs = game.system.model.Actor.character.attributes[key] + for (let skillKey in attrs.skills) { + bonusList.push(key + ".skills." + skillKey + ".modifier") + } + } + for (let key in game.system.model.Actor.character.universal.skills) { + bonusList.push("universal.skills." + key + ".modifier") + } + return bonusList + } + + /* -------------------------------------------- */ + static async ready() { + const skills = await MaleficesUtility.loadCompendium("fvtt-malefices.skills") + this.skills = skills.map(i => i.toObject()) + this.weaponSkills = duplicate(this.skills.filter(item => item.system.isweaponskill)) + this.shieldSkills = duplicate(this.skills.filter(item => item.system.isshieldskill)) + + const rollTables = await MaleficesUtility.loadCompendium("fvtt-malefices.rolltables") + this.rollTables = rollTables.map(i => i.toObject()) + + } + + /* -------------------------------------------- */ + static async loadCompendiumData(compendium) { + const pack = game.packs.get(compendium) + return await pack?.getDocuments() ?? [] + } + + /* -------------------------------------------- */ + static async loadCompendium(compendium, filter = item => true) { + let compendiumData = await MaleficesUtility.loadCompendiumData(compendium) + return compendiumData.filter(filter) + } + + /* -------------------------------------------- */ + static isArmorLight(armor) { + if (armor && (armor.system.armortype.includes("light") || armor.system.armortype.includes("clothes"))) { + return true + } + return false + } + + /* -------------------------------------------- */ + static async chatListeners(html) { + + html.on("click", '.view-item-from-chat', event => { + game.system.Malefices.creator.openItemView(event) + }) + html.on("click", '.roll-defense-melee', event => { + let rollId = $(event.currentTarget).data("roll-id") + let rollData = MaleficesUtility.getRollData(rollId) + rollData.defenseWeaponId = $(event.currentTarget).data("defense-weapon-id") + let actor = game.canvas.tokens.get(rollData.defenderTokenId).actor + if (actor && (game.user.isGM || actor.isOwner)) { + actor.rollDefenseMelee(rollData) + } + }) + html.on("click", '.roll-defense-ranged', event => { + let rollId = $(event.currentTarget).data("roll-id") + let rollData = MaleficesUtility.getRollData(rollId) + let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor + if (defender && (game.user.isGM || defender.isOwner)) { + defender.rollDefenseRanged(rollData) + } + }) + + } + + /* -------------------------------------------- */ + static async preloadHandlebarsTemplates() { + + const templatePaths = [ + 'systems/fvtt-malefices/templates/actors/editor-notes-gm.hbs', + 'systems/fvtt-malefices/templates/items/partial-item-nav.hbs', + 'systems/fvtt-malefices/templates/items/partial-item-description.hbs', + 'systems/fvtt-malefices/templates/items/partial-common-item-fields.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-damage-types.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-weapon-types.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-weapon-categories.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-attributes.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-equipment-types.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-armor-types.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-spell-types.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-spell-levels.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-spell-schools.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-focus-bond.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-focus-treatment.hbs', + 'systems/fvtt-malefices/templates/items/partial-options-focus-core.hbs', + ] + return loadTemplates(templatePaths); + } + + /* -------------------------------------------- */ + static removeChatMessageId(messageId) { + if (messageId) { + game.messages.get(messageId)?.delete(); + } + } + + static findChatMessageId(current) { + return MaleficesUtility.getChatMessageId(MaleficesUtility.findChatMessage(current)); + } + + static getChatMessageId(node) { + return node?.attributes.getNamedItem('data-message-id')?.value; + } + + static findChatMessage(current) { + return MaleficesUtility.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 MaleficesUtility.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 saveRollData(rollData) { + game.socket.emit("system.fvtt-malefices", { + name: "msg_update_roll", data: rollData + }); // Notify all other clients of the roll + this.updateRollData(rollData) + } + + /* -------------------------------------------- */ + static getRollData(id) { + return this.rollDataStore[id] + } + + /* -------------------------------------------- */ + static async displayDefenseMessage(rollData) { + if (rollData.mode == "weapon" && rollData.defenderTokenId) { + let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor + if (game.user.isGM || (game.user.character && game.user.character.id == defender.id)) { + rollData.defender = defender + rollData.defenderWeapons = defender.getEquippedWeapons() + rollData.isRangedAttack = rollData.weapon?.system.isranged + this.createChatWithRollMode(defender.name, { + name: defender.name, + alias: defender.name, + //user: defender.id, + content: await renderTemplate(`systems/fvtt-malefices/templates/chat-request-defense.html`, rollData), + whisper: [defender.id].concat(ChatMessage.getWhisperRecipients('GM')), + }) + } + } + } + + /* -------------------------------------------- */ + static getSuccessResult(rollData) { + if (rollData.sumSuccess <= -3) { + if (rollData.attackRollData.weapon.system.isranged) { + return { result: "miss", fumble: true, hpLossType: "melee" } + } else { + return { result: "miss", fumble: true, attackerHPLoss: "2d3", hpLossType: "melee" } + } + } + if (rollData.sumSuccess == -2) { + if (rollData.attackRollData.weapon.system.isranged) { + return { result: "miss", dangerous_fumble: true } + } else { + return { result: "miss", dangerous_fumble: true, attackerHPLoss: "1d3", hpLossType: "melee" } + } + } + if (rollData.sumSuccess == -1) { + return { result: "miss" } + } + if (rollData.sumSuccess == 0) { + if (rollData.attackRollData.weapon.system.isranged) { + return { result: "target_space", aoe: true } + } else { + return { result: "clash", hack_vs_shields: true } + } + } + if (rollData.sumSuccess == 1) { + return { result: "hit", defenderDamage: "1", entangle: true, knockback: true } + } + if (rollData.sumSuccess == 2) { + return { result: "hit", defenderDamage: "2", critical_1: true, entangle: true, knockback: true, penetrating_impale: true, hack_armors: true } + } + if (rollData.sumSuccess >= 3) { + return { result: "hit", defenderDamage: "3", critical_2: true, entangle: true, knockback: true, penetrating_impale: true, hack_armors: true } + } + } + + /* -------------------------------------------- */ + static async getFumble(weapon) { + const pack = game.packs.get("fvtt-malefices.rolltables") + const index = await pack.getIndex() + let entry + + if (weapon.isranged) { + entry = index.find(e => e.name === "Fumble! (ranged)") + } + if (!weapon.isranged) { + entry = index.find(e => e.name === "Fumble! (melee)") + } + let table = await pack.getDocument(entry._id) + const draw = await table.draw({ displayChat: false, rollMode: "gmroll" }) + return draw.results.length > 0 ? draw.results[0] : undefined + } + + /* -------------------------------------------- */ + static async processSuccessResult(rollData) { + if (game.user.isGM) { // Only GM process this + let result = rollData.successDetails + let attacker = game.actors.get(rollData.actorId) + let defender = game.canvas.tokens.get(rollData.attackRollData.defenderTokenId).actor + + if (attacker && result.attackerHPLoss) { + result.attackerHPLossValue = await attacker.incDecHP("-" + result.attackerHPLoss) + } + if (attacker && defender && result.defenderDamage) { + let dmgDice = (rollData.attackRollData.weapon.system.isranged) ? "d6" : "d8" + result.damageWeaponFormula = result.defenderDamage + dmgDice + result.defenderHPLossValue = await defender.incDecHP("-" + result.damageWeaponFormula) + } + if (result.fumble || (result.dangerous_fumble && MaleficesUtility.isWeaponDangerous(rollData.attackRollData.weapon))) { + result.fumbleDetails = await this.getFumble(rollData.weapon) + } + if (result.critical_1 || result.critical_2) { + let isDeadly = MaleficesUtility.isWeaponDeadly(rollData.attackRollData.weapon) + result.critical = await this.getCritical((result.critical_1) ? "I" : "II", rollData.attackRollData.weapon) + result.criticalText = result.critical.text + } + this.createChatWithRollMode(rollData.alias, { + content: await renderTemplate(`systems/fvtt-malefices/templates/chat-attack-defense-result.html`, rollData) + }) + console.log("Results processed", rollData) + } + } + + /* -------------------------------------------- */ + static async processAttackDefense(rollData) { + if (rollData.attackRollData) { + //console.log("Defender token, ", rollData, rollData.defenderTokenId) + let defender = game.canvas.tokens.get(rollData.attackRollData.defenderTokenId).actor + let sumSuccess = rollData.attackRollData.nbSuccess - rollData.nbSuccess + if (sumSuccess > 0) { + let armorResult = await defender.rollArmorDie(rollData) + rollData.armorResult = armorResult + sumSuccess += rollData.armorResult.nbSuccess + if (sumSuccess < 0) { // Never below 0 + sumSuccess = 0 + } + } + rollData.sumSuccess = sumSuccess + rollData.successDetails = this.getSuccessResult(rollData) + if (game.user.isGM) { + this.processSuccessResult(rollData) + } else { + game.socket.emit("system.fvtt-malefices", { msg: "msg_gm_process_attack_defense", data: rollData }); + } + } + } + + /* -------------------------------------------- */ + static async onSocketMesssage(msg) { + console.log("SOCKET MESSAGE", msg.name) + if (msg.name == "msg_update_roll") { + this.updateRollData(msg.data) + } + if (msg.name == "msg_gm_process_attack_defense") { + this.processSuccessResult(msg.data) + } + if (msg.name == "msg_gm_item_drop" && game.user.isGM) { + let actor = game.actors.get(msg.data.actorId) + let item + if (msg.data.isPack) { + item = await fromUuid("Compendium." + msg.data.isPack + "." + msg.data.itemId) + } else { + item = game.items.get(msg.data.itemId) + } + this.addItemDropToActor(actor, item) + } + } + + /* -------------------------------------------- */ + static computeFocusData(focus) { + let focusData = { + focusPoints: __focusCore[focus.core] + __focusPointTreatment[focus.treatment], + burnChance: __burnChanceTreatment[focus.treatment], + focusRegen: __focusRegenBond[focus.bond], + spellAttackBonus: __bonusSpellAttackBond[focus.bond], + spellDamageBonus: __bonusSpellDamageBond[focus.bond] + } + return focusData + } + + /* -------------------------------------------- */ + 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 getSpellCost(spell) { + return __spellCost[spell.system.level] + } + + /* -------------------------------------------- */ + static getArmorPenalty( item ) { + if (item && (item.type == "shield" || item.type == "armor")) { + return __armorPenalties[item.system.category] + } + return {} + } + + /* -------------------------------------------- */ + 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 updateSkill(skill) { + skill.system.level = skill.system.background + skill.system.basic + skill.system.class + skill.system.explevel + if (skill.system.level > 7) { skill.system.level = 7 } + skill.system.skilldice = __skillLevel2Dice[skill.system.level] + } + + /* -------------------------------------------- */ + static getDiceFromCover(cover) { + if (cover == "cover50") return 1 + return 0 + } + /* -------------------------------------------- */ + static getDiceFromSituational(cover) { + if (cover == "prone") return 1 + if (cover == "dodge") return 1 + if (cover == "moving") return 1 + if (cover == "engaged") return 1 + return 0 + } + + /* -------------------------------------------- */ + static async rollMalefices(rollData) { + + let actor = game.actors.get(rollData.actorId) + + // Build the dice formula + let diceFormula = "1d12" + if (rollData.skill) { + diceFormula += "+" + rollData.skill.finalvalue + } + if (rollData.crafting) { + diceFormula += "+" + rollData.crafting.system.level + } + if (rollData.spellAttack) { + diceFormula += "+" + rollData.spellAttack + } + diceFormula += "+" + rollData.bonusMalusRoll + + if (rollData.skill && rollData.skill.good) { + diceFormula += "+1d4" + } + if (rollData.weapon ) { + diceFormula += "+" + rollData.weapon.attackBonus + } + rollData.diceFormula = diceFormula + + // Performs roll + console.log("Roll formula", diceFormula) + let myRoll = rollData.roll + if (!myRoll) { // New rolls only of no rerolls + myRoll = new Roll(diceFormula).roll({ async: false }) + await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) + } + rollData.roll = myRoll + + rollData.isSuccess = false + if (rollData.targetCheck != "none") { + if (myRoll.total >= Number(rollData.targetCheck)) { + rollData.isSuccess = true + } + } + + if (rollData.spell) { + actor.spentFocusPoints(rollData.spell) + } + + let msg = await this.createChatWithRollMode(rollData.alias, { + content: await renderTemplate(`systems/fvtt-malefices/templates/chat/chat-generic-result.hbs`, rollData) + }) + msg.setFlag("world", "rolldata", rollData) + if (rollData.skillKey == "initiative") { + console.log("REGISTERED") + 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 "selfroll": return [game.user.id]; + } + return undefined; + } + /* -------------------------------------------- */ + 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-malefices", { 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 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 ChatMessage.create(chatOptions); + } + + /* -------------------------------------------- */ + static getBasicRollData() { + let rollData = { + rollId: randomID(16), + bonusMalusRoll: 0, + targetCheck: "none", + rollMode: game.settings.get("core", "rollMode") + } + MaleficesUtility.updateWithTarget(rollData) + return rollData + } + + /* -------------------------------------------- */ + static updateWithTarget(rollData) { + let target = MaleficesUtility.getTarget() + if (target) { + rollData.defenderTokenId = target.id + } + } + + /* -------------------------------------------- */ + static createChatWithRollMode(name, chatOptions) { + return 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/styles/simple.css b/styles/simple.css new file mode 100644 index 0000000..050b6f9 --- /dev/null +++ b/styles/simple.css @@ -0,0 +1,1356 @@ + /* ==================== (A) Fonts ==================== */ + + :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-avd12 .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-avd12 .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-avd12 .sheet-header .profile-img { + -webkit-box-flex: 0; + -ms-flex: 0 0 128px; + flex: 0 0 128px; + width: 128px; + height: auto; + max-height:160px; + margin-top: 0px; + margin-right: 10px; + object-fit: cover; + object-position: 50% 0; + border-width: 0px; +} + +.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-avd12 .sheet-header .header-fields { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.fvtt-avd12 .sheet-header h1.charname { + height: 50px; + padding: 0px; + margin: 5px 0; + border-bottom: 0; +} + +.fvtt-avd12 .sheet-header h1.charname input { + width: 100%; + height: 100%; + margin: 0; +} + +.fvtt-avd12 .sheet-tabs { + -webkit-box-flex: 0; + -ms-flex: 0; + flex: 0; +} + +.fvtt-avd12 .sheet-body, +.fvtt-avd12 .sheet-body .tab, +.fvtt-avd12 .sheet-body .tab .editor { + height: 100%; + font-size: 0.8rem; +} + +.editor { + border: 2; + height: 300px; + padding: 0 3px; +} + +.medium-editor { + border: 2; + height: 240px; + padding: 0 3px; +} + +.small-editor { + border: 2; + height: 120px; + padding: 0 3px; +} + +.fvtt-avd12 .tox .tox-editor-container { + background: #fff; +} + +.fvtt-avd12 .tox .tox-edit-area { + padding: 0 8px; +} + +.fvtt-avd12 .resource-label { + font-weight: bold; + text-transform: uppercase; +} + +.fvtt-avd12 .tabs { + height: 40px; + border-top: 1px solid #AAA; + border-bottom: 1px solid #AAA; + color: #000000; +} + +.fvtt-avd12 .tabs .item { + line-height: 40px; + font-weight: bold; +} + +.fvtt-avd12 .tabs .item.active { + text-decoration: underline; + text-shadow: none; +} + +.fvtt-avd12 .items-list { + list-style: none; + margin: 1px 0; + padding: 0; + overflow-y: auto; +} + +.fvtt-avd12 .items-list .item-header { + font-weight: bold; +} + +.fvtt-avd12 .items-list .item { + height: 30px; + line-height: 24px; + padding: 1px 0; + border-bottom: 1px solid #BBB; +} + +.fvtt-avd12 .items-list .item .item-image { + -webkit-box-flex: 0; + -ms-flex: 0 0 24px; + flex: 0 0 24px; + margin-right: 5px; +} + +.fvtt-avd12 .items-list .item img { + display: block; +} + +.fvtt-avd12 .items-list .item-name { + margin: 0; +} + +.fvtt-avd12 .items-list .item-controls { + -webkit-box-flex: 0; + -ms-flex: 0 0 86px; + flex: 0 0 86px; + text-align: right; +} + + +/* ======================================== */ +/* Sheet */ +.window-app.sheet .window-content .sheet-header{ + color: rgba(228, 240, 240, 0.75); + /*background: url("../images/ui/pc_sheet_bg.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"] { + background:white; + 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: #494e6b; + background: #494e6b; + 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(228, 240, 240, 0.75); + background: #494e6b; + 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(228, 240, 240, 0.75); + background: #494e6b; + 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: #494e6b; + color: rgba(228, 240, 240, 0.75); +} + +/* 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: 0.70rem; + 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(228, 240, 240, 0.75); +} +.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; +} +.competence-xp { + flex-grow: 0; + flex-basis: 2rem; + margin-right: 0.25rem; + margin-left: 0.25rem; +} +.blessures-title { + font-weight: bold; +} +.alchimie-title { + font-weight: bold; +} +.blessure-data { + flex-direction: row; + align-content: flex-start; + justify-content: flex-start; +} +.blessures-soins { + flex-grow: 0; + flex-basis: 32px; + margin-right: 4px; + margin-left: 4px; +} +.blessures-loc { + flex-grow: 0; + flex-basis: 96px; + margin-right: 4px; + margin-left: 4px; +} +.pointsreve-value { + flex-grow: 0; + flex-basis: 64px; + margin-right: 4px; + margin-left: 4px; +} + +.input-sante-header, +.stress-style { + flex-grow: 0; + flex-basis: 64px; + margin-right: 4px; + margin-left: 4px; +} + +.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; +} +.npc-ability-label { + flex-grow: 2; +} + +.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; +} +/* ======================================== */ +.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%; + color: rgba(220,220,220,0.75); +} + +/* 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(220,220,220,0.75); + 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-actor-name { + padding: 4px; +} + +.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-river-full { + height: 5rem; + align-items: flex-start; +} + +.div-river { + align-content: center; + margin-left: 8px; + align-content:space-around; + justify-content: space-around; +} + +.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{ + width: 25px; + height: 25px; + 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/crucible_pause_logo.jpg); + height: 160px; + width: 160px; + top: -80px; + left: calc(50% - 132px); +} + +#logo { + content : url(../images/ui/crucible_game_logo.png); + 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; + background: black; + color: white; +} +.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 { + flex-grow:2; + max-width: 12rem; + min-width: 12rem; +} +.item-name-label-long2 { + 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 { + margin-top: 4px; + flex-grow:1; + max-width: 8rem; + min-width: 8rem; +} +.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; +} +.drop-module-step { + background: linear-gradient(to bottom, #6c95b9fc 5%, #105177ab 100%); + background-color: #7d5d3b00; + border-radius: 3px; + border: 2px ridge #846109; +} +.module-level-box { + background: linear-gradient(to bottom, #6c95b9fc 5%, #105177ab 100%); + border-radius: 5px; + padding: 4px; + margin-bottom: 8px; + margin-left: 12px; + border: 2px ridge #846109; + max-width: 45%; + min-height: 64px; + align-content: center; +} +.color-selected { + background: linear-gradient(to bottom, #9ae470fc 5%, #247e30ab 100%); +} +.flex-center { + align-items: center; + align-content: space-between; +} \ No newline at end of file diff --git a/system.json b/system.json new file mode 100644 index 0000000..670b7f0 --- /dev/null +++ b/system.json @@ -0,0 +1,43 @@ +{ + "description": "Maléfices, Le Jeu de Rôle", + "esmodules": [ + "modules/malefices-main.js" + ], + "gridDistance": 1, + "gridUnits": "u", + "languages": [ + { + "lang": "fr", + "name": "French", + "path": "lang/fr.json", + "flags": {} + } + ], + "authors": [ + { + "name": "Uberwald", + "flags": {} + } + ], + "license": "LICENSE.txt", + "manifest": "https://www.uberwald.me/gitea/public/fvtt-malefices/raw/branch/master/system.json", + "compatibility": { + "minimum": "10", + "verified": "10", + "maximum": "10" + }, + "id": "fvtt-malefices", + "primaryTokenAttribute": "secondary.health", + "secondaryTokenAttribute": "secondary.delirium", + "socket": true, + "styles": [ + "styles/simple.css" + ], + "packs": [ + ], + "title": "Maléfices, le Jeu de Rôle", + "url": "https://www.uberwald.me/gitea/public/fvtt-malefices", + "version": "10.0.0", + "download": "https://www.uberwald.me/gitea/public/fvtt-malefices/archive/fvtt-malefices-v10.0.0.zip", + "background": "systems/fvtt-malefices/images/ui/malefice_welcome_page.webp" +} \ No newline at end of file diff --git a/template.json b/template.json new file mode 100644 index 0000000..341bf2f --- /dev/null +++ b/template.json @@ -0,0 +1,109 @@ +{ + "Actor": { + "types": [ + "personnage", + "pnj" + ], + "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, + "attributs": { + "constitution": { + "label": "Constitution", + "abbrev": "constitution", + "value": 0, + "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": "habilite", + "value": 0, + "max": 0 + }, + "spiritualite": { + "label": "Spiritualite", + "abbrev": "spiritualite", + "value": 0, + "max": 0 + }, + "rationnalite": { + "label": "Rationnalite", + "abbrev": "rationnalite", + "value": 0, + "max": 0 + } + } + }, + "npccore": { + "npctype": "", + "description": "" + } + }, + "personnage": { + "templates": [ + "biodata", + "core" + ] + } + }, + "Item": { + "types": [ + "arme" + ], + "templates": { + }, + "arme" : { + "armetype": 0, + "dommagenormale": 0, + "dommagepart": 0, + "dommagecritique": 0, + "dommagecritiqueKO": false, + "dommagecritiquemort": false + } + } +} \ 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..657ce17 --- /dev/null +++ b/templates/actors/actor-sheet.hbs @@ -0,0 +1,626 @@ +
+ + {{!-- Sheet Header --}} +
+
+

+
+ +
+ +
+ {{#each system.attributes as |attr attrKey|}} +
+
+ {{attr.label}} + +
+ {{#each attr.skills as |skill skillKey|}} + + {{/each}} +
+   +
+ {{#if (eq attrKey "might")}} +
+ Universal +
+ {{#each @root.system.universal.skills as |skill skillKey|}} + + {{/each}} + {{else}} +
+
+ {{/if}} +
+ {{/each}} + +
+ +
+
+
+
+ + + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
+ + {{!-- Skills Tab --}} +
+ +
    +
  • + Level + + +   + + Health + / + + +   + +
  • +
+ +
+
    +
  • + +

    +
    +
  • + {{#each system.mitigation as |mitigation key|}} +
  • + {{mitigation.label}} + {{mitigation.value}} +
  • + {{/each}} +
+ +
    +
  • + +

    +
    +
  • + {{#each system.bonus as |bonus key|}} +
  • + {{upperFirst key}} +
      + {{#each bonus as |value key2|}} +
    • + {{upperFirst key2}} + {{value}} +
    • + {{/each}} +
    +
  • + {{/each}} +
+
+
+ + {{!-- Modules Tab --}} +
+ +
+
    +
  • + +

    +
    +
  • + {{#each modules as |module key|}} +
  • + + {{module.name}} + +
     
    +
    + +
    +
  • + {{/each}} +
+
+ +
+ + + {{!-- Spells Tab --}} +
+ +
+ +
    +
  • + Focus Regen + + + +   + + Focus Points + / + + +   +
  • +
+ +
    +
  • + +

    +
    + + + + + + +
  • + + {{#each spells as |spell key|}} +
  • + + + {{spell.name}} + + {{upperFirst spell.system.spelltype}} + {{upperFirst spell.system.level}} +
     
    +
    + +
    +
  • + {{/each}} + +
+ +
+
+ + {{!-- moves Tab --}} +
+ +
+ + + +
    +
  • + +

    +
    + + + + + + +
  • + + {{#each spells as |spell key|}} +
  • + + + {{spell.name}} + + {{upperFirst spell.system.spelltype}} + {{upperFirst spell.system.level}} +
     
    +
    + +
    +
  • + {{/each}} +
+ +
+
+ + {{!-- traits Tab --}} +
+ +
+ +
    +
  • + +

    +
    + + + + + + +
  • + + {{#each traits as |trait key|}} +
  • + + + {{trait.name}} + + {{upperFirst trait.system.spelltype}} + {{upperFirst trait.system.level}} +
     
    +
    + +
    +
  • + {{/each}} + +
+ +
+
+ + {{!-- Equipement Tab --}} +
+ +
+

Encumbrance

+ Current : {{encCurrent}} + Capacity : {{encCapacity}} +
+ +
    +
  • + +

    +
    + + + + + + + + + +
     
    +
    + +
    +
  • + {{#each moneys as |money key|}} +
  • + + {{money.name}} + + + + + + + + {{#if money.system.idrDice}} + {{money.system.idrDice}} + {{else}} +  -  + {{/if}} + + +
     
    +
    + +
    +
  • + {{/each}} +
+ +
    +
  • + +

    +
    + + + + + + +
    + +
    +
  • + {{#each weapons as |weapon key|}} +
  • + + {{weapon.name}} + + + +
     
    + +
  • + {{/each}} +
+ +
    +
  • + +

    +
    + + + + + + + +
     
    +
    + +
    +
  • + {{#each armors as |armor key|}} +
  • + + {{armor.name}} + {{upperFirst armor.system.armortype}} + {{armor.system.absorprionroll}} + +
     
    + +
  • + {{/each}} +
+ + + + + +
+ +
+ + {{!-- Equipement Tab --}} +
+ + +
    +
  • + +

    +
    + + + +
    + +
    +
  • + {{#each craftingSkills as |crafting key|}} +
  • + + {{crafting.name}} + + +
     
    +
    + +
    +
  • + {{/each}} +
+ +
+ + {{!-- Biography Tab --}} +
+
+
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + + +
  • +
+
+
+ + +
+

Background :

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

Notes :

+
+ {{editor data.biodata.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-generic-result.hbs b/templates/chat/chat-generic-result.hbs new file mode 100644 index 0000000..c967f5d --- /dev/null +++ b/templates/chat/chat-generic-result.hbs @@ -0,0 +1,53 @@ +
+ {{#if actorImg}} + {{alias}} + {{/if}} +

{{alias}}

+
+ +
+ + {{#if img}} +
+ {{name}} +
+ {{/if}} + +
+
+ +
+ +
+ + diff --git a/templates/dialogs/roll-dialog-generic.hbs b/templates/dialogs/roll-dialog-generic.hbs new file mode 100644 index 0000000..a29824e --- /dev/null +++ b/templates/dialogs/roll-dialog-generic.hbs @@ -0,0 +1,88 @@ +
+
+ {{#if img}} + + {{/if}} +

{{title}}

+
+ +
+ + {{#if skill}} +
+ Skill : + {{skill.name}} ({{skill.finalvalue}}) +
+ {{/if}} + + {{#if crafting}} +
+ Crafting : + {{crafting.name}} ({{crafting.system.level}}) +
+ {{/if}} + + {{#if weapon}} +
+ Weapon Attack Bonus : + {{weapon.attackBonus}} +
+ {{/if}} + + {{#if spell}} +
+ Spell : + {{spell.name}} ({{upperFirst spell.system.level}} - Cost : {{spellCost}}) +
+
+ Spell Attack level : + {{spellAttack}} +
+
+ Spell Damage level : + {{spellDamage}} +
+ {{/if}} + +
+ Bonus/Malus : + +
+ + {{#if (eq skillKey "initiative") }} + + {{else}} + {{#if (or spell weapon)}} + + {{else}} +
+ Target check : + +
+ {{/if}} + {{/if}} + +
+ +
\ No newline at end of file diff --git a/templates/items/item-weapon-sheet.hbs b/templates/items/item-weapon-sheet.hbs new file mode 100644 index 0000000..e82ccc0 --- /dev/null +++ b/templates/items/item-weapon-sheet.hbs @@ -0,0 +1,64 @@ +
+
+ +
+

+
+
+ + {{> systems/fvtt-avd12/templates/items/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
+ + {{> systems/fvtt-avd12/templates/items/partial-item-description.hbs}} + +
+ +
+
    + +
  • + + +
  • + +
  • + + +
  • + +
  • + + +
  • + +
  • + + + + + + + +
  • + +
  • + + +
  • + + +
+
+
+ +
+
diff --git a/templates/items/partial-item-description.hbs b/templates/items/partial-item-description.hbs new file mode 100644 index 0000000..26c9623 --- /dev/null +++ b/templates/items/partial-item-description.hbs @@ -0,0 +1,8 @@ +
+
+ +
+ {{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..66a739f --- /dev/null +++ b/templates/items/partial-item-nav.hbs @@ -0,0 +1,8 @@ +{{!-- Sheet Tab Navigation --}} +