Skip to content

Commit

Permalink
Implement buttons for spending resources for spells, skills, misc. ab…
Browse files Browse the repository at this point in the history
…ilities
  • Loading branch information
Azurelol committed Jan 19, 2025
1 parent 865ce6b commit 8c81533
Show file tree
Hide file tree
Showing 37 changed files with 978 additions and 212 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ npm run watch
Or for a one-time build:

```bash
npm run build
npm run push
```

Enable the _Hot-Reload Package Files_ option in your Foundry application configuration for an improved developer experience.
Expand Down
17 changes: 13 additions & 4 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,8 @@
"Instantaneous": "Instantaneous",
"Scene": "Scene",
"Self": "Self",
"Single": "Single",
"Multiple": "Multiple",
"OneCreature": "One creature",
"TwoCreatures": "Up to two creatures",
"ThreeCreatures": "Up to three creatures",
Expand Down Expand Up @@ -839,12 +841,14 @@
"IgnoreAbsorption": "Ignore Absorption",
"IgnoreAll": "Ignore All",
"IgnoreNone": "Ignore None",
"ChatApplyTargeted": "Apply Targeted",
"ChatApplySelected": "Apply Selected",
"ChatApplyTargeted": "Apply to targeted token",
"ChatApplySelected": "Apply to selected token",
"ChatApplyNoTargetSelected": "No actor selected.",
"ChatApplyNoCharacterSelected": "No character selected. Unable to apply.",
"ChatApplySelectedTooltip": "Click to apply effect to selected tokens.",
"ChatApplyEffectNoActorsSelected": "No actors selected. Unable to apply effect.",
"ChatApplyEffectNoActorsTargeted": "No actors targeted. Unable to apply effect.",
"ChatApplyEffectNoActorsSelected": "No actors selected. Unable to apply.",
"ChatApplyEffectNoActorsTargeted": "No actors targeted. Unable to apply.",
"ChatApplyMaxTargetWarning": "Too many actors targeted!",
"ChatApplyDamageTooltip": "Click to apply damage to selected tokens.<br>[Ctrl + Click] to Open Damage Customizer<br>[Shift + Click] to ignore Resistances.<br>[Ctrl + Shift] + Click to ignore Resistances and Immunities.",
"ChatApplyDamageNoActorsSelected": "No actors selected. Unable to apply damage.",
"ChatApplyDamageVulnerable": "<strong>{actor}</strong> was <strong>vulnerable</strong> to <strong>{type}</strong> and took <strong>{damage}</strong> damage from <strong>{from}</strong>",
Expand All @@ -854,7 +858,12 @@
"ChatApplyDamageImmune": "<strong>{actor}</strong> was <strong>immune</strong> to <strong>{type}</strong> and took <strong>{damage}</strong> damage from <strong>{from}</strong>",
"ChatApplyDamageImmuneIgnored": "<strong>{actor}</strong> took <strong>{damage} {type}</strong> damage from <strong>{from} (Immunity ignored)</strong>",
"ChatApplyDamageAbsorb": "<strong>{actor} absorbed {type}</strong> damage from <strong>{from}</strong> and was healed by <strong>{damage}</strong> HP",
"ChatApplyResourceLoss": "Spend",
"ChatApplyResourceLostsTooltip": "Click to spend the given resource on the actor who performed this action.",
"ChatApplyResourceLossSelected": "Spend Selected",
"ChatApplyResourceLossTargeted": "Spend Targeted",
"ChatRevertDamage": "Revert applied damage",
"ChatRevertResourceLoss": "Revert resource loss",
"ChatEvaluateAmountNoActor": "No reference to an actor provided",
"ChatEvaluateAmountNoItem": "No reference to an item provided",
"ChatEvaluateNoSkill": "The referenced skill was not found in the actor",
Expand Down
46 changes: 3 additions & 43 deletions module/checks/accuracy-check.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { CheckHooks } from './check-hooks.mjs';
import { CHECK_RESULT, CHECK_ROLL } from './default-section-order.mjs';
import { FUActor } from '../documents/actors/actor.mjs';
import { CHECK_ROLL } from './default-section-order.mjs';
import { FU, SYSTEM } from '../helpers/config.mjs';
import { CheckConfiguration } from './check-configuration.mjs';
import { Flags } from '../helpers/flags.mjs';

/**
* @typedef TargetData
* @property {string} name
* @property {string} uuid
* @property {string} link
* @property {number} difficulty
*/
import { Targeting } from '../helpers/targeting.mjs';

function handleGenericBonus(actor, modifiers) {
if (actor.system.bonuses.accuracy.accuracyCheck) {
Expand Down Expand Up @@ -146,7 +138,6 @@ function onRenderCheck(data, checkResult, actor, item, flags) {
modifiers: damage.modifiers,
};
}
const applyDamage = damageData != null;

// Push combined data for accuracy and damage
data.push({
Expand All @@ -160,38 +151,7 @@ function onRenderCheck(data, checkResult, actor, item, flags) {

/** @type TargetData[] */
const targets = inspector.getTargets();
const isTargeted = targets?.length > 0;
if (targets) {
data.push({
order: CHECK_RESULT,
partial: isTargeted ? 'systems/projectfu/templates/chat/partials/chat-check-targets.hbs' : 'systems/projectfu/templates/chat/partials/chat-check-notargets.hbs',
data: {
targets: targets,
applyDamage: applyDamage,
},
});
}

if (isTargeted) {
async function showFloatyText(target) {
const actor = await fromUuid(target.uuid);
if (actor instanceof FUActor) {
actor.showFloatyText(game.i18n.localize(target.result === 'hit' ? 'FU.Hit' : 'FU.Miss'));
}
}

if (game.dice3d) {
Hooks.once('diceSoNiceRollComplete', () => {
for (const target of targets) {
showFloatyText(target);
}
});
} else {
for (const target of targets) {
showFloatyText(target);
}
}
}
Targeting.addDamageTargetingSection(data, actor, item, targets, flags, accuracyData, damageData);

(flags[SYSTEM] ??= {})[Flags.ChatMessage.Item] ??= item.toObject();
}
Expand Down
8 changes: 0 additions & 8 deletions module/checks/check-configuration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,6 @@ const initHrZero = (hrZero) => (check) => {
* @property {number} [total]
*/

/**
* @typedef TargetData
* @property {string} name
* @property {string} uuid
* @property {string} link
* @property {number} [difficulty]
*/

/**
* @param {CheckV2, CheckResultV2} check
* @return {CheckConfigurer} check
Expand Down
72 changes: 71 additions & 1 deletion module/checks/check-hooks.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SYSTEM } from '../helpers/config.mjs';
import { Pipeline } from '../pipelines/pipeline.mjs';

export const CheckHooks = Object.freeze({
prepareCheck: `${SYSTEM}.prepareCheck`,
Expand Down Expand Up @@ -106,16 +107,85 @@ const processCheck = (check, actor, item) => {};

/**
* @callback RenderCheckHook
* Hook called to determine how to render the results
* @description Hook called to determine how to render the results
* @param {CheckRenderData} sections
* @param {CheckResultV2} check
* @param {FUActor} actor
* @param {FUItem} [item]
* @param {Object} additionalFlags
* @param {TargetData[]} targets
*/

/**
* @type RenderCheckHook
*/
// eslint-disable-next-line no-unused-vars
const renderCheck = (sections, check, actor, item, additionalFlags) => {};

/**
* @description To be used within a {@link RenderCheckHook}
* @property {FUActor} actor
* @property {FUItem} item
* @property {Object} flags
* @property {TargetData[]} targets A snapshot of the targets at the beginning of the hook
* @property {TargetingDataModel} targeting
* @property {CheckSection} section
* @property {Boolean} executed
* @property {List<Promise<*>>} additions
*/
export class RenderCheckSectionBuilder {
constructor(data, actor, item, targets, flags, order, partial) {
this.data = data;
this.actor = actor;
this.item = item;
this.targets = targets;
this.flags = flags;
this.executed = false;
this.additions = [];
this.section = {
order: order,
partial: partial,
data: {
name: item.name,
actor: actor.uuid,
item: item.uuid,
},
};
}

toggleFlag(flag) {
Pipeline.toggleFlag(this.flags, flag);
}

/**
* @param {Promise<Object, void>} onData
* @remarks Supports asynchronous operation
*/
addData(onData) {
this.additions.push(onData(this.section.data));
}

/**
* @description Pushes the section onto {@link CheckRenderData}
*/
push() {
if (this.executed === true) {
throw Error('Already executed.');
}
this.data.push(async () => {
await Promise.all(this.additions);
if (this.validate()) {
return this.section;
}
return {};
});
this.executed = true;
}

/**
* @returns {boolean} Whether the section should be pushed
*/
validate() {
return true;
}
}
2 changes: 1 addition & 1 deletion module/checks/check-push.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const getPushParams = async (actor) => {
});

if (push === false) {
ui.notifications.error('FU.DialogPushMissingBond', { localize: true });
ui.notifications.warn('FU.ChatEvaluateAmountNoActor', { localize: true });
return;
}

Expand Down
5 changes: 4 additions & 1 deletion module/checks/checks-v2.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OpposedCheck } from './opposed-check.mjs';
import { CheckRetarget } from './check-retarget.mjs';
import { GroupCheck } from './group-check.mjs';
import { SupportCheck } from './support-check.mjs';
import { Targeting } from '../helpers/targeting.mjs';

/**
* @typedef CheckAttributes
Expand Down Expand Up @@ -340,8 +341,10 @@ async function renderCheck(result, actor, item, flags = {}) {
*/
const renderData = [];
const additionalFlags = {};
// TODO: Pass in as a parameter
const targets = Targeting.getSerializedTargetData();

Hooks.callAll(CheckHooks.renderCheck, renderData, result, actor, item, additionalFlags);
Hooks.callAll(CheckHooks.renderCheck, renderData, result, actor, item, additionalFlags, targets);

/**
* @type {CheckSection[]}
Expand Down
36 changes: 3 additions & 33 deletions module/checks/magic-check.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CheckHooks } from './check-hooks.mjs';
import { CHECK_RESULT, CHECK_ROLL } from './default-section-order.mjs';
import { FUActor } from '../documents/actors/actor.mjs';
import { CHECK_ROLL } from './default-section-order.mjs';
import { FU, SYSTEM } from '../helpers/config.mjs';
import { CheckConfiguration } from './check-configuration.mjs';
import { Flags } from '../helpers/flags.mjs';
import { Targeting } from '../helpers/targeting.mjs';

/**
* @param {CheckV2} check
Expand Down Expand Up @@ -145,37 +145,7 @@ function onRenderCheck(data, checkResult, actor, item, flags) {
});
/** @type TargetData[] */
const targets = inspector.getTargets();
const isTargeted = targets?.length > 0;
if (targets) {
data.push({
order: CHECK_RESULT,
partial: isTargeted ? 'systems/projectfu/templates/chat/partials/chat-check-targets.hbs' : 'systems/projectfu/templates/chat/partials/chat-check-notargets.hbs',
data: {
targets: targets,
},
});
}

if (isTargeted) {
async function showFloatyText(target) {
const actor = await fromUuid(target.uuid);
if (actor instanceof FUActor) {
actor.showFloatyText(game.i18n.localize(target.result === 'hit' ? 'FU.Hit' : 'FU.Miss'));
}
}

if (game.dice3d) {
Hooks.once('diceSoNiceRollComplete', () => {
for (const target of targets) {
showFloatyText(target);
}
});
} else {
for (const target of targets) {
showFloatyText(target);
}
}
}
Targeting.addDamageTargetingSection(data, actor, item, targets, flags, accuracyData, damageData);

(flags[SYSTEM] ??= {})[Flags.ChatMessage.Item] ??= item.toObject();
}
Expand Down
16 changes: 16 additions & 0 deletions module/documents/items/common/action-cost-data-model.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FU } from '../../../helpers/config.mjs';

