Feature/517 action cost on success (#546)

* Add checkboxes

* Consume post roll logic
This commit is contained in:
Dapoulp 2025-08-03 18:45:11 +02:00 committed by GitHub
parent 90a97c7105
commit c46d676cc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 93 additions and 18 deletions

View file

@ -208,7 +208,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return !this.hasRoll;
}
async consume(config) {
async consume(config, successCost = false) {
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
for (var cost of config.costs) {
@ -220,8 +220,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
};
}
}
const resources = config.costs
.filter(c => c.enabled !== false)
.filter(c =>
c.enabled !== false
&&
(
(!successCost && (!c.consumeOnSuccess || config.roll?.success))
||
(successCost && c.consumeOnSuccess)
)
)
.map(c => {
const resource = usefulResources[c.key];
return {
@ -233,7 +242,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
});
await this.actor.modifyResource(resources);
if (config.uses?.enabled) this.update({ 'uses.value': this.uses.value + 1 });
if (config.uses?.enabled
&&
(
(!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success))
||
(successCost && config.uses?.consumeOnSuccess)
)
) this.update({ 'uses.value': this.uses.value + 1 });
if(config.roll?.success || successCost)
(config.message ?? config.parent).update({'system.successConsumed': true})
}
/* */

View file

@ -42,10 +42,28 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
damage: new fields.ObjectField(),
costs: new fields.ArrayField(
new fields.ObjectField()
)
),
successConsumed: new fields.BooleanField({ initial: false })
};
}
get actionActor() {
if(!this.source.actor) return null;
return fromUuidSync(this.source.actor);
}
get actionItem() {
const actionActor = this.actionActor;
if(!actionActor || !this.source.item) return null;
return actionActor.items.get(this.source.item);
}
get action() {
const actionItem = this.actionItem;
if(!actionItem || !this.source.action) return null;
return actionItem.system.actionsList?.find(a => a.id === this.source.action);
}
get messageTemplate() {
return 'systems/daggerheart/templates/ui/chat/roll.hbs';
}
@ -74,14 +92,14 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
async updateTargets() {
this.currentTargets = this.getTargetList();
if(!this.targetSelection && this.hasSave) {
if(!this.targetSelection) {
this.currentTargets.forEach(ct => {
if(this.targets.find(t => t.actorId === ct.actorId)) return;
const indexTarget = this.oldTargets.findIndex(ot => ot.actorId === ct.actorId);
if(indexTarget === -1)
this.oldTargets.push(ct);
});
this.setPendingSaves();
if(this.hasSave) this.setPendingSaves();
if(this.currentTargets.length) {
if(!this.parent._id) return;
const updates = await this.parent.update(
@ -91,7 +109,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
}
}
);
if(!updates)
if(!updates && ui.chat.collection.get(this.parent.id))
ui.chat.updateMessage(this.parent);
}
}

View file

@ -9,9 +9,10 @@ export default class CostField extends fields.ArrayField {
initial: 'hope'
}),
keyIsID: new fields.BooleanField(),
value: new fields.NumberField({ nullable: true, initial: 1 }),
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
scalable: new fields.BooleanField({ initial: false }),
step: new fields.NumberField({ nullable: true, initial: null })
step: new fields.NumberField({ nullable: true, initial: null }),
consumeOnSuccess: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label" })
});
super(element, options, context);
}
@ -28,9 +29,9 @@ export default class CostField extends fields.ArrayField {
static calcCosts(costs) {
const resources = CostField.getResources.call(this, costs);
return costs.map(c => {
c.scale = c.scale ?? 1;
c.scale = c.scale ?? 0;
c.step = c.step ?? 1;
c.total = c.value + (c.scale - 1) * c.step;
c.total = c.value + c.scale * c.step;
c.enabled = c.hasOwnProperty('enabled') ? c.enabled : true;
c.max =
c.key === 'fear'
@ -38,7 +39,7 @@ export default class CostField extends fields.ArrayField {
: resources[c.key].isReversed
? resources[c.key].max
: resources[c.key].value;
if (c.scalable) c.maxStep = Math.floor(c.max / c.step);
if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step);
return c;
});
}

View file

@ -11,7 +11,8 @@ export default class UsesField extends fields.SchemaField {
choices: CONFIG.DH.GENERAL.refreshTypes,
initial: null,
nullable: true
})
}),
consumeOnSuccess: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label" })
};
super(usesFields, options, context);
}

View file

@ -147,6 +147,7 @@ export default class D20Roll extends DHRoll {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = this.isCritical || roll.total >= difficulty;
});
data.success = config.targets.some(target => target.hit)
} else if (config.roll.difficulty) {
data.difficulty = config.roll.difficulty;
data.success = roll.isCritical || roll.total >= config.roll.difficulty;

View file

@ -88,7 +88,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
if (targets.length === 0)
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
for (let target of targets) {
let damages = foundry.utils.deepClone(this.system.damage);
if (
@ -106,6 +106,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
});
}
this.consumeOnSuccess();
if (this.system.hasHealing) target.actor.takeHealing(damages);
else target.actor.takeDamage(damages);
}
@ -132,7 +133,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
const targets = this.getTargetList();
if (targets.length === 0)
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
this.consumeOnSuccess();
await action.applyEffects(event, this, targets);
}
}
consumeOnSuccess() {
if(!this.system.successConsumed && !this.system.targetSelection) {
const action = this.system.action;
if(action) action.consume(this.system, true);
}
}
}