diff --git a/README.md b/README.md index 8a8f0e9..8f7445e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ It provides support for **character sheets only**, game content should be drawn * clean-up layout * Add relationship Item Type -* Vaesen, NPC and HQ sheets +* Vaesen and NPC sheets * UX improvements * Dialogs and Chat @@ -36,6 +36,8 @@ It provides support for **character sheets only**, game content should be drawn * Localize Dialog and tooltip texts +* Link conditions on sheet and status icons on the character sheet (adding a status from the token toggels it on the sheet and visa versa) + ## Related Website - https://foundryvtt.com/ @@ -45,6 +47,13 @@ It provides support for **character sheets only**, game content should be drawn [GNU General Public License v3.0](https://choosealicense.com/licenses/gpl-3.0/) ## Release Notes +v2.1.4 +- Added roll dialog to the Recovery Rolls to allow for bonus dice from headquarters etc. +- Removed Damage from results in chat on things which have no damage. +- Added custom condition icons in foundry status icon menu (https://github.com/fvtt-fria-ligan/vaesen-foundry-vtt/blob/master/asset/status_icons.png?raw=true) +- Tightned styled and clarified the roll dialog (https://github.com/fvtt-fria-ligan/vaesen-foundry-vtt/blob/master/asset/roll_dialog.png?raw=true) + + v2.1.3 - (bugfix) Agility modifier for Armor was not calculating in the roll dialog. Now highest negative modifier for armor will apply to agility tests. - (Change) Updated look and feel of Headquarters sheet. Now all upgrades are on first page and "history" is relegated to a tab. diff --git a/asset/roll_dialog.png b/asset/roll_dialog.png new file mode 100644 index 0000000..3e67e69 Binary files /dev/null and b/asset/roll_dialog.png differ diff --git a/asset/status/arm-sling.svg b/asset/status/arm-sling.svg new file mode 100644 index 0000000..cba4c3f --- /dev/null +++ b/asset/status/arm-sling.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status/broken-bone.svg b/asset/status/broken-bone.svg new file mode 100644 index 0000000..34c8ea7 --- /dev/null +++ b/asset/status/broken-bone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status/despair.svg b/asset/status/despair.svg new file mode 100644 index 0000000..54f9e6b --- /dev/null +++ b/asset/status/despair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status/oppression.svg b/asset/status/oppression.svg new file mode 100644 index 0000000..f72c12c --- /dev/null +++ b/asset/status/oppression.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status/pummeled.svg b/asset/status/pummeled.svg new file mode 100644 index 0000000..d945c2b --- /dev/null +++ b/asset/status/pummeled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status/revolt.svg b/asset/status/revolt.svg new file mode 100644 index 0000000..4786a98 --- /dev/null +++ b/asset/status/revolt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status/shattered-heart.svg b/asset/status/shattered-heart.svg new file mode 100644 index 0000000..ff42353 --- /dev/null +++ b/asset/status/shattered-heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status/terror.svg b/asset/status/terror.svg new file mode 100644 index 0000000..221e496 --- /dev/null +++ b/asset/status/terror.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/status_icons.png b/asset/status_icons.png new file mode 100644 index 0000000..c7e1aa1 Binary files /dev/null and b/asset/status_icons.png differ diff --git a/script/actor/vaesen.js b/script/actor/vaesen.js index 39b2c71..3fccdb2 100644 --- a/script/actor/vaesen.js +++ b/script/actor/vaesen.js @@ -9,6 +9,77 @@ export class VaesenActor extends Actor { } + /* -------------------------------------------- */ + findStatusEffectById(id) { + return Array.from(this.effects?.values()) + .find(it => it.data.flags.core?.statusId === id); +} + +/* -------------------------------------------- */ +async deleteStatusEffectById(id, options = {renderSheet: true}) { + console.log("delete by id: " + id); + const effects = Array.from(this.effects?.values()) + .filter(it => it.data.flags.core?.statusId === id); + await this._deleteStatusEffects(effects, options); +} + +/* -------------------------------------------- */ +async _deleteStatusEffects(effects, options) { + console.log(effects); + console.log(effects.map(it => it.id)); + console.log(options); + const ids = Array.from(effects.map(it => it.id)); + await this.deleteEmbeddedDocuments('ActiveEffect', ids, options); + //await this._deleteStatusEffectsByIds(effects.map(it => it.id), options); + +} + +/* -------------------------------------------- */ +async _deleteStatusEffectsByIds(effectIds, options) { + await this.deleteEmbeddedDocuments('ActiveEffect', effectIds, options); +} + +/* -------------------------------------------- */ +async addStatusEffectById(id, options = {renderSheet: false, unique: true}) { + if (this.hasStatusEffectById(id) && options.unique === true) { + return; + } + const statusEffect = CONFIG.statusEffects.find(it => it.id === id); + await this.addStatusEffect(statusEffect, options); +} + +/* -------------------------------------------- */ +async addStatusEffect(statusEffect, options = {renderSheet: false, overlay: false}) { + //await this.deleteStatusEffectById(statusEffect.id, options); + const effect = duplicate(statusEffect); + console.log(effect.label); + console.log(effect.id); + await this.createEmbeddedDocuments("ActiveEffect", [{ + "flags.core.statusId": effect.id, + "flags.core.overlay": options.overlay, + label: effect.label, + icon: effect.icon, + origin: this.uuid, + }], options); +} + + /* -------------------------------------------- */ + hasStatusEffectById(id) { + const effects = this.findStatusEffectById(id); + return (effects !== undefined); +} + +async toggleStatusEffectById(id, options = {renderSheet: true}) { + console.log("over to the character for toggeling"); + + const effect = this.findStatusEffectById(id); + + if (effect) { + await this.deleteStatusEffectById(id); + } else { + await this.addStatusEffectById(id, options) + } +} static async create(data, options={}) { data.token = data.token || {}; diff --git a/script/config.js b/script/config.js new file mode 100644 index 0000000..f3e67f8 --- /dev/null +++ b/script/config.js @@ -0,0 +1,3 @@ +export const tftloop = {}; + +export const vaesen = {}; \ No newline at end of file diff --git a/script/hooks.js b/script/hooks.js index 0de90ce..72ebac7 100644 --- a/script/hooks.js +++ b/script/hooks.js @@ -14,8 +14,16 @@ import { AttackCharacterSheet } from "./sheet/attack.js"; import { UpgradeCharacterSheet } from "./sheet/upgrade.js"; import { prepareRollDialog, push } from "./util/roll.js"; import { registerSystemSettings } from "./util/settings.js"; +import {vaesen} from "./config.js" +import {conditions} from "./util/conditions.js"; + +Hooks.once("ready", function(){ + conditions.onReady(); +}); + Hooks.once("init", () => { + CONFIG.vaesen = vaesen; CONFIG.Combat.initiative = { formula: "1d10", decimals: 0 }; CONFIG.Actor.documentClass = VaesenActor; CONFIG.anonymousSheet = {}; @@ -39,6 +47,18 @@ Hooks.once("init", () => { preloadHandlebarsTemplates(); // Register System Settings registerSystemSettings(); + Token.prototype._drawEffect = async function(src, i, bg, w, tint) { + const multiplier = 3; + const divisor = 3 * this.data.height; + w = (w / 2) * multiplier; + let tex = await loadTexture(src); + let icon = this.effects.addChild(new PIXI.Sprite(tex)); + icon.width = icon.height = w; + icon.y = Math.floor(i / divisor) * w; + icon.x = (i % divisor) * w; + if ( tint ) icon.tint = tint; + this.effects.addChild(icon); + }; }); Hooks.once('diceSoNiceReady', (dice3d) => { @@ -114,3 +134,5 @@ function preloadHandlebarsTemplates() { ]; return loadTemplates(templatePaths); } + + diff --git a/script/sheet/player.js b/script/sheet/player.js index 12650a9..abfcdbf 100644 --- a/script/sheet/player.js +++ b/script/sheet/player.js @@ -1,4 +1,5 @@ import { prepareRollDialog, push, roll } from "../util/roll.js"; +import {conditions} from "../util/conditions.js"; export class PlayerCharacterSheet extends ActorSheet { @@ -43,9 +44,67 @@ export class PlayerCharacterSheet extends ActorSheet { this.setSwag(superData.data); this.computeSkills(superData.data); this.computeItems(superData.data); + //console.log("these are the effects we have: ") + //console.log(this.actor.effects); + //this.affirmConditions(this.actor); return superData; } + + async affirmConditions(actor){ + + + let currentConditions = []; + actor.effects.forEach(function(value, key) { + currentConditions.push(value.data.flags.core?.statusId); + }); + + console.log(currentConditions); + // set state of sheet checks for set conditions + if (currentConditions.indexOf('physical') === -1){ + actor.update({"data.condition.physical.isBroken": false}); + } else { + actor.update({"data.condition.physical.isBroken": true}); + } + if (currentConditions.indexOf('exhausted') === -1){ + actor.update({"data.condition.physical.states.exhausted.isChecked": false}); + } else { + actor.update({"data.condition.physical.states.exhausted.isChecked": true}); + } + if (currentConditions.indexOf('battered') === -1){ + actor.update({"data.condition.physical.states.battered.isChecked": false}); + } else { + actor.update({"data.condition.physical.states.battered.isChecked": true}); + } + if (currentConditions.indexOf('wounded') === -1){ + actor.update({"data.condition.physical.states.wounded.isChecked": false}); + } else { + actor.update({"data.condition.physical.states.wounded.isChecked": true}); + } + if (currentConditions.indexOf('angry') === -1){ + actor.update({"data.condition.mental.states.angry.isChecked": false}); + } else { + actor.update({"data.condition.mental.states.angry.isChecked": true}); + } + if (currentConditions.indexOf('frightened') === -1){ + actor.update({"data.condition.mental.states.frightened.isChecked": false}); + } else { + actor.update({"data.condition.mental.states.frightened.isChecked": true}); + } + if (currentConditions.indexOf('hopeless') === -1){ + actor.update({"data.condition.mental.states.hopeless.isChecked": false}); + } else { + actor.update({"data.condition.mental.states.hopeless.isChecked": true}); + } + if (currentConditions.indexOf('mental') === -1){ + actor.update({"data.condition.mental.isBroken": false}); + } else { + actor.update({"data.condition.mental.isBroken": true}); + } + } + + + activateListeners(html) { super.activateListeners(html); html.find('.item-create').click(ev => { this.onItemCreate(ev); }); @@ -58,9 +117,14 @@ export class PlayerCharacterSheet extends ActorSheet { html.find('.resources b').click(ev => { prepareRollDialog(this, "Resources", 0, 0, this.actor.data.data.resources, 0) }); - + //html.find(".physical .condition").click(conditions.eventsProcessing.onToggleEffect.bind(this)); html.find('.physical .condition').click(ev => { + ev.preventDefault(); const conditionName = $(ev.currentTarget).data("key"); + //let actor = this.actor; + //await actor.toggleStatusEffectByID(conditionName); + + //console.log(conditionName); let conditionValue; if (conditionName === "physical") { conditionValue = this.actor.data.data.condition.physical.isBroken; @@ -77,6 +141,8 @@ export class PlayerCharacterSheet extends ActorSheet { } this._render(); }); + + //html.find(".mental .condition").click(conditions.eventsProcessing.onToggleEffect.bind(this)); html.find('.mental .condition').click(ev => { const conditionName = $(ev.currentTarget).data("key"); let conditionValue; @@ -85,7 +151,7 @@ export class PlayerCharacterSheet extends ActorSheet { this.actor.update({"data.condition.mental.isBroken": !conditionValue}); } else { conditionValue = this.actor.data.data.condition.mental.states[conditionName].isChecked; - if (conditionName === "angry") { + if (conditionName === "angry") { this.actor.update({"data.condition.mental.states.angry.isChecked": !conditionValue}); } else if (conditionName === "frightened") { this.actor.update({"data.condition.mental.states.frightened.isChecked": !conditionValue}); @@ -102,8 +168,10 @@ export class PlayerCharacterSheet extends ActorSheet { const attribute = this.actor.data.data.attribute[attributeName]; const testName = game.i18n.localize(attribute.label + "_ROLL"); let bonus = this.computeBonusFromConditions(attributeName); - prepareRollDialog(this, testName, attribute.value, 0, bonus, 0) + prepareRollDialog(this, testName, attribute.value, 0, bonus, 0, testName, '') }); + + html.find('.skill b').click(ev => { const div = $(ev.currentTarget).parents(".skill"); const skillName = div.data("key"); @@ -112,7 +180,8 @@ export class PlayerCharacterSheet extends ActorSheet { let bonusConditions = this.computeBonusFromConditions(skill.attribute); let bonusArmor = this.computeBonusFromArmor(skillName); const testName = game.i18n.localize(skill.label); - prepareRollDialog(this, testName, attribute.value, skill.value, bonusConditions + bonusArmor, 0) + prepareRollDialog(this, testName, attribute.value, skill.value, bonusConditions + bonusArmor, 0, skill.attribute, testName ) + //prepareRollDialog(this, testName, attribute.value, skill.value, bonusConditions + bonusArmor, 0) }); html.find('.armor .icon').click(ev => { this.onArmorRoll(ev); }); @@ -220,11 +289,11 @@ export class PlayerCharacterSheet extends ActorSheet { if(type==="physical"){ let pool = physique+precision; - roll(this, "Physical Recovery", 0, 0, pool, 0); + prepareRollDialog(this, "Physical Recovery", pool, 0, 0, 0, "Physique + Precision"); } else { let pool = logic+empathy; - roll(this, "Mental Recovery", 0, 0, pool, 0); + prepareRollDialog(this, "Mental Recovery", pool, 0, 0, 0, "Logic + Empathy"); } diff --git a/script/util/conditions.js b/script/util/conditions.js new file mode 100644 index 0000000..3b6247c --- /dev/null +++ b/script/util/conditions.js @@ -0,0 +1,89 @@ + +export class conditions{ + + static EXHAUSTED = "exhausted"; + static BATTERED = "battered"; + static WOUNDED = "wounded"; + static P_BROKEN = "physical"; + static ANGRY = "angry"; + static FRIGHTENED = "frightened"; + static HOPELESS = "hopeless"; + static M_BROKEN = "mental"; + + static conditionPath = "systems/vaesen/asset/status/"; + + static vasenConditions = [ + { + id: "exhausted", + label: "Exhausted", + icon: `${this.conditionPath}oppression.svg` + }, + { + id: "battered", + label: "Battered", + icon: `${this.conditionPath}pummeled.svg` + }, + { + id: "wounded", + label: "Wounded", + icon: `${this.conditionPath}arm-sling.svg` + }, + { + id: "physical", + label: "Physically Broken", + icon: `${this.conditionPath}broken-bone.svg` + }, + { + id: "angry", + label: "Angry", + icon: `${this.conditionPath}revolt.svg` + }, + { + id: "frightened", + label: "Frightened", + icon: `${this.conditionPath}terror.svg` + }, + { + id: "hopeless", + label: "Hopeless", + icon: `${this.conditionPath}despair.svg` + }, + { + id: "mental", + label: "Mentally Broken", + icon: `${this.conditionPath}shattered-heart.svg` + } + ]; + + + static allConditions = conditions.vasenConditions; + + static onReady() { + + CONFIG.vaesen.allConditions = this.allConditions; + CONFIG.statusEffects = conditions.vasenConditions; + } + + + static getStatusEffectBy(id){ + return this.allStatusEffects.find(effect => effect.id === id); + } + +} + +conditions.eventsProcessing = { + "onToggleEffect": async function (event) { + + + event.preventDefault(); + //let element = event.currentTarget; + let actor = this.actor; + let conditionName = $(event.currentTarget).data("key"); + //let effectId = element.dataset.effectId; + console.log(conditionName); + console.log(actor); + + await actor.toggleStatusEffectById(conditionName) + + } +} \ No newline at end of file diff --git a/script/util/roll.js b/script/util/roll.js index dc8d76f..3753ea9 100644 --- a/script/util/roll.js +++ b/script/util/roll.js @@ -1,11 +1,21 @@ -export function prepareRollDialog(sheet, testName, attributeDefault, skillDefault, bonusDefault, damageDefault) { - let attributeHtml = buildInputHtmlDialog("Attribute", attributeDefault); - let skillHtml = buildInputHtmlDialog("Skill", skillDefault); - let bonusHtml = buildInputHtmlDialog("Bonus", bonusDefault); - let damageHtml = buildInputHtmlDialog("Damage", damageDefault); +export function prepareRollDialog(sheet, testName, attributeDefault, skillDefault, bonusDefault, damageDefault, attName = "Attribute", skName = "Skill"){ + let attributeHtml = buildHtmlDialog(attName, attributeDefault, "attribute"); + let skillHtml = buildHtmlDialog(skName, skillDefault, "skill"); + let bonusHtml = buildInputHtmlDialog("Bonus Dice", bonusDefault, "bonus"); + let damageHtml = buildInputHtmlDialog("Damage", damageDefault, "damage"); + + let d = new Dialog({ title: "Test : " + testName, - content: buildDivHtmlDialog(attributeHtml + skillHtml + bonusHtml + damageHtml), + content: buildDivHtmlDialog(` +
` + diceName + + `:
+` + diceName + + `:
+