/**
* @property {FU.resources} resource.value The resource type
* @property {Number} amount.value The resource cost
s
*/
export class ActionCostDataModel extends foundry.abstract.DataModel {
static defineSchema() {
const { NumberField, StringField } = foundry.data.fields;
return {
resource: new StringField({ initial: FU.resources.mp, required: true }),
amount: new NumberField({ initial: 0, integer: true, nullable: false }),
};
}
}
15 changes: 15 additions & 0 deletions module/documents/items/common/targeting-data-model.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Targeting } from '../../../helpers/targeting.mjs';

/**
* @property {FU.targetingRules} rule.value The type of targeting rule to use
* @property {Number} max.value The maximum number of target
*/
export class TargetingDataModel extends foundry.abstract.DataModel {
static defineSchema() {
const { NumberField, StringField } = foundry.data.fields;
return {
rule: new StringField({ initial: Targeting.rule.self, required: true }),
max: new NumberField({ initial: 0, min: 0, max: 3, integer: true, nullable: false }),
};
}
}
8 changes: 4 additions & 4 deletions module/documents/items/item.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class FUItem extends Item {

const qualText = this.system.quality?.value || '';
const detailString = [attackString, damageString].filter(Boolean).join('⬥');
const qualityString = [capitalizeFirst(this.system.mpCost.value), capitalizeFirst(this.system.target.value), capitalizeFirst(this.system.duration.value), qualText].filter(Boolean).join(' ⬥ ');
const qualityString = [capitalizeFirst(this.system.cost.amount), capitalizeFirst(this.system.targeting.rule), capitalizeFirst(this.system.duration.value), qualText].filter(Boolean).join(' ⬥ ');

return {
attackString,
Expand Down Expand Up @@ -883,7 +883,7 @@ export class FUItem extends Item {
* @return {Promise<chatMessage>}
*/
async rollSpell(hrZero) {
const { rollInfo, opportunity, description, summary, mpCost, target, duration, defense } = this.system;
const { rollInfo, opportunity, description, summary, cost, targeting, duration, defense } = this.system;
let defenseAbbr = !defense ? game.i18n.localize('FU.MagicDefenseAbbr') : defenseAbbr;
let checkDamage = undefined;
if (rollInfo?.damage?.hasDamage?.value) {
Expand All @@ -902,8 +902,8 @@ export class FUItem extends Item {
img: this.img,
id: this.id,
duration: duration.value,
target: target.value,
mpCost: mpCost.value,
target: targeting.rule,
mpCost: cost.amount,
defense: defenseAbbr,
opportunity: opportunity,
summary: summary.value,
Expand Down
6 changes: 6 additions & 0 deletions module/documents/items/misc/misc-ability-data-model.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ProgressDataModel } from '../common/progress-data-model.mjs';
import { MiscAbilityMigrations } from './misc-ability-migrations.mjs';
import { FU } from '../../../helpers/config.mjs';
import { CheckHooks } from '../../../checks/check-hooks.mjs';
import { ActionCostDataModel } from '../common/action-cost-data-model.mjs';
import { TargetingDataModel } from '../common/targeting-data-model.mjs';

Hooks.on(CheckHooks.renderCheck, (sections, check, actor, item) => {
if (item?.system instanceof MiscAbilityDataModel) {
Expand Down Expand Up @@ -39,6 +41,8 @@ Hooks.on(CheckHooks.renderCheck, (sections, check, actor, item) => {
* @property {DamageDataModel} rollInfo.damage
* @property {boolean} isOffensive.value
* @property {boolean} hasRoll.value
* @property {ActionCostDataModel} cost
* @property {TargetingDataModel} targeting
*/
export class MiscAbilityDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
Expand Down Expand Up @@ -76,6 +80,8 @@ export class MiscAbilityDataModel extends foundry.abstract.TypeDataModel {
damage: new EmbeddedDataField(DamageDataModel, {}),
}),
hasRoll: new SchemaField({ value: new BooleanField() }),
cost: new EmbeddedDataField(ActionCostDataModel, {}),
targeting: new EmbeddedDataField(TargetingDataModel, {}),
};
}

Expand Down
Loading

0 comments on commit 8c81533

Please sign in to comment.