mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Feature/517 action cost on success (#546)
* Add checkboxes * Consume post roll logic
This commit is contained in:
parent
90a97c7105
commit
c46d676cc3
11 changed files with 93 additions and 18 deletions
|
|
@ -85,6 +85,13 @@
|
||||||
},
|
},
|
||||||
"applyTo": {
|
"applyTo": {
|
||||||
"label": "Targeted Resource"
|
"label": "Targeted Resource"
|
||||||
|
},
|
||||||
|
"consumeOnSuccess": {
|
||||||
|
"label": "Consume on Success only",
|
||||||
|
"short": "(on Success only)"
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"stepTooltip": "+{step} per step"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return !this.hasRoll;
|
return !this.hasRoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
async consume(config) {
|
async consume(config, successCost = false) {
|
||||||
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
|
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||||
|
|
||||||
for (var cost of config.costs) {
|
for (var cost of config.costs) {
|
||||||
|
|
@ -220,8 +220,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resources = config.costs
|
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 => {
|
.map(c => {
|
||||||
const resource = usefulResources[c.key];
|
const resource = usefulResources[c.key];
|
||||||
return {
|
return {
|
||||||
|
|
@ -233,7 +242,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.actor.modifyResource(resources);
|
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})
|
||||||
}
|
}
|
||||||
/* */
|
/* */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,28 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
damage: new fields.ObjectField(),
|
damage: new fields.ObjectField(),
|
||||||
costs: new fields.ArrayField(
|
costs: new fields.ArrayField(
|
||||||
new fields.ObjectField()
|
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() {
|
get messageTemplate() {
|
||||||
return 'systems/daggerheart/templates/ui/chat/roll.hbs';
|
return 'systems/daggerheart/templates/ui/chat/roll.hbs';
|
||||||
}
|
}
|
||||||
|
|
@ -74,14 +92,14 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
async updateTargets() {
|
async updateTargets() {
|
||||||
this.currentTargets = this.getTargetList();
|
this.currentTargets = this.getTargetList();
|
||||||
if(!this.targetSelection && this.hasSave) {
|
if(!this.targetSelection) {
|
||||||
this.currentTargets.forEach(ct => {
|
this.currentTargets.forEach(ct => {
|
||||||
if(this.targets.find(t => t.actorId === ct.actorId)) return;
|
if(this.targets.find(t => t.actorId === ct.actorId)) return;
|
||||||
const indexTarget = this.oldTargets.findIndex(ot => ot.actorId === ct.actorId);
|
const indexTarget = this.oldTargets.findIndex(ot => ot.actorId === ct.actorId);
|
||||||
if(indexTarget === -1)
|
if(indexTarget === -1)
|
||||||
this.oldTargets.push(ct);
|
this.oldTargets.push(ct);
|
||||||
});
|
});
|
||||||
this.setPendingSaves();
|
if(this.hasSave) this.setPendingSaves();
|
||||||
if(this.currentTargets.length) {
|
if(this.currentTargets.length) {
|
||||||
if(!this.parent._id) return;
|
if(!this.parent._id) return;
|
||||||
const updates = await this.parent.update(
|
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);
|
ui.chat.updateMessage(this.parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,10 @@ export default class CostField extends fields.ArrayField {
|
||||||
initial: 'hope'
|
initial: 'hope'
|
||||||
}),
|
}),
|
||||||
keyIsID: new fields.BooleanField(),
|
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 }),
|
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);
|
super(element, options, context);
|
||||||
}
|
}
|
||||||
|
|
@ -28,9 +29,9 @@ export default class CostField extends fields.ArrayField {
|
||||||
static calcCosts(costs) {
|
static calcCosts(costs) {
|
||||||
const resources = CostField.getResources.call(this, costs);
|
const resources = CostField.getResources.call(this, costs);
|
||||||
return costs.map(c => {
|
return costs.map(c => {
|
||||||
c.scale = c.scale ?? 1;
|
c.scale = c.scale ?? 0;
|
||||||
c.step = c.step ?? 1;
|
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.enabled = c.hasOwnProperty('enabled') ? c.enabled : true;
|
||||||
c.max =
|
c.max =
|
||||||
c.key === 'fear'
|
c.key === 'fear'
|
||||||
|
|
@ -38,7 +39,7 @@ export default class CostField extends fields.ArrayField {
|
||||||
: resources[c.key].isReversed
|
: resources[c.key].isReversed
|
||||||
? resources[c.key].max
|
? resources[c.key].max
|
||||||
: resources[c.key].value;
|
: 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;
|
return c;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ export default class UsesField extends fields.SchemaField {
|
||||||
choices: CONFIG.DH.GENERAL.refreshTypes,
|
choices: CONFIG.DH.GENERAL.refreshTypes,
|
||||||
initial: null,
|
initial: null,
|
||||||
nullable: true
|
nullable: true
|
||||||
})
|
}),
|
||||||
|
consumeOnSuccess: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label" })
|
||||||
};
|
};
|
||||||
super(usesFields, options, context);
|
super(usesFields, options, context);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ export default class D20Roll extends DHRoll {
|
||||||
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
||||||
target.hit = this.isCritical || roll.total >= difficulty;
|
target.hit = this.isCritical || roll.total >= difficulty;
|
||||||
});
|
});
|
||||||
|
data.success = config.targets.some(target => target.hit)
|
||||||
} else if (config.roll.difficulty) {
|
} else if (config.roll.difficulty) {
|
||||||
data.difficulty = config.roll.difficulty;
|
data.difficulty = config.roll.difficulty;
|
||||||
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
|
|
||||||
if (targets.length === 0)
|
if (targets.length === 0)
|
||||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||||
|
|
||||||
for (let target of targets) {
|
for (let target of targets) {
|
||||||
let damages = foundry.utils.deepClone(this.system.damage);
|
let damages = foundry.utils.deepClone(this.system.damage);
|
||||||
if (
|
if (
|
||||||
|
|
@ -106,6 +106,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.consumeOnSuccess();
|
||||||
if (this.system.hasHealing) target.actor.takeHealing(damages);
|
if (this.system.hasHealing) target.actor.takeHealing(damages);
|
||||||
else target.actor.takeDamage(damages);
|
else target.actor.takeDamage(damages);
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +133,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
const targets = this.getTargetList();
|
const targets = this.getTargetList();
|
||||||
if (targets.length === 0)
|
if (targets.length === 0)
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||||
|
this.consumeOnSuccess();
|
||||||
await action.applyEffects(event, this, targets);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -381,7 +381,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.scalable-input {
|
.scalable-input {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr auto;
|
||||||
|
// display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
@ -394,6 +396,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: nowrap;
|
||||||
font-family: @font-body;
|
font-family: @font-body;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
@ -402,6 +407,11 @@
|
||||||
width: 6ch;
|
width: 6ch;
|
||||||
text-align: end;
|
text-align: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
font-size: var(--font-size-10);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
<a><i class="fa-solid fa-plus icon-button" data-action="addElement"></i></a>
|
<a><i class="fa-solid fa-plus icon-button" data-action="addElement"></i></a>
|
||||||
</legend>
|
</legend>
|
||||||
{{#each source as |cost index|}}
|
{{#each source as |cost index|}}
|
||||||
|
{{#if @root.hasRoll}}
|
||||||
|
{{formField ../fields.consumeOnSuccess value=cost.consumeOnSuccess name=(concat "cost." index ".consumeOnSuccess") classes="checkbox" rootId=partId localize=true}}
|
||||||
|
{{/if}}
|
||||||
<div class="nest-inputs">
|
<div class="nest-inputs">
|
||||||
{{formField ../fields.scalable label="Scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox"}}
|
{{formField ../fields.scalable label="Scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox"}}
|
||||||
{{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="Resource" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
|
{{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="Resource" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<fieldset class="one-column">
|
<fieldset class="one-column">
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.uses"}}</legend>
|
<legend>{{localize "DAGGERHEART.GENERAL.uses"}}</legend>
|
||||||
|
{{#if hasRoll}}
|
||||||
|
{{formField fields.consumeOnSuccess value=source.consumeOnSuccess name="uses.consumeOnSuccess" classes="checkbox" rootId=partId localize=true}}
|
||||||
|
{{/if}}
|
||||||
<div class="nest-inputs">
|
<div class="nest-inputs">
|
||||||
{{formField fields.value label="Spent" value=source.value name="uses.value" rootId=partId}}
|
{{formField fields.value label="Spent" value=source.value name="uses.value" rootId=partId}}
|
||||||
{{formField fields.max label="Max" value=source.max name="uses.max" rootId=partId}}
|
{{formField fields.max label="Max" value=source.max name="uses.max" rootId=partId}}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-fields nest-inputs">
|
<div class="form-fields nest-inputs">
|
||||||
<input name="uses.enabled" type="checkbox"{{#if uses.enabled}} checked{{/if}}>
|
<input name="uses.enabled" type="checkbox"{{#if uses.enabled}} checked{{/if}}>
|
||||||
<label for="uses.enabled">Uses</label>
|
<label for="uses.enabled">Uses{{#if uses.consumeOnSuccess}}<span class="hint">{{localize "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.short"}}{{/if}}</span></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div></div>
|
||||||
<label class="modifier-label">{{uses.value}}/{{formulaValue uses.max @root.rollConfig.data}}</label>
|
<label class="modifier-label">{{uses.value}}/{{formulaValue uses.max @root.rollConfig.data}}</label>
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
@ -17,11 +18,13 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="form-fields nest-inputs">
|
<div class="form-fields nest-inputs">
|
||||||
<input name="costs.{{index}}.enabled" type="checkbox"{{#if enabled}} checked{{/if}}>
|
<input name="costs.{{index}}.enabled" type="checkbox"{{#if enabled}} checked{{/if}}>
|
||||||
<label>{{label}}</label>
|
<label>{{label}}{{#if cost.consumeOnSuccess}}<span class="hint">{{localize "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.short"}}</span>{{/if}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{#if (and scalable (gt maxStep 1))}}
|
{{#if (and scalable (gt maxStep 1))}}
|
||||||
<input type="range" value="{{scale}}" min="1" max="{{maxStep}}" step="1" name="costs.{{index}}.scale">
|
<input type="range" value="{{scale}}" min="0" max="{{maxStep}}" step="1" name="costs.{{index}}.scale" data-tooltip="{{localize "DAGGERHEART.ACTIONS.Settings.cost.stepTooltip" step=step}}" data-tooltip-direction="UP">
|
||||||
|
{{else}}
|
||||||
|
<div></div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<label class="modifier-label">{{total}}/{{max}}</label>
|
<label class="modifier-label">{{total}}/{{max}}</label>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue