mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
[Feature] 1033 - Consume Quantity On Use (#1106)
* Initial migration * Updated compendium YML * Added Quantity as a possible cost * Added quantity consumption to all Compendium Consumables * . * Added DestroyOnEmpty property
This commit is contained in:
parent
9dd773001d
commit
8fd63d5963
397 changed files with 1276 additions and 930 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { abilities } from "../../config/actorConfig.mjs";
|
||||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
);
|
||||
context.costs = updatedCosts.map(x => ({
|
||||
...x,
|
||||
label: x.keyIsID
|
||||
label: x.itemId
|
||||
? this.action.parent.parent.name
|
||||
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
||||
}));
|
||||
|
|
@ -115,7 +115,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.isLite = this.config.roll?.lite;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
if(this.actor.system.traits) context.abilities = this.getTraitModifiers();
|
||||
if (this.actor.system.traits) context.abilities = this.getTraitModifiers();
|
||||
|
||||
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
||||
context.reactionOverride = this.reactionOverride;
|
||||
|
|
@ -124,12 +124,15 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
getTraitModifiers() {
|
||||
return Object.values(abilities).map(a => ({ id: a.id, label: `${game.i18n.localize(a.label)} (${this.actor.system.traits[a.id]?.value.signedString() ?? 0})` }))
|
||||
return Object.values(abilities).map(a => ({
|
||||
id: a.id,
|
||||
label: `${game.i18n.localize(a.label)} (${this.actor.system.traits[a.id]?.value.signedString() ?? 0})`
|
||||
}));
|
||||
}
|
||||
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
|
||||
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
if (this.config.costs) {
|
||||
|
|
@ -141,7 +144,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.roll[key] = value;
|
||||
});
|
||||
}
|
||||
if(rest.hasOwnProperty("trait")) {
|
||||
if (rest.hasOwnProperty('trait')) {
|
||||
this.config.roll.trait = rest.trait;
|
||||
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
||||
|
|
@ -169,14 +172,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
||||
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
|
||||
: [
|
||||
...this.config.costs,
|
||||
{
|
||||
extKey: button.dataset.key,
|
||||
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
...this.config.costs,
|
||||
{
|
||||
extKey: button.dataset.key,
|
||||
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -132,12 +132,19 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
||||
const resource = this.action.parent.resource;
|
||||
if (resource) {
|
||||
options[this.action.parent.parent.id] = {
|
||||
options.resource = {
|
||||
label: 'DAGGERHEART.GENERAL.itemResource',
|
||||
group: 'Global'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.action.parent.metadata.isQuantifiable) {
|
||||
options.quantity = {
|
||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||
group: 'Global'
|
||||
};
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
@ -164,13 +171,14 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
|
||||
_prepareSubmitData(_event, formData) {
|
||||
const submitData = foundry.utils.expandObject(formData.object);
|
||||
|
||||
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
|
||||
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
||||
const data = foundry.utils.getProperty(submitData, keyPath);
|
||||
const dataValues = data ? Object.values(data) : [];
|
||||
if (keyPath === 'cost') {
|
||||
for (var value of dataValues) {
|
||||
const item = this.action.parent.parent.id === value.key;
|
||||
value.keyIsID = Boolean(item);
|
||||
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
});
|
||||
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
||||
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
||||
element.addEventListener('click', e => e.stopPropagation());
|
||||
});
|
||||
|
||||
// Add listener for armor marks input
|
||||
|
|
@ -676,7 +677,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
})
|
||||
});
|
||||
|
||||
if(result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
|
||||
if (result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
|
||||
}
|
||||
|
||||
//TODO: redo toggleEquipItem method
|
||||
|
|
|
|||
|
|
@ -84,8 +84,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
return {
|
||||
key: c.key,
|
||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||
target: resource.target,
|
||||
keyIsID: resource.keyIsID
|
||||
target: resource.target
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -561,6 +561,19 @@ export const refreshTypes = {
|
|||
}
|
||||
};
|
||||
|
||||
export const itemAbilityCosts = {
|
||||
resource: {
|
||||
id: 'resource',
|
||||
label: 'DAGGERHEART.GENERAL.resource',
|
||||
group: 'Global'
|
||||
},
|
||||
quantity: {
|
||||
id: 'quantity',
|
||||
label: 'DAGGERHEART.GENERAL.quantity',
|
||||
group: 'Global'
|
||||
}
|
||||
};
|
||||
|
||||
export const abilityCosts = {
|
||||
hitPoints: {
|
||||
id: 'hitPoints',
|
||||
|
|
@ -586,7 +599,8 @@ export const abilityCosts = {
|
|||
id: 'fear',
|
||||
label: 'Fear',
|
||||
group: 'TYPES.Actor.adversary'
|
||||
}
|
||||
},
|
||||
resource: itemAbilityCosts.resource
|
||||
};
|
||||
|
||||
export const countdownTypes = {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default class CostField extends fields.ArrayField {
|
|||
* Action Workflow order
|
||||
*/
|
||||
static order = 150;
|
||||
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const element = new fields.SchemaField({
|
||||
|
|
@ -14,7 +14,7 @@ export default class CostField extends fields.ArrayField {
|
|||
required: true,
|
||||
initial: 'hope'
|
||||
}),
|
||||
keyIsID: new fields.BooleanField(),
|
||||
itemId: new fields.StringField({ nullable: true, initial: null }),
|
||||
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
|
||||
scalable: new fields.BooleanField({ initial: false }),
|
||||
step: new fields.NumberField({ nullable: true, initial: null }),
|
||||
|
|
@ -34,23 +34,23 @@ export default class CostField extends fields.ArrayField {
|
|||
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
|
||||
*/
|
||||
static async execute(config, successCost = false) {
|
||||
const actor= this.actor.system.partner ?? this.actor,
|
||||
const actor = this.actor.system.partner ?? this.actor,
|
||||
usefulResources = {
|
||||
...foundry.utils.deepClone(actor.system.resources),
|
||||
fear: {
|
||||
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||
reversed: false
|
||||
}
|
||||
};
|
||||
...foundry.utils.deepClone(actor.system.resources),
|
||||
fear: {
|
||||
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||
reversed: false
|
||||
}
|
||||
};
|
||||
|
||||
if(this.parent?.parent) {
|
||||
if (this.parent?.parent) {
|
||||
for (var cost of config.costs) {
|
||||
if (cost.keyIsID) {
|
||||
if (cost.itemId) {
|
||||
usefulResources[cost.key] = {
|
||||
value: cost.value,
|
||||
target: this.parent.parent,
|
||||
keyIsID: true
|
||||
itemId: cost.itemId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -69,12 +69,12 @@ export default class CostField extends fields.ArrayField {
|
|||
key: c.key,
|
||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||
target: resource.target,
|
||||
keyIsID: resource.keyIsID
|
||||
itemId: resource.itemId
|
||||
});
|
||||
return a;
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
await actor.modifyResource(resources);
|
||||
}
|
||||
|
||||
|
|
@ -95,14 +95,19 @@ export default class CostField extends fields.ArrayField {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Must be called within Action context.
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
*/
|
||||
static calcCosts(costs) {
|
||||
const resources = CostField.getResources.call(this, costs);
|
||||
return costs.map(c => {
|
||||
let filteredCosts = costs;
|
||||
if (this.parent.metadata.isQuantifiable && this.parent.consumeOnUse === false) {
|
||||
filteredCosts = filteredCosts.filter(c => c.key !== 'quantity');
|
||||
}
|
||||
|
||||
return filteredCosts.map(c => {
|
||||
c.scale = c.scale ?? 0;
|
||||
c.step = c.step ?? 1;
|
||||
c.total = c.value + c.scale * c.step;
|
||||
|
|
@ -121,7 +126,7 @@ export default class CostField extends fields.ArrayField {
|
|||
/**
|
||||
* Check if the current Actor currently has all needed resources.
|
||||
* Must be called within Action context.
|
||||
* @param {*} costs
|
||||
* @param {*} costs
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static hasCost(costs) {
|
||||
|
|
@ -153,8 +158,8 @@ export default class CostField extends fields.ArrayField {
|
|||
/**
|
||||
* Get all Actor resources + parent Item potential one.
|
||||
* Must be called within Action context.
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
*/
|
||||
static getResources(costs) {
|
||||
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||
|
|
@ -162,11 +167,8 @@ export default class CostField extends fields.ArrayField {
|
|||
actorResources.hope = foundry.utils.deepClone(this.actor.system.partner.system.resources.hope);
|
||||
const itemResources = {};
|
||||
for (let itemResource of costs) {
|
||||
if (itemResource.keyIsID) {
|
||||
itemResources[itemResource.key] = {
|
||||
value: this.parent.resource.value ?? 0,
|
||||
max: CostField.formatMax.call(this, this.parent?.resource?.max)
|
||||
};
|
||||
if (itemResource.itemId) {
|
||||
itemResources[itemResource.key] = CostField.getItemIdCostResource.bind(this)(itemResource);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,10 +178,44 @@ export default class CostField extends fields.ArrayField {
|
|||
};
|
||||
}
|
||||
|
||||
static getItemIdCostResource(itemResource) {
|
||||
switch (itemResource.key) {
|
||||
case CONFIG.DH.GENERAL.itemAbilityCosts.resource.id:
|
||||
return {
|
||||
value: this.parent.resource.value ?? 0,
|
||||
max: CostField.formatMax.call(this, this.parent?.resource?.max)
|
||||
};
|
||||
case CONFIG.DH.GENERAL.itemAbilityCosts.quantity.id:
|
||||
return {
|
||||
value: this.parent.quantity ?? 0,
|
||||
max: this.parent.quantity ?? 0
|
||||
};
|
||||
default:
|
||||
return { value: 0, max: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
static getItemIdCostUpdate(r) {
|
||||
switch (r.key) {
|
||||
case CONFIG.DH.GENERAL.itemAbilityCosts.resource.id:
|
||||
return {
|
||||
path: 'system.resource.value',
|
||||
value: r.target.system.resource.value + r.value
|
||||
};
|
||||
case CONFIG.DH.GENERAL.itemAbilityCosts.quantity.id:
|
||||
return {
|
||||
path: 'system.quantity',
|
||||
value: r.target.system.quantity + r.value
|
||||
};
|
||||
default:
|
||||
return { path: '', value: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
*
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
*/
|
||||
static getRealCosts(costs) {
|
||||
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import { ActionField } from '../fields/actionField.mjs';
|
||||
|
||||
export default class DHConsumable extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -19,7 +18,8 @@ export default class DHConsumable extends BaseDataItem {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
consumeOnUse: new fields.BooleanField({ initial: false })
|
||||
consumeOnUse: new fields.BooleanField({ initial: true }),
|
||||
destroyOnEmpty: new fields.BooleanField({ initial: true })
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -27,5 +27,4 @@ export default class DHConsumable extends BaseDataItem {
|
|||
|
||||
/**@override */
|
||||
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/items/round-potion.svg';
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -659,13 +659,22 @@ export default class DhpActor extends Actor {
|
|||
};
|
||||
|
||||
resources.forEach(r => {
|
||||
if (r.keyIsID) {
|
||||
updates.items[r.key] = {
|
||||
target: r.target,
|
||||
resources: {
|
||||
'system.resource.value': r.target.system.resource.value + r.value
|
||||
}
|
||||
};
|
||||
if (r.itemId) {
|
||||
const { path, value } = game.system.api.fields.ActionFields.CostField.getItemIdCostUpdate(r);
|
||||
|
||||
if (
|
||||
r.key === 'quantity' &&
|
||||
r.target.type === 'consumable' &&
|
||||
value === 0 &&
|
||||
r.target.system.destroyOnEmpty
|
||||
) {
|
||||
r.target.delete();
|
||||
} else {
|
||||
updates.items[r.key] = {
|
||||
target: r.target,
|
||||
resources: { [path]: value }
|
||||
};
|
||||
}
|
||||
} else {
|
||||
switch (r.key) {
|
||||
case 'fear':
|
||||
|
|
|
|||
|
|
@ -96,5 +96,60 @@ export async function runMigrations() {
|
|||
lastMigrationVersion = '1.1.1';
|
||||
}
|
||||
|
||||
if (foundry.utils.isNewerVersion('1.2.0', lastMigrationVersion)) {
|
||||
const lockedPacks = [];
|
||||
const compendiumItems = [];
|
||||
for (let pack of game.packs) {
|
||||
if (pack.locked) {
|
||||
lockedPacks.push(pack.collection);
|
||||
await pack.configure({ locked: false });
|
||||
}
|
||||
const documents = await pack.getDocuments();
|
||||
|
||||
compendiumItems.push(...documents.filter(x => x.system?.metadata?.hasActions));
|
||||
compendiumItems.push(
|
||||
...documents
|
||||
.filter(x => x.items)
|
||||
.flatMap(actor => actor.items.filter(x => x.system?.metadata?.hasActions))
|
||||
);
|
||||
}
|
||||
|
||||
const worldItems = game.items.filter(x => x.system.metadata.hasActions);
|
||||
const worldActorItems = Array.from(game.actors).flatMap(actor =>
|
||||
actor.items.filter(x => x.system.metadata.hasActions)
|
||||
);
|
||||
|
||||
const validCostKeys = Object.keys(CONFIG.DH.GENERAL.abilityCosts);
|
||||
for (let item of [...worldItems, ...worldActorItems, ...compendiumItems]) {
|
||||
for (let action of item.system.actions) {
|
||||
const resourceCostIndexes = Object.keys(action.cost).reduce(
|
||||
(acc, index) => (!validCostKeys.includes(action.cost[index].key) ? [...acc, Number(index)] : acc),
|
||||
[]
|
||||
);
|
||||
if (resourceCostIndexes.length === 0) continue;
|
||||
|
||||
await action.update({
|
||||
cost: action.cost.map((cost, index) => {
|
||||
const { keyIsID, ...rest } = cost;
|
||||
if (!resourceCostIndexes.includes(index)) return { ...rest };
|
||||
|
||||
return {
|
||||
...rest,
|
||||
key: 'resource',
|
||||
itemId: cost.key
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let packId of lockedPacks) {
|
||||
const pack = game.packs.get(packId);
|
||||
await pack.configure({ locked: true });
|
||||
}
|
||||
|
||||
lastMigrationVersion = '1.2.0';
|
||||
}
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue