Skip to content

Commit

Permalink
Allow setting quantity for daily crafting abilities instead of redrag…
Browse files Browse the repository at this point in the history
…ging (#17586)
  • Loading branch information
CarlosFdez authored Dec 3, 2024
1 parent 85da5a7 commit 66f4572
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 27 deletions.
70 changes: 46 additions & 24 deletions src/module/actor/character/crafting/ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,13 @@ class CraftingAbility implements CraftingAbilityData {
const rollOptions = formula?.item.getRollOptions("item") ?? [];
const matching = formula ? this.craftableItems.find((c) => c.predicate.test(rollOptions)) : null;
const batchSize = matching?.batchSize ?? this.batchSize ?? 1;
const batches = Math.ceil((prepData.quantity || 1) / batchSize);
return formula
? {
...formula,
batchSize,
quantity: this.resource ? Math.ceil((prepData.quantity || 1) / batchSize) * batchSize : batchSize,
batches,
quantity: batches * batchSize,
expended: !!prepData.expended,
isSignatureItem: !!prepData.isSignatureItem,
}
Expand All @@ -98,14 +100,16 @@ class CraftingAbility implements CraftingAbilityData {
async getSheetData(): Promise<CraftingAbilitySheetData> {
const preparedCraftingFormulas = await this.getPreparedCraftingFormulas();
const prepared = [...preparedCraftingFormulas];
const remainingSlots = Math.max(0, this.maxSlots - prepared.length);
const consumed = prepared.reduce((sum, p) => sum + p.batches, 0);
const remainingSlots = Math.max(0, this.maxSlots - consumed);

return {
label: this.label,
slug: this.slug,
isAlchemical: this.isAlchemical,
isPrepared: this.isPrepared,
isDailyPrep: this.isDailyPrep,
insufficient: this.maxSlots > 0 && consumed > this.maxSlots,
maxItemLevel: this.maxItemLevel,
resource: this.resource ? this.actor.getResource(this.resource) : null,
resourceCost: await this.calculateResourceCost(),
Expand All @@ -125,27 +129,6 @@ class CraftingAbility implements CraftingAbilityData {
return Math.ceil(values.reduce((total, part) => total + part, 0));
}

async prepareFormula(formula: CraftingFormula): Promise<void> {
if (!this.resource && this.preparedFormulaData.length >= this.maxSlots) {
ui.notifications.warn(game.i18n.localize("PF2E.CraftingTab.Alerts.MaxSlots"));
return;
}

if (!this.canCraft(formula.item)) {
return;
}

const quantity = await this.#batchSizeFor(formula);
const existing = this.preparedFormulaData.find((f) => f.uuid === formula.uuid);
if (existing && this.resource) {
existing.quantity = quantity;
} else {
this.preparedFormulaData.push({ uuid: formula.uuid, quantity });
}

return this.#updateRuleElement();
}

/** Returns true if the item can be created by this ability, which requires it to pass predication and be of sufficient level */
canCraft(item: PhysicalItemPF2e, { warn = true } = {}): boolean {
const rollOptions = item.getRollOptions("item");
Expand All @@ -168,6 +151,29 @@ class CraftingAbility implements CraftingAbilityData {
return true;
}

async prepareFormula(formula: CraftingFormula): Promise<void> {
const prepared = await this.getPreparedCraftingFormulas();
const consumed = prepared.reduce((sum, p) => sum + p.batches, 0);
if (!this.resource && consumed >= this.maxSlots) {
ui.notifications.warn(game.i18n.localize("PF2E.CraftingTab.Alerts.MaxSlots"));
return;
}

if (!this.canCraft(formula.item)) {
return;
}

const quantity = await this.#batchSizeFor(formula);
const existingIdx = this.preparedFormulaData.findIndex((f) => f.uuid === formula.uuid);
if (existingIdx > -1 && (this.resource || this.isDailyPrep)) {
return this.setFormulaQuantity(existingIdx, "increase");
} else {
this.preparedFormulaData.push({ uuid: formula.uuid, quantity });
}

return this.#updateRuleElement();
}

async unprepareFormula(index: number): Promise<void> {
const formula = this.preparedFormulaData[index];
if (!formula) return;
Expand All @@ -179,6 +185,15 @@ class CraftingAbility implements CraftingAbilityData {
const data = this.preparedFormulaData[index];
if (!data) return;

// Make sure we can increase first
if (value === "increase" || (typeof value === "number" && value > 0)) {
const prepared = await this.getPreparedCraftingFormulas();
const consumed = prepared.reduce((sum, p) => sum + p.batches, 0);
if (this.maxSlots && consumed >= this.maxSlots) {
return;
}
}

const currentQuantity = data.quantity ?? 0;
const item = this.fieldDiscovery ? await fromUuid<ItemPF2e>(data.uuid) : null;
const adjustment = this.fieldDiscovery?.test(item?.getRollOptions("item") ?? [])
Expand Down Expand Up @@ -302,10 +317,12 @@ class CraftingAbility implements CraftingAbilityData {
/** Returns what items should be created by this ability during daily preparation, and what the resource expenditure should be */
async calculateDailyCrafting(): Promise<DailyCraftingResult> {
if (!this.isDailyPrep) {
return { items: [], resource: null };
return { items: [], resource: null, insufficient: false };
}

const prepared = await this.getPreparedCraftingFormulas();
const consumed = prepared.reduce((sum, p) => sum + p.batches, 0);

return {
items: prepared
.filter((f) => !f.expended)
Expand All @@ -327,6 +344,7 @@ class CraftingAbility implements CraftingAbilityData {
cost: await this.calculateResourceCost(),
}
: null,
insufficient: this.maxSlots > 0 && consumed > this.maxSlots,
};
}

Expand Down Expand Up @@ -363,6 +381,8 @@ interface CraftingAbilitySheetData {
isAlchemical: boolean;
isPrepared: boolean;
isDailyPrep: boolean;
/** This is true if we do not have sufficient slots or resources to craft this ability */
insufficient: boolean;
maxSlots: number;
maxItemLevel: number;
resource: ResourceData | null;
Expand All @@ -374,6 +394,8 @@ interface CraftingAbilitySheetData {
interface DailyCraftingResult {
items: PreCreate<PhysicalItemSource>[];
resource: { slug: string; cost: number } | null;
/** True if this item is internally insufficient. It does not compare with other crafting abilties */
insufficient: boolean;
}

export { CraftingAbility };
Expand Down
6 changes: 6 additions & 0 deletions src/module/actor/character/crafting/crafting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ class CharacterCrafting {
return;
}

// If any of our results is insufficient on its own, return early
if (results.some((r) => r.insufficient)) {
ui.notifications.warn("PF2E.Actor.Character.Crafting.MissingResource", { localize: true });
return;
}

// Compute total resource cost by resource
const resourceCosts = results.reduce((costs: Record<string, number>, result) => {
if (result.resource) {
Expand Down
5 changes: 4 additions & 1 deletion src/module/actor/character/crafting/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ interface CraftingFormula extends CraftingFormulaData {
}

/** A formula prepared in a crafting ability whose item has been loaded */
type PreparedFormula = Required<PreparedFormulaData> & CraftingFormula;
interface PreparedFormula extends Required<PreparedFormulaData>, CraftingFormula {
/** The number of batches of this item being created. This is quantity divided by batch size */
batches: number;
}

interface CraftingAbilityData {
slug: string;
Expand Down
9 changes: 7 additions & 2 deletions static/templates/actors/character/tabs/crafting.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@
{{#each crafting.abilities.prepared as |ability i|}}
<li class="crafting-entry item-container item" data-container-type="craftingEntry" data-ability="{{ability.slug}}">
<div class="action-header">
<h3 class="item-name hide-container-toggle title">{{ability.label}}</h3>
<h3 class="item-name hide-container-toggle title">
{{ability.label}}
{{#if ability.insufficient}}
<i class="fa-solid fa-warning" data-tooltip="PF2E.Actor.Character.Crafting.MissingResource"></i>
{{/if}}
</h3>
<span class="level">{{localize "PF2E.LevelN" level=ability.maxItemLevel}}</span>
</div>
<ol class="directory-list item-list formula-list">
Expand Down Expand Up @@ -232,7 +237,7 @@
<div class="dc"></div>
<div class="cost"></div>
<div class="quantity">
{{#if ability.resource}}
{{#if (or ability.resource ability.isDailyPrep)}}
<a class="adjust decrease" data-action="decrease-craft-quantity">&ndash;</a>
<input type="number" data-craft-quantity value="{{formula.quantity}}" />
<a class="adjust increase" data-action="increase-craft-quantity">+</a>
Expand Down

0 comments on commit 66f4572

Please sign in to comment.