diff --git a/daggerheart.mjs b/daggerheart.mjs index 167dbc49..6b634ffc 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -16,7 +16,7 @@ import Resources from './module/applications/resources.mjs'; import { NarrativeCountdowns, registerCountdownApplicationHooks } from './module/applications/countdowns.mjs'; import DHDualityRoll from './module/data/chat-message/dualityRoll.mjs'; import { DualityRollColor } from './module/data/settings/Appearance.mjs'; -import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/applications/roll.mjs' +import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/applications/roll.mjs'; import { DhMeasuredTemplate } from './module/placeables/_module.mjs'; import { renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs'; import { renderMeasuredTemplate } from './module/enrichers/TemplateEnricher.mjs'; diff --git a/lang/en.json b/lang/en.json index ed989ee0..94599465 100755 --- a/lang/en.json +++ b/lang/en.json @@ -207,6 +207,12 @@ "Session": "Session", "Shortrest": "Short Rest", "Longrest": "Long Rest" + }, + "Damage": { + "Severe": "Severe", + "Major": "Major", + "Minor": "Minor", + "None": "None" } }, "ActionType": { @@ -1084,6 +1090,21 @@ "Title": "Ownership Selection - {name}", "Default": "Default Ownership" }, + "DamageReduction": { + "Title": "Damage Reduction", + "ArmorMarks": "Armor Marks", + "UsedMarks": "Used Marks", + "Stress": "Stress", + "ArmorWithStress": "Spend 1 stress to use an extra mark", + "UnncessaryStress": "You don't need to expend stress", + "StressReduction": "Reduce By Stress", + "Notifications": { + "DamageAlreadyNone": "The damage has already been reduced to none", + "NoAvailableArmorMarks": "You have no more available armor marks", + "NotEnoughStress": "You don't have enough stress", + "DamageIgnore": "{character} did not take damage" + } + }, "Sheets": { "PC": { "Name": "Name", diff --git a/module/applications/config/Action.mjs b/module/applications/config/Action.mjs index 6551527d..591a7263 100644 --- a/module/applications/config/Action.mjs +++ b/module/applications/config/Action.mjs @@ -62,7 +62,8 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { context.tabs = this._getTabs(); context.config = SYSTEM; if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id)); - if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack') context.hasBaseDamage = !!this.action.parent.damage; + if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack') + context.hasBaseDamage = !!this.action.parent.damage; context.getRealIndex = this.getRealIndex.bind(this); context.disableOption = this.disableOption.bind(this); context.isNPC = this.action.actor.type !== 'character'; @@ -77,9 +78,9 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { disableOption(index, options, choices) { const filtered = foundry.utils.deepClone(options); Object.keys(filtered).forEach(o => { - if(choices.find((c, idx) => c.type === o && index !== idx)) delete filtered[o]; + if (choices.find((c, idx) => c.type === o && index !== idx)) delete filtered[o]; }); - return filtered + return filtered; } getRealIndex(index) { @@ -113,14 +114,16 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) { data = foundry.utils.mergeObject(this.action.toObject(), submitData), container = foundry.utils.getProperty(this.action.parent, this.action.systemPath); let newActions; - if(Array.isArray(container)) { + if (Array.isArray(container)) { newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data); } else newActions = data; - + const updates = await this.action.parent.parent.update({ [`system.${this.action.systemPath}`]: newActions }); if (!updates) return; - this.action = Array.isArray(container) ? foundry.utils.getProperty(updates.system, this.action.systemPath)[this.action.index] : foundry.utils.getProperty(updates.system, this.action.systemPath); + this.action = Array.isArray(container) + ? foundry.utils.getProperty(updates.system, this.action.systemPath)[this.action.index] + : foundry.utils.getProperty(updates.system, this.action.systemPath); this.render(); } diff --git a/module/applications/costSelectionDialog.mjs b/module/applications/costSelectionDialog.mjs index 2b948a04..8abc9a7a 100644 --- a/module/applications/costSelectionDialog.mjs +++ b/module/applications/costSelectionDialog.mjs @@ -42,7 +42,7 @@ export default class CostSelectionDialog extends HandlebarsApplicationMixin(Appl } async _prepareContext(_options) { - console.log(this.costs) + console.log(this.costs); const updatedCosts = this.action.calcCosts(this.costs), updatedUses = this.action.calcUses(this.uses); return { @@ -56,12 +56,12 @@ export default class CostSelectionDialog extends HandlebarsApplicationMixin(Appl const data = foundry.utils.expandObject(formData.object); this.costs = foundry.utils.mergeObject(this.costs, data.costs); this.uses = foundry.utils.mergeObject(this.uses, data.uses); - this.render(true) + this.render(true); } static sendCost(event) { event.preventDefault(); - this.resolve({ costs: this.action.getRealCosts(this.costs), uses: this.uses}); + this.resolve({ costs: this.action.getRealCosts(this.costs), uses: this.uses }); this.close(); } -} \ No newline at end of file +} diff --git a/module/applications/damageReductionDialog.mjs b/module/applications/damageReductionDialog.mjs new file mode 100644 index 00000000..676def9e --- /dev/null +++ b/module/applications/damageReductionDialog.mjs @@ -0,0 +1,220 @@ +import { damageKeyToNumber, getDamageLabel } from '../helpers/utils.mjs'; + +const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; + +export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(resolve, reject, actor, damage) { + super({}); + + this.resolve = resolve; + this.reject = reject; + this.actor = actor; + this.damage = damage; + + const maxArmorMarks = Math.min( + actor.system.armorScore - actor.system.armor.system.marks.value, + actor.system.rules.maxArmorMarked.total + ); + + const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => { + acc[foundry.utils.randomID()] = { selected: false }; + return acc; + }, {}); + const stress = [...Array(actor.system.rules.maxArmorMarked.stressExtra ?? 0).keys()].reduce((acc, _) => { + acc[foundry.utils.randomID()] = { selected: false }; + return acc; + }, {}); + this.marks = { armor, stress }; + + this.availableStressReductions = Object.keys(actor.system.rules.stressDamageReduction).reduce((acc, key) => { + const dr = actor.system.rules.stressDamageReduction[key]; + if (dr.enabled) { + if (acc === null) acc = {}; + + const damage = damageKeyToNumber(key); + acc[damage] = { + cost: dr.cost, + selected: false, + from: getDamageLabel(damage), + to: getDamageLabel(damage - 1) + }; + } + + return acc; + }, null); + } + + get title() { + return game.i18n.localize('DAGGERHEART.DamageReduction.Title'); + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'views', 'damage-reduction'], + position: { + width: 240, + height: 'auto' + }, + actions: { + setMarks: this.setMarks, + useStressReduction: this.useStressReduction, + takeDamage: this.takeDamage + }, + form: { + handler: this.updateData, + submitOnChange: true, + closeOnSubmit: false + } + }; + + /** @override */ + static PARTS = { + damageSelection: { + id: 'damageReduction', + template: 'systems/daggerheart/templates/views/damageReduction.hbs' + } + }; + + /* -------------------------------------------- */ + + /** @inheritDoc */ + get title() { + return game.i18n.localize('DAGGERHEART.DamageReduction.Title'); + } + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + + const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } = + this.getDamageInfo(); + + context.armorScore = this.actor.system.armorScore; + context.armorMarks = currentMarks; + context.basicMarksUsed = selectedArmorMarks.length === this.actor.system.rules.maxArmorMarked.total; + + const stressReductionStress = this.availableStressReductions + ? stressReductions.reduce((acc, red) => acc + red.cost, 0) + : 0; + context.stress = + selectedStressMarks.length > 0 || this.availableStressReductions + ? { + value: + this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress, + maxTotal: this.actor.system.resources.stress.maxTotal + } + : null; + + context.marks = this.marks; + context.availableStressReductions = this.availableStressReductions; + + context.damage = getDamageLabel(this.damage); + context.reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null; + context.currentDamage = context.reducedDamage ?? context.damage; + + return context; + } + + static updateData(event, _, formData) { + const form = foundry.utils.expandObject(formData.object); + this.render(true); + } + + getDamageInfo = () => { + const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected); + const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected); + const stressReductions = Object.values(this.availableStressReductions).filter(red => red.selected); + const currentMarks = + this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length; + + const currentDamage = + this.damage - selectedArmorMarks.length - selectedStressMarks.length - stressReductions.length; + + return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage }; + }; + + static setMarks(_, target) { + const currentMark = this.marks[target.dataset.type][target.dataset.key]; + const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo(); + if (!currentMark.selected && currentDamage === 0) { + ui.notifications.info(game.i18n.localize('DAGGERHEART.DamageReduction.Notifications.DamageAlreadyNone')); + return; + } + + if (!currentMark.selected && currentMarks === this.actor.system.armorScore) { + ui.notifications.info( + game.i18n.localize('DAGGERHEART.DamageReduction.Notifications.NoAvailableArmorMarks') + ); + return; + } + + if (currentMark.selected) { + const currentDamageLabel = getDamageLabel(currentDamage); + for (let reduction of stressReductions) { + if (reduction.selected && reduction.to === currentDamageLabel) { + reduction.selected = false; + } + } + + if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) { + selectedStressMarks.forEach(mark => (mark.selected = false)); + } + } + + currentMark.selected = !currentMark.selected; + this.render(); + } + + static useStressReduction(_, target) { + const damageValue = Number(target.dataset.reduction); + const stressReduction = this.availableStressReductions[damageValue]; + const { currentDamage, selectedStressMarks, stressReductions } = this.getDamageInfo(); + + if (stressReduction.selected) { + stressReduction.selected = false; + + const currentDamageLabel = getDamageLabel(currentDamage); + for (let reduction of stressReductions) { + if (reduction.selected && reduction.to === currentDamageLabel) { + reduction.selected = false; + } + } + + this.render(); + } else { + const stressReductionStress = this.availableStressReductions + ? stressReductions.reduce((acc, red) => acc + red.cost, 0) + : 0; + const currentStress = + this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress; + if (currentStress + stressReduction.cost > this.actor.system.resources.stress.maxTotal) { + ui.notifications.info(game.i18n.localize('DAGGERHEART.DamageReduction.Notifications.NotEnoughStress')); + return; + } + + const reducedDamage = currentDamage !== this.damage ? getDamageLabel(currentDamage) : null; + const currentDamageLabel = reducedDamage ?? getDamageLabel(this.damage); + + if (stressReduction.from !== currentDamageLabel) return; + + stressReduction.selected = true; + this.render(); + } + } + + static async takeDamage() { + const { selectedArmorMarks, selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo(); + const armorSpent = selectedArmorMarks.length + selectedStressMarks.length; + const stressSpent = selectedStressMarks.length + stressReductions.reduce((acc, red) => acc + red.cost, 0); + + this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent }); + await this.close(true); + } + + async close(fromSave) { + if (!fromSave) { + this.reject(); + } + + await super.close({}); + } +} diff --git a/module/applications/roll.mjs b/module/applications/roll.mjs index 30385205..486780d5 100644 --- a/module/applications/roll.mjs +++ b/module/applications/roll.mjs @@ -11,61 +11,65 @@ export class DHRoll extends Roll { super(formula, data, options); } - static async build(config={}, message={}) { + static async build(config = {}, message = {}) { const roll = await this.buildConfigure(config, message); - if(!roll) return; - await this.buildEvaluate(roll, config, message={}); - await this.buildPost(roll, config, message={}); + if (!roll) return; + await this.buildEvaluate(roll, config, (message = {})); + await this.buildPost(roll, config, (message = {})); return roll; } - - static async buildConfigure(config={}, message={}) { - config.hooks = [...(config.hooks ?? []), ""]; + + static async buildConfigure(config = {}, message = {}) { + config.hooks = [...(config.hooks ?? []), '']; config.dialog ??= {}; - for ( const hook of config.hooks ) { - if ( Hooks.call(`${SYSTEM.id}.preRoll${hook.capitalize()}`, config, message) === false ) return null; + for (const hook of config.hooks) { + if (Hooks.call(`${SYSTEM.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; } this.applyKeybindings(config); - - if(config.dialog.configure !== false) { + + // let roll; + // if(config.dialog?.configure === false) { + // roll = new this('', config.actor, config); + // } else { + if (config.dialog.configure !== false) { // Open Roll Dialog const DialogClass = config.dialog?.class ?? this.DefaultDialog; config = await DialogClass.configure(config, message); - if(!config) return; + if (!config) return; } let roll = new this(config.formula, config.data, config); - for ( const hook of config.hooks ) { - if ( Hooks.call(`${SYSTEM.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false ) return []; + for (const hook of config.hooks) { + if (Hooks.call(`${SYSTEM.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false) + return []; } return roll; } - - static async buildEvaluate(roll, config={}, message={}) { - if(config.evaluate !== false) await roll.evaluate(); + + static async buildEvaluate(roll, config = {}, message = {}) { + if (config.evaluate !== false) await roll.evaluate(); this.postEvaluate(roll, config); } - - static async buildPost(roll, config, message) { - for ( const hook of config.hooks ) { - if ( Hooks.call(`${SYSTEM.id}.postRoll${hook.capitalize()}`, config, message) === false ) return null; - } - - // Create Chat Message - if(message.data) { + static async buildPost(roll, config, message) { + for (const hook of config.hooks) { + if (Hooks.call(`${SYSTEM.id}.postRoll${hook.capitalize()}`, config, message) === false) return null; + } + + // Create Chat Message + if (message.data) { } else { const messageData = {}; await this.toMessage(roll, config); } } - static async postEvaluate(roll, config={}) {} + static async postEvaluate(roll, config = {}) {} static async toMessage(roll, config) { - const cls = getDocumentClass("ChatMessage"), + const cls = getDocumentClass('ChatMessage'), msg = { type: this.messageType, user: game.user.id, @@ -88,13 +92,13 @@ export class DHRoll extends Roll { // D20Die export class DualityDie extends foundry.dice.terms.Die { - constructor({ number=1, faces=12, ...args }={}) { + constructor({ number = 1, faces = 12, ...args } = {}) { super({ number, faces, ...args }); } } export class D20Roll extends DHRoll { - constructor(formula, data={}, options={}) { + constructor(formula, data = {}, options = {}) { super(formula, data, options); this.createBaseDice(); this.configureModifiers(); @@ -112,21 +116,21 @@ export class D20Roll extends DHRoll { // static messageTemplate = 'systems/daggerheart/templates/chat/adversary-roll.hbs'; - static messageTemplate = async (config) => { + static messageTemplate = async config => { return 'systems/daggerheart/templates/chat/adversary-roll.hbs'; - } + }; static CRITICAL_TRESHOLD = 20; static DefaultDialog = D20RollDialog; get d20() { - if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) this.createBaseDice(); + if (!(this.terms[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); return this.terms[0]; } set d20(faces) { - if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) this.createBaseDice(); + if (!(this.terms[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); this.terms[0].faces = faces; } @@ -135,7 +139,7 @@ export class D20Roll extends DHRoll { } get isCritical() { - if ( !this.d20._evaluated ) return; + if (!this.d20._evaluated) return; return this.d20.total >= this.constructor.CRITICAL_TRESHOLD; } @@ -153,75 +157,78 @@ export class D20Roll extends DHRoll { advantage: config.event.altKey, disadvantage: config.event.ctrlKey }; - + // Should the roll configuration dialog be displayed? config.dialog.configure ??= !Object.values(keys).some(k => k); // Determine advantage mode const advantage = config.advantage || keys.advantage; const disadvantage = config.disadvantage || keys.disadvantage; - if ( advantage && !disadvantage ) config.advantage = this.ADV_MODE.ADVANTAGE; - else if ( !advantage && disadvantage ) config.advantage = this.ADV_MODE.DISADVANTAGE; + if (advantage && !disadvantage) config.advantage = this.ADV_MODE.ADVANTAGE; + else if (!advantage && disadvantage) config.advantage = this.ADV_MODE.DISADVANTAGE; else config.advantage = this.ADV_MODE.NORMAL; } createBaseDice() { - if ( this.terms[0] instanceof foundry.dice.terms.Die ) return; + if (this.terms[0] instanceof foundry.dice.terms.Die) return; this.terms[0] = new foundry.dice.terms.Die({ faces: 20 }); } applyAdvantage() { - this.d20.modifiers.findSplice(m => ["kh", "kl"].includes(m)); - if ( !this.hasAdvantage && !this.hasAdvantage ) this.number = 1; + this.d20.modifiers.findSplice(m => ['kh', 'kl'].includes(m)); + if (!this.hasAdvantage && !this.hasAdvantage) this.number = 1; else { this.d20.number = 2; - this.d20.modifiers.push(this.hasAdvantage ? "kh" : "kl"); + this.d20.modifiers.push(this.hasAdvantage ? 'kh' : 'kl'); } } // Trait bonus != Adversary configureModifiers() { - this.applyAdvantage(); this.applyBaseBonus(); this.options.experiences?.forEach(m => { - if(this.options.data.experiences?.[m]) this.options.roll.modifiers.push( - { + if (this.options.data.experiences?.[m]) + this.options.roll.modifiers.push({ label: this.options.data.experiences[m].description, value: this.options.data.experiences[m].total - } - ); - }) + }); + }); this.options.roll.modifiers?.forEach(m => { this.terms.push(...this.formatModifier(m.value)); - }) + }); - if(this.options.extraFormula) this.terms.push(new foundry.dice.terms.OperatorTerm({operator: '+'}), ...this.constructor.parse(this.options.extraFormula, this.getRollData())); + if (this.options.extraFormula) + this.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(this.options.extraFormula, this.getRollData()) + ); // this.resetFormula(); } applyBaseBonus() { - if(this.options.type === "attack") this.terms.push(...this.formatModifier(this.options.data.attack.roll.bonus)); + if (this.options.type === 'attack') + this.terms.push(...this.formatModifier(this.options.data.attack.roll.bonus)); } - static async postEvaluate(roll, config={}) { + static async postEvaluate(roll, config = {}) { if (config.targets?.length) { config.targets.forEach(target => { - const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion + const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion; target.hit = roll.total >= difficulty; - }) - } else if(config.roll.difficulty) roll.success = roll.total >= config.roll.difficulty; + }); + } else if (config.roll.difficulty) roll.success = roll.total >= config.roll.difficulty; config.roll.total = roll.total; config.roll.formula = roll.formula; config.roll.advantage = { type: config.advantage, dice: roll.dAdvantage?.denomination, value: roll.dAdvantage?.total - } - config.roll.modifierTotal = config.roll.modifiers.reduce((a,c) => a + c.value, 0); + }; + config.roll.modifierTotal = config.roll.modifiers.reduce((a, c) => a + c.value, 0); config.roll.dice = []; roll.dice.forEach(d => { config.roll.dice.push({ @@ -229,8 +236,8 @@ export class D20Roll extends DHRoll { total: d.total, formula: d.formula, results: d.results - }) - }) + }); + }); } getRollData() { @@ -239,51 +246,54 @@ export class D20Roll extends DHRoll { formatModifier(modifier) { const numTerm = modifier < 0 ? '-' : '+'; - return [new foundry.dice.terms.OperatorTerm({operator: numTerm}), new foundry.dice.terms.NumericTerm({number: Math.abs(modifier)})]; + return [ + new foundry.dice.terms.OperatorTerm({ operator: numTerm }), + new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) + ]; } resetFormula() { - return this._formula = this.constructor.getFormula(this.terms); + return (this._formula = this.constructor.getFormula(this.terms)); } } export class DualityRoll extends D20Roll { - constructor(formula, data={}, options={}) { + constructor(formula, data = {}, options = {}) { super(formula, data, options); } static messageType = 'dualityRoll'; // static messageTemplate = 'systems/daggerheart/templates/chat/duality-roll.hbs'; - - static messageTemplate = async (config) => { + + static messageTemplate = async config => { return 'systems/daggerheart/templates/chat/duality-roll.hbs'; - } + }; static DefaultDialog = D20RollDialog; get dHope() { // if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) return; - if ( !(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie) ) this.createBaseDice(); + if (!(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie)) this.createBaseDice(); return this.dice[0]; // return this.#hopeDice; } set dHope(faces) { - if ( !(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie) ) this.createBaseDice(); + if (!(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie)) this.createBaseDice(); this.terms[0].faces = faces; // this.#hopeDice = `d${face}`; } get dFear() { // if ( !(this.terms[1] instanceof foundry.dice.terms.Die) ) return; - if ( !(this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie) ) this.createBaseDice(); + if (!(this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie)) this.createBaseDice(); return this.dice[1]; // return this.#fearDice; } set dFear(faces) { - if ( !(this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie) ) this.createBaseDice(); + if (!(this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie)) this.createBaseDice(); this.dice[1].faces = faces; // this.#fearDice = `d${face}`; } @@ -293,17 +303,17 @@ export class DualityRoll extends D20Roll { } get isCritical() { - if ( !this.dHope._evaluated || !this.dFear._evaluated ) return; + if (!this.dHope._evaluated || !this.dFear._evaluated) return; return this.dHope.total === this.dFear.total; } get withHope() { - if(!this._evaluated) return; + if (!this._evaluated) return; return this.dHope.total > this.dFear.total; } get withFear() { - if(!this._evaluated) return; + if (!this._evaluated) return; return this.dHope.total < this.dFear.total; } @@ -311,94 +321,99 @@ export class DualityRoll extends D20Roll { return null; } - - get totalLabel() { - const label = - this.withHope - ? 'DAGGERHEART.General.Hope' - : this.withFear - ? 'DAGGERHEART.General.Fear' - : 'DAGGERHEART.General.CriticalSuccess'; + const label = this.withHope + ? 'DAGGERHEART.General.Hope' + : this.withFear + ? 'DAGGERHEART.General.Fear' + : 'DAGGERHEART.General.CriticalSuccess'; return game.i18n.localize(label); } createBaseDice() { - if ( this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie && this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie ) return; - if ( !(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie) ) this.terms[0] = new CONFIG.Dice.daggerheart.DualityDie(); - this.terms[1] = new foundry.dice.terms.OperatorTerm({operator:'+'}); - if ( !(this.dice[2] instanceof CONFIG.Dice.daggerheart.DualityDie) ) this.terms[2] = new CONFIG.Dice.daggerheart.DualityDie(); + if ( + this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie && + this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie + ) + return; + if (!(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie)) + this.terms[0] = new CONFIG.Dice.daggerheart.DualityDie(); + this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' }); + if (!(this.dice[2] instanceof CONFIG.Dice.daggerheart.DualityDie)) + this.terms[2] = new CONFIG.Dice.daggerheart.DualityDie(); } applyAdvantage() { const dieFaces = 6, bardRallyFaces = this.hasBarRally, - advDie = new foundry.dice.terms.Die({faces: dieFaces}); - if(this.hasAdvantage || this.hasDisadvantage || bardRallyFaces) this.terms.push(new foundry.dice.terms.OperatorTerm({operator:'+'})); - if(bardRallyFaces) { - const rallyDie = new foundry.dice.terms.Die({faces: bardRallyFaces}); - if(this.hasAdvantage) { - this.terms.push(new foundry.dice.terms.PoolTerm({ - terms: [advDie.formula, rallyDie.formula], - modifiers: ["kh"] - })) - } else if(this.hasDisadvantage){ - this.terms.push(advDie, new foundry.dice.terms.OperatorTerm({operator:'+'}), rallyDie); + advDie = new foundry.dice.terms.Die({ faces: dieFaces }); + if (this.hasAdvantage || this.hasDisadvantage || bardRallyFaces) + this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: '+' })); + if (bardRallyFaces) { + const rallyDie = new foundry.dice.terms.Die({ faces: bardRallyFaces }); + if (this.hasAdvantage) { + this.terms.push( + new foundry.dice.terms.PoolTerm({ + terms: [advDie.formula, rallyDie.formula], + modifiers: ['kh'] + }) + ); + } else if (this.hasDisadvantage) { + this.terms.push(advDie, new foundry.dice.terms.OperatorTerm({ operator: '+' }), rallyDie); } - } else if(this.hasAdvantage || this.hasDisadvantage) this.terms.push(advDie); + } else if (this.hasAdvantage || this.hasDisadvantage) this.terms.push(advDie); } applyBaseBonus() { - if(!this.options.roll.modifiers) this.options.roll.modifiers = []; - if(this.options.roll?.trait) this.options.roll.modifiers.push( - { + if (!this.options.roll.modifiers) this.options.roll.modifiers = []; + if (this.options.roll?.trait) + this.options.roll.modifiers.push({ label: `DAGGERHEART.Abilities.${this.options.roll.trait}.name`, value: this.options.data.traits[this.options.roll.trait].total - } - ); + }); } - static async postEvaluate(roll, config={}) { + static async postEvaluate(roll, config = {}) { super.postEvaluate(roll, config); config.roll.hope = { dice: roll.dHope.denomination, value: roll.dHope.total - } + }; config.roll.fear = { dice: roll.dFear.denomination, value: roll.dFear.total - } + }; config.roll.result = { duality: roll.withHope ? 1 : roll.withFear ? -1 : 0, total: roll.dHope.total + roll.dFear.total, label: roll.totalLabel - } + }; } } export class DamageRoll extends DHRoll { - constructor(formula, data={}, options={}) { - super(formula, data, options) + constructor(formula, data = {}, options = {}) { + super(formula, data, options); } static messageType = 'damageRoll'; // static messageTemplate = 'systems/daggerheart/templates/chat/damage-roll.hbs'; - static messageTemplate = async (config) => { + static messageTemplate = async config => { return await foundry.applications.handlebars.renderTemplate( config.messageTemplate ?? 'systems/daggerheart/templates/chat/damage-roll.hbs', config - ) - } + ); + }; static DefaultDialog = DamageDialog; - - static async postEvaluate(roll, config={}) { + + static async postEvaluate(roll, config = {}) { config.roll = { result: roll.total, dice: roll.dice - } - if(roll.healing) config.roll.type = roll.healing.type + }; + if (roll.healing) config.roll.type = roll.healing.type; } -} \ No newline at end of file +} diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs index 152004b7..989ea8c4 100644 --- a/module/applications/sheets/character.mjs +++ b/module/applications/sheets/character.mjs @@ -372,7 +372,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { trait: button.dataset.attribute /* label: abilityLabel, modifier: button.dataset.value */ - }, + } /* chatMessage: { template: 'systems/daggerheart/templates/chat/duality-roll.hbs' } */ diff --git a/module/data/action/action.mjs b/module/data/action/action.mjs index 679f23d2..1d0e9fb3 100644 --- a/module/data/action/action.mjs +++ b/module/data/action/action.mjs @@ -87,7 +87,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { range: new fields.StringField({ choices: SYSTEM.GENERAL.range, required: false, - blank: true, + blank: true // initial: null }), ...this.defineExtraSchema() @@ -111,7 +111,8 @@ export class DHBaseAction extends foundry.abstract.DataModel { type: new fields.StringField({ choices: SYSTEM.ACTIONS.targetTypes, initial: SYSTEM.ACTIONS.targetTypes.any.id, - nullable: true, initial: null + nullable: true, + initial: null }), amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }) }), @@ -132,8 +133,8 @@ export class DHBaseAction extends foundry.abstract.DataModel { }) }, extraSchemas = {}; - - this.extraSchemas.forEach(s => extraSchemas[s] = extraFields[s]); + + this.extraSchemas.forEach(s => (extraSchemas[s] = extraFields[s])); return extraSchemas; } @@ -172,8 +173,8 @@ export class DHBaseAction extends foundry.abstract.DataModel { trait: parent.system.trait }; } - if(parent?.type === 'weapon' && !!this.schema.fields.damage) { - updateSource['damage'] = {includeBase: true}; + if (parent?.type === 'weapon' && !!this.schema.fields.damage) { + updateSource['damage'] = { includeBase: true }; } if (parent?.system?.range) { updateSource['range'] = parent?.system?.range; @@ -187,9 +188,14 @@ export class DHBaseAction extends foundry.abstract.DataModel { ...actorData.toObject(), prof: actorData.proficiency?.value ?? 1, cast: actorData.spellcast?.value ?? 1, - scale: this.cost.length ? this.cost.reduce((a,c) => {a[c.type] = c.value; return a},{}) : 1, + scale: this.cost.length + ? this.cost.reduce((a, c) => { + a[c.type] = c.value; + return a; + }, {}) + : 1, roll: {} - } + }; } async use(event, ...args) { @@ -208,26 +214,27 @@ export class DHBaseAction extends foundry.abstract.DataModel { }; // this.proceedChatDisplay(config); - + // Filter selected targets based on Target parameters config.targets = await this.getTarget(config); - if(!config.targets) return ui.notifications.warn("Too many targets selected for that actions."); - + if (!config.targets) return ui.notifications.warn('Too many targets selected for that actions.'); + // Filter selected targets based on Range parameters config.range = await this.checkRange(config); - if(!config.range.hasRange) return ui.notifications.warn("No Target within range."); + if (!config.range.hasRange) return ui.notifications.warn('No Target within range.'); // Display Uses/Costs Dialog & Check if Actor get enough resources config = { ...config, - ...await this.getCost(config) - } - if(!this.hasRoll() && (!config.costs.hasCost || !this.hasUses(config.uses))) return ui.notifications.warn("You don't have the resources to use that action."); + ...(await this.getCost(config)) + }; + if (!this.hasRoll() && (!config.costs.hasCost || !this.hasUses(config.uses))) + return ui.notifications.warn("You don't have the resources to use that action."); // Proceed with Roll config = await this.proceedRoll(config); - if(this.roll && !config.roll.result) return; - + if (this.roll && !config.roll.result) return; + // Update Actor resources based on Action Cost configuration this.spendCost(config.costs.values); this.spendUses(config.uses); @@ -254,7 +261,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { type: this.actionType, difficulty: this.roll?.difficulty } - } + }; // config = await this.actor.diceRoll(config, this); return this.actor.diceRoll(config, this); } @@ -262,20 +269,20 @@ export class DHBaseAction extends foundry.abstract.DataModel { /* COST */ async getCost(config) { - let costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : {values: [], hasCost: true}; + let costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : { values: [], hasCost: true }; let uses = this.getUses(); if (!config.event.shiftKey && !this.hasRoll()) { const dialogClosed = new Promise((resolve, _) => { new CostSelectionDialog(costs, uses, this, resolve).render(true); }); - ({costs, uses} = await dialogClosed); + ({ costs, uses } = await dialogClosed); } - return {costs, uses}; + return { costs, uses }; } getRealCosts(costs) { const realCosts = costs?.length ? costs.filter(c => c.enabled) : []; - return {values: realCosts, hasCost: this.hasCost(realCosts)} + return { values: realCosts, hasCost: this.hasCost(realCosts) }; } calcCosts(costs) { @@ -284,32 +291,32 @@ export class DHBaseAction extends foundry.abstract.DataModel { c.step = c.step ?? 1; c.total = c.value * c.scale * c.step; c.enabled = c.hasOwnProperty('enabled') ? c.enabled : true; - return c - }) + return c; + }); } hasCost(costs) { - return costs.reduce((a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), true) + return costs.reduce((a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), true); } async spendCost(config) { - if(!config.costs?.values?.length) return; + if (!config.costs?.values?.length) return; return await this.actor.modifyResource(config.costs.values); } /* COST */ /* USES */ async spendUses(config) { - if(!this.uses.max || config.enabled === false) return; + if (!this.uses.max || config.enabled === false) return; const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject()); newActions[this.index].uses.value++; await this.item.update({ [`system.${this.systemPath}`]: newActions }); } getUses() { - if(!this.uses) return {hasUse: true} + if (!this.uses) return { hasUse: true }; const uses = foundry.utils.deepClone(this.uses); - if(!uses.value) uses.value = 0; + if (!uses.value) uses.value = 0; return uses; } @@ -325,23 +332,28 @@ export class DHBaseAction extends foundry.abstract.DataModel { } /* USES */ - /* TARGET */ async getTarget(config) { - if(this.target?.type === SYSTEM.ACTIONS.targetTypes.self.id) return this.formatTarget(this.actor.token ?? this.actor.prototypeToken); + if (this.target?.type === SYSTEM.ACTIONS.targetTypes.self.id) + return this.formatTarget(this.actor.token ?? this.actor.prototypeToken); let targets = Array.from(game.user.targets); // foundry.CONST.TOKEN_DISPOSITIONS.FRIENDLY - if(this.target?.type && this.target.type !== SYSTEM.ACTIONS.targetTypes.any.id) { + if (this.target?.type && this.target.type !== SYSTEM.ACTIONS.targetTypes.any.id) { targets = targets.filter(t => this.isTargetFriendly(t)); - if(this.target.amount && targets.length > this.target.amount) return false; + if (this.target.amount && targets.length > this.target.amount) return false; } return targets.map(t => this.formatTarget(t)); } isTargetFriendly(target) { - const actorDisposition = this.actor.token ? this.actor.token.disposition : this.actor.prototypeToken.disposition, + const actorDisposition = this.actor.token + ? this.actor.token.disposition + : this.actor.prototypeToken.disposition, targetDisposition = target.document.disposition; - return (this.target.type === SYSTEM.ACTIONS.targetTypes.friendly.id && actorDisposition === targetDisposition) || (this.target.type === SYSTEM.ACTIONS.targetTypes.hostile.id && (actorDisposition + targetDisposition === 0)) + return ( + (this.target.type === SYSTEM.ACTIONS.targetTypes.friendly.id && actorDisposition === targetDisposition) || + (this.target.type === SYSTEM.ACTIONS.targetTypes.hostile.id && actorDisposition + targetDisposition === 0) + ); } formatTarget(actor) { @@ -351,56 +363,57 @@ export class DHBaseAction extends foundry.abstract.DataModel { img: actor.actor.img, difficulty: actor.actor.system.difficulty, evasion: actor.actor.system.evasion?.total - } + }; } /* TARGET */ - + /* RANGE */ async checkRange(config) { - if(!this.range || !this.actor) return true; - return {values: [], hasRange: true}; + if (!this.range || !this.actor) return true; + return { values: [], hasRange: true }; } /* RANGE */ /* EFFECTS */ - async applyEffects(event, data, force=false) { - if(!this.effects?.length || !data.system.targets.length) return; - data.system.targets.forEach(async (token) => { - if(!token.hit && !force) return; - this.effects.forEach(async (e) => { + async applyEffects(event, data, force = false) { + if (!this.effects?.length || !data.system.targets.length) return; + data.system.targets.forEach(async token => { + if (!token.hit && !force) return; + this.effects.forEach(async e => { const actor = canvas.tokens.get(token.id)?.actor, effect = this.item.effects.get(e._id); - if(!actor || !effect) return; + if (!actor || !effect) return; await this.applyEffect(effect, actor); - }) - }) - + }); + }); } async applyEffect(effect, actor) { - // Enable an existing effect on the target if it originated from this effect - const existingEffect = actor.effects.find(e => e.origin === origin.uuid); - if ( existingEffect ) { - return existingEffect.update(foundry.utils.mergeObject({ + // Enable an existing effect on the target if it originated from this effect + const existingEffect = actor.effects.find(e => e.origin === origin.uuid); + if (existingEffect) { + return existingEffect.update( + foundry.utils.mergeObject({ ...effect.constructor.getInitialDuration(), disabled: false - })); - } - - // Otherwise, create a new effect on the target - const effectData = foundry.utils.mergeObject({ - ...effect.toObject(), - disabled: false, - transfer: false, - origin: origin.uuid - }); - await ActiveEffect.implementation.create(effectData, { parent: actor }); + }) + ); + } + + // Otherwise, create a new effect on the target + const effectData = foundry.utils.mergeObject({ + ...effect.toObject(), + disabled: false, + transfer: false, + origin: origin.uuid + }); + await ActiveEffect.implementation.create(effectData, { parent: actor }); } /* EFFECTS */ /* CHAT */ async proceedChatDisplay(config) { - if(!this.chatDisplay) return; + if (!this.chatDisplay) return; } /* CHAT */ } @@ -412,14 +425,14 @@ export class DHDamageAction extends DHBaseAction { async use(event, ...args) { const config = await super.use(event, args); - if(!config || ['error', 'warning'].includes(config.type)) return; - if(!this.directDamage) return; + if (!config || ['error', 'warning'].includes(config.type)) return; + if (!this.directDamage) return; return await this.rollDamage(event, config); } async rollDamage(event, data) { let formula = this.damage.parts.map(p => p.getFormula(this.actor)).join(' + '); - + if (!formula || formula == '') return; let roll = { formula: formula, total: formula }, bonusDamage = []; @@ -427,10 +440,15 @@ export class DHDamageAction extends DHBaseAction { const config = { title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: this.name }), formula, - targets: (data.system?.targets ?? data.targets).map(x => ({ id: x.id, name: x.name, img: x.img, hit: true })) - } - - roll = CONFIG.Dice.daggerheart.DamageRoll.build(config) + targets: (data.system?.targets ?? data.targets).map(x => ({ + id: x.id, + name: x.name, + img: x.img, + hit: true + })) + }; + + roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); } } @@ -475,24 +493,31 @@ export class DHHealingAction extends DHBaseAction { async use(event, ...args) { const config = await super.use(event, args); - if(!config || ['error', 'warning'].includes(config.type)) return; - if(this.hasRoll()) return; + if (!config || ['error', 'warning'].includes(config.type)) return; + if (this.hasRoll()) return; return await this.rollHealing(event, config); } async rollHealing(event, data) { let formula = this.healing.value.getFormula(this.actor); - + if (!formula || formula == '') return; let roll = { formula: formula, total: formula }, bonusDamage = []; const config = { - title: game.i18n.format('DAGGERHEART.Chat.HealingRoll.Title', { healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label) }), + title: game.i18n.format('DAGGERHEART.Chat.HealingRoll.Title', { + healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label) + }), formula, - targets: (data.system?.targets ?? data.targets).map(x => ({ id: x.id, name: x.name, img: x.img, hit: true })), + targets: (data.system?.targets ?? data.targets).map(x => ({ + id: x.id, + name: x.name, + img: x.img, + hit: true + })), messageTemplate: 'systems/daggerheart/templates/chat/healing-roll.hbs' - } + }; roll = CONFIG.Dice.daggerheart.DamageRoll.build(config); } @@ -511,13 +536,12 @@ export class DHSummonAction extends DHBaseAction { } async use(event, ...args) { - if ( !this.canSummon || !canvas.scene ) return; + if (!this.canSummon || !canvas.scene) return; const config = await super.use(event, args); - } get canSummon() { - return game.user.can("TOKEN_CREATE"); + return game.user.can('TOKEN_CREATE'); } } @@ -526,7 +550,7 @@ export class DHEffectAction extends DHBaseAction { async use(event, ...args) { const config = await super.use(event, args); - if(['error', 'warning'].includes(config.type)) return; + if (['error', 'warning'].includes(config.type)) return; return await this.chatApplyEffects(event, config); } @@ -570,7 +594,7 @@ export class DHMacroAction extends DHBaseAction { async use(event, ...args) { const config = await super.use(event, args); - if(['error', 'warning'].includes(config.type)) return; + if (['error', 'warning'].includes(config.type)) return; const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID, macro = await fromUuid(fixUUID); try { diff --git a/module/data/action/actionDice.mjs b/module/data/action/actionDice.mjs index 5cbd1558..fe934f1e 100644 --- a/module/data/action/actionDice.mjs +++ b/module/data/action/actionDice.mjs @@ -11,7 +11,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel { initial: 'proficiency', label: 'Multiplier' }), - flatMultiplier : new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }), + flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }), dice: new fields.StringField({ choices: SYSTEM.GENERAL.diceTypes, initial: 'd6', label: 'Formula' }), bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }), custom: new fields.SchemaField({ diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index ea00a0ca..2aa4262b 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -55,12 +55,11 @@ export default class DhpAdversary extends BaseDataActor { type: 'weapon' }, damage: { - parts: [{ - multiplier: 'flat', - dice: 'd20', - bonus: 2, - flatMultiplier: 3 - }] + parts: [ + { + multiplier: 'flat' + } + ] } } }), diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 7f82ba5a..6653e71c 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -17,6 +17,12 @@ const resourceField = max => max: new foundry.data.fields.NumberField({ initial: max, integer: true }) }); +const stressDamageReductionRule = () => + new foundry.data.fields.SchemaField({ + enabled: new foundry.data.fields.BooleanField({ required: true, initial: false }), + cost: new foundry.data.fields.NumberField({ integer: true }) + }); + export default class DhCharacter extends BaseDataActor { static get metadata() { return foundry.utils.mergeObject(super.metadata, { @@ -90,6 +96,18 @@ export default class DhCharacter extends BaseDataActor { attack: new fields.NumberField({ integer: true, initial: 0 }), spellcast: new fields.NumberField({ integer: true, initial: 0 }), armorScore: new fields.NumberField({ integer: true, initial: 0 }) + }), + rules: new fields.SchemaField({ + maxArmorMarked: new fields.SchemaField({ + value: new fields.NumberField({ required: true, integer: true, initial: 1 }), + bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }), + stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 }) + }), + stressDamageReduction: new fields.SchemaField({ + severe: stressDamageReductionRule(), + major: stressDamageReductionRule(), + minor: stressDamageReductionRule() + }) }) }; } @@ -239,6 +257,9 @@ export default class DhCharacter extends BaseDataActor { experience.total = experience.value + experience.bonus; } + this.rules.maxArmorMarked.total = this.rules.maxArmorMarked.value + this.rules.maxArmorMarked.bonus; + + this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0; this.resources.hitPoints.maxTotal = this.resources.hitPoints.max + this.resources.hitPoints.bonus; this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus; this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus; diff --git a/module/data/chat-message/adversaryRoll.mjs b/module/data/chat-message/adversaryRoll.mjs index 46c550be..211e32dd 100644 --- a/module/data/chat-message/adversaryRoll.mjs +++ b/module/data/chat-message/adversaryRoll.mjs @@ -1,5 +1,5 @@ -import DhpActor from "../../documents/actor.mjs"; -import ActionField from "../fields/actionField.mjs"; +import DhpActor from '../../documents/actor.mjs'; +import ActionField from '../fields/actionField.mjs'; export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel { static defineSchema() { diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index b0fdf0ae..c7f5af0b 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -29,7 +29,6 @@ export default class DHArmor extends BaseDataItem { }) ), marks: new fields.SchemaField({ - max: new fields.NumberField({ initial: 6, integer: true }), value: new fields.NumberField({ initial: 0, integer: true }) }), baseThresholds: new fields.SchemaField({ diff --git a/module/dialogs/d20RollDialog.mjs b/module/dialogs/d20RollDialog.mjs index 0c3c1319..b682f734 100644 --- a/module/dialogs/d20RollDialog.mjs +++ b/module/dialogs/d20RollDialog.mjs @@ -1,15 +1,18 @@ const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(config={}, options={}) { + constructor(config = {}, options = {}) { super(options); this.config = config; this.config.experiences = []; - - if(config.source?.action) { + + if (config.source?.action) { this.item = config.data.parent.items.get(config.source.item); - this.action = config.data.attack?._id == config.source.action ? config.data.attack : this.item.system.actions.find(a => a._id === config.source.action); + this.action = + config.data.attack?._id == config.source.action + ? config.data.attack + : this.item.system.actions.find(a => a._id === config.source.action); } } @@ -47,17 +50,20 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.experiences = Object.keys(this.config.data.experiences).map(id => ({ id, ...this.config.data.experiences[id] })); + context.experiences = Object.keys(this.config.data.experiences).map(id => ({ + id, + ...this.config.data.experiences[id] + })); context.selectedExperiences = this.config.experiences; context.advantage = this.config.advantage; /* context.diceOptions = this.diceOptions; */ context.canRoll = true; - if(this.config.costs?.length) { + if (this.config.costs?.length) { const updatedCosts = this.action.calcCosts(this.config.costs); - context.costs = updatedCosts + context.costs = updatedCosts; context.canRoll = this.action.getRealCosts(updatedCosts)?.hasCost; } - if(this.config.uses?.max) { + if (this.config.uses?.max) { context.uses = this.action.calcUses(this.config.uses); context.canRoll = context.canRoll && this.action.hasUses(context.uses); } @@ -66,8 +72,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio static updateRollConfiguration(event, _, formData) { const { ...rest } = foundry.utils.expandObject(formData.object); - if(this.config.costs) this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs); - if(this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses); + if (this.config.costs) this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs); + if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses); this.render(); } @@ -87,19 +93,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } static async submitRoll() { - await this.close({ submitted: true }); + await this.close({ submitted: true }); } /** @override */ - _onClose(options={}) { - if ( !options.submitted ) this.config = false; + _onClose(options = {}) { + if (!options.submitted) this.config = false; } - static async configure(config={}) { + static async configure(config = {}) { return new Promise(resolve => { const app = new this(config); - app.addEventListener("close", () => resolve(app.config), { once: true }); + app.addEventListener('close', () => resolve(app.config), { once: true }); app.render({ force: true }); }); } -} \ No newline at end of file +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 0a7e467c..a2136403 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -4,6 +4,7 @@ import RollSelectionDialog from '../applications/rollSelectionDialog.mjs'; import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs'; import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import DHDualityRoll from '../data/chat-message/dualityRoll.mjs'; +import DamageReductionDialog from '../applications/damageReductionDialog.mjs'; export default class DhpActor extends Actor { async _preCreate(data, options, user) { @@ -362,66 +363,78 @@ export default class DhpActor extends Actor { async takeDamage(damage, type) { const hpDamage = damage >= this.system.damageThresholds.severe - ? -3 + ? 3 : damage >= this.system.damageThresholds.major - ? -2 + ? 2 : damage >= this.system.damageThresholds.minor - ? -1 + ? 1 : 0; - await this.modifyResource([{value: hpDamage, type}]); - /* const update = { - 'system.resources.hitPoints.value': Math.min( - this.system.resources.hitPoints.value + hpDamage, - this.system.resources.hitPoints.max - ) - }; - if (game.user.isGM) { - await this.update(update); + if ( + this.type === 'character' && + this.system.armor && + this.system.armor.system.marks.value < this.system.armorScore + ) { + new Promise((resolve, reject) => { + new DamageReductionDialog(resolve, reject, this, hpDamage).render(true); + }) + .then(async ({ modifiedDamage, armorSpent, stressSpent }) => { + const resources = [ + { value: modifiedDamage, type: 'hitPoints' }, + ...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []), + ...(stressSpent ? [{ value: stressSpent, type: 'stress' }] : []) + ]; + await this.modifyResource(resources); + }) + .catch(() => { + const cls = getDocumentClass('ChatMessage'); + const msg = new cls({ + user: game.user.id, + content: game.i18n.format('DAGGERHEART.DamageReduction.Notifications.DamageIgnore', { + character: this.name + }) + }); + cls.create(msg.toObject()); + }); } else { - await game.socket.emit(`system.${SYSTEM.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateDocument, - uuid: this.uuid, - update: update - } - }); - } */ + await this.modifyResource([{ value: hpDamage, type: 'hitPoints' }]); + } } async modifyResource(resources) { - if(!resources.length) return; - let updates = { actor: { target: this, resources: {} }, armor: { target: this.armor, resources: {} } }; + if (!resources.length) return; + let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } }; resources.forEach(r => { - switch (type) { - case 'armorStrack': - // resource = 'system.stacks.value'; - // target = this.armor; - // update = Math.min(this.marks.value + value, this.marks.max); - updates.armor.resources['system.stacks.value'] = Math.min(this.marks.value + value, this.marks.max); + switch (r.type) { + case 'armorStack': + updates.armor.resources['system.marks.value'] = Math.min( + this.system.armor.system.marks.value + r.value, + this.system.armorScore + ); break; default: - // resource = `system.resources.${type}`; - // target = this; - // update = Math.min(this.resources[type].value + value, this.resources[type].max); - updates.armor.resources[`system.resources.${type}`] = Math.min(this.resources[type].value + value, this.resources[type].max); + updates.actor.resources[`system.resources.${r.type}.value`] = Math.min( + this.system.resources[r.type].value + r.value, + this.system.resources[r.type].max + ); break; } - }) - Object.values(updates).forEach(async (u) => { - if (game.user.isGM) { - await u.target.update(u.resources); - } else { - await game.socket.emit(`system.${SYSTEM.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateDocument, - uuid: u.target.uuid, - update: u.resources - } - }); + }); + Object.values(updates).forEach(async u => { + if (Object.keys(u.resources).length > 0) { + if (game.user.isGM) { + await u.target.update(u.resources); + } else { + await game.socket.emit(`system.${SYSTEM.id}`, { + action: socketEvent.GMUpdate, + data: { + action: GMUpdateEvent.UpdateDocument, + uuid: u.target.uuid, + update: u.resources + } + }); + } } - }) + }); } } diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 1e12e430..6b257f03 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -235,3 +235,29 @@ Roll.replaceFormulaData = function (formula, data, { missing, warn = false } = { formula = terms.reduce((a, c) => a.replaceAll(`@${c.term}`, data[c.term] ?? c.default), formula); return nativeReplaceFormulaData(formula, data, { missing, warn }); }; + +export const getDamageLabel = damage => { + switch (damage) { + case 3: + return game.i18n.localize('DAGGERHEART.General.Damage.Severe'); + case 2: + return game.i18n.localize('DAGGERHEART.General.Damage.Major'); + case 1: + return game.i18n.localize('DAGGERHEART.General.Damage.Minor'); + case 0: + return game.i18n.localize('DAGGERHEART.General.Damage.None'); + } +}; + +export const damageKeyToNumber = key => { + switch (key) { + case 'severe': + return 3; + case 'major': + return 2; + case 'minor': + return 1; + case 'none': + return 0; + } +}; diff --git a/module/ui/chatLog.mjs b/module/ui/chatLog.mjs index 822fb6ca..93366402 100644 --- a/module/ui/chatLog.mjs +++ b/module/ui/chatLog.mjs @@ -67,7 +67,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo getAction(actor, itemId, actionId) { const item = actor.items.get(itemId), - action = actor.system.attack?._id === actionId ? actor.system.attack : item?.system?.actions?.find(a => a._id === actionId); + action = + actor.system.attack?._id === actionId + ? actor.system.attack + : item?.system?.actions?.find(a => a._id === actionId); return action; } @@ -75,9 +78,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo event.stopPropagation(); const actor = await this.getActor(message.system.source.actor); if (!actor || !game.user.isGM) return true; - if(message.system.source.item && message.system.source.action) { + if (message.system.source.item && message.system.source.action) { const action = this.getAction(actor, message.system.source.item, message.system.source.action); - if(!action || !action?.rollDamage) return; + if (!action || !action?.rollDamage) return; await action.rollDamage(event, message); } }; @@ -86,9 +89,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo event.stopPropagation(); const actor = this.getActor(message.system.source.actor); if (!actor || !game.user.isGM) return true; - if(message.system.source.item && message.system.source.action) { + if (message.system.source.item && message.system.source.action) { const action = this.getAction(actor, message.system.source.item, message.system.source.action); - if(!action || !action?.rollHealing) return; + if (!action || !action?.rollHealing) return; await action.rollHealing(event, message); } }; @@ -97,12 +100,12 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo event.stopPropagation(); const actor = this.getActor(message.system.source.actor); if (!actor || !game.user.isGM) return true; - if(message.system.source.item && message.system.source.action) { + if (message.system.source.item && message.system.source.action) { const action = this.getAction(actor, message.system.source.item, message.system.source.action); - if(!action || !action?.applyEffects) return; + if (!action || !action?.applyEffects) return; await action.applyEffects(event, message); } - } + }; hoverTarget = event => { event.stopPropagation(); @@ -149,7 +152,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected')); for (var target of targets) { - await target.actor.modifyResource([{value: healing, type: event.currentTarget.dataset.type}]); + await target.actor.modifyResource([{ value: healing, type: event.currentTarget.dataset.type }]); } }; diff --git a/styles/daggerheart.css b/styles/daggerheart.css index fc5772d4..2fb08edf 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -3115,6 +3115,125 @@ div.daggerheart.views.multiclass { .daggerheart.views.ownership-selection .ownership-outer-container .ownership-container select { margin: 4px 0; } +.daggerheart.views.damage-reduction .window-content { + padding: 8px 0; +} +.daggerheart.views.damage-reduction .damage-reduction-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} +.daggerheart.views.damage-reduction .damage-reduction-container .section-container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} +.daggerheart.views.damage-reduction .damage-reduction-container .padded { + padding: 0 8px; +} +.daggerheart.views.damage-reduction .damage-reduction-container .armor-title { + margin: 0; + white-space: nowrap; +} +.daggerheart.views.damage-reduction .damage-reduction-container .resources-container { + display: flex; + gap: 8px; + width: 100%; +} +.daggerheart.views.damage-reduction .damage-reduction-container .resources-container .resource-container { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection { + display: flex; + align-items: center; + width: 100%; + margin: 0; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner { + display: flex; + gap: 2px; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner:not(:last-child) { + margin-right: 8px; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container { + cursor: pointer; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + height: 26px; + padding: 0 1px; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.4; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container.selected { + opacity: 1; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container.inactive { + cursor: initial; + opacity: 0.2; +} +.daggerheart.views.damage-reduction .damage-reduction-container .mark-selection .mark-selection-inner .mark-container .fa-shield { + position: relative; + right: 0.5px; +} +.daggerheart.views.damage-reduction .damage-reduction-container .stress-reduction-container { + margin: 0; + width: 100%; +} +.daggerheart.views.damage-reduction .damage-reduction-container .stress-reduction-container .stress-reduction { + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + height: 26px; + padding: 0 4px; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + opacity: 0.4; +} +.daggerheart.views.damage-reduction .damage-reduction-container .stress-reduction-container .stress-reduction.active { + opacity: 1; + cursor: pointer; +} +.daggerheart.views.damage-reduction .damage-reduction-container .stress-reduction-container .stress-reduction.selected { + opacity: 1; + background: var(--color-warm-2); + color: white; +} +.daggerheart.views.damage-reduction .damage-reduction-container .stress-reduction-container .stress-reduction .stress-reduction-cost { + display: flex; + align-items: center; +} +.daggerheart.views.damage-reduction .damage-reduction-container .markers-subtitle { + margin: -4px 0 0 0; +} +.daggerheart.views.damage-reduction .damage-reduction-container .markers-subtitle.bold { + font-variant: all-small-caps; + font-weight: bold; +} +.daggerheart.views.damage-reduction .damage-reduction-container footer { + display: flex; + width: 100%; +} +.daggerheart.views.damage-reduction .damage-reduction-container footer button { + flex: 1; +} +.daggerheart.views.damage-reduction .damage-reduction-container footer button .damage-value { + font-weight: bold; +} +.daggerheart.views.damage-reduction .damage-reduction-container footer button .damage-value.reduced-value { + opacity: 0.4; + text-decoration: line-through; +} :root { --shadow-text-stroke: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; --fear-animation: background 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease, opacity 0.3s ease; diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 3920be5b..a19117da 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -11,6 +11,7 @@ @import './characterCreation.less'; @import './levelup.less'; @import './ownershipSelection.less'; +@import './damageReduction.less'; @import './resources.less'; @import './countdown.less'; @import './settings.less'; diff --git a/styles/damageReduction.less b/styles/damageReduction.less new file mode 100644 index 00000000..e3ffc2e9 --- /dev/null +++ b/styles/damageReduction.less @@ -0,0 +1,145 @@ +.daggerheart.views.damage-reduction { + .window-content { + padding: 8px 0; + } + + .damage-reduction-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + + .section-container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + } + + .padded { + padding: 0 8px; + } + + .armor-title { + margin: 0; + white-space: nowrap; + } + + .resources-container { + display: flex; + gap: 8px; + width: 100%; + + .resource-container { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + } + } + + .mark-selection { + display: flex; + align-items: center; + width: 100%; + margin: 0; + + .mark-selection-inner { + display: flex; + gap: 2px; + + &:not(:last-child) { + margin-right: 8px; + } + + .mark-container { + cursor: pointer; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + height: 26px; + padding: 0 1px; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.4; + + &.selected { + opacity: 1; + } + + &.inactive { + cursor: initial; + opacity: 0.2; + } + + .fa-shield { + position: relative; + right: 0.5px; + } + } + } + } + + .stress-reduction-container { + margin: 0; + width: 100%; + + .stress-reduction { + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + height: 26px; + padding: 0 4px; + font-size: 18px; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + opacity: 0.4; + + &.active { + opacity: 1; + cursor: pointer; + } + + &.selected { + opacity: 1; + background: var(--color-warm-2); + color: white; + } + + .stress-reduction-cost { + display: flex; + align-items: center; + } + } + } + + .markers-subtitle { + margin: -4px 0 0 0; + + &.bold { + font-variant: all-small-caps; + font-weight: bold; + } + } + + footer { + display: flex; + width: 100%; + + button { + flex: 1; + + .damage-value { + font-weight: bold; + + &.reduced-value { + opacity: 0.4; + text-decoration: line-through; + } + } + } + } + } +} diff --git a/styles/less/actors/character.less b/styles/less/actors/character.less index e8d7d5fd..e3833e3b 100644 --- a/styles/less/actors/character.less +++ b/styles/less/actors/character.less @@ -1,11 +1,11 @@ -@import '../utils/colors.less'; -@import '../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .window-content { - display: flex; - flex-direction: row; - height: 100%; - width: 100%; - } -} +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .window-content { + display: flex; + flex-direction: row; + height: 100%; + width: 100%; + } +} diff --git a/styles/less/actors/character/biography.less b/styles/less/actors/character/biography.less index c4ce81ac..635cf8d5 100644 --- a/styles/less/actors/character/biography.less +++ b/styles/less/actors/character/biography.less @@ -1,20 +1,20 @@ -@import '../../utils/colors.less'; -@import '../../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .tab.biography { - .items-section { - display: flex; - flex-direction: column; - gap: 10px; - height: 100%; - overflow-y: auto; - mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); - padding-bottom: 40px; - height: 100%; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - } - } -} +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .tab.biography { + .items-section { + display: flex; + flex-direction: column; + gap: 10px; + height: 100%; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); + padding-bottom: 40px; + height: 100%; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + } +} diff --git a/styles/less/actors/character/features.less b/styles/less/actors/character/features.less index 406544e6..6fc86ac3 100644 --- a/styles/less/actors/character/features.less +++ b/styles/less/actors/character/features.less @@ -1,20 +1,20 @@ -@import '../../utils/colors.less'; -@import '../../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .tab.features { - .features-sections { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); - padding: 20px 0; - padding-top: 10px; - height: 95%; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - } - } -} +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .tab.features { + .features-sections { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); + padding: 20px 0; + padding-top: 10px; + height: 95%; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + } +} diff --git a/styles/less/actors/character/inventory.less b/styles/less/actors/character/inventory.less index 1b2beaf8..a6caf22b 100644 --- a/styles/less/actors/character/inventory.less +++ b/styles/less/actors/character/inventory.less @@ -1,65 +1,65 @@ -@import '../../utils/colors.less'; -@import '../../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .tab.inventory { - .search-section { - display: flex; - gap: 10px; - align-items: center; - - .search-bar { - position: relative; - color: light-dark(@dark-blue-50, @beige-50); - width: 100%; - padding-top: 5px; - - input { - border-radius: 50px; - font-family: @font-body; - background: light-dark(@dark-blue-10, @golden-10); - border: none; - outline: 2px solid transparent; - transition: all 0.3s ease; - padding: 0 20px; - - &:hover { - outline: 2px solid light-dark(@dark, @golden); - } - - &:placeholder { - color: light-dark(@dark-blue-50, @beige-50); - } - } - - .icon { - align-content: center; - height: 32px; - position: absolute; - right: 20px; - font-size: 16px; - z-index: 1; - color: light-dark(@dark-blue-50, @beige-50); - } - } - } - - .items-section { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); - padding: 20px 0; - height: 80%; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - } - - .currency-section { - display: flex; - gap: 10px; - } - } -} +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .tab.inventory { + .search-section { + display: flex; + gap: 10px; + align-items: center; + + .search-bar { + position: relative; + color: light-dark(@dark-blue-50, @beige-50); + width: 100%; + padding-top: 5px; + + input { + border-radius: 50px; + font-family: @font-body; + background: light-dark(@dark-blue-10, @golden-10); + border: none; + outline: 2px solid transparent; + transition: all 0.3s ease; + padding: 0 20px; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + + &:placeholder { + color: light-dark(@dark-blue-50, @beige-50); + } + } + + .icon { + align-content: center; + height: 32px; + position: absolute; + right: 20px; + font-size: 16px; + z-index: 1; + color: light-dark(@dark-blue-50, @beige-50); + } + } + } + + .items-section { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); + padding: 20px 0; + height: 80%; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + + .currency-section { + display: flex; + gap: 10px; + } + } +} diff --git a/styles/less/actors/character/loadout.less b/styles/less/actors/character/loadout.less index 0eb514e2..37ed56bd 100644 --- a/styles/less/actors/character/loadout.less +++ b/styles/less/actors/character/loadout.less @@ -1,101 +1,101 @@ -@import '../../utils/colors.less'; -@import '../../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .tab.loadout { - .search-section { - display: flex; - align-items: center; - justify-content: space-between; - - .search-bar { - position: relative; - color: light-dark(@dark-blue-50, @beige-50); - width: 80%; - padding-top: 5px; - - input { - border-radius: 50px; - font-family: @font-body; - background: light-dark(@dark-blue-10, @golden-10); - border: none; - outline: 2px solid transparent; - transition: all 0.3s ease; - padding: 0 20px; - - &:hover { - outline: 2px solid light-dark(@dark, @golden); - } - - &:placeholder { - color: light-dark(@dark-blue-50, @beige-50); - } - } - - .icon { - align-content: center; - height: 32px; - position: absolute; - right: 20px; - font-size: 16px; - z-index: 1; - color: light-dark(@dark-blue-50, @beige-50); - } - } - - .btn-toogle-view { - background: light-dark(@dark-blue-10, @dark-blue); - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 15px; - padding: 0; - gap: 0; - width: 62px; - - span { - margin: 1px; - width: 26px; - color: light-dark(@dark-blue, @golden); - - &.list-icon { - i { - margin-left: 3px; - } - } - &.grid-icon { - i { - margin-right: 3px; - } - } - - &.list-active { - border-radius: 32px 3px 3px 32px; - background-color: light-dark(@dark-blue, @golden); - color: light-dark(@beige, @dark-blue); - padding: 2px; - } - - &.grid-active { - border-radius: 3px 32px 32px 3px; - background-color: light-dark(@dark-blue, @golden); - color: light-dark(@beige, @dark-blue); - padding: 2px; - } - } - } - } - - .items-section { - display: flex; - flex-direction: column; - gap: 10px; - height: 100%; - overflow-y: auto; - mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); - padding: 20px 0; - height: 90%; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - } - } -} +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .tab.loadout { + .search-section { + display: flex; + align-items: center; + justify-content: space-between; + + .search-bar { + position: relative; + color: light-dark(@dark-blue-50, @beige-50); + width: 80%; + padding-top: 5px; + + input { + border-radius: 50px; + font-family: @font-body; + background: light-dark(@dark-blue-10, @golden-10); + border: none; + outline: 2px solid transparent; + transition: all 0.3s ease; + padding: 0 20px; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + + &:placeholder { + color: light-dark(@dark-blue-50, @beige-50); + } + } + + .icon { + align-content: center; + height: 32px; + position: absolute; + right: 20px; + font-size: 16px; + z-index: 1; + color: light-dark(@dark-blue-50, @beige-50); + } + } + + .btn-toogle-view { + background: light-dark(@dark-blue-10, @dark-blue); + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 15px; + padding: 0; + gap: 0; + width: 62px; + + span { + margin: 1px; + width: 26px; + color: light-dark(@dark-blue, @golden); + + &.list-icon { + i { + margin-left: 3px; + } + } + &.grid-icon { + i { + margin-right: 3px; + } + } + + &.list-active { + border-radius: 32px 3px 3px 32px; + background-color: light-dark(@dark-blue, @golden); + color: light-dark(@beige, @dark-blue); + padding: 2px; + } + + &.grid-active { + border-radius: 3px 32px 32px 3px; + background-color: light-dark(@dark-blue, @golden); + color: light-dark(@beige, @dark-blue); + padding: 2px; + } + } + } + } + + .items-section { + display: flex; + flex-direction: column; + gap: 10px; + height: 100%; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); + padding: 20px 0; + height: 90%; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + } +} diff --git a/styles/less/actors/character/sheet.less b/styles/less/actors/character/sheet.less index 5317b3b5..6ca1ca4f 100644 --- a/styles/less/actors/character/sheet.less +++ b/styles/less/actors/character/sheet.less @@ -1,32 +1,32 @@ -@import '../../utils/colors.less'; -@import '../../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .window-content { - display: grid; - grid-template-columns: 275px 1fr; - grid-template-rows: 283px 1fr; - gap: 15px 0; - height: 100%; - width: 100%; - - .character-sidebar-sheet { - grid-row: 1 / span 2; - grid-column: 1; - } - - .character-header-sheet { - grid-row: 1; - grid-column: 2; - } - - .tab { - grid-row: 2; - grid-column: 2; - } - - .old-sheet { - width: 500px; - } - } -} +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .window-content { + display: grid; + grid-template-columns: 275px 1fr; + grid-template-rows: 283px 1fr; + gap: 15px 0; + height: 100%; + width: 100%; + + .character-sidebar-sheet { + grid-row: 1 / span 2; + grid-column: 1; + } + + .character-header-sheet { + grid-row: 1; + grid-column: 2; + } + + .tab { + grid-row: 2; + grid-column: 2; + } + + .old-sheet { + width: 500px; + } + } +} diff --git a/styles/less/actors/character/sidebar.less b/styles/less/actors/character/sidebar.less index b3ac42e0..4f8a1f7e 100644 --- a/styles/less/actors/character/sidebar.less +++ b/styles/less/actors/character/sidebar.less @@ -1,283 +1,283 @@ -@import '../../utils/colors.less'; -@import '../../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .character-sidebar-sheet { - width: 275px; - min-width: 275px; - border-right: 1px solid light-dark(@dark-blue, @golden); - background-image: url('../assets/parchments/dh-parchment-dark.png'); - - .theme-light & { - background: transparent; - } - - img { - height: 235px; - width: 275px; - border-bottom: 1px solid light-dark(@dark-blue, @golden); - cursor: pointer; - object-fit: cover; - } - - .info-section { - position: relative; - display: flex; - flex-direction: column; - top: -20px; - gap: 30px; - margin-bottom: -10px; - - .resources-section { - display: flex; - justify-content: space-evenly; - - .status-bar { - position: relative; - width: 100px; - height: 40px; - justify-items: center; - - .status-label { - position: relative; - top: 40px; - height: 22px; - width: 79px; - clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); - background: light-dark(@dark-blue, @golden); - - h4 { - font-weight: bold; - text-align: center; - line-height: 18px; - color: light-dark(@beige, @dark-blue); - } - } - .status-value { - position: absolute; - display: flex; - padding: 0 6px; - font-size: 1.5rem; - align-items: center; - width: 100px; - height: 40px; - justify-content: center; - text-align: center; - z-index: 2; - color: @beige; - - input[type='number'] { - background: transparent; - font-size: 1.5rem; - width: 40px; - height: 30px; - text-align: center; - border: none; - outline: 2px solid transparent; - color: @beige; - - &.bar-input { - padding: 0; - color: @beige; - backdrop-filter: none; - background: transparent; - transition: all 0.3s ease; - - &:hover, - &:focus { - background: @semi-transparent-dark-blue; - backdrop-filter: blur(9.5px); - } - } - } - - .bar-label { - width: 40px; - } - } - .progress-bar { - position: absolute; - appearance: none; - width: 100px; - height: 40px; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 6px; - z-index: 1; - - &::-webkit-progress-bar { - border: none; - background: @dark-blue; - border-radius: 6px; - } - &::-webkit-progress-value { - background: @gradient-hp; - border-radius: 6px; - } - &.stress-color::-webkit-progress-value { - background: @gradient-stress; - border-radius: 6px; - } - &::-moz-progress-value, - &::-moz-progress-bar { - border-radius: 6px; - } - - &::-moz-progress-bar { - background: @gradient-hp; - } - &.stress-color::-moz-progress-bar { - background: @gradient-stress; - border-radius: 6px; - } - } - } - } - - .status-section { - display: flex; - flex-wrap: wrap; - gap: 5px; - justify-content: center; - - .status-number { - justify-items: center; - - .status-value { - position: relative; - display: flex; - width: 50px; - height: 30px; - border: 1px solid light-dark(@dark-blue, @golden); - border-bottom: none; - border-radius: 6px 6px 0 0; - padding: 0 6px; - font-size: 1.2rem; - align-items: center; - justify-content: center; - background: light-dark(transparent, @dark-blue); - z-index: 2; - - &.armor-slots { - width: 80px; - height: 30px; - } - } - - .status-label { - padding: 2px 10px; - width: 100%; - border-radius: 3px; - background: light-dark(@dark-blue, @golden); - - h4 { - font-weight: bold; - text-align: center; - line-height: 18px; - font-size: 12px; - color: light-dark(@beige, @dark-blue); - } - } - } - } - } - - .items-sidebar-list { - display: flex; - flex-direction: column; - gap: 5px; - - .inventory-item { - padding: 0 10px; - } - } - - .equipment-section { - .title { - display: flex; - gap: 15px; - align-items: center; - - h3 { - font-size: 20px; - } - } - .items-list { - display: flex; - flex-direction: column; - gap: 10px; - align-items: center; - } - } - - .loadout-section { - .title { - display: flex; - gap: 15px; - align-items: center; - - h3 { - font-size: 20px; - } - } - } - - .experience-section { - .title { - display: flex; - gap: 15px; - align-items: center; - - h3 { - font-size: 20px; - } - } - - .experience-list { - display: flex; - flex-direction: column; - gap: 5px; - width: 100%; - margin-top: 10px; - align-items: center; - - .experience-row { - display: flex; - gap: 5px; - width: 250px; - align-items: center; - justify-content: space-between; - - input[type='text'] { - height: 32px; - width: 180px; - border: 1px solid transparent; - outline: 2px solid transparent; - font-size: 14px; - font-family: @font-body; - transition: all 0.3s ease; - color: light-dark(@dark, @beige); - - &:hover { - outline: 2px solid light-dark(@dark, @beige); - } - } - } - - .experience-value { - height: 25px; - width: 35px; - font-size: 14px; - font-family: @font-body; - color: light-dark(@dark, @beige); - align-content: center; - text-align: center; - background: url(../assets/svg/experience-shield.svg) no-repeat; - - .theme-light & { - background: url('../assets/svg/experience-shield-light.svg') no-repeat; - } - } - } - } - } -} +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .character-sidebar-sheet { + width: 275px; + min-width: 275px; + border-right: 1px solid light-dark(@dark-blue, @golden); + background-image: url('../assets/parchments/dh-parchment-dark.png'); + + .theme-light & { + background: transparent; + } + + img { + height: 235px; + width: 275px; + border-bottom: 1px solid light-dark(@dark-blue, @golden); + cursor: pointer; + object-fit: cover; + } + + .info-section { + position: relative; + display: flex; + flex-direction: column; + top: -20px; + gap: 30px; + margin-bottom: -10px; + + .resources-section { + display: flex; + justify-content: space-evenly; + + .status-bar { + position: relative; + width: 100px; + height: 40px; + justify-items: center; + + .status-label { + position: relative; + top: 40px; + height: 22px; + width: 79px; + clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(@beige, @dark-blue); + } + } + .status-value { + position: absolute; + display: flex; + padding: 0 6px; + font-size: 1.5rem; + align-items: center; + width: 100px; + height: 40px; + justify-content: center; + text-align: center; + z-index: 2; + color: @beige; + + input[type='number'] { + background: transparent; + font-size: 1.5rem; + width: 40px; + height: 30px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: @beige; + + &.bar-input { + padding: 0; + color: @beige; + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; + + &:hover, + &:focus { + background: @semi-transparent-dark-blue; + backdrop-filter: blur(9.5px); + } + } + } + + .bar-label { + width: 40px; + } + } + .progress-bar { + position: absolute; + appearance: none; + width: 100px; + height: 40px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + z-index: 1; + + &::-webkit-progress-bar { + border: none; + background: @dark-blue; + border-radius: 6px; + } + &::-webkit-progress-value { + background: @gradient-hp; + border-radius: 6px; + } + &.stress-color::-webkit-progress-value { + background: @gradient-stress; + border-radius: 6px; + } + &::-moz-progress-value, + &::-moz-progress-bar { + border-radius: 6px; + } + + &::-moz-progress-bar { + background: @gradient-hp; + } + &.stress-color::-moz-progress-bar { + background: @gradient-stress; + border-radius: 6px; + } + } + } + } + + .status-section { + display: flex; + flex-wrap: wrap; + gap: 5px; + justify-content: center; + + .status-number { + justify-items: center; + + .status-value { + position: relative; + display: flex; + width: 50px; + height: 30px; + border: 1px solid light-dark(@dark-blue, @golden); + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 0 6px; + font-size: 1.2rem; + align-items: center; + justify-content: center; + background: light-dark(transparent, @dark-blue); + z-index: 2; + + &.armor-slots { + width: 80px; + height: 30px; + } + } + + .status-label { + padding: 2px 10px; + width: 100%; + border-radius: 3px; + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + font-size: 12px; + color: light-dark(@beige, @dark-blue); + } + } + } + } + } + + .items-sidebar-list { + display: flex; + flex-direction: column; + gap: 5px; + + .inventory-item { + padding: 0 10px; + } + } + + .equipment-section { + .title { + display: flex; + gap: 15px; + align-items: center; + + h3 { + font-size: 20px; + } + } + .items-list { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + } + } + + .loadout-section { + .title { + display: flex; + gap: 15px; + align-items: center; + + h3 { + font-size: 20px; + } + } + } + + .experience-section { + .title { + display: flex; + gap: 15px; + align-items: center; + + h3 { + font-size: 20px; + } + } + + .experience-list { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + margin-top: 10px; + align-items: center; + + .experience-row { + display: flex; + gap: 5px; + width: 250px; + align-items: center; + justify-content: space-between; + + input[type='text'] { + height: 32px; + width: 180px; + border: 1px solid transparent; + outline: 2px solid transparent; + font-size: 14px; + font-family: @font-body; + transition: all 0.3s ease; + color: light-dark(@dark, @beige); + + &:hover { + outline: 2px solid light-dark(@dark, @beige); + } + } + } + + .experience-value { + height: 25px; + width: 35px; + font-size: 14px; + font-family: @font-body; + color: light-dark(@dark, @beige); + align-content: center; + text-align: center; + background: url(../assets/svg/experience-shield.svg) no-repeat; + + .theme-light & { + background: url('../assets/svg/experience-shield-light.svg') no-repeat; + } + } + } + } + } +} diff --git a/styles/less/global/inventory-fieldset-items.less b/styles/less/global/inventory-fieldset-items.less index b09ad1b3..0c427bf7 100644 --- a/styles/less/global/inventory-fieldset-items.less +++ b/styles/less/global/inventory-fieldset-items.less @@ -1,17 +1,17 @@ -@import '../utils/colors.less'; -@import '../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .items-list { - display: flex; - flex-direction: column; - gap: 10px; - align-items: center; - } - .card-list { - display: flex; - flex-direction: row; - gap: 10px; - align-items: center; - } -} +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .items-list { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + } + .card-list { + display: flex; + flex-direction: row; + gap: 10px; + align-items: center; + } +} diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index a5b829ed..a14bd83e 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -1,133 +1,133 @@ -@import '../utils/colors.less'; -@import '../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .inventory-item { - display: grid; - grid-template-columns: 40px 1fr 60px; - gap: 10px; - width: 100%; - - .item-img { - height: 40px; - width: 40px; - border-radius: 3px; - border: none; - cursor: pointer; - object-fit: cover; - } - - .item-label { - font-family: @font-body; - align-self: center; - - .item-name { - font-size: 14px; - } - - .item-tags, - .item-labels { - display: flex; - gap: 10px; - - .tag { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 3px 5px; - font-size: 12px; - - background: light-dark(@dark-15, @beige-15); - border: 1px solid light-dark(@dark, @beige); - border-radius: 3px; - } - - .label { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - font-size: 12px; - } - } - } - - .controls { - display: flex; - align-items: center; - justify-content: end; - gap: 8px; - - a { - text-align: center; - - &.unequipped { - opacity: 0.4; - } - } - } - } - .card-item { - position: relative; - height: 120px; - width: 100px; - border: 1px solid light-dark(@dark-blue, @golden); - border-radius: 6px; - cursor: pointer; - - &:hover { - .card-label { - padding-top: 15px; - .controls { - opacity: 1; - visibility: visible; - transition: all 0.3s ease; - max-height: 16px; - } - } - } - - .card-img { - height: 100%; - width: 100%; - object-fit: cover; - } - - .card-label { - display: flex; - flex-direction: column; - height: fit-content; - align-items: center; - gap: 5px; - padding-top: 5px; - padding-bottom: 5px; - width: 100%; - position: absolute; - background-color: @dark-blue; - bottom: 0; - mask-image: linear-gradient(180deg, transparent 0%, black 20%); - - .card-name { - font-family: @font-body; - font-style: normal; - font-weight: 400; - font-size: 12px; - line-height: 15px; - - color: @beige; - } - - .controls { - display: flex; - gap: 15px; - align-items: center; - max-height: 0px; - opacity: 0; - visibility: collapse; - transition: all 0.3s ease; - color: @beige; - } - } - } -} +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .inventory-item { + display: grid; + grid-template-columns: 40px 1fr 60px; + gap: 10px; + width: 100%; + + .item-img { + height: 40px; + width: 40px; + border-radius: 3px; + border: none; + cursor: pointer; + object-fit: cover; + } + + .item-label { + font-family: @font-body; + align-self: center; + + .item-name { + font-size: 14px; + } + + .item-tags, + .item-labels { + display: flex; + gap: 10px; + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; + } + } + } + + .controls { + display: flex; + align-items: center; + justify-content: end; + gap: 8px; + + a { + text-align: center; + + &.unequipped { + opacity: 0.4; + } + } + } + } + .card-item { + position: relative; + height: 120px; + width: 100px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + cursor: pointer; + + &:hover { + .card-label { + padding-top: 15px; + .controls { + opacity: 1; + visibility: visible; + transition: all 0.3s ease; + max-height: 16px; + } + } + } + + .card-img { + height: 100%; + width: 100%; + object-fit: cover; + } + + .card-label { + display: flex; + flex-direction: column; + height: fit-content; + align-items: center; + gap: 5px; + padding-top: 5px; + padding-bottom: 5px; + width: 100%; + position: absolute; + background-color: @dark-blue; + bottom: 0; + mask-image: linear-gradient(180deg, transparent 0%, black 20%); + + .card-name { + font-family: @font-body; + font-style: normal; + font-weight: 400; + font-size: 12px; + line-height: 15px; + + color: @beige; + } + + .controls { + display: flex; + gap: 15px; + align-items: center; + max-height: 0px; + opacity: 0; + visibility: collapse; + transition: all 0.3s ease; + color: @beige; + } + } + } +} diff --git a/styles/less/global/tab-navigation.less b/styles/less/global/tab-navigation.less index a3b48d87..a13c7ea2 100755 --- a/styles/less/global/tab-navigation.less +++ b/styles/less/global/tab-navigation.less @@ -1,18 +1,18 @@ -@import '../utils/colors.less'; -@import '../utils/fonts.less'; - -.sheet.daggerheart.dh-style { - .tab-navigation { - margin: 5px 0; - height: 40px; - - .feature-tab { - border: none; - - a { - color: light-dark(@dark-blue, @golden); - font-family: @font-body; - } - } - } -} +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.sheet.daggerheart.dh-style { + .tab-navigation { + margin: 5px 0; + height: 40px; + + .feature-tab { + border: none; + + a { + color: light-dark(@dark-blue, @golden); + font-family: @font-body; + } + } + } +} diff --git a/styles/less/utils/colors.less b/styles/less/utils/colors.less index 8dbb0f63..eefff4a2 100755 --- a/styles/less/utils/colors.less +++ b/styles/less/utils/colors.less @@ -1,21 +1,21 @@ -@primary-blue: #1488cc; -@secondary-blue: #2b32b2; -@golden: #f3c267; -@golden-40: #f3c26740; -@dark-blue-40: #18162e40; -@golden-10: #f3c26710; -@dark-blue-10: #18162e10; -@dark-blue-50: #18162e50; -@dark-blue: #18162e; -@deep-black: #0e0d15; -@beige: #efe6d8; -@beige-15: #efe6d815; -@beige-50: #efe6d850; -@dark-blue: rgb(24, 22, 46); -@semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); -@dark: #222; -@dark-15: #22222215; -@light-black: rgba(0, 0, 0, 0.3); -@soft-shadow: rgba(0, 0, 0, 0.05); -@gradient-hp: linear-gradient(15deg, rgb(70, 20, 10) 0%, rgb(190, 0, 0) 42%, rgb(252, 176, 69) 100%); -@gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%); +@primary-blue: #1488cc; +@secondary-blue: #2b32b2; +@golden: #f3c267; +@golden-40: #f3c26740; +@dark-blue-40: #18162e40; +@golden-10: #f3c26710; +@dark-blue-10: #18162e10; +@dark-blue-50: #18162e50; +@dark-blue: #18162e; +@deep-black: #0e0d15; +@beige: #efe6d8; +@beige-15: #efe6d815; +@beige-50: #efe6d850; +@dark-blue: rgb(24, 22, 46); +@semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); +@dark: #222; +@dark-15: #22222215; +@light-black: rgba(0, 0, 0, 0.3); +@soft-shadow: rgba(0, 0, 0, 0.05); +@gradient-hp: linear-gradient(15deg, rgb(70, 20, 10) 0%, rgb(190, 0, 0) 42%, rgb(252, 176, 69) 100%); +@gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%); diff --git a/templates/views/damageReduction.hbs b/templates/views/damageReduction.hbs new file mode 100644 index 00000000..33bf53aa --- /dev/null +++ b/templates/views/damageReduction.hbs @@ -0,0 +1,76 @@ +