mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-18 07:59:03 +01:00
Merged with main
This commit is contained in:
commit
480d04fee5
784 changed files with 13985 additions and 27621 deletions
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
|
||||||
|
|
||||||
# Create a zip file with all files required by the module to add to the release
|
# Create a zip file with all files required by the module to add to the release
|
||||||
- run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js assets/ templates/ styles/daggerheart.css packs/ lang/
|
- run: zip -r ./system.zip system.json README.md LICENSE build/daggerheart.js build/tagify.css styles/daggerheart.css assets/ templates/ packs/ lang/
|
||||||
|
|
||||||
# Create a release for this specific version
|
# Create a release for this specific version
|
||||||
- name: Update Release with Files
|
- name: Update Release with Files
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [User Install Guide](#user-install)
|
- [User Install Guide](#user-install)
|
||||||
- [Developer Setup](#developer-setup)
|
- [Developer Setup](#development-setup)
|
||||||
- [Contribution Info](#contributing)
|
- [Contribution Info](#contributing)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
|
||||||
131
daggerheart.mjs
131
daggerheart.mjs
|
|
@ -3,12 +3,12 @@ import * as applications from './module/applications/_module.mjs';
|
||||||
import * as models from './module/data/_module.mjs';
|
import * as models from './module/data/_module.mjs';
|
||||||
import * as documents from './module/documents/_module.mjs';
|
import * as documents from './module/documents/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { DhDualityRollEnricher, DhTemplateEnricher } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||||
import { NarrativeCountdowns, registerCountdownApplicationHooks } from './module/applications/ui/countdowns.mjs';
|
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
|
||||||
import { DualityRollColor } from './module/data/settings/Appearance.mjs';
|
import { DualityRollColor } from './module/data/settings/Appearance.mjs';
|
||||||
import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs';
|
import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs';
|
||||||
import { renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs';
|
import { enrichedDualityRoll, renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs';
|
||||||
import { renderMeasuredTemplate } from './module/enrichers/TemplateEnricher.mjs';
|
import { renderMeasuredTemplate } from './module/enrichers/TemplateEnricher.mjs';
|
||||||
import { registerCountdownHooks } from './module/data/countdowns.mjs';
|
import { registerCountdownHooks } from './module/data/countdowns.mjs';
|
||||||
import {
|
import {
|
||||||
|
|
@ -18,6 +18,9 @@ import {
|
||||||
} from './module/systemRegistration/_module.mjs';
|
} from './module/systemRegistration/_module.mjs';
|
||||||
import { placeables } from './module/canvas/_module.mjs';
|
import { placeables } from './module/canvas/_module.mjs';
|
||||||
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
|
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
|
||||||
|
import { registerDHActorHooks } from './module/documents/actor.mjs';
|
||||||
|
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
||||||
|
import { renderDamageButton } from './module/enrichers/DamageEnricher.mjs';
|
||||||
|
|
||||||
Hooks.once('init', () => {
|
Hooks.once('init', () => {
|
||||||
CONFIG.DH = SYSTEM;
|
CONFIG.DH = SYSTEM;
|
||||||
|
|
@ -27,23 +30,16 @@ Hooks.once('init', () => {
|
||||||
documents
|
documents
|
||||||
};
|
};
|
||||||
|
|
||||||
CONFIG.TextEditor.enrichers.push(
|
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||||
...[
|
|
||||||
{
|
|
||||||
pattern: /\[\[\/dr\s?(.*?)\]\]/g,
|
|
||||||
enricher: DhDualityRollEnricher
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: /^@Template\[(.*)\]$/g,
|
|
||||||
enricher: DhTemplateEnricher
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
CONFIG.statusEffects = Object.values(SYSTEM.GENERAL.conditions).map(x => ({
|
CONFIG.statusEffects = [
|
||||||
...x,
|
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
||||||
name: game.i18n.localize(x.name)
|
...Object.values(SYSTEM.GENERAL.conditions).map(x => ({
|
||||||
}));
|
...x,
|
||||||
|
name: game.i18n.localize(x.name),
|
||||||
|
systemEffect: true
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
CONFIG.Dice.daggerheart = {
|
CONFIG.Dice.daggerheart = {
|
||||||
DualityDie: DualityDie,
|
DualityDie: DualityDie,
|
||||||
|
|
@ -56,6 +52,14 @@ Hooks.once('init', () => {
|
||||||
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]];
|
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]];
|
||||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||||
|
|
||||||
|
const { DocumentSheetConfig } = foundry.applications.apps;
|
||||||
|
CONFIG.Token.documentClass = documents.DhToken;
|
||||||
|
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
||||||
|
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
||||||
|
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
||||||
|
makeDefault: true
|
||||||
|
});
|
||||||
|
|
||||||
CONFIG.Item.documentClass = documents.DHItem;
|
CONFIG.Item.documentClass = documents.DHItem;
|
||||||
|
|
||||||
//Registering the Item DataModel
|
//Registering the Item DataModel
|
||||||
|
|
@ -93,12 +97,12 @@ Hooks.once('init', () => {
|
||||||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||||
|
|
||||||
foundry.applications.apps.DocumentSheetConfig.unregisterSheet(
|
DocumentSheetConfig.unregisterSheet(
|
||||||
CONFIG.ActiveEffect.documentClass,
|
CONFIG.ActiveEffect.documentClass,
|
||||||
'core',
|
'core',
|
||||||
foundry.applications.sheets.ActiveEffectConfig
|
foundry.applications.sheets.ActiveEffectConfig
|
||||||
);
|
);
|
||||||
foundry.applications.apps.DocumentSheetConfig.registerSheet(
|
DocumentSheetConfig.registerSheet(
|
||||||
CONFIG.ActiveEffect.documentClass,
|
CONFIG.ActiveEffect.documentClass,
|
||||||
SYSTEM.id,
|
SYSTEM.id,
|
||||||
applications.sheetConfigs.ActiveEffectConfig,
|
applications.sheetConfigs.ActiveEffectConfig,
|
||||||
|
|
@ -107,6 +111,8 @@ Hooks.once('init', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
||||||
|
|
||||||
CONFIG.Combat.dataModels = {
|
CONFIG.Combat.dataModels = {
|
||||||
base: models.DhCombat
|
base: models.DhCombat
|
||||||
};
|
};
|
||||||
|
|
@ -119,9 +125,12 @@ Hooks.once('init', () => {
|
||||||
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
||||||
|
|
||||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||||
|
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||||
|
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
||||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||||
|
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||||
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||||
|
|
||||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||||
|
|
@ -152,40 +161,22 @@ Hooks.on('ready', () => {
|
||||||
|
|
||||||
registerCountdownHooks();
|
registerCountdownHooks();
|
||||||
socketRegistration.registerSocketHooks();
|
socketRegistration.registerSocketHooks();
|
||||||
registerCountdownApplicationHooks();
|
|
||||||
registerRollDiceHooks();
|
registerRollDiceHooks();
|
||||||
|
registerDHActorHooks();
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.once('dicesoniceready', () => {});
|
Hooks.once('dicesoniceready', () => {});
|
||||||
|
|
||||||
Hooks.on('renderChatMessageHTML', (_, element) => {
|
Hooks.on('renderChatMessageHTML', (_, element) => {
|
||||||
element
|
enricherRenderSetup(element);
|
||||||
.querySelectorAll('.duality-roll-button')
|
|
||||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
|
||||||
|
|
||||||
element
|
|
||||||
.querySelectorAll('.measured-template-button')
|
|
||||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('renderJournalEntryPageProseMirrorSheet', (_, element) => {
|
Hooks.on('renderJournalEntryPageProseMirrorSheet', (_, element) => {
|
||||||
element
|
enricherRenderSetup(element);
|
||||||
.querySelectorAll('.duality-roll-button')
|
|
||||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
|
||||||
|
|
||||||
element
|
|
||||||
.querySelectorAll('.measured-template-button')
|
|
||||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('renderHandlebarsApplication', (_, element) => {
|
Hooks.on('renderHandlebarsApplication', (_, element) => {
|
||||||
element
|
enricherRenderSetup(element);
|
||||||
.querySelectorAll('.duality-roll-button')
|
|
||||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
|
||||||
|
|
||||||
element
|
|
||||||
.querySelectorAll('.measured-template-button')
|
|
||||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('chatMessage', (_, message) => {
|
Hooks.on('chatMessage', (_, message) => {
|
||||||
|
|
@ -197,49 +188,21 @@ Hooks.on('chatMessage', (_, message) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const traitValue = rollCommand.trait?.toLowerCase();
|
const traitValue = rollCommand.trait?.toLowerCase();
|
||||||
const advantageState = rollCommand.advantage ? true : rollCommand.disadvantage ? false : null;
|
const advantage = rollCommand.advantage
|
||||||
|
? CONFIG.DH.ACTIONS.advandtageState.advantage.value
|
||||||
|
: rollCommand.disadvantage
|
||||||
|
? CONFIG.DH.ACTIONS.advandtageState.disadvantage.value
|
||||||
|
: undefined;
|
||||||
|
const difficulty = rollCommand.difficulty;
|
||||||
|
|
||||||
// Target not required if an attribute is not used.
|
const target = getCommandTarget();
|
||||||
const target = traitValue ? getCommandTarget() : undefined;
|
const title = traitValue
|
||||||
if (target || !traitValue) {
|
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
new Promise(async (resolve, reject) => {
|
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||||
const trait = target ? target.system.traits[traitValue] : undefined;
|
})
|
||||||
if (traitValue && !trait) {
|
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.attributeFaulty'));
|
|
||||||
reject();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = traitValue
|
|
||||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
|
||||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
|
||||||
})
|
|
||||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
title: title,
|
|
||||||
roll: {
|
|
||||||
trait: traitValue
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
traits: {
|
|
||||||
[traitValue]: trait
|
|
||||||
}
|
|
||||||
},
|
|
||||||
source: target,
|
|
||||||
hasSave: false,
|
|
||||||
dialog: { configure: false },
|
|
||||||
evaluate: true,
|
|
||||||
advantage: rollCommand.advantage == true,
|
|
||||||
disadvantage: rollCommand.disadvantage == true
|
|
||||||
};
|
|
||||||
|
|
||||||
await CONFIG.Dice.daggerheart['DualityRoll'].build(config);
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
enrichedDualityRoll({ traitValue, target, difficulty, title, label: 'test', actionType: null, advantage });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
448
lang/en.json
448
lang/en.json
|
|
@ -13,6 +13,9 @@
|
||||||
"armor": "Armor",
|
"armor": "Armor",
|
||||||
"beastform": "Beastform"
|
"beastform": "Beastform"
|
||||||
},
|
},
|
||||||
|
"ActiveEffect": {
|
||||||
|
"beastform": "Beastform"
|
||||||
|
},
|
||||||
"Actor": {
|
"Actor": {
|
||||||
"character": "Character",
|
"character": "Character",
|
||||||
"companion": "Companion",
|
"companion": "Companion",
|
||||||
|
|
@ -20,6 +23,9 @@
|
||||||
"environment": "Environment"
|
"environment": "Environment"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CONTROLS": {
|
||||||
|
"inFront": "In Front"
|
||||||
|
},
|
||||||
"DAGGERHEART": {
|
"DAGGERHEART": {
|
||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
"Config": {
|
"Config": {
|
||||||
|
|
@ -30,8 +36,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Settings": {
|
"Settings": {
|
||||||
|
"attackBonus": "Attack Bonus",
|
||||||
|
"attackName": "Attack Name",
|
||||||
|
"includeBase": { "label": "Include Item Damage" },
|
||||||
|
"multiplier": "Multiplier",
|
||||||
"resultBased": {
|
"resultBased": {
|
||||||
"label": "Formula based on Hope/Fear result."
|
"label": "Formula based on Hope/Fear result."
|
||||||
|
},
|
||||||
|
"applyTo": {
|
||||||
|
"label": "Targeted Resource"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
|
|
@ -96,9 +109,14 @@
|
||||||
"tier": { "label": "Tier" },
|
"tier": { "label": "Tier" },
|
||||||
"type": { "label": "Type" }
|
"type": { "label": "Type" }
|
||||||
},
|
},
|
||||||
|
"hordeDamage": "Horde Damage",
|
||||||
"horderHp": "Horde/HP"
|
"horderHp": "Horde/HP"
|
||||||
},
|
},
|
||||||
"Character": {
|
"Character": {
|
||||||
|
"advantageSources": {
|
||||||
|
"label": "Advantage Sources",
|
||||||
|
"hint": "Add single words or short text as reminders and hints of what a character has advantage on."
|
||||||
|
},
|
||||||
"age": "Age",
|
"age": "Age",
|
||||||
"companionFeatures": "Companion Features",
|
"companionFeatures": "Companion Features",
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
|
|
@ -107,7 +125,12 @@
|
||||||
"sendToChat": "Send To Chat",
|
"sendToChat": "Send To Chat",
|
||||||
"toLoadout": "Send to Loadout",
|
"toLoadout": "Send to Loadout",
|
||||||
"toVault": "Send to Vault",
|
"toVault": "Send to Vault",
|
||||||
"unequip": "Unequip"
|
"unequip": "Unequip",
|
||||||
|
"useItem": "Use Item"
|
||||||
|
},
|
||||||
|
"disadvantageSources": {
|
||||||
|
"label": "Disadvantage Sources",
|
||||||
|
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
|
||||||
},
|
},
|
||||||
"faith": "Faith",
|
"faith": "Faith",
|
||||||
"levelUp": "You can level up",
|
"levelUp": "You can level up",
|
||||||
|
|
@ -129,7 +152,9 @@
|
||||||
"partner": { "label": "Partner" },
|
"partner": { "label": "Partner" },
|
||||||
"resources": {
|
"resources": {
|
||||||
"stress": {
|
"stress": {
|
||||||
"value": { "label": "Stress" }
|
"value": { "label": "Stress" },
|
||||||
|
"currentStress": { "label": "Current Stress" },
|
||||||
|
"maxStress": { "label": "Max Stress" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -158,12 +183,25 @@
|
||||||
},
|
},
|
||||||
"APPLICATIONS": {
|
"APPLICATIONS": {
|
||||||
"CharacterCreation": {
|
"CharacterCreation": {
|
||||||
|
"setupTabs": {
|
||||||
|
"ancestry": "Ancestry",
|
||||||
|
"community": "Community",
|
||||||
|
"class": "Class",
|
||||||
|
"experience": "Experience",
|
||||||
|
"traits": "Traits",
|
||||||
|
"domainCards": "Domain Cards"
|
||||||
|
},
|
||||||
|
"ancestryNamePlaceholder": "Your ancestry's name",
|
||||||
|
"buttonTitle": "Character Setup",
|
||||||
"choice": "Choice",
|
"choice": "Choice",
|
||||||
"finishCreation": "Finish Character Setup",
|
"finishCreation": "Finish Character Setup",
|
||||||
"heritage": "Heritage",
|
"heritage": "Heritage",
|
||||||
"initialExperiences": "Initial Experiences",
|
"initialExperiences": "Initial Experiences",
|
||||||
|
"mixedAncestry": "Mixed Ancestry",
|
||||||
"newExperience": "New Experience..",
|
"newExperience": "New Experience..",
|
||||||
"selectAncestry": "Select Ancestry",
|
"selectAncestry": "Select Ancestry",
|
||||||
|
"selectPrimaryAncestry": "Select Primary Ancestry",
|
||||||
|
"selectSecondaryAncestry": "Select Secondary Ancestry",
|
||||||
"selectArmor": "Select Armor",
|
"selectArmor": "Select Armor",
|
||||||
"selectClass": "Select Class",
|
"selectClass": "Select Class",
|
||||||
"selectCommunity": "Select Community",
|
"selectCommunity": "Select Community",
|
||||||
|
|
@ -171,6 +209,7 @@
|
||||||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||||
"selectSubclass": "Select Subclass",
|
"selectSubclass": "Select Subclass",
|
||||||
"startingItems": "Starting Items",
|
"startingItems": "Starting Items",
|
||||||
|
"story": "Story",
|
||||||
"suggestedArmor": "Suggested Armor",
|
"suggestedArmor": "Suggested Armor",
|
||||||
"suggestedPrimaryWeapon": "Suggested Primary Weapon",
|
"suggestedPrimaryWeapon": "Suggested Primary Weapon",
|
||||||
"suggestedSecondaryWeapon": "Suggested Secondary Weapon",
|
"suggestedSecondaryWeapon": "Suggested Secondary Weapon",
|
||||||
|
|
@ -185,6 +224,16 @@
|
||||||
"requestingSpotlight": "Requesting The Spotlight",
|
"requestingSpotlight": "Requesting The Spotlight",
|
||||||
"requestSpotlight": "Request The Spotlight"
|
"requestSpotlight": "Request The Spotlight"
|
||||||
},
|
},
|
||||||
|
"ContextMenu": {
|
||||||
|
"disableEffect": "Disable Effect",
|
||||||
|
"enableEffect": "Enable Effect",
|
||||||
|
"equip": "Equip",
|
||||||
|
"sendToChat": "Send To Chat",
|
||||||
|
"toLoadout": "Send to Loadout",
|
||||||
|
"toVault": "Send to Vault",
|
||||||
|
"unequip": "Unequip",
|
||||||
|
"useItem": "Use Item"
|
||||||
|
},
|
||||||
"Countdown": {
|
"Countdown": {
|
||||||
"addCountdown": "Add Countdown",
|
"addCountdown": "Add Countdown",
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
|
|
@ -213,6 +262,10 @@
|
||||||
"encounter": "Encounter"
|
"encounter": "Encounter"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DeleteConfirmation": {
|
||||||
|
"title": "Delete {type} - {name}",
|
||||||
|
"text": "Are you sure you want to delete {name}?"
|
||||||
|
},
|
||||||
"DamageReduction": {
|
"DamageReduction": {
|
||||||
"armorMarks": "Armor Marks",
|
"armorMarks": "Armor Marks",
|
||||||
"armorWithStress": "Spend 1 stress to use an extra mark",
|
"armorWithStress": "Spend 1 stress to use an extra mark",
|
||||||
|
|
@ -227,8 +280,9 @@
|
||||||
"title": "{actor} - Death Move"
|
"title": "{actor} - Death Move"
|
||||||
},
|
},
|
||||||
"Downtime": {
|
"Downtime": {
|
||||||
"downtimeHeader": "Downtime Moves ({current}/{max})",
|
|
||||||
"longRest": {
|
"longRest": {
|
||||||
|
"title": "Long Rest",
|
||||||
|
"moves": "Long Rest Moves ({current}/{max})",
|
||||||
"clearStress": {
|
"clearStress": {
|
||||||
"description": "Describe how you blow off steam or pull yourself together, and clear all marked Stress.",
|
"description": "Describe how you blow off steam or pull yourself together, and clear all marked Stress.",
|
||||||
"name": "Clear Stress"
|
"name": "Clear Stress"
|
||||||
|
|
@ -245,7 +299,6 @@
|
||||||
"description": "Describe how you patch yourself up and remove all marked Hit Points. You may also do this on an ally instead.",
|
"description": "Describe how you patch yourself up and remove all marked Hit Points. You may also do this on an ally instead.",
|
||||||
"name": "Tend to Wounds"
|
"name": "Tend to Wounds"
|
||||||
},
|
},
|
||||||
"title": "Long Rest",
|
|
||||||
"workOnAProject": {
|
"workOnAProject": {
|
||||||
"description": "Establish or continue work on a project.",
|
"description": "Establish or continue work on a project.",
|
||||||
"name": "Work on a Project"
|
"name": "Work on a Project"
|
||||||
|
|
@ -253,6 +306,7 @@
|
||||||
},
|
},
|
||||||
"shortRest": {
|
"shortRest": {
|
||||||
"title": "Short Rest",
|
"title": "Short Rest",
|
||||||
|
"moves": "Short Rest Moves ({current}/{max})",
|
||||||
"tendToWounds": {
|
"tendToWounds": {
|
||||||
"name": "Tend to Wounds",
|
"name": "Tend to Wounds",
|
||||||
"description": "Describe how you hastily patch yourself up, then clear a number of Hit Points equal to 1d4 + your tier. You can do this to an ally instead."
|
"description": "Describe how you hastily patch yourself up, then clear a number of Hit Points equal to 1d4 + your tier. You can do this to an ally instead."
|
||||||
|
|
@ -269,6 +323,12 @@
|
||||||
"name": "Prepare",
|
"name": "Prepare",
|
||||||
"description": "Describe how you prepare yourself for the path ahead, then gain a Hope. If you choose to Prepare with one or more members of your party, you each gain 2 Hope."
|
"description": "Describe how you prepare yourself for the path ahead, then gain a Hope. If you choose to Prepare with one or more members of your party, you each gain 2 Hope."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"takeDowntime": "Take Downtime"
|
||||||
|
},
|
||||||
|
"HUD": {
|
||||||
|
"tokenHUD": {
|
||||||
|
"genericEffects": "Foundry Effects"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Levelup": {
|
"Levelup": {
|
||||||
|
|
@ -374,6 +434,15 @@
|
||||||
"OwnershipSelection": {
|
"OwnershipSelection": {
|
||||||
"title": "Ownership Selection - {name}",
|
"title": "Ownership Selection - {name}",
|
||||||
"default": "Default Ownership"
|
"default": "Default Ownership"
|
||||||
|
},
|
||||||
|
"ResourceDice": {
|
||||||
|
"title": "{name} Resource",
|
||||||
|
"rerollDice": "Reroll Dice"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CLASS": {
|
||||||
|
"Feature": {
|
||||||
|
"rallyDice": "Bardic Rally Dice"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
|
|
@ -478,14 +547,10 @@
|
||||||
"name": "Impenetrable",
|
"name": "Impenetrable",
|
||||||
"description": "Once per short rest, when you would mark your last Hit Point, you can instead mark a Stress."
|
"description": "Once per short rest, when you would mark your last Hit Point, you can instead mark a Stress."
|
||||||
},
|
},
|
||||||
"magic": {
|
"magical": {
|
||||||
"name": "Magic",
|
"name": "Magical",
|
||||||
"description": "You can't mark an Armor Slot to reduce physical damage."
|
"description": "You can't mark an Armor Slot to reduce physical damage."
|
||||||
},
|
},
|
||||||
"painful": {
|
|
||||||
"name": "Painful",
|
|
||||||
"description": "Each time you mark an Armor Slot, you must mark a Stress."
|
|
||||||
},
|
|
||||||
"physical": {
|
"physical": {
|
||||||
"name": "Physical",
|
"name": "Physical",
|
||||||
"description": "You can't mark an Armor Slot to reduce magic damage."
|
"description": "You can't mark an Armor Slot to reduce magic damage."
|
||||||
|
|
@ -527,14 +592,19 @@
|
||||||
"description": "You reduce incoming magic damage by your Armor Score before applying it to your damage thresholds."
|
"description": "You reduce incoming magic damage by your Armor Score before applying it to your damage thresholds."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BeastformType": {
|
||||||
|
"normal": "Normal",
|
||||||
|
"evolved": "Evolved",
|
||||||
|
"hybrid": "Hybrid"
|
||||||
|
},
|
||||||
"Burden": {
|
"Burden": {
|
||||||
"oneHanded": "One-Handed",
|
"oneHanded": "One-Handed",
|
||||||
"twoHanded": "Two-Handed"
|
"twoHanded": "Two-Handed"
|
||||||
},
|
},
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"vulnerable": {
|
"dead": {
|
||||||
"name": "Vulnerable",
|
"name": "Dead",
|
||||||
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again."
|
"description": "The character is dead"
|
||||||
},
|
},
|
||||||
"hidden": {
|
"hidden": {
|
||||||
"name": "Hidden",
|
"name": "Hidden",
|
||||||
|
|
@ -543,6 +613,14 @@
|
||||||
"restrained": {
|
"restrained": {
|
||||||
"name": "Restrained",
|
"name": "Restrained",
|
||||||
"description": "When an effect makes a creature Restrained, it means they cannot move until this condition is cleared.\nThey can still take actions from their current position."
|
"description": "When an effect makes a creature Restrained, it means they cannot move until this condition is cleared.\nThey can still take actions from their current position."
|
||||||
|
},
|
||||||
|
"unconcious": {
|
||||||
|
"name": "Unconcious",
|
||||||
|
"description": "Your character can’t move or act while unconscious, they can’t be targeted by an attack."
|
||||||
|
},
|
||||||
|
"vulnerable": {
|
||||||
|
"name": "Vulnerable",
|
||||||
|
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CountdownType": {
|
"CountdownType": {
|
||||||
|
|
@ -620,8 +698,16 @@
|
||||||
"armorStack": {
|
"armorStack": {
|
||||||
"name": "Armor Stack",
|
"name": "Armor Stack",
|
||||||
"abbreviation": "AS"
|
"abbreviation": "AS"
|
||||||
|
},
|
||||||
|
"fear": {
|
||||||
|
"name": "Fear",
|
||||||
|
"abbreviation": "FR"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ItemResourceType": {
|
||||||
|
"simple": "Simple",
|
||||||
|
"diceValue": "Dice Value"
|
||||||
|
},
|
||||||
"Range": {
|
"Range": {
|
||||||
"self": {
|
"self": {
|
||||||
"name": "Self",
|
"name": "Self",
|
||||||
|
|
@ -655,11 +741,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"RollTypes": {
|
"RollTypes": {
|
||||||
"ability": {
|
"trait": {
|
||||||
"name": "Ability"
|
"name": "Trait"
|
||||||
},
|
},
|
||||||
"weapon": {
|
"attack": {
|
||||||
"name": "Weapon"
|
"name": "Attack"
|
||||||
},
|
},
|
||||||
"spellcast": {
|
"spellcast": {
|
||||||
"name": "SpellCast"
|
"name": "SpellCast"
|
||||||
|
|
@ -668,6 +754,10 @@
|
||||||
"name": "Dice Set"
|
"name": "Dice Set"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"SelectAction": {
|
||||||
|
"selectType": "Select Action Type",
|
||||||
|
"selectAction": "Action Selection"
|
||||||
|
},
|
||||||
"Traits": {
|
"Traits": {
|
||||||
"agility": {
|
"agility": {
|
||||||
"name": "Agility",
|
"name": "Agility",
|
||||||
|
|
@ -727,7 +817,7 @@
|
||||||
"WeaponFeature": {
|
"WeaponFeature": {
|
||||||
"barrier": {
|
"barrier": {
|
||||||
"name": "Barrier",
|
"name": "Barrier",
|
||||||
"description": "+{armorScore} to Armor Score; -1 to Evasion"
|
"description": "Gain your character's Tier + 1 to Armor Score; -1 to Evasion"
|
||||||
},
|
},
|
||||||
"bonded": {
|
"bonded": {
|
||||||
"name": "Bonded",
|
"name": "Bonded",
|
||||||
|
|
@ -843,7 +933,7 @@
|
||||||
},
|
},
|
||||||
"paired": {
|
"paired": {
|
||||||
"name": "Paired",
|
"name": "Paired",
|
||||||
"description": "+{bonusDamage} to primary weapon damage to targets within Melee range"
|
"description": "Add your character's Tier + 1 to primary weapon damage against targets within Melee range"
|
||||||
},
|
},
|
||||||
"parry": {
|
"parry": {
|
||||||
"name": "Parry",
|
"name": "Parry",
|
||||||
|
|
@ -863,7 +953,7 @@
|
||||||
},
|
},
|
||||||
"protective": {
|
"protective": {
|
||||||
"name": "Protective",
|
"name": "Protective",
|
||||||
"description": "+{tier} to Armor Score"
|
"description": "Add your character's Tier to your Armor Score"
|
||||||
},
|
},
|
||||||
"quick": {
|
"quick": {
|
||||||
"name": "Quick",
|
"name": "Quick",
|
||||||
|
|
@ -889,6 +979,10 @@
|
||||||
"name": "Scary",
|
"name": "Scary",
|
||||||
"description": "On a successful attack, the target must mark a Stress."
|
"description": "On a successful attack, the target must mark a Stress."
|
||||||
},
|
},
|
||||||
|
"selfCorrecting": {
|
||||||
|
"name": "Self Correcting",
|
||||||
|
"description": "When you roll a 1 on a damage die, it deals 6 damage instead."
|
||||||
|
},
|
||||||
"serrated": {
|
"serrated": {
|
||||||
"name": "Serrated",
|
"name": "Serrated",
|
||||||
"description": "When you roll a 1 on a damage die, it deals 8 damage instead."
|
"description": "When you roll a 1 on a damage die, it deals 8 damage instead."
|
||||||
|
|
@ -908,10 +1002,6 @@
|
||||||
"timebending": {
|
"timebending": {
|
||||||
"name": "Timebending",
|
"name": "Timebending",
|
||||||
"description": "You can choose the target of your attack after making your attack roll."
|
"description": "You can choose the target of your attack after making your attack roll."
|
||||||
},
|
|
||||||
"versatile": {
|
|
||||||
"name": "Versatile",
|
|
||||||
"description": "This weapon can also be used with these statistics—{characterTrait}, {range}, {damage}."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -941,6 +1031,10 @@
|
||||||
"stress": {
|
"stress": {
|
||||||
"name": "Stress"
|
"name": "Stress"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Attachments": {
|
||||||
|
"attachHint": "Drop items here to attach them",
|
||||||
|
"transferHint": "If checked, this effect will be applied to any actor that owns this Effect's parent Item. The effect is always applied if this Item is attached to another one."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENERAL": {
|
"GENERAL": {
|
||||||
|
|
@ -950,32 +1044,79 @@
|
||||||
},
|
},
|
||||||
"Advantage": {
|
"Advantage": {
|
||||||
"full": "Advantage",
|
"full": "Advantage",
|
||||||
|
"plural": "Advantages",
|
||||||
"short": "Adv"
|
"short": "Adv"
|
||||||
},
|
},
|
||||||
"Adversary": {
|
"Adversary": {
|
||||||
"singular": "Adversary",
|
"singular": "Adversary",
|
||||||
"plural": "Adversaries"
|
"plural": "Adversaries"
|
||||||
},
|
},
|
||||||
|
"Bonuses": {
|
||||||
|
"rest": {
|
||||||
|
"shortRest": {
|
||||||
|
"shortRestMoves": {
|
||||||
|
"label": "Short Rest: Bonus Short Rest Moves",
|
||||||
|
"hint": "The number of extra Short Rest Moves the character can take during a Short Rest."
|
||||||
|
},
|
||||||
|
"longRestMoves": {
|
||||||
|
"label": "Short Rest: Bonus Long Rest Moves",
|
||||||
|
"hint": "The number of extra Long Rest Moves the character can take during a Short Rest."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"longRest": {
|
||||||
|
"shortRestMoves": {
|
||||||
|
"label": "Long Rest: Bonus Short Rest Moves",
|
||||||
|
"hint": "The number of extra Short Rest Moves the character can take during a Long Rest."
|
||||||
|
},
|
||||||
|
"longRestMoves": {
|
||||||
|
"label": "Long Rest: Bonus Long Rest Moves",
|
||||||
|
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Character": {
|
"Character": {
|
||||||
"singular": "Character",
|
"singular": "Character",
|
||||||
"plural": "Characters"
|
"plural": "Characters"
|
||||||
},
|
},
|
||||||
|
"Cost": {
|
||||||
|
"single": "Cost",
|
||||||
|
"plural": "Costs"
|
||||||
|
},
|
||||||
"Damage": {
|
"Damage": {
|
||||||
"severe": "Severe",
|
"severe": "Severe",
|
||||||
"major": "Major",
|
"major": "Major",
|
||||||
"minor": "Minor",
|
"minor": "Minor",
|
||||||
"none": "None"
|
"none": "None",
|
||||||
|
"allDamage": "All Damage",
|
||||||
|
"physicalDamage": "Physical Damage",
|
||||||
|
"magicalDamage": "Magical Damage",
|
||||||
|
"primaryWeapon": "Primary Weapon Damage",
|
||||||
|
"secondaryWeapon": "Secondary Weapon Damage"
|
||||||
|
},
|
||||||
|
"DamageResistance": {
|
||||||
|
"none": "None",
|
||||||
|
"resistance": "Resistance",
|
||||||
|
"immunity": "Immunity",
|
||||||
|
"physicalReduction": "Physical Damage Reduction",
|
||||||
|
"magicalReduction": "Magical Damage Reduction"
|
||||||
},
|
},
|
||||||
"DamageThresholds": {
|
"DamageThresholds": {
|
||||||
"title": "Damage Thresholds",
|
"title": "Damage Thresholds",
|
||||||
"minor": "Minor",
|
"minor": "Minor",
|
||||||
"major": "Major",
|
"major": "Major",
|
||||||
"severe": "Severe"
|
"severe": "Severe",
|
||||||
|
"majorThreshold": "Major Damage Threshold",
|
||||||
|
"severeThreshold": "Severe Damage Threshold"
|
||||||
},
|
},
|
||||||
"Dice": {
|
"Dice": {
|
||||||
"single": "Die",
|
"single": "Die",
|
||||||
"plural": "Dice"
|
"plural": "Dice"
|
||||||
},
|
},
|
||||||
|
"Difficulty": {
|
||||||
|
"all": "Difficulty: all",
|
||||||
|
"reaction": "Difficulty: reaction"
|
||||||
|
},
|
||||||
"Disadvantage": {
|
"Disadvantage": {
|
||||||
"full": "Disadvantage",
|
"full": "Disadvantage",
|
||||||
"short": "Dis"
|
"short": "Dis"
|
||||||
|
|
@ -985,39 +1126,39 @@
|
||||||
"plural": "Domains",
|
"plural": "Domains",
|
||||||
"arcana": {
|
"arcana": {
|
||||||
"label": "Arcana",
|
"label": "Arcana",
|
||||||
"Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled."
|
"description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled."
|
||||||
},
|
},
|
||||||
"blade": {
|
"blade": {
|
||||||
"label": "Blade",
|
"label": "Blade",
|
||||||
"Description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death."
|
"description": "This is the domain of those who dedicate their lives to the mastery of weapons. Whether by blade, bow, or perhaps a more specialized arm, those who follow this path have the skill to cut short the lives of others. Blade requires study and dedication from its followers, in exchange for inexorable power over death."
|
||||||
},
|
},
|
||||||
"bone": {
|
"bone": {
|
||||||
"label": "Bone",
|
"label": "Bone",
|
||||||
"Description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training."
|
"description": "This is the domain of mastery of swiftness and tactical mastery. Practitioners of this domain have an uncanny control over their own physical abilities, and an eye for predicting the behaviors of others in combat. Bone grants its adherents unparalleled understanding of bodies and their movements in exchange for diligent training."
|
||||||
},
|
},
|
||||||
"codex": {
|
"codex": {
|
||||||
"label": "Codex",
|
"label": "Codex",
|
||||||
"Description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge."
|
"description": "This is the domain of intensive magical study. Those who seek magical knowledge turn to the recipes of power recorded in books, on scrolls, etched into walls, or tattooed on bodies. Codex offers a commanding and versatile understanding of magic to those devotees who are willing to seek beyond the common knowledge."
|
||||||
},
|
},
|
||||||
"grace": {
|
"grace": {
|
||||||
"label": "Grace",
|
"label": "Grace",
|
||||||
"Description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language."
|
"description": "This is the domain of charisma. Through rapturous storytelling, clever charm, or a shroud of lies, those who channel this power define the realities of their adversaries, bending perception to their will. Grace offers its wielders raw magnetism and mastery over language."
|
||||||
},
|
},
|
||||||
"midnight": {
|
"midnight": {
|
||||||
"label": "Midnight",
|
"label": "Midnight",
|
||||||
"Description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas."
|
"description": "This is the domain of shadows and secrecy. Whether by clever tricks, or cloak of night those who channel these forces are practiced in that art of obscurity and there is nothing hidden they cannot reach. Midnight offers practitioners the incredible power to control and create enigmas."
|
||||||
},
|
},
|
||||||
"sage": {
|
"sage": {
|
||||||
"label": "Sage",
|
"label": "Sage",
|
||||||
"Description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator."
|
"description": "This is the domain of the natural world. Those who walk this path tap into the unfettered power of the earth and its creatures to unleash raw magic. Sage grants its adherents the vitality of a blooming flower and ferocity of a hungry predator."
|
||||||
},
|
},
|
||||||
"splendor": {
|
"splendor": {
|
||||||
"label": "Splendor",
|
"label": "Splendor",
|
||||||
"Description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life."
|
"description": "This is the domain of life. Through this magic, followers gain the ability to heal, though such power also grants the wielder some control over death. Splendor offers its disciples the magnificent ability to both give and end life."
|
||||||
},
|
},
|
||||||
"valor": {
|
"valor": {
|
||||||
"label": "Valor",
|
"label": "Valor",
|
||||||
"Description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others."
|
"description": "This is the domain of protection. Whether through attack or defense, those who choose this discipline channel formidable strength to protect their allies in battle. Valor offers great power to those who raise their shield in defense of others."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Effect": {
|
"Effect": {
|
||||||
|
|
@ -1028,15 +1169,73 @@
|
||||||
"single": "Experience",
|
"single": "Experience",
|
||||||
"plural": "Experiences"
|
"plural": "Experiences"
|
||||||
},
|
},
|
||||||
|
"Healing": {
|
||||||
|
"healingAmount": "Healing Amount"
|
||||||
|
},
|
||||||
|
"Modifier": {
|
||||||
|
"single": "Modifier",
|
||||||
|
"plural": "Modifiers"
|
||||||
|
},
|
||||||
"Neutral": {
|
"Neutral": {
|
||||||
"full": "None",
|
"full": "None",
|
||||||
"short": "no"
|
"short": "no"
|
||||||
},
|
},
|
||||||
|
"Range": {
|
||||||
|
"other": "Range Increase: Other",
|
||||||
|
"spell": "Range Increase: Spell",
|
||||||
|
"weapon": "Range Increase: Weapon"
|
||||||
|
},
|
||||||
"RefreshType": {
|
"RefreshType": {
|
||||||
"session": "Session",
|
"session": "Session",
|
||||||
"shortrest": "Short Rest",
|
"shortrest": "Short Rest",
|
||||||
"longrest": "Long Rest"
|
"longrest": "Long Rest"
|
||||||
},
|
},
|
||||||
|
"Resource": {
|
||||||
|
"single": "Resource",
|
||||||
|
"plural": "Resources"
|
||||||
|
},
|
||||||
|
"Roll": {
|
||||||
|
"attack": "Attack Roll",
|
||||||
|
"difficulty": "Roll (Difficulty {difficulty})",
|
||||||
|
"primaryWeaponAttack": "Primary Weapon Attack Roll",
|
||||||
|
"secondaryWeaponAttack": "Secondary Weapon Attack Roll",
|
||||||
|
"spellcast": "Spellcast Roll",
|
||||||
|
"trait": "Trait Roll",
|
||||||
|
"action": "Action Roll",
|
||||||
|
"reaction": "Reaction Roll"
|
||||||
|
},
|
||||||
|
"Rules": {
|
||||||
|
"damageReduction": {
|
||||||
|
"increasePerArmorMark": {
|
||||||
|
"label": "Damage Reduction per Armor Slot",
|
||||||
|
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
||||||
|
},
|
||||||
|
"maxArmorMarkedBonus": "Max Armor Used",
|
||||||
|
"maxArmorMarkedStress": {
|
||||||
|
"label": "Max Armor Used With Stress",
|
||||||
|
"hint": "If this value is set you can use up to that much stress to spend additional Armor Marks beyond your normal maximum."
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"severe": {
|
||||||
|
"label": "Stress Damage Reduction: Severe",
|
||||||
|
"hint": "The cost in stress you can pay to reduce severe damage down to major."
|
||||||
|
},
|
||||||
|
"major": {
|
||||||
|
"label": "Stress Damage Reduction: Major",
|
||||||
|
"hint": "The cost in stress you can pay to reduce major damage down to minor."
|
||||||
|
},
|
||||||
|
"minor": {
|
||||||
|
"label": "Stress Damage Reduction: Minor",
|
||||||
|
"hint": "The cost in stress you can pay to reduce minor damage to none."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attack": {
|
||||||
|
"damage": {
|
||||||
|
"value": { "label": "Base Attack: Damage" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Tabs": {
|
"Tabs": {
|
||||||
"details": "Details",
|
"details": "Details",
|
||||||
"attack": "Attack",
|
"attack": "Attack",
|
||||||
|
|
@ -1065,52 +1264,124 @@
|
||||||
"specialization": "Specialization",
|
"specialization": "Specialization",
|
||||||
"mastery": "Mastery",
|
"mastery": "Mastery",
|
||||||
"optional": "Optional",
|
"optional": "Optional",
|
||||||
|
"recovery": "Recovery",
|
||||||
"setup": "Setup",
|
"setup": "Setup",
|
||||||
"equipment": "Equipment"
|
"equipment": "Equipment",
|
||||||
},
|
"attachments": "Attachments",
|
||||||
"Tiers": {
|
"advanced": "Advanced",
|
||||||
"singular": "Tier",
|
|
||||||
"tier1": "Tier 1",
|
"tier1": "Tier 1",
|
||||||
"tier2": "Tier 2",
|
"tier2": "Tier 2",
|
||||||
"tier3": "Tier 3",
|
"tier3": "Tier 3",
|
||||||
"tier4": "Tier 4"
|
"tier4": "tier 4"
|
||||||
|
},
|
||||||
|
"Tiers": {
|
||||||
|
"singular": "Tier",
|
||||||
|
"1": "Tier 1",
|
||||||
|
"2": "Tier 2",
|
||||||
|
"3": "Tier 3",
|
||||||
|
"4": "Tier 4"
|
||||||
},
|
},
|
||||||
"Trait": {
|
"Trait": {
|
||||||
"single": "Trait",
|
"single": "Trait",
|
||||||
"plural": "Traits"
|
"plural": "Traits"
|
||||||
},
|
},
|
||||||
|
"actorName": "Actor Name",
|
||||||
|
"amount": "Amount",
|
||||||
|
"armorScore": "Armor Score",
|
||||||
"activeEffects": "Active Effects",
|
"activeEffects": "Active Effects",
|
||||||
|
"armorSlots": "Armor Slots",
|
||||||
"attack": "Attack",
|
"attack": "Attack",
|
||||||
"basics": "Basics",
|
"basics": "Basics",
|
||||||
"bonus": "Bonus",
|
"bonus": "Bonus",
|
||||||
"burden": "Burden",
|
"burden": "Burden",
|
||||||
"check": "{check} Check",
|
"check": "{check} Check",
|
||||||
|
"continue": "Continue",
|
||||||
"criticalSuccess": "Critical Success",
|
"criticalSuccess": "Critical Success",
|
||||||
|
"damage": "Damage",
|
||||||
|
"damageType": "Damage Type",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
|
"difficulty": "Difficulty",
|
||||||
|
"dropActorsHere": "Drop Actors here",
|
||||||
"duality": "Duality",
|
"duality": "Duality",
|
||||||
"dualityRoll": "Duality Roll",
|
"dualityRoll": "Duality Roll",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"evasion": "Evasion",
|
"evasion": "Evasion",
|
||||||
|
"equipment": "Equipment",
|
||||||
|
"experience": {
|
||||||
|
"single": "Experience",
|
||||||
|
"plural": "Experiences"
|
||||||
|
},
|
||||||
|
"failure": "Failure",
|
||||||
"fear": "Fear",
|
"fear": "Fear",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"hitPoints": "Hit Points",
|
"formula": "Formula",
|
||||||
|
"healing": "Healing",
|
||||||
|
"HitPoints": {
|
||||||
|
"single": "Hit Point",
|
||||||
|
"plural": "Hit Points",
|
||||||
|
"short": "HP"
|
||||||
|
},
|
||||||
"hope": "Hope",
|
"hope": "Hope",
|
||||||
|
"hordeHp": "Horde HP",
|
||||||
|
"identify": "Identity",
|
||||||
|
"imagePath": "Image Path",
|
||||||
"inactiveEffects": "Inactive Effects",
|
"inactiveEffects": "Inactive Effects",
|
||||||
"inventory": "Inventory",
|
"inventory": "Inventory",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"modifier": "Modifier",
|
"levelUp": "Level Up",
|
||||||
|
"loadout": "Loadout",
|
||||||
|
"max": "Max",
|
||||||
"multiclass": "Multiclass",
|
"multiclass": "Multiclass",
|
||||||
|
"newCategory": "New Category",
|
||||||
|
"none": "None",
|
||||||
|
"partner": "Partner",
|
||||||
|
"proficiency": "Proficiency",
|
||||||
"quantity": "Quantity",
|
"quantity": "Quantity",
|
||||||
"range": "Range",
|
"range": "Range",
|
||||||
|
"recovery": "Recovery",
|
||||||
|
"resource": "Resource",
|
||||||
|
"roll": "Roll",
|
||||||
|
"rollAll": "Roll All",
|
||||||
|
"rollDamage": "Roll Damage",
|
||||||
|
"save": "Save",
|
||||||
|
"scalable": "Scalable",
|
||||||
|
"situationalBonus": "Situational Bonus",
|
||||||
"stress": "Stress",
|
"stress": "Stress",
|
||||||
|
"success": "Success",
|
||||||
"take": "Take",
|
"take": "Take",
|
||||||
"target": "Target",
|
"Target": {
|
||||||
|
"single": "Target",
|
||||||
|
"plural": "Targets"
|
||||||
|
},
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
|
"total": "Total",
|
||||||
|
"true": "True",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
"unarmed": "Unarmed",
|
||||||
|
"unarmedStrike": "Unarmed Strike",
|
||||||
"unarmored": "Unarmored",
|
"unarmored": "Unarmored",
|
||||||
"use": "Use"
|
"use": "Use",
|
||||||
|
"used": "Used",
|
||||||
|
"uses": "Uses",
|
||||||
|
"value": "Value",
|
||||||
|
"withThing": "With {thing}"
|
||||||
},
|
},
|
||||||
"ITEMS": {
|
"ITEMS": {
|
||||||
|
"FIELDS": {
|
||||||
|
"resource": {
|
||||||
|
"amount": { "label": "Amount" },
|
||||||
|
"dieFaces": { "label": "Die Faces" },
|
||||||
|
"icon": { "label": "Icon" },
|
||||||
|
"max": { "label": "Max" },
|
||||||
|
"recovery": { "label": "Recovery" },
|
||||||
|
"type": { "label": "Type" },
|
||||||
|
"value": { "label": "Value" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Ancestry": {
|
||||||
|
"primaryFeature": "Primary Feature",
|
||||||
|
"secondaryFeature": "Secondary Feature"
|
||||||
|
},
|
||||||
"Armor": {
|
"Armor": {
|
||||||
"baseScore": "Base Score",
|
"baseScore": "Base Score",
|
||||||
"baseThresholds": {
|
"baseThresholds": {
|
||||||
|
|
@ -1121,20 +1392,39 @@
|
||||||
},
|
},
|
||||||
"Beastform": {
|
"Beastform": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
|
"beastformType": { "label": "Beastform Type" },
|
||||||
"tier": { "label": "Tier" },
|
"tier": { "label": "Tier" },
|
||||||
|
"mainTrait": { "label": "Main Trait" },
|
||||||
"examples": { "label": "Examples" },
|
"examples": { "label": "Examples" },
|
||||||
"advantageOn": { "label": "Gain Advantage On" },
|
"advantageOn": { "label": "Gain Advantage On" },
|
||||||
"tokenImg": { "label": "Token Image" },
|
"tokenImg": { "label": "Token Image" },
|
||||||
|
"tokenRingImg": { "label": "Subject Texture" },
|
||||||
"tokenSize": {
|
"tokenSize": {
|
||||||
"placeholder": "Using character dimensions",
|
"placeholder": "Using character dimensions",
|
||||||
"height": { "label": "Height" },
|
"height": { "label": "Height" },
|
||||||
"width": { "label": "Width" }
|
"width": { "label": "Width" }
|
||||||
|
},
|
||||||
|
"evolved": {
|
||||||
|
"maximumTier": { "label": "Maximum Tier" },
|
||||||
|
"mainTraitBonus": { "label": "Main Trait Bonus" }
|
||||||
|
},
|
||||||
|
"hybrid": {
|
||||||
|
"beastformOptions": { "label": "Nr Beastforms" },
|
||||||
|
"advantages": { "label": "Nr Advantages" },
|
||||||
|
"features": { "label": "Nr Features" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"attackName": "Beast Attack",
|
||||||
|
"beastformEffect": "Beastform Transformation",
|
||||||
"dialogTitle": "Beastform Selection",
|
"dialogTitle": "Beastform Selection",
|
||||||
"tokenTitle": "Beastform Token",
|
"tokenTitle": "Beastform Token",
|
||||||
"transform": "Transform",
|
"transform": "Transform",
|
||||||
"beastformEffect": "Beastform Transformation"
|
"evolve": "Evolve",
|
||||||
|
"evolvedFeatureTitle": "Evolved",
|
||||||
|
"evolvedDrag": "Drag a form here to evolve it.",
|
||||||
|
"hybridize": "Hybridize",
|
||||||
|
"hybridizeFeatureTitle": "Hybrid Features",
|
||||||
|
"hybridizeDrag": "Drag a form here to hybridize it."
|
||||||
},
|
},
|
||||||
"Class": {
|
"Class": {
|
||||||
"hopeFeatures": "Hope Features",
|
"hopeFeatures": "Hope Features",
|
||||||
|
|
@ -1155,8 +1445,8 @@
|
||||||
},
|
},
|
||||||
"DomainCard": {
|
"DomainCard": {
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"foundation": "Foundation",
|
|
||||||
"recallCost": "Recall Cost",
|
"recallCost": "Recall Cost",
|
||||||
|
"foundationTitle": "Foundation",
|
||||||
"specializationTitle": "Specialization",
|
"specializationTitle": "Specialization",
|
||||||
"masteryTitle": "Mastery"
|
"masteryTitle": "Mastery"
|
||||||
},
|
},
|
||||||
|
|
@ -1164,6 +1454,7 @@
|
||||||
"spellcastingTrait": "Spellcasting Trait"
|
"spellcastingTrait": "Spellcasting Trait"
|
||||||
},
|
},
|
||||||
"Weapon": {
|
"Weapon": {
|
||||||
|
"weaponType": "Weapon Type",
|
||||||
"primaryWeapon": "Primary Weapon",
|
"primaryWeapon": "Primary Weapon",
|
||||||
"secondaryWeapon": "Secondary Weapon"
|
"secondaryWeapon": "Secondary Weapon"
|
||||||
}
|
}
|
||||||
|
|
@ -1171,7 +1462,8 @@
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"Appearance": {
|
"Appearance": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"displayFear": { "label": "Fear Display" }
|
"displayFear": { "label": "Fear Display" },
|
||||||
|
"showGenericStatusEffects": { "label": "Show Foundry Status Effects" }
|
||||||
},
|
},
|
||||||
"fearDisplay": {
|
"fearDisplay": {
|
||||||
"token": "Tokens",
|
"token": "Tokens",
|
||||||
|
|
@ -1185,17 +1477,18 @@
|
||||||
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
||||||
},
|
},
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"hope": {
|
"hopeFear": {
|
||||||
"label": "Hope",
|
"label": "Hope & Fear",
|
||||||
"hint": "Automatically increase a character's hope on a hope duality roll result."
|
"gm": { "label": "GM" },
|
||||||
|
"players": { "label": "Players" }
|
||||||
},
|
},
|
||||||
"actionPoints": {
|
"actionPoints": {
|
||||||
"label": "Action Points",
|
"label": "Action Points",
|
||||||
"hint": "Automatically give and take Action Points as combatants take their turns."
|
"hint": "Automatically give and take Action Points as combatants take their turns."
|
||||||
},
|
},
|
||||||
"countdowns": {
|
"hordeDamage": {
|
||||||
"label": "Countdowns",
|
"label": "Automatic Horde Damage",
|
||||||
"hint": "Automatically progress non-custom countdowns"
|
"hint": "Automatically active horde effect to lower damage when reaching half or lower HP."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1226,6 +1519,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Menu": {
|
"Menu": {
|
||||||
|
"title": "Daggerheart Game Settings",
|
||||||
"automation": {
|
"automation": {
|
||||||
"name": "Automation Settings",
|
"name": "Automation Settings",
|
||||||
"label": "Configure Automation",
|
"label": "Configure Automation",
|
||||||
|
|
@ -1285,8 +1579,8 @@
|
||||||
},
|
},
|
||||||
"UI": {
|
"UI": {
|
||||||
"Chat": {
|
"Chat": {
|
||||||
"dualityRoll": {
|
"applyEffect": {
|
||||||
"abilityCheckTitle": "{ability} Check"
|
"title": "Apply Effects - {name}"
|
||||||
},
|
},
|
||||||
"attackRoll": {
|
"attackRoll": {
|
||||||
"title": "Attack - {attack}",
|
"title": "Attack - {attack}",
|
||||||
|
|
@ -1302,25 +1596,28 @@
|
||||||
"hitTarget": "Hit Targets",
|
"hitTarget": "Hit Targets",
|
||||||
"selectedTarget": "Selected"
|
"selectedTarget": "Selected"
|
||||||
},
|
},
|
||||||
"applyEffect": {
|
|
||||||
"title": "Apply Effects - {name}"
|
|
||||||
},
|
|
||||||
"healingRoll": {
|
|
||||||
"title": "Heal - {healing}",
|
|
||||||
"heal": "Heal"
|
|
||||||
},
|
|
||||||
"deathMove": {
|
"deathMove": {
|
||||||
"title": "Death Move"
|
"title": "Death Move"
|
||||||
},
|
},
|
||||||
"domainCard": {
|
"domainCard": {
|
||||||
"title": "Domain Card"
|
"title": "Domain Card"
|
||||||
},
|
},
|
||||||
|
"dualityRoll": {
|
||||||
|
"abilityCheckTitle": "{ability} Check"
|
||||||
|
},
|
||||||
|
"featureTitle": "Class Feature",
|
||||||
"foundationCard": {
|
"foundationCard": {
|
||||||
"ancestryTitle": "Ancestry Card",
|
"ancestryTitle": "Ancestry Card",
|
||||||
"communityTitle": "Community Card",
|
"communityTitle": "Community Card",
|
||||||
"subclassFeatureTitle": "Subclass Feature"
|
"subclassFeatureTitle": "Subclass Feature"
|
||||||
},
|
},
|
||||||
"featureTitle": "Class Feature"
|
"healingRoll": {
|
||||||
|
"title": "Heal - {healing}",
|
||||||
|
"heal": "Heal"
|
||||||
|
},
|
||||||
|
"resourceRoll": {
|
||||||
|
"playerMessage": "{user} rerolled their {name}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
"adversaryMissing": "The linked adversary doesn't exist in the world.",
|
"adversaryMissing": "The linked adversary doesn't exist in the world.",
|
||||||
|
|
@ -1360,9 +1657,27 @@
|
||||||
"damageAlreadyNone": "The damage has already been reduced to none",
|
"damageAlreadyNone": "The damage has already been reduced to none",
|
||||||
"noAvailableArmorMarks": "You have no more available armor marks",
|
"noAvailableArmorMarks": "You have no more available armor marks",
|
||||||
"notEnoughStress": "You don't have enough stress",
|
"notEnoughStress": "You don't have enough stress",
|
||||||
"damageIgnore": "{character} did not take damage"
|
"damageIgnore": "{character} did not take damage",
|
||||||
|
"featureIsMissing": "Feature is missing",
|
||||||
|
"actionIsMissing": "Action is missing",
|
||||||
|
"attackIsMissing": "Attack is missing",
|
||||||
|
"unownedActionMacro": "Cannot make a Use macro for an Action not on your character",
|
||||||
|
"unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters",
|
||||||
|
"featureNotHope": "This feature is used as something else than a Hope feature and cannot be used here.",
|
||||||
|
"featureNotClass": "This feature is used as something else than a Class feature and cannot be used here.",
|
||||||
|
"featureNotPrimary": "This feature is used as something else than a Primary feature and cannot be used here.",
|
||||||
|
"featureNotSecondary": "This feature is used as something else than a Secondary feature and cannot be used here.",
|
||||||
|
"featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.",
|
||||||
|
"featureNotSpecialization": "This feature is used as something else than a Specialization feature and cannot be used here.",
|
||||||
|
"featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here.",
|
||||||
|
"beastformMissingEffect": "The Beastform is missing a Beastform Effect. Cannot be used.",
|
||||||
|
"beastformToManyAdvantages": "You cannot select any more advantages.",
|
||||||
|
"beastformToManyFeatures": "You cannot select any more features.",
|
||||||
|
"beastformEquipWeapon": "You cannot use weapons while in a Beastform."
|
||||||
},
|
},
|
||||||
"Tooltip": {
|
"Tooltip": {
|
||||||
|
"disableEffect": "Disable Effect",
|
||||||
|
"enableEffect": "Enable Effect",
|
||||||
"openItemWorld": "Open Item World",
|
"openItemWorld": "Open Item World",
|
||||||
"openActorWorld": "Open Actor World",
|
"openActorWorld": "Open Actor World",
|
||||||
"sendToChat": "Send to Chat",
|
"sendToChat": "Send to Chat",
|
||||||
|
|
@ -1371,7 +1686,10 @@
|
||||||
"unequip": "Unequip",
|
"unequip": "Unequip",
|
||||||
"sendToVault": "Send to Vault",
|
"sendToVault": "Send to Vault",
|
||||||
"sendToLoadout": "Send to Loadout",
|
"sendToLoadout": "Send to Loadout",
|
||||||
"makeDeathMove": "Make a Death Move"
|
"makeDeathMove": "Make a Death Move",
|
||||||
|
"rangeAndTarget": "Range & Target",
|
||||||
|
"dragApplyEffect": "Drag effect to apply it to an actor",
|
||||||
|
"appliedEvenIfSuccessful": "Applied even if save succeeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export * as characterCreation from './characterCreation/_module.mjs';
|
export * as characterCreation from './characterCreation/_module.mjs';
|
||||||
export * as dialogs from './dialogs/_module.mjs';
|
export * as dialogs from './dialogs/_module.mjs';
|
||||||
|
export * as hud from './hud/_module.mjs';
|
||||||
export * as levelup from './levelup/_module.mjs';
|
export * as levelup from './levelup/_module.mjs';
|
||||||
export * as settings from './settings/_module.mjs';
|
export * as settings from './settings/_module.mjs';
|
||||||
export * as sheets from './sheets/_module.mjs';
|
export * as sheets from './sheets/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
|
|
||||||
this.setup = {
|
this.setup = {
|
||||||
traits: this.character.system.traits,
|
traits: this.character.system.traits,
|
||||||
ancestry: this.character.system.ancestry ?? {},
|
ancestryName: '',
|
||||||
|
mixedAncestry: false,
|
||||||
|
primaryAncestry: this.character.system.ancestry ?? {},
|
||||||
|
secondaryAncestry: {},
|
||||||
community: this.character.system.community ?? {},
|
community: this.character.system.community ?? {},
|
||||||
class: this.character.system.class?.value ?? {},
|
class: this.character.system.class?.value ?? {},
|
||||||
subclass: this.character.system.class?.subclass ?? {},
|
subclass: this.character.system.class?.subclass ?? {},
|
||||||
experiences: {
|
experiences: {
|
||||||
[foundry.utils.randomID()]: { description: '', value: 2 },
|
[foundry.utils.randomID()]: { name: '', value: 2 },
|
||||||
[foundry.utils.randomID()]: { description: '', value: 2 }
|
[foundry.utils.randomID()]: { name: '', value: 2 }
|
||||||
},
|
},
|
||||||
domainCards: {
|
domainCards: {
|
||||||
[foundry.utils.randomID()]: {},
|
[foundry.utils.randomID()]: {},
|
||||||
|
|
@ -47,12 +50,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'],
|
||||||
position: { width: 800, height: 'auto' },
|
position: { width: 700, height: 'auto' },
|
||||||
actions: {
|
actions: {
|
||||||
viewCompendium: this.viewCompendium,
|
viewCompendium: this.viewCompendium,
|
||||||
viewItem: this.viewItem,
|
viewItem: this.viewItem,
|
||||||
useSuggestedTraits: this.useSuggestedTraits,
|
useSuggestedTraits: this.useSuggestedTraits,
|
||||||
equipmentChoice: this.equipmentChoice,
|
equipmentChoice: this.equipmentChoice,
|
||||||
|
setupGoNext: this.setupGoNext,
|
||||||
finish: this.finish
|
finish: this.finish
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -76,6 +80,12 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
|
||||||
setup: { template: 'systems/daggerheart/templates/characterCreation/tabs/setup.hbs' },
|
setup: { template: 'systems/daggerheart/templates/characterCreation/tabs/setup.hbs' },
|
||||||
|
ancestry: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/ancestry.hbs' },
|
||||||
|
community: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/community.hbs' },
|
||||||
|
class: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/class.hbs' },
|
||||||
|
traits: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/traits.hbs' },
|
||||||
|
experience: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/experience.hbs' },
|
||||||
|
domainCards: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/domainCards.hbs' },
|
||||||
equipment: { template: 'systems/daggerheart/templates/characterCreation/tabs/equipment.hbs' },
|
equipment: { template: 'systems/daggerheart/templates/characterCreation/tabs/equipment.hbs' },
|
||||||
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.hbs' },
|
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.hbs' },
|
||||||
footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' }
|
||||||
|
|
@ -107,6 +117,51 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static SETUPTABS = {
|
||||||
|
ancestry: {
|
||||||
|
active: true,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'setup',
|
||||||
|
id: 'ancestry',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.ancestry'
|
||||||
|
},
|
||||||
|
community: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'setup',
|
||||||
|
id: 'community',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.community'
|
||||||
|
},
|
||||||
|
class: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'setup',
|
||||||
|
id: 'class',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.class'
|
||||||
|
},
|
||||||
|
traits: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'setup',
|
||||||
|
id: 'traits',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.traits'
|
||||||
|
},
|
||||||
|
experience: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'setup',
|
||||||
|
id: 'experience',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.experience'
|
||||||
|
},
|
||||||
|
domainCards: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'setup',
|
||||||
|
id: 'domainCards',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.domainCards'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_getTabs(tabs) {
|
_getTabs(tabs) {
|
||||||
for (const v of Object.values(tabs)) {
|
for (const v of Object.values(tabs)) {
|
||||||
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
||||||
|
|
@ -114,14 +169,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
|
|
||||||
switch (v.id) {
|
switch (v.id) {
|
||||||
case 'setup':
|
case 'setup':
|
||||||
|
const ancestryFinished = this.setup.primaryAncestry.uuid;
|
||||||
|
const communityFinished = this.setup.community.uuid;
|
||||||
const classFinished = this.setup.class.uuid && this.setup.subclass.uuid;
|
const classFinished = this.setup.class.uuid && this.setup.subclass.uuid;
|
||||||
const heritageFinished = this.setup.ancestry.uuid && this.setup.community.uuid;
|
|
||||||
const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null);
|
const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null);
|
||||||
const experiencesFinished = Object.values(this.setup.experiences).every(x => x.description);
|
const experiencesFinished = Object.values(this.setup.experiences).every(x => x.name);
|
||||||
const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid);
|
const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid);
|
||||||
v.finished =
|
v.finished =
|
||||||
|
ancestryFinished &&
|
||||||
|
communityFinished &&
|
||||||
classFinished &&
|
classFinished &&
|
||||||
heritageFinished &&
|
|
||||||
traitsFinished &&
|
traitsFinished &&
|
||||||
experiencesFinished &&
|
experiencesFinished &&
|
||||||
domainCardsFinished;
|
domainCardsFinished;
|
||||||
|
|
@ -146,15 +203,44 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getSetupTabs(tabs) {
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
||||||
|
v.cssClass = v.active ? 'active' : '';
|
||||||
|
|
||||||
|
switch (v.id) {
|
||||||
|
case 'community':
|
||||||
|
v.disabled = this.setup.visibility < 2;
|
||||||
|
break;
|
||||||
|
case 'class':
|
||||||
|
v.disabled = this.setup.visibility < 3;
|
||||||
|
break;
|
||||||
|
case 'traits':
|
||||||
|
v.disabled = this.setup.visibility < 4;
|
||||||
|
break;
|
||||||
|
case 'experience':
|
||||||
|
v.disabled = this.setup.visibility < 5;
|
||||||
|
break;
|
||||||
|
case 'domainCards':
|
||||||
|
v.disabled = this.setup.visibility < 6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
changeTab(tab, group, options) {
|
changeTab(tab, group, options) {
|
||||||
super.changeTab(tab, group, options);
|
super.changeTab(tab, group, options);
|
||||||
|
|
||||||
for (var listTab of Object.keys(this.constructor.TABS)) {
|
if (group === 'primary') {
|
||||||
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
|
for (var listTab of Object.keys(this.constructor.TABS)) {
|
||||||
if (listTab === tab) {
|
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
|
||||||
marker.classList.add('active');
|
if (listTab === tab) {
|
||||||
} else {
|
marker.classList.add('active');
|
||||||
marker.classList.remove('active');
|
} else {
|
||||||
|
marker.classList.remove('active');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,6 +249,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
this._dragDrop.forEach(d => d.bind(htmlElement));
|
this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.mixed-ancestry-slider').forEach(element => {
|
||||||
|
element.addEventListener('input', this.mixedAncestryToggle.bind(this));
|
||||||
|
element.addEventListener('click', this.mixedAncestryToggle.bind(this));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
|
|
@ -174,7 +265,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
|
|
||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context) {
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
|
case 'footer':
|
||||||
|
context.isLastTab = this.tabGroups.setup === 'domainCards';
|
||||||
|
switch (this.tabGroups.setup) {
|
||||||
|
case null:
|
||||||
|
case 'ancestry':
|
||||||
|
context.nextDisabled = this.setup.visibility === 1;
|
||||||
|
break;
|
||||||
|
case 'community':
|
||||||
|
context.nextDisabled = this.setup.visibility === 2;
|
||||||
|
break;
|
||||||
|
case 'class':
|
||||||
|
context.nextDisabled = this.setup.visibility === 3;
|
||||||
|
break;
|
||||||
|
case 'traits':
|
||||||
|
context.nextDisabled = this.setup.visibility === 4;
|
||||||
|
break;
|
||||||
|
case 'experience':
|
||||||
|
context.nextDisabled = this.setup.visibility === 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
case 'setup':
|
case 'setup':
|
||||||
|
context.setupTabs = this._getSetupTabs(this.constructor.SETUPTABS);
|
||||||
const availableTraitModifiers = game.settings
|
const availableTraitModifiers = game.settings
|
||||||
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
|
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
|
||||||
.traitArray.map(trait => ({ key: trait, name: trait }));
|
.traitArray.map(trait => ({ key: trait, name: trait }));
|
||||||
|
|
@ -215,13 +329,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
context.experience = {
|
context.experience = {
|
||||||
values: this.setup.experiences,
|
values: this.setup.experiences,
|
||||||
nrTotal: Object.keys(this.setup.experiences).length,
|
nrTotal: Object.keys(this.setup.experiences).length,
|
||||||
nrSelected: Object.values(this.setup.experiences).reduce(
|
nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.name ? 1 : 0), 0)
|
||||||
(acc, exp) => acc + (exp.description ? 1 : 0),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
context.ancestry = { ...this.setup.ancestry, compendium: 'ancestries' };
|
context.mixedAncestry = Number(this.setup.mixedAncestry);
|
||||||
|
context.ancestryName = this.setup.ancestryName;
|
||||||
|
context.primaryAncestry = { ...this.setup.primaryAncestry, compendium: 'ancestries' };
|
||||||
|
context.secondaryAncestry = { ...this.setup.secondaryAncestry, compendium: 'ancestries' };
|
||||||
context.community = { ...this.setup.community, compendium: 'communities' };
|
context.community = { ...this.setup.community, compendium: 'communities' };
|
||||||
context.class = { ...this.setup.class, compendium: 'classes' };
|
context.class = { ...this.setup.class, compendium: 'classes' };
|
||||||
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
|
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
|
||||||
|
|
@ -278,18 +392,29 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixedAncestryToggle(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.setup.mixedAncestry = !this.setup.mixedAncestry;
|
||||||
|
if (!this.setup.mixedAncestry) this.setup.secondaryAncestry = {};
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
getUpdateVisibility() {
|
getUpdateVisibility() {
|
||||||
switch (this.setup.visibility) {
|
switch (this.setup.visibility) {
|
||||||
|
case 6:
|
||||||
|
return 6;
|
||||||
case 5:
|
case 5:
|
||||||
return 5;
|
return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5;
|
||||||
case 4:
|
case 4:
|
||||||
return Object.values(this.setup.experiences).every(x => x.description) ? 5 : 4;
|
return Object.values(this.setup.traits).every(x => x.value !== null) ? 5 : 4;
|
||||||
case 3:
|
case 3:
|
||||||
return Object.values(this.setup.traits).every(x => x.value !== null) ? 4 : 3;
|
return this.setup.class.uuid && this.setup.subclass.uuid ? 4 : 3;
|
||||||
case 2:
|
case 2:
|
||||||
return this.setup.ancestry.uuid && this.setup.community.uuid ? 3 : 2;
|
return this.setup.community.uuid ? 3 : 2;
|
||||||
case 1:
|
case 1:
|
||||||
return this.setup.class.uuid && this.setup.subclass.uuid ? 2 : 1;
|
return this.setup.primaryAncestry.uuid ? 2 : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,8 +473,44 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setupGoNext() {
|
||||||
|
switch (this.setup.visibility) {
|
||||||
|
case 2:
|
||||||
|
this.tabGroups.setup = 'community';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this.tabGroups.setup = 'class';
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.tabGroups.setup = 'traits';
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this.tabGroups.setup = 'experience';
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
this.tabGroups.setup = 'domainCards';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async finish() {
|
static async finish() {
|
||||||
await this.character.createEmbeddedDocuments('Item', [this.setup.ancestry]);
|
const primaryAncestryFeature = this.setup.primaryAncestry.system.primaryFeature;
|
||||||
|
const secondaryAncestryFeature = this.setup.secondaryAncestry?.uuid
|
||||||
|
? this.setup.secondaryAncestry.system.secondaryFeature
|
||||||
|
: this.setup.primaryAncestry.system.secondaryFeature;
|
||||||
|
|
||||||
|
const ancestry = {
|
||||||
|
...this.setup.primaryAncestry,
|
||||||
|
name: this.setup.ancestryName ?? this.setup.primaryAncestry.name,
|
||||||
|
system: {
|
||||||
|
...this.setup.primaryAncestry.system,
|
||||||
|
features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.character.createEmbeddedDocuments('Item', [ancestry]);
|
||||||
await this.character.createEmbeddedDocuments('Item', [this.setup.community]);
|
await this.character.createEmbeddedDocuments('Item', [this.setup.community]);
|
||||||
await this.character.createEmbeddedDocuments('Item', [this.setup.class]);
|
await this.character.createEmbeddedDocuments('Item', [this.setup.class]);
|
||||||
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
|
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
|
||||||
|
|
@ -396,8 +557,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
const item = await foundry.utils.fromUuid(data.uuid);
|
const item = await foundry.utils.fromUuid(data.uuid);
|
||||||
if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) {
|
if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) {
|
||||||
this.setup.ancestry = {
|
this.setup.ancestryName = item.name;
|
||||||
|
this.setup.primaryAncestry = {
|
||||||
|
...item,
|
||||||
|
effects: Array.from(item.effects).map(x => x.toObject()),
|
||||||
|
uuid: item.uuid
|
||||||
|
};
|
||||||
|
} else if (item.type === 'ancestry' && event.target.closest('.secondary-ancestry-card')) {
|
||||||
|
this.setup.secondaryAncestry = {
|
||||||
...item,
|
...item,
|
||||||
effects: Array.from(item.effects).map(x => x.toObject()),
|
effects: Array.from(item.effects).map(x => x.toObject()),
|
||||||
uuid: item.uuid
|
uuid: item.uuid
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
export { default as BeastformDialog } from './beastformDialog.mjs';
|
export { default as BeastformDialog } from './beastformDialog.mjs';
|
||||||
export { default as costSelectionDialog } from './costSelectionDialog.mjs';
|
|
||||||
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
||||||
export { default as DamageDialog } from './damageDialog.mjs';
|
export { default as DamageDialog } from './damageDialog.mjs';
|
||||||
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
||||||
|
|
@ -7,3 +6,4 @@ export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs';
|
||||||
export { default as DeathMove } from './deathMove.mjs';
|
export { default as DeathMove } from './deathMove.mjs';
|
||||||
export { default as Downtime } from './downtime.mjs';
|
export { default as Downtime } from './downtime.mjs';
|
||||||
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||||
|
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,261 @@
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
export default class BeastformDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class BeastformDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(configData) {
|
constructor(configData, item) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.item = item;
|
||||||
|
|
||||||
this.configData = configData;
|
this.configData = configData;
|
||||||
this.selected = null;
|
this.selected = null;
|
||||||
|
this.evolved = { form: null };
|
||||||
|
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
||||||
|
|
||||||
|
this._dragDrop = this._createDragDropHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'views', 'dh-style', 'beastform-selection'],
|
classes: ['daggerheart', 'views', 'dialog', 'dh-style', 'beastform-selection'],
|
||||||
position: {
|
position: {
|
||||||
width: 600,
|
width: 600,
|
||||||
height: 'auto'
|
height: 'auto'
|
||||||
},
|
},
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-paw'
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
selectBeastform: this.selectBeastform,
|
selectBeastform: this.selectBeastform,
|
||||||
|
toggleHybridFeature: this.toggleHybridFeature,
|
||||||
|
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
||||||
submitBeastform: this.submitBeastform
|
submitBeastform: this.submitBeastform
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
handler: this.updateBeastform,
|
handler: this.updateBeastform,
|
||||||
submitOnChange: true,
|
submitOnChange: true,
|
||||||
submitOnClose: false
|
submitOnClose: false
|
||||||
}
|
},
|
||||||
|
dragDrop: [{ dragSelector: '.beastform-container', dropSelector: '.advanced-form-container' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.ITEMS.Beastform.dialogTitle');
|
return this.item.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
beastform: {
|
header: { template: 'systems/daggerheart/templates/dialogs/beastform/header.hbs' },
|
||||||
template: 'systems/daggerheart/templates/dialogs/beastformDialog.hbs'
|
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
|
||||||
|
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
|
||||||
|
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
|
||||||
|
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static TABS = {
|
||||||
|
primary: {
|
||||||
|
tabs: [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }],
|
||||||
|
initial: '1',
|
||||||
|
labelPrefix: 'DAGGERHEART.GENERAL.Tiers'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
changeTab(tab, group, options) {
|
||||||
|
super.changeTab(tab, group, options);
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
_createDragDropHandlers() {
|
||||||
|
return this.options.dragDrop.map(d => {
|
||||||
|
d.callbacks = {
|
||||||
|
dragstart: this._onDragStart.bind(this),
|
||||||
|
drop: this._onDrop.bind(this)
|
||||||
|
};
|
||||||
|
return new foundry.applications.ux.DragDrop.implementation(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
|
|
||||||
context.beastformTiers = game.items.reduce((acc, x) => {
|
context.selected = this.selected;
|
||||||
const tier = CONFIG.DH.GENERAL.tiers[x.system.tier];
|
context.selectedBeastformEffect = this.selected?.effects?.find?.(x => x.type === 'beastform');
|
||||||
if (x.type !== 'beastform' || tier.value > this.configData.tierLimit) return acc;
|
|
||||||
|
|
||||||
if (!acc[tier.value]) acc[tier.value] = { label: game.i18n.localize(tier.label), values: {} };
|
context.evolved = this.evolved;
|
||||||
acc[tier.value].values[x.uuid] = { selected: this.selected == x.uuid, value: x };
|
|
||||||
|
|
||||||
|
context.hybridForms = Object.keys(this.hybrid.forms).reduce((acc, formKey) => {
|
||||||
|
if (!this.hybrid.forms[formKey]) {
|
||||||
|
acc[formKey] = null;
|
||||||
|
} else {
|
||||||
|
const data = this.hybrid.forms[formKey].toObject();
|
||||||
|
acc[formKey] = {
|
||||||
|
...data,
|
||||||
|
system: {
|
||||||
|
...data.system,
|
||||||
|
features: this.hybrid.forms[formKey].system.features.map(feature => ({
|
||||||
|
...feature.toObject(),
|
||||||
|
uuid: feature.uuid,
|
||||||
|
selected: Boolean(this.hybrid.features?.[formKey]?.[feature.uuid])
|
||||||
|
})),
|
||||||
|
advantageOn: Object.keys(data.system.advantageOn).reduce((acc, key) => {
|
||||||
|
acc[key] = {
|
||||||
|
...data.system.advantageOn[key],
|
||||||
|
selected: Boolean(this.hybrid.advantages?.[formKey]?.[key])
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {}); // Also get from compendium when added
|
}, {});
|
||||||
context.canSubmit = this.selected;
|
|
||||||
|
const maximumDragTier = Math.max(
|
||||||
|
this.selected?.system?.evolved?.maximumTier ?? 0,
|
||||||
|
this.selected?.system?.hybrid?.maximumTier ?? 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const compendiumBeastforms = await game.packs.get(`daggerheart.beastforms`)?.getDocuments();
|
||||||
|
const beastformTiers = [...(compendiumBeastforms ? compendiumBeastforms : []), ...game.items].reduce(
|
||||||
|
(acc, x) => {
|
||||||
|
const tier = CONFIG.DH.GENERAL.tiers[x.system.tier];
|
||||||
|
if (x.type !== 'beastform' || tier.id > this.configData.tierLimit) return acc;
|
||||||
|
|
||||||
|
if (!acc[tier.id]) acc[tier.id] = { label: game.i18n.localize(tier.label), values: {} };
|
||||||
|
|
||||||
|
acc[tier.id].values[x.uuid] = {
|
||||||
|
selected: this.selected?.uuid == x.uuid,
|
||||||
|
value: x,
|
||||||
|
draggable:
|
||||||
|
!['evolved', 'hybrid'].includes(x.system.beastformType) && maximumDragTier
|
||||||
|
? x.system.tier <= maximumDragTier
|
||||||
|
: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
context.tier = beastformTiers[this.tabGroups.primary];
|
||||||
|
context.tierKey = this.tabGroups.primary;
|
||||||
|
|
||||||
|
context.canSubmit = this.canSubmit();
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canSubmit() {
|
||||||
|
if (this.selected) {
|
||||||
|
switch (this.selected.system.beastformType) {
|
||||||
|
case 'normal':
|
||||||
|
return true;
|
||||||
|
case 'evolved':
|
||||||
|
return this.evolved.form;
|
||||||
|
case 'hybrid':
|
||||||
|
const selectedAdvantages = Object.values(this.hybrid.advantages).reduce(
|
||||||
|
(acc, form) => acc + Object.values(form).length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const selectedFeatures = Object.values(this.hybrid.features).reduce(
|
||||||
|
(acc, form) => acc + Object.values(form).length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const advantagesSelected = selectedAdvantages === this.selected.system.hybrid.advantages;
|
||||||
|
const featuresSelected = selectedFeatures === this.selected.system.hybrid.features;
|
||||||
|
return advantagesSelected && featuresSelected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static updateBeastform(event, _, formData) {
|
static updateBeastform(event, _, formData) {
|
||||||
this.selected = foundry.utils.mergeObject(this.selected, formData.object);
|
this.selected = foundry.utils.mergeObject(this.selected, formData.object);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
static selectBeastform(_, target) {
|
static async selectBeastform(_, target) {
|
||||||
this.selected = this.selected === target.dataset.uuid ? null : target.dataset.uuid;
|
this.element.querySelectorAll('.beastform-container ').forEach(element => {
|
||||||
|
if (element.dataset.uuid === target.dataset.uuid && this.selected?.uuid !== target.dataset.uuid) {
|
||||||
|
element.classList.remove('inactive');
|
||||||
|
} else {
|
||||||
|
element.classList.add('inactive');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const uuid = this.selected?.uuid === target.dataset.uuid ? null : target.dataset.uuid;
|
||||||
|
this.selected = uuid ? await foundry.utils.fromUuid(uuid) : null;
|
||||||
|
|
||||||
|
if (this.selected) {
|
||||||
|
if (this.selected.system.beastformType !== 'evolved') this.evolved.form = null;
|
||||||
|
if (this.selected.system.beastformType !== 'hybrid') {
|
||||||
|
this.hybrid.forms = {};
|
||||||
|
this.hybrid.advantages = {};
|
||||||
|
this.hybrid.features = {};
|
||||||
|
} else {
|
||||||
|
this.hybrid.forms = [...Array(this.selected.system.hybrid.beastformOptions).keys()].reduce((acc, _) => {
|
||||||
|
acc[foundry.utils.randomID()] = null;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static toggleHybridFeature(_, button) {
|
||||||
|
const current = this.hybrid.features[button.dataset.form];
|
||||||
|
if (!current) this.hybrid.features[button.dataset.form] = {};
|
||||||
|
|
||||||
|
if (this.hybrid.features[button.dataset.form][button.id])
|
||||||
|
delete this.hybrid.features[button.dataset.form][button.id];
|
||||||
|
else {
|
||||||
|
const currentFeatures = Object.values(this.hybrid.features).reduce(
|
||||||
|
(acc, form) => acc + Object.values(form).length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (currentFeatures === this.selected.system.hybrid.features) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyFeatures'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const feature = this.hybrid.forms[button.dataset.form].system.features.find(x => x.uuid === button.id);
|
||||||
|
this.hybrid.features[button.dataset.form][button.id] = feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static toggleHybridAdvantage(_, button) {
|
||||||
|
const current = this.hybrid.advantages[button.dataset.form];
|
||||||
|
if (!current) this.hybrid.advantages[button.dataset.form] = {};
|
||||||
|
|
||||||
|
if (this.hybrid.advantages[button.dataset.form][button.id])
|
||||||
|
delete this.hybrid.advantages[button.dataset.form][button.id];
|
||||||
|
else {
|
||||||
|
const currentAdvantages = Object.values(this.hybrid.advantages).reduce(
|
||||||
|
(acc, form) => acc + Object.values(form).length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (currentAdvantages === this.selected.system.hybrid.advantages) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyAdvantages'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const advantage = this.hybrid.forms[button.dataset.form].system.advantageOn[button.id];
|
||||||
|
this.hybrid.advantages[button.dataset.form][button.id] = advantage;
|
||||||
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,14 +265,61 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
_onClose(options = {}) {
|
_onClose(options = {}) {
|
||||||
if (!options.submitted) this.config = false;
|
if (!options.submitted) this.selected = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async configure(configData) {
|
static async configure(configData, item) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const app = new this(configData);
|
const app = new this(configData, item);
|
||||||
app.addEventListener('close', () => resolve(app.selected), { once: true });
|
const featureItem = item;
|
||||||
|
app.addEventListener(
|
||||||
|
'close',
|
||||||
|
() => resolve({ selected: app.selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem }),
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
app.render({ force: true });
|
app.render({ force: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _onDragStart(event) {
|
||||||
|
const target = event.currentTarget;
|
||||||
|
const abort = () => event.preventDefault();
|
||||||
|
if (!this.selected) abort();
|
||||||
|
|
||||||
|
const draggedForm = await foundry.utils.fromUuid(target.dataset.uuid);
|
||||||
|
if (['evolved', 'hybrid'].includes(draggedForm.system.beastformType)) abort();
|
||||||
|
|
||||||
|
if (this.selected.system.beastformType === 'evolved') {
|
||||||
|
if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) abort();
|
||||||
|
}
|
||||||
|
if (this.selected.system.beastformType === 'hybrid') {
|
||||||
|
if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(target.dataset));
|
||||||
|
event.dataTransfer.setDragImage(target, 60, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
|
||||||
|
const item = await fromUuid(data.uuid);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
if (event.target.closest('.advanced-form-container.evolved')) {
|
||||||
|
this.evolved.form = item;
|
||||||
|
} else {
|
||||||
|
const hybridContainer = event.target.closest('.advanced-form-container.hybridized');
|
||||||
|
if (hybridContainer) {
|
||||||
|
const existingId = Object.keys(this.hybrid.forms).find(
|
||||||
|
key => this.hybrid.forms[key]?.uuid === item.uuid
|
||||||
|
);
|
||||||
|
if (existingId) this.hybrid.forms[existingId] = null;
|
||||||
|
|
||||||
|
this.hybrid.forms[hybridContainer.id] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class CostSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(costs, uses, action, resolve) {
|
|
||||||
super({});
|
|
||||||
this.costs = costs;
|
|
||||||
this.uses = uses;
|
|
||||||
this.action = action;
|
|
||||||
this.resolve = resolve;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
tag: 'form',
|
|
||||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
|
||||||
position: {
|
|
||||||
width: 400,
|
|
||||||
height: 'auto'
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
sendCost: this.sendCost
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
handler: this.updateForm,
|
|
||||||
submitOnChange: true,
|
|
||||||
closeOnSubmit: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static PARTS = {
|
|
||||||
costSelection: {
|
|
||||||
id: 'costSelection',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
get title() {
|
|
||||||
return `Cost Options`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const updatedCosts = this.action.calcCosts(this.costs),
|
|
||||||
updatedUses = this.action.calcUses(this.uses);
|
|
||||||
return {
|
|
||||||
costs: updatedCosts,
|
|
||||||
uses: updatedUses,
|
|
||||||
canUse: this.action.hasCost(updatedCosts) && this.action.hasUses(updatedUses)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static async updateForm(event, _, formData) {
|
|
||||||
const data = foundry.utils.expandObject(formData.object);
|
|
||||||
this.costs = foundry.utils.mergeObject(this.costs, data.costs);
|
|
||||||
this.uses = foundry.utils.mergeObject(this.uses, data.uses);
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static sendCost(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.resolve({ costs: this.action.getRealCosts(this.costs), uses: this.uses });
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -22,7 +22,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
id: 'roll-selection',
|
id: 'roll-selection',
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
||||||
position: {
|
position: {
|
||||||
width: 550
|
width: 'auto'
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
icon: 'fa-solid fa-dice'
|
icon: 'fa-solid fa-dice'
|
||||||
|
|
@ -52,10 +52,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
rollSelection: {
|
rollSelection: {
|
||||||
id: 'rollSelection',
|
id: 'rollSelection',
|
||||||
template: 'systems/daggerheart/templates/dialogs/dice-roll/rollSelection.hbs'
|
template: 'systems/daggerheart/templates/dialogs/dice-roll/rollSelection.hbs'
|
||||||
},
|
|
||||||
costSelection: {
|
|
||||||
id: 'costSelection',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -63,9 +59,22 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.rollConfig = this.config;
|
context.rollConfig = this.config;
|
||||||
context.hasRoll = !!this.config.roll;
|
context.hasRoll = !!this.config.roll;
|
||||||
|
context.canRoll = true;
|
||||||
|
context.selectedRollMode = this.config.selectedRollMode;
|
||||||
|
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
||||||
|
action,
|
||||||
|
label,
|
||||||
|
icon
|
||||||
|
}));
|
||||||
|
|
||||||
if (this.config.costs?.length) {
|
if (this.config.costs?.length) {
|
||||||
const updatedCosts = this.action.calcCosts(this.config.costs);
|
const updatedCosts = this.action.calcCosts(this.config.costs);
|
||||||
context.costs = updatedCosts;
|
context.costs = updatedCosts.map(x => ({
|
||||||
|
...x,
|
||||||
|
label: x.keyIsID
|
||||||
|
? this.action.parent.parent.name
|
||||||
|
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
||||||
|
}));
|
||||||
context.canRoll = this.action.hasCost(updatedCosts);
|
context.canRoll = this.action.hasCost(updatedCosts);
|
||||||
this.config.data.scale = this.config.costs[0].total;
|
this.config.data.scale = this.config.costs[0].total;
|
||||||
}
|
}
|
||||||
|
|
@ -73,9 +82,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.uses = this.action.calcUses(this.config.uses);
|
context.uses = this.action.calcUses(this.config.uses);
|
||||||
context.canRoll = context.canRoll && this.action.hasUses(context.uses);
|
context.canRoll = context.canRoll && this.action.hasUses(context.uses);
|
||||||
}
|
}
|
||||||
if(this.roll) {
|
if (this.roll) {
|
||||||
context.roll = this.roll;
|
context.roll = this.roll;
|
||||||
context.rollType = this.roll?.constructor.name;
|
context.rollType = this.roll?.constructor.name;
|
||||||
|
context.rallyDie = this.roll.rallyChoices;
|
||||||
context.experiences = Object.keys(this.config.data.experiences).map(id => ({
|
context.experiences = Object.keys(this.config.data.experiences).map(id => ({
|
||||||
id,
|
id,
|
||||||
...this.config.data.experiences[id]
|
...this.config.data.experiences[id]
|
||||||
|
|
@ -84,7 +94,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.advantage = this.config.roll?.advantage;
|
context.advantage = this.config.roll?.advantage;
|
||||||
context.disadvantage = this.config.roll?.disadvantage;
|
context.disadvantage = this.config.roll?.disadvantage;
|
||||||
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
||||||
context.canRoll = true;
|
|
||||||
context.isLite = this.config.roll?.lite;
|
context.isLite = this.config.roll?.lite;
|
||||||
context.extraFormula = this.config.extraFormula;
|
context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
|
|
@ -94,6 +103,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
static updateRollConfiguration(event, _, formData) {
|
static updateRollConfiguration(event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
|
this.config.selectedRollMode = rest.selectedRollMode;
|
||||||
|
|
||||||
if (this.config.costs) {
|
if (this.config.costs) {
|
||||||
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs);
|
||||||
}
|
}
|
||||||
|
|
@ -117,11 +128,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
static selectExperience(_, button) {
|
static selectExperience(_, button) {
|
||||||
/* if (this.config.experiences.find(x => x === button.dataset.key)) {
|
|
||||||
this.config.experiences = this.config.experiences.filter(x => x !== button.dataset.key);
|
|
||||||
} else {
|
|
||||||
this.config.experiences = [...this.config.experiences, button.dataset.key];
|
|
||||||
} */
|
|
||||||
this.config.experiences =
|
this.config.experiences =
|
||||||
this.config.experiences.indexOf(button.dataset.key) > -1
|
this.config.experiences.indexOf(button.dataset.key) > -1
|
||||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'roll-selection',
|
id: 'roll-selection',
|
||||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
||||||
position: {
|
position: {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 'auto'
|
height: 'auto'
|
||||||
},
|
},
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-dice'
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
|
|
@ -34,17 +37,33 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.title = this.config.title;
|
context.config = CONFIG.DH;
|
||||||
context.extraFormula = this.config.extraFormula;
|
context.title = this.config.title
|
||||||
|
? this.config.title
|
||||||
|
: game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
|
||||||
|
// context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
|
context.directDamage = this.config.directDamage;
|
||||||
|
context.selectedRollMode = this.config.selectedRollMode;
|
||||||
|
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({
|
||||||
|
action,
|
||||||
|
label,
|
||||||
|
icon
|
||||||
|
}));
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
this.config.extraFormula = rest.extraFormula;
|
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
||||||
|
this.config.selectedRollMode = rest.selectedRollMode;
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs';
|
import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
const { DialogV2, ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(resolve, reject, actor, damage) {
|
constructor(resolve, reject, actor, damage, damageType) {
|
||||||
super({});
|
super({});
|
||||||
|
|
||||||
this.resolve = resolve;
|
this.resolve = resolve;
|
||||||
|
|
@ -11,37 +11,46 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.damage = damage;
|
this.damage = damage;
|
||||||
|
|
||||||
const maxArmorMarks = Math.min(
|
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
||||||
actor.system.armorScore - actor.system.armor.system.marks.value,
|
const maxArmorMarks = canApplyArmor
|
||||||
actor.system.rules.maxArmorMarked.total
|
? Math.min(
|
||||||
);
|
actor.system.armorScore - actor.system.armor.system.marks.value,
|
||||||
|
actor.system.rules.damageReduction.maxArmorMarked.value
|
||||||
|
)
|
||||||
|
: 0;
|
||||||
|
|
||||||
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
|
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
|
||||||
acc[foundry.utils.randomID()] = { selected: false };
|
acc[foundry.utils.randomID()] = { selected: false };
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
const stress = [...Array(actor.system.rules.maxArmorMarked.stressExtra ?? 0).keys()].reduce((acc, _) => {
|
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
||||||
acc[foundry.utils.randomID()] = { selected: false };
|
(acc, _) => {
|
||||||
return acc;
|
acc[foundry.utils.randomID()] = { selected: false };
|
||||||
}, {});
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
this.marks = { armor, stress };
|
this.marks = { armor, stress };
|
||||||
|
|
||||||
this.availableStressReductions = Object.keys(actor.system.rules.stressDamageReduction).reduce((acc, key) => {
|
this.availableStressReductions = Object.keys(actor.system.rules.damageReduction.stressDamageReduction).reduce(
|
||||||
const dr = actor.system.rules.stressDamageReduction[key];
|
(acc, key) => {
|
||||||
if (dr.enabled) {
|
const dr = actor.system.rules.damageReduction.stressDamageReduction[key];
|
||||||
if (acc === null) acc = {};
|
if (dr.enabled) {
|
||||||
|
if (acc === null) acc = {};
|
||||||
|
|
||||||
const damage = damageKeyToNumber(key);
|
const damage = damageKeyToNumber(key);
|
||||||
acc[damage] = {
|
acc[damage] = {
|
||||||
cost: dr.cost,
|
cost: dr.cost,
|
||||||
selected: false,
|
selected: false,
|
||||||
from: getDamageLabel(damage),
|
from: getDamageLabel(damage),
|
||||||
to: getDamageLabel(damage - 1)
|
to: getDamageLabel(damage - 1)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, null);
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -90,7 +99,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
context.armorScore = this.actor.system.armorScore;
|
context.armorScore = this.actor.system.armorScore;
|
||||||
context.armorMarks = currentMarks;
|
context.armorMarks = currentMarks;
|
||||||
context.basicMarksUsed = selectedArmorMarks.length === this.actor.system.rules.maxArmorMarked.total;
|
context.basicMarksUsed =
|
||||||
|
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value;
|
||||||
|
|
||||||
const stressReductionStress = this.availableStressReductions
|
const stressReductionStress = this.availableStressReductions
|
||||||
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
||||||
|
|
@ -100,7 +110,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
? {
|
? {
|
||||||
value:
|
value:
|
||||||
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
|
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
|
||||||
maxTotal: this.actor.system.resources.stress.maxTotal
|
max: this.actor.system.resources.stress.max
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
|
@ -122,12 +132,15 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
getDamageInfo = () => {
|
getDamageInfo = () => {
|
||||||
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected);
|
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected);
|
||||||
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
||||||
const stressReductions = Object.values(this.availableStressReductions).filter(red => red.selected);
|
const stressReductions = this.availableStressReductions
|
||||||
|
? Object.values(this.availableStressReductions).filter(red => red.selected)
|
||||||
|
: [];
|
||||||
const currentMarks =
|
const currentMarks =
|
||||||
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
|
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
|
||||||
|
|
||||||
const currentDamage =
|
const armorMarkReduction =
|
||||||
this.damage - selectedArmorMarks.length - selectedStressMarks.length - stressReductions.length;
|
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
||||||
|
const currentDamage = this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length;
|
||||||
|
|
||||||
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
|
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
|
||||||
};
|
};
|
||||||
|
|
@ -184,7 +197,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
: 0;
|
: 0;
|
||||||
const currentStress =
|
const currentStress =
|
||||||
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress;
|
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress;
|
||||||
if (currentStress + stressReduction.cost > this.actor.system.resources.stress.maxTotal) {
|
if (currentStress + stressReduction.cost > this.actor.system.resources.stress.max) {
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.notEnoughStress'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.notEnoughStress'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -210,9 +223,17 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
async close(fromSave) {
|
async close(fromSave) {
|
||||||
if (!fromSave) {
|
if (!fromSave) {
|
||||||
this.reject();
|
this.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
await super.close({});
|
await super.close({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async armorStackQuery({ actorId, damage, type }) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const actor = await fromUuid(actorId);
|
||||||
|
if (!actor || !actor?.isOwner) reject();
|
||||||
|
new DamageReductionDialog(resolve, reject, actor, damage, type).render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default class DamageSelectionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
||||||
position: {
|
position: {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 'auto'
|
height: 'auto'
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,23 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.shortrest = shortrest;
|
this.shortrest = shortrest;
|
||||||
|
|
||||||
const options = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves;
|
this.moveData = foundry.utils.deepClone(
|
||||||
this.moveData = shortrest ? options.shortRest : options.longRest;
|
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves
|
||||||
|
);
|
||||||
|
this.nrChoices = {
|
||||||
|
shortRest: {
|
||||||
|
taken: 0,
|
||||||
|
max:
|
||||||
|
(shortrest ? this.moveData.shortRest.nrChoices : 0) +
|
||||||
|
actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].shortMoves
|
||||||
|
},
|
||||||
|
longRest: {
|
||||||
|
taken: 0,
|
||||||
|
max:
|
||||||
|
(!shortrest ? this.moveData.longRest.nrChoices : 0) +
|
||||||
|
actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].longMoves
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -17,8 +32,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'views', 'downtime'],
|
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'downtime'],
|
||||||
position: { width: 680, height: 'auto' },
|
position: { width: 'auto', height: 'auto' },
|
||||||
actions: {
|
actions: {
|
||||||
selectMove: this.selectMove,
|
selectMove: this.selectMove,
|
||||||
takeDowntime: this.takeDowntime
|
takeDowntime: this.takeDowntime
|
||||||
|
|
@ -29,7 +44,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
application: {
|
application: {
|
||||||
id: 'downtime',
|
id: 'downtime',
|
||||||
template: 'systems/daggerheart/templates/dialogs/downtime.hbs'
|
template: 'systems/daggerheart/templates/dialogs/downtime/downtime.hbs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,46 +52,85 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
htmlElement
|
htmlElement
|
||||||
.querySelectorAll('.activity-image')
|
.querySelectorAll('.activity-container')
|
||||||
.forEach(element => element.addEventListener('contextmenu', this.deselectMove.bind(this)));
|
.forEach(element => element.addEventListener('contextmenu', this.deselectMove.bind(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
|
context.title = game.i18n.localize(
|
||||||
|
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
|
||||||
|
);
|
||||||
context.selectedActivity = this.selectedActivity;
|
context.selectedActivity = this.selectedActivity;
|
||||||
context.moveData = this.moveData;
|
context.moveData = this.moveData;
|
||||||
context.nrCurrentChoices = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
|
|
||||||
context.disabledDowntime = context.nrCurrentChoices < context.moveData.nrChoices;
|
const shortRestMovesSelected = this.#nrSelectedMoves('shortRest');
|
||||||
|
const longRestMovesSelected = this.#nrSelectedMoves('longRest');
|
||||||
|
context.nrChoices = {
|
||||||
|
...this.nrChoices,
|
||||||
|
shortRest: {
|
||||||
|
...this.nrChoices.shortRest,
|
||||||
|
current: this.nrChoices.shortRest.taken + shortRestMovesSelected
|
||||||
|
},
|
||||||
|
longRest: {
|
||||||
|
...this.nrChoices.longRest,
|
||||||
|
current: this.nrChoices.longRest.taken + longRestMovesSelected
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
context.shortRestMoves = this.nrChoices.shortRest.max > 0 ? this.moveData.shortRest : null;
|
||||||
|
context.longRestMoves = this.nrChoices.longRest.max > 0 ? this.moveData.longRest : null;
|
||||||
|
|
||||||
|
context.disabledDowntime = shortRestMovesSelected === 0 && longRestMovesSelected === 0;
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static selectMove(_, button) {
|
static selectMove(_, target) {
|
||||||
const nrSelected = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
|
const { category, move } = target.dataset;
|
||||||
if (nrSelected === this.moveData.nrChoices) {
|
|
||||||
|
const nrSelected = this.#nrSelectedMoves(category);
|
||||||
|
|
||||||
|
if (nrSelected + this.nrChoices[category].taken >= this.nrChoices[category].max) {
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noMoreMoves'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noMoreMoves'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const move = button.dataset.move;
|
this.moveData[category].moves[move].selected = this.moveData[category].moves[move].selected
|
||||||
this.moveData.moves[move].selected = this.moveData.moves[move].selected
|
? this.moveData[category].moves[move].selected + 1
|
||||||
? this.moveData.moves[move].selected + 1
|
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
deselectMove(event) {
|
deselectMove(event) {
|
||||||
const move = event.currentTarget.dataset.move;
|
const button = event.target.closest('.activity-container');
|
||||||
this.moveData.moves[move].selected = this.moveData.moves[move].selected
|
const { move, category } = button.dataset;
|
||||||
? this.moveData.moves[move].selected - 1
|
this.moveData[category].moves[move].selected = this.moveData[category].moves[move].selected
|
||||||
|
? this.moveData[category].moves[move].selected - 1
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
|
// On macOS with a single-button mouse (e.g. a laptop trackpad),
|
||||||
|
// right-click is triggered with ctrl+click, which triggers both a
|
||||||
|
// `contextmenu` event and a regular click event. We need to stop
|
||||||
|
// event propagation to prevent the click event from triggering the
|
||||||
|
// `selectMove` function and undoing the change we just made.
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
// Having stopped propagation, we're no longer subject to Foundry's
|
||||||
|
// default `contextmenu` handler, so we also have to prevent the
|
||||||
|
// default behaviour to prevent a context menu from appearing.
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async takeDowntime() {
|
static async takeDowntime() {
|
||||||
const moves = Object.values(this.moveData.moves).filter(x => x.selected);
|
const moves = Object.values(this.moveData).flatMap(category => {
|
||||||
|
return Object.values(category.moves)
|
||||||
|
.filter(x => x.selected)
|
||||||
|
.flatMap(move => [...Array(move.selected).keys()].map(_ => move));
|
||||||
|
});
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const msg = new cls({
|
const msg = new cls({
|
||||||
|
|
@ -88,7 +142,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/ui/chat/downtime.hbs',
|
'systems/daggerheart/templates/ui/chat/downtime.hbs',
|
||||||
{
|
{
|
||||||
title: `${this.actor.name} - ${game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${this.shortRest ? 'shortRest' : 'longRest'}.title`)}`,
|
title: `${this.actor.name} - ${game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`)}`,
|
||||||
moves: moves
|
moves: moves
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -96,11 +150,33 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
cls.create(msg.toObject());
|
cls.create(msg.toObject());
|
||||||
|
|
||||||
this.close();
|
// Reset selection and update number of taken moves
|
||||||
|
for (const [catName, category] of Object.entries(this.moveData)) {
|
||||||
|
for (const move of Object.values(category.moves)) {
|
||||||
|
if (move.selected > 0) {
|
||||||
|
this.nrChoices[catName].taken += move.selected;
|
||||||
|
move.selected = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can close the window when all moves are taken
|
||||||
|
if (
|
||||||
|
this.nrChoices.shortRest.taken >= this.nrChoices.shortRest.max &&
|
||||||
|
this.nrChoices.longRest.taken >= this.nrChoices.longRest.max
|
||||||
|
) {
|
||||||
|
this.close();
|
||||||
|
} else {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, element, formData) {
|
static async updateData(event, element, formData) {
|
||||||
this.customActivity = foundry.utils.mergeObject(this.customActivity, formData.object);
|
this.customActivity = foundry.utils.mergeObject(this.customActivity, formData.object);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nrSelectedMoves(category) {
|
||||||
|
return Object.values(this.moveData[category].moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
99
module/applications/dialogs/resourceDiceDialog.mjs
Normal file
99
module/applications/dialogs/resourceDiceDialog.mjs
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { itemAbleRollParse } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class ResourceDiceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(item, actor, options = {}) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.item = item;
|
||||||
|
this.actor = actor;
|
||||||
|
this.diceStates = foundry.utils.deepClone(item.system.resource.diceStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'resource-dice'],
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-dice'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
rerollDice: this.rerollDice,
|
||||||
|
save: this.save
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
handler: this.updateResourceDice,
|
||||||
|
submitOnChange: true,
|
||||||
|
submitOnClose: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
resourceDice: {
|
||||||
|
id: 'resourceDice',
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/dice-roll/resourceDice.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return game.i18n.format('DAGGERHEART.APPLICATIONS.ResourceDice.title', { name: this.item.name });
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
context.item = this.item;
|
||||||
|
context.actor = this.actor;
|
||||||
|
context.diceStates = this.diceStates;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateResourceDice(event, _, formData) {
|
||||||
|
const { diceStates } = foundry.utils.expandObject(formData.object);
|
||||||
|
this.diceStates = Object.keys(diceStates).reduce((acc, key) => {
|
||||||
|
const resourceState = this.item.system.resource.diceStates[key];
|
||||||
|
acc[key] = { ...diceStates[key], used: Boolean(resourceState?.used) };
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async save() {
|
||||||
|
this.rollValues = Object.values(this.diceStates);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async rerollDice() {
|
||||||
|
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
|
||||||
|
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
|
||||||
|
const roll = await new Roll(diceFormula).evaluate();
|
||||||
|
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||||
|
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
|
||||||
|
this.resetUsed = true;
|
||||||
|
|
||||||
|
const cls = getDocumentClass('ChatMessage');
|
||||||
|
const msg = new cls({
|
||||||
|
user: game.user.id,
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/chat/resource-roll.hbs',
|
||||||
|
{
|
||||||
|
user: this.actor.name,
|
||||||
|
name: this.item.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
cls.create(msg.toObject());
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(item, actor, options = {}) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const app = new this(item, actor, options);
|
||||||
|
app.addEventListener('close', () => resolve(app.rollValues), { once: true });
|
||||||
|
app.render({ force: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
1
module/applications/hud/_module.mjs
Normal file
1
module/applications/hud/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as DHTokenHUD } from './tokenHUD.mjs';
|
||||||
84
module/applications/hud/tokenHUD.mjs
Normal file
84
module/applications/hud/tokenHUD.mjs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ['daggerheart']
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
hud: {
|
||||||
|
root: true,
|
||||||
|
template: 'systems/daggerheart/templates/hud/tokenHUD.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||||
|
const effect = context.statusEffects[key];
|
||||||
|
if (effect.systemEffect) acc[key] = effect;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const useGeneric = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
|
).showGenericStatusEffects;
|
||||||
|
context.genericStatusEffects = useGeneric
|
||||||
|
? Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||||
|
const effect = context.statusEffects[key];
|
||||||
|
if (!effect.systemEffect) acc[key] = effect;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getStatusEffectChoices() {
|
||||||
|
// Include all HUD-enabled status effects
|
||||||
|
const choices = {};
|
||||||
|
for (const status of CONFIG.statusEffects) {
|
||||||
|
if (
|
||||||
|
status.hud === false ||
|
||||||
|
(foundry.utils.getType(status.hud) === 'Object' &&
|
||||||
|
status.hud.actorTypes?.includes(this.document.actor.type) === false)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
choices[status.id] = {
|
||||||
|
_id: status._id,
|
||||||
|
id: status.id,
|
||||||
|
systemEffect: status.systemEffect,
|
||||||
|
title: game.i18n.localize(status.name ?? /** @deprecated since v12 */ status.label),
|
||||||
|
src: status.img ?? /** @deprecated since v12 */ status.icon,
|
||||||
|
isActive: false,
|
||||||
|
isOverlay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the status of effects which are active for the token actor
|
||||||
|
const activeEffects = this.actor?.effects || [];
|
||||||
|
for (const effect of activeEffects) {
|
||||||
|
for (const statusId of effect.statuses) {
|
||||||
|
const status = choices[statusId];
|
||||||
|
if (!status) continue;
|
||||||
|
if (status._id) {
|
||||||
|
if (status._id !== effect.id) continue;
|
||||||
|
} else {
|
||||||
|
if (effect.statuses.size !== 1) continue;
|
||||||
|
}
|
||||||
|
status.isActive = true;
|
||||||
|
if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag status CSS class
|
||||||
|
for (const status of Object.values(choices)) {
|
||||||
|
status.cssClass = [status.isActive ? 'active' : null, status.isOverlay ? 'overlay' : null].filterJoin(' ');
|
||||||
|
}
|
||||||
|
return choices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -223,8 +223,8 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
||||||
|
|
||||||
context.achievements = {
|
context.achievements = {
|
||||||
proficiency: {
|
proficiency: {
|
||||||
old: this.actor.system.proficiency.total,
|
old: this.actor.system.proficiency,
|
||||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
new: this.actor.system.proficiency + achivementProficiency,
|
||||||
shown: achivementProficiency > 0
|
shown: achivementProficiency > 0
|
||||||
},
|
},
|
||||||
damageThresholds: {
|
damageThresholds: {
|
||||||
|
|
@ -332,16 +332,16 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
||||||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||||
},
|
},
|
||||||
hitPoints: {
|
hitPoints: {
|
||||||
old: this.actor.system.resources.hitPoints.maxTotal,
|
old: this.actor.system.resources.hitPoints.max,
|
||||||
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||||
},
|
},
|
||||||
stress: {
|
stress: {
|
||||||
old: this.actor.system.resources.stress.maxTotal,
|
old: this.actor.system.resources.stress.max,
|
||||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||||
},
|
},
|
||||||
evasion: {
|
evasion: {
|
||||||
old: this.actor.system.evasion.total,
|
old: this.actor.system.evasion,
|
||||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||||
|
|
@ -349,8 +349,8 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
||||||
if (!acc) acc = {};
|
if (!acc) acc = {};
|
||||||
acc[traitKey] = {
|
acc[traitKey] = {
|
||||||
label: game.i18n.localize(abilities[traitKey].label),
|
label: game.i18n.localize(abilities[traitKey].label),
|
||||||
old: this.actor.system.traits[traitKey].total,
|
old: this.actor.system.traits[traitKey].max,
|
||||||
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
|
new: this.actor.system.traits[traitKey].max + advancement.trait[traitKey]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
|
|
|
||||||
|
|
@ -122,12 +122,12 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||||
context.advancements = {
|
context.advancements = {
|
||||||
statistics: {
|
statistics: {
|
||||||
stress: {
|
stress: {
|
||||||
old: this.actor.system.resources.stress.maxTotal,
|
old: this.actor.system.resources.stress.max,
|
||||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||||
},
|
},
|
||||||
evasion: {
|
evasion: {
|
||||||
old: this.actor.system.evasion.total,
|
old: this.actor.system.evasion,
|
||||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
experiences:
|
experiences:
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
|
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
|
||||||
context.showTabs = this.tabGroups.primary !== 'summary';
|
context.showTabs = this.tabGroups.primary !== 'summary';
|
||||||
break;
|
break;
|
||||||
const { current: currentActorLevel, changed: changedActorLevel } = this.actor.system.levelData.level;
|
|
||||||
const actorArmor = this.actor.system.armor;
|
const actorArmor = this.actor.system.armor;
|
||||||
const levelKeys = Object.keys(this.levelup.levels);
|
const levelKeys = Object.keys(this.levelup.levels);
|
||||||
let achivementProficiency = 0;
|
let achivementProficiency = 0;
|
||||||
|
|
@ -157,8 +157,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
context.achievements = {
|
context.achievements = {
|
||||||
proficiency: {
|
proficiency: {
|
||||||
old: this.actor.system.proficiency.total,
|
old: this.actor.system.proficiency,
|
||||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
new: this.actor.system.proficiency + achivementProficiency,
|
||||||
shown: achivementProficiency > 0
|
shown: achivementProficiency > 0
|
||||||
},
|
},
|
||||||
damageThresholds: {
|
damageThresholds: {
|
||||||
|
|
@ -265,16 +265,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||||
},
|
},
|
||||||
hitPoints: {
|
hitPoints: {
|
||||||
old: this.actor.system.resources.hitPoints.maxTotal,
|
old: this.actor.system.resources.hitPoints.max,
|
||||||
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||||
},
|
},
|
||||||
stress: {
|
stress: {
|
||||||
old: this.actor.system.resources.stress.maxTotal,
|
old: this.actor.system.resources.stress.max,
|
||||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||||
},
|
},
|
||||||
evasion: {
|
evasion: {
|
||||||
old: this.actor.system.evasion.total,
|
old: this.actor.system.evasion,
|
||||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||||
|
|
@ -282,8 +282,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
if (!acc) acc = {};
|
if (!acc) acc = {};
|
||||||
acc[traitKey] = {
|
acc[traitKey] = {
|
||||||
label: game.i18n.localize(abilities[traitKey].label),
|
label: game.i18n.localize(abilities[traitKey].label),
|
||||||
old: this.actor.system.traits[traitKey].total,
|
old: this.actor.system.traits[traitKey].value,
|
||||||
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
|
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,17 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.name');
|
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'daggerheart-appearance-settings',
|
id: 'daggerheart-appearance-settings',
|
||||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||||
position: { width: '600', height: 'auto' },
|
position: { width: '600', height: 'auto' },
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-gears'
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
save: this.save
|
save: this.save
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,17 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.automation.name');
|
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'daggerheart-automation-settings',
|
id: 'daggerheart-automation-settings',
|
||||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
classes: ['daggerheart', 'dh-style', 'dialog', 'setting'],
|
||||||
position: { width: '600', height: 'auto' },
|
position: { width: '600', height: 'auto' },
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-gears'
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
save: this.save
|
save: this.save
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ import DHActionConfig from '../../sheets-configs/action-config.mjs';
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class DhSettingsActionView extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DhSettingsActionView extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(resolve, reject, title, name, img, description, actions) {
|
constructor(resolve, reject, title, name, icon, img, description, actions) {
|
||||||
super({});
|
super({});
|
||||||
|
|
||||||
this.resolve = resolve;
|
this.resolve = resolve;
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
this.viewTitle = title;
|
this.viewTitle = title;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.icon = icon;
|
||||||
this.img = img;
|
this.img = img;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.actions = actions;
|
this.actions = actions;
|
||||||
|
|
@ -23,7 +24,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||||
position: { width: '400', height: 'auto' },
|
position: { width: 440, height: 'auto' },
|
||||||
actions: {
|
actions: {
|
||||||
editImage: this.onEditImage,
|
editImage: this.onEditImage,
|
||||||
addItem: this.addItem,
|
addItem: this.addItem,
|
||||||
|
|
@ -46,6 +47,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.name = this.name;
|
context.name = this.name;
|
||||||
|
context.icon = this.icon;
|
||||||
context.img = this.img;
|
context.img = this.img;
|
||||||
context.description = this.description;
|
context.description = this.description;
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(context.description);
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(context.description);
|
||||||
|
|
@ -55,8 +57,9 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateData(event, element, formData) {
|
static async updateData(event, element, formData) {
|
||||||
const { name, img, description } = foundry.utils.expandObject(formData.object);
|
const { name, icon, description } = foundry.utils.expandObject(formData.object);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.icon = icon;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -65,6 +68,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
||||||
static async saveForm(event) {
|
static async saveForm(event) {
|
||||||
this.resolve({
|
this.resolve({
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
icon: this.icon,
|
||||||
img: this.img,
|
img: this.img,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
actions: this.actions
|
actions: this.actions
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,17 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.homebrew.name');
|
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'daggerheart-homebrew-settings',
|
id: 'daggerheart-homebrew-settings',
|
||||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
classes: ['daggerheart', 'dh-style', 'dialog', 'setting'],
|
||||||
position: { width: '600', height: 'auto' },
|
position: { width: '600', height: 'auto' },
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-gears'
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addItem: this.addItem,
|
addItem: this.addItem,
|
||||||
editItem: this.editItem,
|
editItem: this.editItem,
|
||||||
|
|
@ -76,6 +79,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
reject,
|
reject,
|
||||||
game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves'),
|
game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves'),
|
||||||
move.name,
|
move.name,
|
||||||
|
move.icon,
|
||||||
move.img,
|
move.img,
|
||||||
move.description,
|
move.description,
|
||||||
move.actions
|
move.actions
|
||||||
|
|
@ -87,6 +91,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
await this.settings.updateSource({
|
await this.settings.updateSource({
|
||||||
[`restMoves.${type}.moves.${id}`]: {
|
[`restMoves.${type}.moves.${id}`]: {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
icon: data.icon,
|
||||||
img: data.img,
|
img: data.img,
|
||||||
description: data.description
|
description: data.description
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,17 @@ export default class DhRangeMeasurementSettings extends HandlebarsApplicationMix
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.automation.name');
|
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'daggerheart-automation-settings',
|
id: 'daggerheart-automation-settings',
|
||||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||||
position: { width: '600', height: 'auto' },
|
position: { width: '600', height: 'auto' },
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-gears'
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
save: this.save
|
save: this.save
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,17 @@ export default class DHVariantRuleSettings extends HandlebarsApplicationMixin(Ap
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.variantRules.name');
|
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
id: 'daggerheart-appearance-settings',
|
id: 'daggerheart-appearance-settings',
|
||||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||||
position: { width: '600', height: 'auto' },
|
position: { width: '600', height: 'auto' },
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-gears'
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
save: this.save
|
save: this.save
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,5 @@ export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||||
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
||||||
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
|
||||||
|
export { default as DhTokenConfig } from './token-config.mjs';
|
||||||
|
export { default as DhPrototypeTokenConfig } from './prototype-token-config.mjs';
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,6 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
id: 'effect',
|
id: 'effect',
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
||||||
}
|
}
|
||||||
/* form: {
|
|
||||||
id: 'action',
|
|
||||||
template: 'systems/daggerheart/templates/config/action.hbs'
|
|
||||||
} */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static TABS = {
|
static TABS = {
|
||||||
|
|
@ -111,13 +107,14 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
context.getRealIndex = this.getRealIndex.bind(this);
|
context.getRealIndex = this.getRealIndex.bind(this);
|
||||||
context.getEffectDetails = this.getEffectDetails.bind(this);
|
context.getEffectDetails = this.getEffectDetails.bind(this);
|
||||||
|
context.costOptions = this.getCostOptions();
|
||||||
context.disableOption = this.disableOption.bind(this);
|
context.disableOption = this.disableOption.bind(this);
|
||||||
context.isNPC = this.action.actor && this.action.actor.type !== 'character';
|
context.isNPC = this.action.actor?.isNPC;
|
||||||
context.hasRoll = this.action.hasRoll;
|
context.hasRoll = this.action.hasRoll;
|
||||||
|
|
||||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
||||||
context.tierOptions = [
|
context.tierOptions = [
|
||||||
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.tier1') },
|
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
|
||||||
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
|
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -129,8 +126,21 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
this.render(true);
|
this.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableOption(index, options, choices) {
|
getCostOptions() {
|
||||||
const filtered = foundry.utils.deepClone(options);
|
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
||||||
|
const resource = this.action.parent.resource;
|
||||||
|
if (resource) {
|
||||||
|
options[this.action.parent.parent.id] = {
|
||||||
|
label: this.action.parent.parent.name,
|
||||||
|
group: 'TYPES.Actor.character'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableOption(index, costOptions, choices) {
|
||||||
|
const filtered = foundry.utils.deepClone(costOptions);
|
||||||
Object.keys(filtered).forEach(o => {
|
Object.keys(filtered).forEach(o => {
|
||||||
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
|
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
|
||||||
});
|
});
|
||||||
|
|
@ -146,11 +156,19 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
return this.action.item.effects.get(id);
|
return this.action.item.effects.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
_prepareSubmitData(event, formData) {
|
_prepareSubmitData(_event, formData) {
|
||||||
const submitData = foundry.utils.expandObject(formData.object);
|
const submitData = foundry.utils.expandObject(formData.object);
|
||||||
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
||||||
const data = foundry.utils.getProperty(submitData, keyPath);
|
const data = foundry.utils.getProperty(submitData, keyPath);
|
||||||
if (data) foundry.utils.setProperty(submitData, keyPath, Object.values(data));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
|
||||||
}
|
}
|
||||||
return submitData;
|
return submitData;
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +179,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
|
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
|
||||||
let newActions;
|
let newActions;
|
||||||
if (Array.isArray(container)) {
|
if (Array.isArray(container)) {
|
||||||
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way
|
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject());
|
||||||
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
|
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
|
||||||
} else newActions = data;
|
} else newActions = data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,24 @@
|
||||||
|
import autocomplete from 'autocompleter';
|
||||||
|
|
||||||
export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig {
|
export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
const ignoredActorKeys = ['config', 'DhEnvironment'];
|
||||||
|
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||||
|
if (!ignoredActorKeys.includes(key)) {
|
||||||
|
const model = game.system.api.models.actors[key];
|
||||||
|
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||||
|
const group = game.i18n.localize(model.metadata.label);
|
||||||
|
const choices = CONFIG.Token.documentClass
|
||||||
|
.getTrackedAttributeChoices(attributes, model)
|
||||||
|
.map(x => ({ ...x, group: group }));
|
||||||
|
acc.push(...choices);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['daggerheart', 'sheet', 'dh-style']
|
classes: ['daggerheart', 'sheet', 'dh-style']
|
||||||
};
|
};
|
||||||
|
|
@ -27,36 +47,62 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
const changeChoices = this.changeChoices;
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
|
||||||
|
autocomplete({
|
||||||
|
input: element,
|
||||||
|
fetch: function (text, update) {
|
||||||
|
if (!text) {
|
||||||
|
update(changeChoices);
|
||||||
|
} else {
|
||||||
|
text = text.toLowerCase();
|
||||||
|
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
|
||||||
|
update(suggestions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function (item, search) {
|
||||||
|
const label = game.i18n.localize(item.label);
|
||||||
|
const matchIndex = label.toLowerCase().indexOf(search);
|
||||||
|
|
||||||
|
const beforeText = label.slice(0, matchIndex);
|
||||||
|
const matchText = label.slice(matchIndex, matchIndex + search.length);
|
||||||
|
const after = label.slice(matchIndex + search.length, label.length);
|
||||||
|
|
||||||
|
const element = document.createElement('li');
|
||||||
|
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||||
|
if (item.hint) {
|
||||||
|
element.dataset.tooltip = game.i18n.localize(item.hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
},
|
||||||
|
renderGroup: function (label) {
|
||||||
|
const itemElement = document.createElement('div');
|
||||||
|
itemElement.textContent = game.i18n.localize(label);
|
||||||
|
return itemElement;
|
||||||
|
},
|
||||||
|
onSelect: function (item) {
|
||||||
|
element.value = `system.${item.value}`;
|
||||||
|
},
|
||||||
|
click: e => e.fetch(),
|
||||||
|
customize: function (_input, _inputRect, container) {
|
||||||
|
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||||
|
},
|
||||||
|
minLength: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context) {
|
||||||
const partContext = await super._preparePartContext(partId, context);
|
const partContext = await super._preparePartContext(partId, context);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'changes':
|
case 'changes':
|
||||||
const fieldPaths = [];
|
|
||||||
const validFieldPath = fieldPath => this.validFieldPath(fieldPath, this.#unapplicablePaths);
|
|
||||||
context.document.parent.system.schema.apply(function () {
|
|
||||||
if (!(this instanceof foundry.data.fields.SchemaField)) {
|
|
||||||
if (validFieldPath(this.fieldPath)) {
|
|
||||||
fieldPaths.push(this.fieldPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
context.fieldPaths = fieldPaths;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return partContext;
|
return partContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
#unapplicablePaths = ['story', 'pronouns', 'description'];
|
|
||||||
validFieldPath(fieldPath, unapplicablePaths) {
|
|
||||||
const splitPath = fieldPath.split('.');
|
|
||||||
if (splitPath.length > 1 && unapplicablePaths.includes(splitPath[1])) return false;
|
|
||||||
|
|
||||||
/* The current value of a resource should not be modified */
|
|
||||||
if (new RegExp(/resources.*\.value/).exec(fieldPath)) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,18 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #removeExperience(_, target) {
|
static async #removeExperience(_, target) {
|
||||||
|
const experience = this.actor.system.experiences[target.dataset.experience];
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize(`DAGGERHEART.GENERAL.Experience.single`),
|
||||||
|
name: experience.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: experience.name })
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getDocFromElement } from '../../helpers/utils.mjs';
|
||||||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
@ -9,8 +10,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
actions: {
|
actions: {
|
||||||
addCategory: DHEnvironmentSettings.#addCategory,
|
addCategory: DHEnvironmentSettings.#addCategory,
|
||||||
removeCategory: DHEnvironmentSettings.#removeCategory,
|
removeCategory: DHEnvironmentSettings.#removeCategory,
|
||||||
viewAdversary: this.#viewAdversary,
|
deleteAdversary: DHEnvironmentSettings.#deleteAdversary
|
||||||
deleteAdversary: this.#deleteAdversary
|
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{ dragSelector: null, dropSelector: '.category-container' },
|
{ dragSelector: null, dropSelector: '.category-container' },
|
||||||
|
|
@ -69,23 +69,30 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null });
|
await this.actor.update({ [`system.potentialAdversaries.-=${target.dataset.categoryId}`]: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #viewAdversary(_, button) {
|
/**
|
||||||
const adversary = await foundry.utils.fromUuid(button.dataset.adversary);
|
*
|
||||||
if (!adversary) {
|
* @type {ApplicationClickAction}
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.adversaryMissing'));
|
* @returns
|
||||||
return;
|
*/
|
||||||
}
|
static async #deleteAdversary(_event, target) {
|
||||||
|
const doc = getDocFromElement(target);
|
||||||
|
const { category } = target.dataset;
|
||||||
|
const path = `system.potentialAdversaries.${category}.adversaries`;
|
||||||
|
|
||||||
adversary.sheet.render({ force: true });
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
}
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize('TYPES.Actor.adversary'),
|
||||||
|
name: doc.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
||||||
|
});
|
||||||
|
|
||||||
static async #deleteAdversary(event, target) {
|
if (!confirmed) return;
|
||||||
const adversaryKey = target.dataset.adversary;
|
|
||||||
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
|
const adversaries = foundry.utils.getProperty(this.actor, path);
|
||||||
console.log(target.dataset.potentialAdversar);
|
const newAdversaries = adversaries.filter(a => a.uuid !== doc.uuid);
|
||||||
const newAdversaries = foundry.utils
|
|
||||||
.getProperty(this.actor, path)
|
|
||||||
.filter(x => x && (x?.uuid ?? x) !== adversaryKey);
|
|
||||||
await this.actor.update({ [path]: newAdversaries });
|
await this.actor.update({ [path]: newAdversaries });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _prepareResourcesTab() {
|
||||||
|
const token = this.token;
|
||||||
|
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
||||||
|
const attributeSource =
|
||||||
|
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
||||||
|
? this.actor?.type
|
||||||
|
: this.actor?.system;
|
||||||
|
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
||||||
|
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
||||||
|
return {
|
||||||
|
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
||||||
|
bar1: token.getBarAttribute?.('bar1'),
|
||||||
|
bar2: token.getBarAttribute?.('bar2'),
|
||||||
|
turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES,
|
||||||
|
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
20
module/applications/sheets-configs/token-config.mjs
Normal file
20
module/applications/sheets-configs/token-config.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _prepareResourcesTab() {
|
||||||
|
const token = this.token;
|
||||||
|
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
||||||
|
const attributeSource =
|
||||||
|
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
||||||
|
? this.actor?.type
|
||||||
|
: this.actor?.system;
|
||||||
|
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
||||||
|
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
||||||
|
return {
|
||||||
|
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
||||||
|
bar1: token.getBarAttribute?.('bar1'),
|
||||||
|
bar2: token.getBarAttribute?.('bar2'),
|
||||||
|
turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES,
|
||||||
|
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,9 +8,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
position: { width: 660, height: 766 },
|
position: { width: 660, height: 766 },
|
||||||
window: { resizable: true },
|
window: { resizable: true },
|
||||||
actions: {
|
actions: {
|
||||||
reactionRoll: AdversarySheet.#reactionRoll,
|
reactionRoll: AdversarySheet.#reactionRoll
|
||||||
useItem: this.useItem,
|
|
||||||
toChat: this.toChat
|
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true
|
||||||
|
|
@ -28,7 +26,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'features' }, { id: 'notes' }, { id: 'effects' }],
|
tabs: [{ id: 'features' }, { id: 'effects' }, { id: 'notes' }],
|
||||||
initial: 'features',
|
initial: 'features',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
@ -41,10 +39,63 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem(element) {
|
/**@inheritdoc */
|
||||||
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
|
async _preparePartContext(partId, context, options) {
|
||||||
item = this.document.items.get(itemId);
|
context = await super._preparePartContext(partId, context, options);
|
||||||
return item;
|
switch (partId) {
|
||||||
|
case 'header':
|
||||||
|
await this._prepareHeaderContext(context, options);
|
||||||
|
break;
|
||||||
|
case 'notes':
|
||||||
|
await this._prepareNotesContext(context, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Biography part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareNotesContext(context, _options) {
|
||||||
|
const { system } = this.document;
|
||||||
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
const paths = {
|
||||||
|
notes: 'notes'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, path] of Object.entries(paths)) {
|
||||||
|
const value = foundry.utils.getProperty(system, path);
|
||||||
|
context[key] = {
|
||||||
|
field: system.schema.getField(path),
|
||||||
|
value,
|
||||||
|
enriched: await TextEditor.implementation.enrichHTML(value, {
|
||||||
|
secrets: this.document.isOwner,
|
||||||
|
relativeTo: this.document
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Header part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareHeaderContext(context, _options) {
|
||||||
|
const { system } = this.document;
|
||||||
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
context.description = await TextEditor.implementation.enrichHTML(system.description, {
|
||||||
|
secrets: this.document.isOwner,
|
||||||
|
relativeTo: this.document
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -72,42 +123,4 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
|
|
||||||
this.actor.diceRoll(config);
|
this.actor.diceRoll(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async useItem(event) {
|
|
||||||
const action = this.getItem(event) ?? this.actor.system.attack;
|
|
||||||
action.use(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async toChat(event, button) {
|
|
||||||
if (button?.dataset?.type === 'experience') {
|
|
||||||
const experience = this.document.system.experiences[button.dataset.uuid];
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
|
||||||
const systemData = {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
|
|
||||||
description: `${experience.name} ${experience.total.signedString()}`
|
|
||||||
};
|
|
||||||
const msg = new cls({
|
|
||||||
type: 'abilityUse',
|
|
||||||
user: game.user.id,
|
|
||||||
system: systemData,
|
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
|
|
||||||
systemData
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
cls.create(msg.toObject());
|
|
||||||
} else {
|
|
||||||
const item = this.getItem(event) ?? this.document.system.attack;
|
|
||||||
item.toChat(this.document.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { abilities } from '../../../config/actorConfig.mjs';
|
||||||
import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs';
|
import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs';
|
||||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
|
import { getDocFromElement, itemAbleRollParse } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
|
|
@ -14,7 +15,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
classes: ['character'],
|
classes: ['character'],
|
||||||
position: { width: 850, height: 800 },
|
position: { width: 850, height: 800 },
|
||||||
actions: {
|
actions: {
|
||||||
triggerContextMenu: CharacterSheet.#triggerContextMenu,
|
|
||||||
toggleVault: CharacterSheet.#toggleVault,
|
toggleVault: CharacterSheet.#toggleVault,
|
||||||
rollAttribute: CharacterSheet.#rollAttribute,
|
rollAttribute: CharacterSheet.#rollAttribute,
|
||||||
toggleHope: CharacterSheet.#toggleHope,
|
toggleHope: CharacterSheet.#toggleHope,
|
||||||
|
|
@ -23,17 +23,39 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
makeDeathMove: CharacterSheet.#makeDeathMove,
|
makeDeathMove: CharacterSheet.#makeDeathMove,
|
||||||
levelManagement: CharacterSheet.#levelManagement,
|
levelManagement: CharacterSheet.#levelManagement,
|
||||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||||
useItem: this.useItem, //TODO Fix this
|
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||||
toChat: this.toChat
|
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||||
|
useDowntime: this.useDowntime
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true
|
||||||
},
|
},
|
||||||
dragDrop: [],
|
dragDrop: [
|
||||||
|
{
|
||||||
|
dragSelector: '[data-item-id][draggable="true"]',
|
||||||
|
dropSelector: null
|
||||||
|
}
|
||||||
|
],
|
||||||
contextMenus: [
|
contextMenus: [
|
||||||
{
|
{
|
||||||
handler: CharacterSheet._getContextMenuOptions,
|
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||||
selector: '[data-item-id]',
|
selector: '[data-item-uuid][data-type="domainCard"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: CharacterSheet.#getEquipamentContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: CharacterSheet.#getItemContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="consumable"], [data-item-uuid][data-type="miscellaneous"]',
|
||||||
options: {
|
options: {
|
||||||
parentClassHooks: false,
|
parentClassHooks: false,
|
||||||
fixed: true
|
fixed: true
|
||||||
|
|
@ -85,6 +107,22 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.inventory-item-resource').forEach(element => {
|
||||||
|
element.addEventListener('change', this.updateItemResource.bind(this));
|
||||||
|
});
|
||||||
|
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
||||||
|
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add listener for armor marks input
|
||||||
|
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
|
||||||
|
element.addEventListener('change', this.updateArmorMarks.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
@ -97,20 +135,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this._createSearchFilter();
|
this._createSearchFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
getItem(element) {
|
|
||||||
const listElement = (element.target ?? element).closest('[data-item-id]');
|
|
||||||
const itemId = listElement.dataset.itemId;
|
|
||||||
|
|
||||||
switch (listElement.dataset.type) {
|
|
||||||
case 'effect':
|
|
||||||
return this.document.effects.get(itemId);
|
|
||||||
default:
|
|
||||||
return this.document.items.get(itemId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -160,98 +184,135 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
case 'sidebar':
|
case 'sidebar':
|
||||||
await this._prepareSidebarContext(context, options);
|
await this._prepareSidebarContext(context, options);
|
||||||
break;
|
break;
|
||||||
|
case 'biography':
|
||||||
|
await this._prepareBiographyContext(context, options);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Loadout part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
async _prepareLoadoutContext(context, _options) {
|
async _prepareLoadoutContext(context, _options) {
|
||||||
context.listView = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList);
|
context.cardView = !game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Sidebar part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
async _prepareSidebarContext(context, _options) {
|
async _prepareSidebarContext(context, _options) {
|
||||||
context.isDeath = this.document.system.deathMoveViable;
|
context.isDeath = this.document.system.deathMoveViable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Biography part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareBiographyContext(context, _options) {
|
||||||
|
const { system } = this.document;
|
||||||
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
const paths = {
|
||||||
|
background: 'biography.background',
|
||||||
|
connections: 'biography.connections'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, path] of Object.entries(paths)) {
|
||||||
|
const value = foundry.utils.getProperty(system, path);
|
||||||
|
context[key] = {
|
||||||
|
field: system.schema.getField(path),
|
||||||
|
value,
|
||||||
|
enriched: await TextEditor.implementation.enrichHTML(value, {
|
||||||
|
secrets: this.document.isOwner,
|
||||||
|
relativeTo: this.document
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Context Menu */
|
/* Context Menu */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the set of ContextMenu options.
|
* Get the set of ContextMenu options for DomainCards.
|
||||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
* @this {CharacterSheet}
|
* @this {CharacterSheet}
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
static _getContextMenuOptions() {
|
static #getDomainCardContextOptions() {
|
||||||
/**
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
* Get the item from the element.
|
const options = [
|
||||||
* @param {HTMLElement} el
|
|
||||||
* @returns {foundry.documents.Item?}
|
|
||||||
*/
|
|
||||||
const getItem = el => this.actor.items.get(el.closest('[data-item-id]')?.dataset.itemId);
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem',
|
name: 'toLoadout',
|
||||||
icon: '<i class="fa-solid fa-burst"></i>',
|
icon: 'fa-solid fa-arrow-up',
|
||||||
condition: el => {
|
condition: target => getDocFromElement(target).system.inVault,
|
||||||
const item = getItem(el);
|
callback: target => getDocFromElement(target).update({ 'system.inVault': false })
|
||||||
return !['class', 'subclass'].includes(item.type);
|
|
||||||
},
|
|
||||||
callback: (button, event) => CharacterSheet.useItem.call(this, event, button)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip',
|
name: 'toVault',
|
||||||
icon: '<i class="fa-solid fa-hands"></i>',
|
icon: 'fa-solid fa-arrow-down',
|
||||||
condition: el => {
|
condition: target => !getDocFromElement(target).system.inVault,
|
||||||
const item = getItem(el);
|
callback: target => getDocFromElement(target).update({ 'system.inVault': true })
|
||||||
return ['weapon', 'armor'].includes(item.type) && !item.system.equipped;
|
|
||||||
},
|
|
||||||
callback: CharacterSheet.#toggleEquipItem.bind(this)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip',
|
|
||||||
icon: '<i class="fa-solid fa-hands"></i>',
|
|
||||||
condition: el => {
|
|
||||||
const item = getItem(el);
|
|
||||||
return ['weapon', 'armor'].includes(item.type) && item.system.equipped;
|
|
||||||
},
|
|
||||||
callback: CharacterSheet.#toggleEquipItem.bind(this)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout',
|
|
||||||
icon: '<i class="fa-solid fa-arrow-up"></i>',
|
|
||||||
condition: el => {
|
|
||||||
const item = getItem(el);
|
|
||||||
return ['domainCard'].includes(item.type) && item.system.inVault;
|
|
||||||
},
|
|
||||||
callback: target => getItem(target).update({ 'system.inVault': false })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault',
|
|
||||||
icon: '<i class="fa-solid fa-arrow-down"></i>',
|
|
||||||
condition: el => {
|
|
||||||
const item = getItem(el);
|
|
||||||
return ['domainCard'].includes(item.type) && !item.system.inVault;
|
|
||||||
},
|
|
||||||
callback: target => getItem(target).update({ 'system.inVault': true })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat',
|
|
||||||
icon: '<i class="fa-regular fa-message"></i>',
|
|
||||||
callback: CharacterSheet.toChat.bind(this)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit',
|
|
||||||
icon: '<i class="fa-solid fa-pen-to-square"></i>',
|
|
||||||
callback: target => getItem(target).sheet.render({ force: true })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete',
|
|
||||||
icon: '<i class="fa-solid fa-trash"></i>',
|
|
||||||
callback: el => getItem(el).delete()
|
|
||||||
}
|
}
|
||||||
];
|
].map(option => ({
|
||||||
|
...option,
|
||||||
|
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||||
|
icon: `<i class="${option.icon}"></i>`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...options, ...this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true })];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options for Armors and Weapons.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {CharacterSheet}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static #getEquipamentContextOptions() {
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
name: 'equip',
|
||||||
|
icon: 'fa-solid fa-hands',
|
||||||
|
condition: target => !getDocFromElement(target).system.equipped,
|
||||||
|
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'unequip',
|
||||||
|
icon: 'fa-solid fa-hands',
|
||||||
|
condition: target => getDocFromElement(target).system.equipped,
|
||||||
|
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||||
|
}
|
||||||
|
].map(option => ({
|
||||||
|
...option,
|
||||||
|
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||||
|
icon: `<i class="${option.icon}"></i>`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...options, ...this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true })];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options for Consumable and Miscellaneous.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {CharacterSheet}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static #getItemContextOptions() {
|
||||||
|
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||||
}
|
}
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Filter Tracking */
|
/* Filter Tracking */
|
||||||
|
|
@ -345,7 +406,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this.#filteredItems.inventory.search.clear();
|
this.#filteredItems.inventory.search.clear();
|
||||||
|
|
||||||
for (const li of html.querySelectorAll('.inventory-item')) {
|
for (const li of html.querySelectorAll('.inventory-item')) {
|
||||||
const item = this.document.items.get(li.dataset.itemId);
|
const item = getDocFromElement(li);
|
||||||
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
||||||
if (matchesSearch) this.#filteredItems.inventory.search.add(item.id);
|
if (matchesSearch) this.#filteredItems.inventory.search.add(item.id);
|
||||||
const { menu } = this.#filteredItems.inventory;
|
const { menu } = this.#filteredItems.inventory;
|
||||||
|
|
@ -365,14 +426,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this.#filteredItems.loadout.search.clear();
|
this.#filteredItems.loadout.search.clear();
|
||||||
|
|
||||||
for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) {
|
for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) {
|
||||||
const item = this.document.items.get(li.dataset.itemId);
|
const item = getDocFromElement(li);
|
||||||
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
||||||
if (matchesSearch) this.#filteredItems.loadout.search.add(item.id);
|
if (matchesSearch) this.#filteredItems.loadout.search.add(item.id);
|
||||||
const { menu } = this.#filteredItems.loadout;
|
const { menu } = this.#filteredItems.loadout;
|
||||||
li.hidden = !(menu.has(item.id) && matchesSearch);
|
li.hidden = !(menu.has(item.id) && matchesSearch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Filter Menus */
|
/* Filter Menus */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -416,7 +477,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this.#filteredItems.inventory.menu.clear();
|
this.#filteredItems.inventory.menu.clear();
|
||||||
|
|
||||||
for (const li of html.querySelectorAll('.inventory-item')) {
|
for (const li of html.querySelectorAll('.inventory-item')) {
|
||||||
const item = this.document.items.get(li.dataset.itemId);
|
const item = getDocFromElement(li);
|
||||||
|
|
||||||
const matchesMenu =
|
const matchesMenu =
|
||||||
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
|
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
|
||||||
|
|
@ -437,7 +498,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this.#filteredItems.loadout.menu.clear();
|
this.#filteredItems.loadout.menu.clear();
|
||||||
|
|
||||||
for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) {
|
for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) {
|
||||||
const item = this.document.items.get(li.dataset.itemId);
|
const item = getDocFromElement(li);
|
||||||
|
|
||||||
const matchesMenu =
|
const matchesMenu =
|
||||||
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
|
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
|
||||||
|
|
@ -448,6 +509,35 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Listener Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async updateItemResource(event) {
|
||||||
|
const item = getDocFromElement(event.currentTarget);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const max = item.system.resource.max ? itemAbleRollParse(item.system.resource.max, this.document, item) : null;
|
||||||
|
const value = max ? Math.min(Number(event.currentTarget.value), max) : event.currentTarget.value;
|
||||||
|
await item.update({ 'system.resource.value': value });
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateItemQuantity(event) {
|
||||||
|
const item = getDocFromElement(event.currentTarget);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
await item.update({ 'system.quantity': event.currentTarget.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateArmorMarks(event) {
|
||||||
|
const armor = this.document.system.armor;
|
||||||
|
if (!armor) return;
|
||||||
|
|
||||||
|
const maxMarks = this.document.system.armorScore;
|
||||||
|
const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks);
|
||||||
|
await armor.update({ 'system.marks.value': value });
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -495,7 +585,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
const config = {
|
const config = {
|
||||||
event: event,
|
event: event,
|
||||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilitychecktitle', {
|
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
ability: abilityLabel
|
ability: abilityLabel
|
||||||
}),
|
}),
|
||||||
roll: {
|
roll: {
|
||||||
|
|
@ -505,13 +595,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this.document.diceRoll(config);
|
this.document.diceRoll(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: redo toggleEquipItem method
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the equipped state of an item (armor or weapon).
|
* Toggles the equipped state of an item (armor or weapon).
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleEquipItem(_event, button) {
|
static async #toggleEquipItem(_event, button) {
|
||||||
//TODO: redo this
|
const item = getDocFromElement(button);
|
||||||
const item = this.actor.items.get(button.closest('[data-item-id]')?.dataset.itemId);
|
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
if (item.system.equipped) {
|
if (item.system.equipped) {
|
||||||
await item.update({ 'system.equipped': false });
|
await item.update({ 'system.equipped': false });
|
||||||
|
|
@ -528,6 +619,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
await item.update({ 'system.equipped': true });
|
await item.update({ 'system.equipped': true });
|
||||||
break;
|
break;
|
||||||
case 'weapon':
|
case 'weapon':
|
||||||
|
if (this.document.effects.find(x => x.type === 'beastform')) {
|
||||||
|
return ui.notifications.warn(
|
||||||
|
game.i18n.localize('DAGGERHEART.UI.Notifications.beastformEquipWeapon')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(item);
|
await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(item);
|
||||||
|
|
||||||
await item.update({ 'system.equipped': true });
|
await item.update({ 'system.equipped': true });
|
||||||
|
|
@ -559,76 +656,69 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
* Toggles whether an item is stored in the vault.
|
* Toggles whether an item is stored in the vault.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleVault(event, button) {
|
static async #toggleVault(_event, button) {
|
||||||
const docId = button.closest('[data-item-id]')?.dataset.itemId;
|
const doc = getDocFromElement(button);
|
||||||
const doc = this.document.items.get(docId);
|
|
||||||
await doc?.update({ 'system.inVault': !doc.system.inVault });
|
await doc?.update({ 'system.inVault': !doc.system.inVault });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger the context menu.
|
* Toggle the used state of a resource dice.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static #triggerContextMenu(event, _) {
|
static async #toggleResourceDice(event, target) {
|
||||||
return CONFIG.ux.ContextMenu.triggerContextMenu(event);
|
const item = getDocFromElement(target);
|
||||||
|
|
||||||
|
const { dice } = event.target.closest('.item-resource').dataset;
|
||||||
|
const diceState = item.system.resource.diceStates[dice];
|
||||||
|
|
||||||
|
await item.update({
|
||||||
|
[`system.resource.diceStates.${dice}.used`]: diceState ? !diceState.used : true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use a item
|
* Handle the roll values of resource dice.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async useItem(event, button) {
|
static async #handleResourceDice(_, target) {
|
||||||
const item = this.getItem(button);
|
const item = getDocFromElement(target);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
// Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board
|
const rollValues = await game.system.api.applications.dialogs.ResourceDiceDialog.create(item, this.document);
|
||||||
if (item.type === 'feature') {
|
if (!rollValues) return;
|
||||||
item.use(event);
|
|
||||||
} else if (item instanceof ActiveEffect) {
|
await item.update({
|
||||||
item.toChat(this);
|
'system.resource.diceStates': rollValues.reduce((acc, state, index) => {
|
||||||
} else {
|
acc[index] = { value: state.value, used: state.used };
|
||||||
const wasUsed = await item.use(event);
|
return acc;
|
||||||
if (wasUsed && item.type === 'weapon') {
|
}, {})
|
||||||
Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static useDowntime(_, button) {
|
||||||
* Send item to Chat
|
new game.system.api.applications.dialogs.Downtime(this.document, button.dataset.type === 'shortRest').render(
|
||||||
* @type {ApplicationClickAction}
|
true
|
||||||
*/
|
);
|
||||||
static async toChat(event, button) {
|
|
||||||
if (button?.dataset?.type === 'experience') {
|
|
||||||
const experience = this.document.system.experiences[button.dataset.uuid];
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
|
||||||
const systemData = {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
|
|
||||||
description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
|
|
||||||
};
|
|
||||||
const msg = new cls({
|
|
||||||
type: 'abilityUse',
|
|
||||||
user: game.user.id,
|
|
||||||
system: systemData,
|
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
|
|
||||||
systemData
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
cls.create(msg.toObject());
|
|
||||||
} else {
|
|
||||||
const item = this.getItem(event);
|
|
||||||
if (!item) return;
|
|
||||||
item.toChat(this.document.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDragStart(_, event) {
|
async _onDragStart(event) {
|
||||||
|
const item = getDocFromElement(event.target);
|
||||||
|
|
||||||
|
const dragData = {
|
||||||
|
type: item.documentName,
|
||||||
|
uuid: item.uuid
|
||||||
|
};
|
||||||
|
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||||
|
|
||||||
super._onDragStart(event);
|
super._onDragStart(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
// Prevent event bubbling to avoid duplicate handling
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
super._onDrop(event);
|
super._onDrop(event);
|
||||||
this._onDropItem(event, TextEditor.getDragEventData(event));
|
this._onDropItem(event, TextEditor.getDragEventData(event));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['actor', 'companion'],
|
classes: ['actor', 'companion'],
|
||||||
position: { width: 300 },
|
position: { width: 300 },
|
||||||
actions: {
|
actions: {}
|
||||||
viewActor: this.viewActor,
|
|
||||||
useItem: this.useItem,
|
|
||||||
toChat: this.toChat
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
@ -29,52 +25,4 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/* Application Clicks Actions */
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
static async viewActor(_, button) {
|
|
||||||
const target = button.closest('[data-item-uuid]');
|
|
||||||
const actor = await foundry.utils.fromUuid(target.dataset.itemUuid);
|
|
||||||
if (!actor) return;
|
|
||||||
|
|
||||||
actor.sheet.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAction(element) {
|
|
||||||
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
|
|
||||||
item = this.document.system.actions.find(x => x.id === itemId);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async useItem(event) {
|
|
||||||
const action = this.getAction(event) ?? this.actor.system.attack;
|
|
||||||
action.use(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async toChat(event, button) {
|
|
||||||
if (button?.dataset?.type === 'experience') {
|
|
||||||
const experience = this.document.system.experiences[button.dataset.uuid];
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
|
||||||
const systemData = {
|
|
||||||
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
|
|
||||||
description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
|
|
||||||
};
|
|
||||||
const msg = new cls({
|
|
||||||
type: 'abilityUse',
|
|
||||||
user: game.user.id,
|
|
||||||
system: systemData,
|
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
|
|
||||||
systemData
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
cls.create(msg.toObject());
|
|
||||||
} else {
|
|
||||||
const item = this.getAction(event) ?? this.document.system.attack;
|
|
||||||
item.toChat(this.document.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,7 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
position: {
|
position: {
|
||||||
width: 500
|
width: 500
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {},
|
||||||
useItem: this.useItem,
|
|
||||||
toChat: this.toChat
|
|
||||||
},
|
|
||||||
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
|
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -35,47 +32,67 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/**@inheritdoc */
|
||||||
|
async _preparePartContext(partId, context, options) {
|
||||||
getItem(element) {
|
context = await super._preparePartContext(partId, context, options);
|
||||||
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
|
switch (partId) {
|
||||||
item = this.document.items.get(itemId);
|
case 'header':
|
||||||
return item;
|
await this._prepareHeaderContext(context, options);
|
||||||
|
break;
|
||||||
|
case 'notes':
|
||||||
|
await this._prepareNotesContext(context, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/* Application Clicks Actions */
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Prepare render context for the Biography part.
|
||||||
* @type {ApplicationClickAction}
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
*/
|
*/
|
||||||
async viewAdversary(_, button) {
|
async _prepareNotesContext(context, _options) {
|
||||||
const target = button.closest('[data-item-uuid]');
|
const { system } = this.document;
|
||||||
const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid);
|
const { TextEditor } = foundry.applications.ux;
|
||||||
if (!adversary) {
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.adversaryMissing'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
adversary.sheet.render({ force: true });
|
const paths = {
|
||||||
}
|
notes: 'notes'
|
||||||
|
};
|
||||||
|
|
||||||
static async useItem(event, button) {
|
for (const [key, path] of Object.entries(paths)) {
|
||||||
const action = this.getItem(event);
|
const value = foundry.utils.getProperty(system, path);
|
||||||
if (!action) {
|
context[key] = {
|
||||||
await this.viewAdversary(event, button);
|
field: system.schema.getField(path),
|
||||||
} else {
|
value,
|
||||||
action.use(event);
|
enriched: await TextEditor.implementation.enrichHTML(value, {
|
||||||
|
secrets: this.document.isOwner,
|
||||||
|
relativeTo: this.document
|
||||||
|
})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async toChat(event) {
|
/**
|
||||||
const item = this.getItem(event);
|
* Prepare render context for the Header part.
|
||||||
item.toChat(this.document.id);
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareHeaderContext(context, _options) {
|
||||||
|
const { system } = this.document;
|
||||||
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
|
context.description = await TextEditor.implementation.enrichHTML(system.description, {
|
||||||
|
secrets: this.document.isOwner,
|
||||||
|
relativeTo: this.document
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const item = event.currentTarget.closest('.inventory-item');
|
const item = event.currentTarget.closest('.inventory-item');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export { default as DHApplicationMixin } from './application-mixin.mjs';
|
export { default as DHApplicationMixin } from './application-mixin.mjs';
|
||||||
export { default as DHBaseItemSheet } from './base-item.mjs';
|
export { default as DHBaseItemSheet } from './base-item.mjs';
|
||||||
export { default as DHHeritageSheet } from './heritage-sheet.mjs';
|
export { default as DHHeritageSheet } from './heritage-sheet.mjs';
|
||||||
|
export { default as DHItemAttachmentSheet } from './item-attachment-sheet.mjs';
|
||||||
export { default as DHBaseActorSheet } from './base-actor.mjs';
|
export { default as DHBaseActorSheet } from './base-actor.mjs';
|
||||||
export { default as DHBaseActorSettings } from './actor-setting.mjs';
|
export { default as DHBaseActorSettings } from './actor-setting.mjs';
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,12 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
|
||||||
context.isNPC = this.actor.isNPC;
|
context.isNPC = this.actor.isNPC;
|
||||||
|
|
||||||
|
if (context.systemFields.attack) {
|
||||||
|
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
||||||
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
import { tagifyElement } from '../../../helpers/utils.mjs';
|
import { getDocFromElement, tagifyElement } from '../../../helpers/utils.mjs';
|
||||||
|
import DHActionConfig from '../../sheets-configs/action-config.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} DragDropConfig
|
* @typedef {object} DragDropConfig
|
||||||
|
|
@ -71,11 +76,34 @@ export default function DHApplicationMixin(Base) {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['daggerheart', 'sheet', 'dh-style'],
|
classes: ['daggerheart', 'sheet', 'dh-style'],
|
||||||
actions: {
|
actions: {
|
||||||
|
triggerContextMenu: DHSheetV2.#triggerContextMenu,
|
||||||
createDoc: DHSheetV2.#createDoc,
|
createDoc: DHSheetV2.#createDoc,
|
||||||
editDoc: DHSheetV2.#editDoc,
|
editDoc: DHSheetV2.#editDoc,
|
||||||
deleteDoc: DHSheetV2.#deleteDoc
|
deleteDoc: DHSheetV2.#deleteDoc,
|
||||||
|
toChat: DHSheetV2.#toChat,
|
||||||
|
useItem: DHSheetV2.#useItem,
|
||||||
|
useAction: DHSheetV2.#useAction,
|
||||||
|
toggleEffect: DHSheetV2.#toggleEffect,
|
||||||
|
toggleExtended: DHSheetV2.#toggleExtended
|
||||||
},
|
},
|
||||||
contextMenus: [],
|
contextMenus: [
|
||||||
|
{
|
||||||
|
handler: DHSheetV2.#getEffectContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="effect"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
handler: DHSheetV2.#getActionContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="action"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
dragDrop: [],
|
dragDrop: [],
|
||||||
tagifyConfigs: []
|
tagifyConfigs: []
|
||||||
};
|
};
|
||||||
|
|
@ -99,6 +127,27 @@ export default function DHApplicationMixin(Base) {
|
||||||
this._createTagifyElements(this.options.tagifyConfigs);
|
this._createTagifyElements(this.options.tagifyConfigs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Sync Parts */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
_syncPartState(partId, newElement, priorElement, state) {
|
||||||
|
super._syncPartState(partId, newElement, priorElement, state);
|
||||||
|
for (const el of priorElement.querySelectorAll('.extensible.extended')) {
|
||||||
|
const { actionId, itemUuid } = el.parentElement.dataset;
|
||||||
|
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
|
||||||
|
const newExtensible = newElement.querySelector(selector);
|
||||||
|
|
||||||
|
if (!newExtensible) continue;
|
||||||
|
newExtensible.classList.add('extended');
|
||||||
|
const descriptionElement = newExtensible.querySelector('.invetory-description');
|
||||||
|
if (descriptionElement) {
|
||||||
|
this.#prepareInventoryDescription(newExtensible, descriptionElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Tags */
|
/* Tags */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -185,12 +234,146 @@ export default function DHApplicationMixin(Base) {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the set of ContextMenu options which should be used for journal entry pages in the sidebar.
|
* Get the set of ContextMenu options for DomainCards.
|
||||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]}
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {CharacterSheet}
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
_getEntryContextOptions() {
|
static #getEffectContextOptions() {
|
||||||
return [];
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
name: 'disableEffect',
|
||||||
|
icon: 'fa-solid fa-lightbulb',
|
||||||
|
condition: target => !getDocFromElement(target).disabled,
|
||||||
|
callback: target => getDocFromElement(target).update({ disabled: true })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableEffect',
|
||||||
|
icon: 'fa-regular fa-lightbulb',
|
||||||
|
condition: target => getDocFromElement(target).disabled,
|
||||||
|
callback: target => getDocFromElement(target).update({ disabled: false })
|
||||||
|
}
|
||||||
|
].map(option => ({
|
||||||
|
...option,
|
||||||
|
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||||
|
icon: `<i class="${option.icon}"></i>`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...options, ...this._getContextMenuCommonOptions.call(this, { toChat: true })];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options for Actions.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {DHSheetV2}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static #getActionContextOptions() {
|
||||||
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
|
const getAction = target => {
|
||||||
|
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||||
|
const { actions, attack } = this.document.system;
|
||||||
|
return attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||||
|
icon: 'fa-solid fa-burst',
|
||||||
|
condition:
|
||||||
|
this.document instanceof foundry.documents.Actor ||
|
||||||
|
(this.document instanceof foundry.documents.Item && this.document.parent),
|
||||||
|
callback: (target, event) => getAction(target).use(event)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||||
|
icon: 'fa-solid fa-message',
|
||||||
|
callback: target => getAction(target).toChat(this.document.id)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CONTROLS.CommonEdit',
|
||||||
|
icon: 'fa-solid fa-pen-to-square',
|
||||||
|
callback: target => new DHActionConfig(getAction(target)).render({ force: true })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CONTROLS.CommonDelete',
|
||||||
|
icon: 'fa-solid fa-trash',
|
||||||
|
condition: target => {
|
||||||
|
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||||
|
const { attack } = this.document.system;
|
||||||
|
return attack?.id !== actionId;
|
||||||
|
},
|
||||||
|
callback: async target => {
|
||||||
|
const action = getAction(target);
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||||
|
name: action.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
|
||||||
|
name: action.name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
return this.document.update({
|
||||||
|
'system.actions': this.document.system.actions.filter(a => a.id !== action.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
].map(option => ({
|
||||||
|
...option,
|
||||||
|
icon: `<i class="${option.icon}"></i>`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
*/
|
||||||
|
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
name: 'CONTROLS.CommonEdit',
|
||||||
|
icon: 'fa-solid fa-pen-to-square',
|
||||||
|
callback: target => getDocFromElement(target).sheet.render({ force: true })
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (usable)
|
||||||
|
options.unshift({
|
||||||
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||||
|
icon: 'fa-solid fa-burst',
|
||||||
|
callback: (target, event) => getDocFromElement(target).use(event)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (toChat)
|
||||||
|
options.unshift({
|
||||||
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||||
|
icon: 'fa-solid fa-message',
|
||||||
|
callback: target => getDocFromElement(target).toChat(this.document.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deletable)
|
||||||
|
options.push({
|
||||||
|
name: 'CONTROLS.CommonDelete',
|
||||||
|
icon: 'fa-solid fa-trash',
|
||||||
|
callback: (target, event) => {
|
||||||
|
const doc = getDocFromElement(target);
|
||||||
|
if (event.shiftKey) return doc.delete();
|
||||||
|
else return doc.deleteDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return options.map(option => ({
|
||||||
|
...option,
|
||||||
|
icon: `<i class="${option.icon}"></i>`
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -207,50 +390,233 @@ export default function DHApplicationMixin(Base) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Prepare Descriptions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares and enriches an inventory item or action description for display.
|
||||||
|
* @param {HTMLElement} extensibleElement - The parent element containing the description.
|
||||||
|
* @param {HTMLElement} descriptionElement - The element where the enriched description will be rendered.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async #prepareInventoryDescription(extensibleElement, descriptionElement) {
|
||||||
|
const parent = extensibleElement.closest('[data-item-uuid], [data-action-id]');
|
||||||
|
const { actionId, itemUuid } = parent?.dataset || {};
|
||||||
|
if (!actionId && !itemUuid) return;
|
||||||
|
|
||||||
|
const doc = itemUuid
|
||||||
|
? getDocFromElement(extensibleElement)
|
||||||
|
: this.document.system.attack?.id === actionId
|
||||||
|
? this.document.system.attack
|
||||||
|
: this.document.system.actions?.find(a => a.id === actionId);
|
||||||
|
if (!doc) return;
|
||||||
|
|
||||||
|
const description = doc.system?.description ?? doc.description;
|
||||||
|
const isAction = !!actionId;
|
||||||
|
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
|
description,
|
||||||
|
{
|
||||||
|
relativeTo: isAction ? doc.parent : doc,
|
||||||
|
rollData: doc.getRollData?.(),
|
||||||
|
secrets: isAction ? doc.parent.isOwner : doc.isOwner
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an embedded document.
|
* Create an embedded document.
|
||||||
* @param {PointerEvent} event - The originating click event
|
* @type {ApplicationClickAction}
|
||||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
|
|
||||||
*/
|
*/
|
||||||
static async #createDoc(event, button) {
|
static async #createDoc(event, target) {
|
||||||
const { documentClass, type } = button.dataset;
|
const { documentClass, type, inVault, disabled } = target.dataset;
|
||||||
console.log(documentClass, type);
|
const parentIsItem = this.document.documentName === 'Item';
|
||||||
const parent = this.document;
|
const parent = parentIsItem && documentClass === 'Item' ? null : this.document;
|
||||||
|
|
||||||
const cls = getDocumentClass(documentClass);
|
if (type === 'action') {
|
||||||
return await cls.createDocuments(
|
const { type: actionType } =
|
||||||
[
|
(await foundry.applications.api.DialogV2.input({
|
||||||
|
window: { title: 'Select Action Type' },
|
||||||
|
classes: ['daggerheart', 'dh-style'],
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
||||||
|
{
|
||||||
|
types: CONFIG.DH.ACTIONS.actionTypes,
|
||||||
|
itemName: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectAction')
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ok: {
|
||||||
|
label: game.i18n.format('DOCUMENT.Create', {
|
||||||
|
type: game.i18n.localize('DAGGERHEART.GENERAL.Action.single')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})) ?? {};
|
||||||
|
if (!actionType) return;
|
||||||
|
const cls = game.system.api.models.actions.actionsTypes[actionType];
|
||||||
|
const action = new cls(
|
||||||
{
|
{
|
||||||
name: cls.defaultName({ type, parent }),
|
_id: foundry.utils.randomID(),
|
||||||
type
|
type: actionType,
|
||||||
|
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
|
||||||
|
...cls.getSourceConfig(this.document)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parent: this.document
|
||||||
}
|
}
|
||||||
],
|
);
|
||||||
{ parent, renderSheet: !event.shiftKey }
|
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
|
||||||
);
|
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
return action;
|
||||||
|
} else {
|
||||||
|
const cls = getDocumentClass(documentClass);
|
||||||
|
const data = {
|
||||||
|
name: cls.defaultName({ type, parent }),
|
||||||
|
type
|
||||||
|
};
|
||||||
|
if (inVault) data['system.inVault'] = true;
|
||||||
|
if (disabled) data.disabled = true;
|
||||||
|
|
||||||
|
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
||||||
|
if (parentIsItem && type === 'feature') {
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': this.document.system.toObject().features.concat(doc.uuid)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders an embedded document.
|
* Renders an embedded document.
|
||||||
* @param {PointerEvent} event - The originating click event
|
* @type {ApplicationClickAction}
|
||||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
|
|
||||||
*/
|
*/
|
||||||
static #editDoc(_event, button) {
|
static #editDoc(_event, target) {
|
||||||
const { type, docId } = button.dataset;
|
const doc = getDocFromElement(target);
|
||||||
this.document.getEmbeddedDocument(type, docId, { strict: true }).sheet.render({ force: true });
|
if (doc) return doc.sheet.render({ force: true });
|
||||||
|
|
||||||
|
// TODO: REDO this
|
||||||
|
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||||
|
const { actions, attack } = this.document.system;
|
||||||
|
const action = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||||
|
new DHActionConfig(action).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete an embedded document.
|
* Delete an embedded document.
|
||||||
* @param {PointerEvent} _event - The originating click event
|
* @type {ApplicationClickAction}
|
||||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
|
|
||||||
*/
|
*/
|
||||||
static async #deleteDoc(_event, button) {
|
static async #deleteDoc(event, target) {
|
||||||
const { type, docId } = button.dataset;
|
const doc = getDocFromElement(target);
|
||||||
await this.document.getEmbeddedDocument(type, docId, { strict: true }).delete();
|
|
||||||
|
if (doc) {
|
||||||
|
if (event.shiftKey) return doc.delete();
|
||||||
|
else return await doc.deleteDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: REDO this
|
||||||
|
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||||
|
const { actions, attack } = this.document.system;
|
||||||
|
if (attack?.id === actionId) return;
|
||||||
|
const action = actions.find(a => a.id === actionId);
|
||||||
|
|
||||||
|
if (!event.shiftKey) {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||||
|
name: action.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.document.update({
|
||||||
|
'system.actions': actions.filter(a => a.id !== action.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send item to Chat
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toChat(_event, target) {
|
||||||
|
let doc = getDocFromElement(target);
|
||||||
|
|
||||||
|
// TODO: REDO this
|
||||||
|
if (!doc) {
|
||||||
|
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||||
|
const { actions, attack } = this.document.system;
|
||||||
|
doc = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||||
|
}
|
||||||
|
return doc.toChat(this.document.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a item
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #useItem(event, target) {
|
||||||
|
let doc = getDocFromElement(target);
|
||||||
|
// TODO: REDO this
|
||||||
|
if (!doc) {
|
||||||
|
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||||
|
const { actions, attack } = this.document.system;
|
||||||
|
doc = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||||
|
if (this.document instanceof foundry.documents.Item && !this.document.parent) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await doc.use(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a item
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #useAction(event, target) {
|
||||||
|
const doc = getDocFromElement(target);
|
||||||
|
const { actionId } = target.closest('[data-action-id]').dataset;
|
||||||
|
const { actions, attack } = doc.system;
|
||||||
|
const action = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
|
||||||
|
await action.use(event, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle a ActiveEffect
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #toggleEffect(_, target) {
|
||||||
|
const doc = getDocFromElement(target);
|
||||||
|
await doc.update({ disabled: !doc.disabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger the context menu.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static #triggerContextMenu(event, _) {
|
||||||
|
return CONFIG.ux.ContextMenu.triggerContextMenu(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the 'extended' class on the .extensible element inside inventory-item-content
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
* @this {DHSheetV2}
|
||||||
|
*/
|
||||||
|
static async #toggleExtended(_, target) {
|
||||||
|
const container = target.closest('.inventory-item');
|
||||||
|
const extensible = container?.querySelector('.extensible');
|
||||||
|
const t = extensible?.classList.toggle('extended');
|
||||||
|
|
||||||
|
const descriptionElement = extensible?.querySelector('.invetory-description');
|
||||||
|
if (t && !!descriptionElement) this.#prepareInventoryDescription(extensible, descriptionElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,24 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
submitOnChange: true
|
submitOnChange: true
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
openSettings: DHBaseActorSheet.#openSettings
|
openSettings: DHBaseActorSheet.#openSettings,
|
||||||
|
sendExpToChat: DHBaseActorSheet.#sendExpToChat
|
||||||
},
|
},
|
||||||
dragDrop: []
|
contextMenus: [
|
||||||
|
{
|
||||||
|
handler: DHBaseActorSheet.#getFeatureContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="feature"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**@type {typeof DHBaseActorSettings}*/
|
/**@type {typeof DHBaseActorSettings}*/
|
||||||
#settingSheet;
|
#settingSheet;
|
||||||
|
|
||||||
|
|
@ -35,6 +48,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
return (this.#settingSheet ??= SheetClass ? new SheetClass({ document: this.document }) : null);
|
return (this.#settingSheet ??= SheetClass ? new SheetClass({ document: this.document }) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Prepare Context */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
|
|
@ -42,6 +59,54 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
async _preparePartContext(partId, context, options) {
|
||||||
|
context = await super._preparePartContext(partId, context, options);
|
||||||
|
switch (partId) {
|
||||||
|
case 'effects':
|
||||||
|
await this._prepareEffectsContext(context, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the Effect part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareEffectsContext(context, _options) {
|
||||||
|
context.effects = {
|
||||||
|
actives: [],
|
||||||
|
inactives: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const effect of this.actor.allApplicableEffects()) {
|
||||||
|
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
||||||
|
list.push(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Context Menu */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of ContextMenu options for Features.
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {DHSheetV2}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static #getFeatureContextOptions() {
|
||||||
|
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Clicks Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the Actor Setting Sheet
|
* Open the Actor Setting Sheet
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -49,4 +114,50 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
static async #openSettings() {
|
static async #openSettings() {
|
||||||
await this.settingSheet.render({ force: true });
|
await this.settingSheet.render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send Experience to Chat
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #sendExpToChat(_, button) {
|
||||||
|
const experience = this.document.system.experiences[button.dataset.id];
|
||||||
|
|
||||||
|
const systemData = {
|
||||||
|
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
|
||||||
|
description: `${experience.name} ${experience.value.signedString()}`
|
||||||
|
};
|
||||||
|
|
||||||
|
foundry.documents.ChatMessage.implementation.create({
|
||||||
|
type: 'abilityUse',
|
||||||
|
user: game.user.id,
|
||||||
|
system: systemData,
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
|
||||||
|
systemData
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Drag/Drop */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On dragStart on the item.
|
||||||
|
* @param {DragEvent} event - The drag event
|
||||||
|
*/
|
||||||
|
async _onDragStart(event) {
|
||||||
|
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
|
||||||
|
|
||||||
|
if (attackItem) {
|
||||||
|
const attackData = {
|
||||||
|
type: 'Attack',
|
||||||
|
actorUuid: this.document.uuid,
|
||||||
|
img: this.document.system.attack.img,
|
||||||
|
fromInternal: true
|
||||||
|
};
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
||||||
|
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import DHActionConfig from '../../sheets-configs/action-config.mjs';
|
import { getDocFromElement } from '../../../helpers/utils.mjs';
|
||||||
import DHApplicationMixin from './application-mixin.mjs';
|
import DHApplicationMixin from './application-mixin.mjs';
|
||||||
|
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
|
|
@ -15,20 +15,31 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['item'],
|
classes: ['item'],
|
||||||
position: { width: 600 },
|
position: { width: 600 },
|
||||||
|
window: { resizable: true },
|
||||||
form: {
|
form: {
|
||||||
submitOnChange: true
|
submitOnChange: true
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addAction: DHBaseItemSheet.#addAction,
|
|
||||||
editAction: DHBaseItemSheet.#editAction,
|
|
||||||
removeAction: DHBaseItemSheet.#removeAction,
|
removeAction: DHBaseItemSheet.#removeAction,
|
||||||
addFeature: DHBaseItemSheet.#addFeature,
|
addFeature: DHBaseItemSheet.#addFeature,
|
||||||
editFeature: DHBaseItemSheet.#editFeature,
|
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
||||||
removeFeature: DHBaseItemSheet.#removeFeature
|
addResource: DHBaseItemSheet.#addResource,
|
||||||
|
removeResource: DHBaseItemSheet.#removeResource
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{ dragSelector: null, dropSelector: '.tab.features .drop-section' },
|
{ dragSelector: null, dropSelector: '.tab.features .drop-section' },
|
||||||
{ dragSelector: '.feature-item', dropSelector: null }
|
{ dragSelector: '.feature-item', dropSelector: null },
|
||||||
|
{ dragSelector: '.action-item', dropSelector: null }
|
||||||
|
],
|
||||||
|
contextMenus: [
|
||||||
|
{
|
||||||
|
handler: DHBaseItemSheet.#getFeatureContextOptions,
|
||||||
|
selector: '[data-item-uuid][data-type="feature"]',
|
||||||
|
options: {
|
||||||
|
parentClassHooks: false,
|
||||||
|
fixed: true
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,7 +48,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }],
|
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }, { id: 'effects' }],
|
||||||
initial: 'description',
|
initial: 'description',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
@ -48,8 +59,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context, options) {
|
||||||
await super._preparePartContext(partId, context);
|
await super._preparePartContext(partId, context, options);
|
||||||
const { TextEditor } = foundry.applications.ux;
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
|
|
@ -61,77 +72,78 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
secrets: this.item.isOwner
|
secrets: this.item.isOwner
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'effects':
|
||||||
|
await this._prepareEffectsContext(context, options);
|
||||||
|
break;
|
||||||
|
case 'features':
|
||||||
|
context.isGM = game.user.isGM;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/* Application Clicks Actions */
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a dialog prompting the user to select an action type.
|
* Prepare render context for the Effect part.
|
||||||
*
|
* @param {ApplicationRenderContext} context
|
||||||
* @returns {Promise<object>} An object containing the selected action type.
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
*/
|
*/
|
||||||
static async selectActionType() {
|
async _prepareEffectsContext(context, _options) {
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
context.effects = {
|
||||||
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
actives: [],
|
||||||
{ types: CONFIG.DH.ACTIONS.actionTypes }
|
inactives: []
|
||||||
),
|
};
|
||||||
title = 'Select Action Type';
|
|
||||||
|
|
||||||
return foundry.applications.api.DialogV2.prompt({
|
for (const effect of this.item.effects) {
|
||||||
window: { title },
|
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
||||||
content,
|
list.push(effect);
|
||||||
ok: {
|
|
||||||
label: title,
|
|
||||||
callback: (event, button, dialog) => button.form.elements.type.value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new action to the item, prompting the user for its type.
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #addAction(_event, _button) {
|
|
||||||
const actionType = await DHBaseItemSheet.selectActionType();
|
|
||||||
if (!actionType) return;
|
|
||||||
try {
|
|
||||||
const cls =
|
|
||||||
game.system.api.models.actions.actionsTypes[actionType] ??
|
|
||||||
game.system.api.models.actions.actionsTypes.attack,
|
|
||||||
action = new cls(
|
|
||||||
{
|
|
||||||
_id: foundry.utils.randomID(),
|
|
||||||
type: actionType,
|
|
||||||
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
|
|
||||||
...cls.getSourceConfig(this.document)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parent: this.document
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
|
|
||||||
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Context Menu */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit an existing action on the item
|
* Get the set of ContextMenu options for Features.
|
||||||
* @type {ApplicationClickAction}
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||||
|
* @this {DHSheetV2}
|
||||||
|
* @protected
|
||||||
*/
|
*/
|
||||||
static async #editAction(_event, button) {
|
static #getFeatureContextOptions() {
|
||||||
const action = this.document.system.actions[button.dataset.index];
|
const options = this._getContextMenuCommonOptions({ usable: true, toChat: true, deletable: false });
|
||||||
await new DHActionConfig(action).render({ force: true });
|
options.push({
|
||||||
|
name: 'CONTROLS.CommonDelete',
|
||||||
|
icon: '<i class="fa-solid fa-trash"></i>',
|
||||||
|
callback: async target => {
|
||||||
|
const feature = getDocFromElement(target);
|
||||||
|
if (!feature) return;
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize(`TYPES.Item.feature`),
|
||||||
|
name: feature.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
|
||||||
|
name: feature.name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': this.document.system.toObject().features.filter(uuid => uuid !== feature.uuid)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Clicks Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an action from the item.
|
* Remove an action from the item.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -139,6 +151,21 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
static async #removeAction(event, button) {
|
static async #removeAction(event, button) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const actionIndex = button.closest('[data-index]').dataset.index;
|
const actionIndex = button.closest('[data-index]').dataset.index;
|
||||||
|
const action = this.document.system.actions[actionIndex];
|
||||||
|
|
||||||
|
if (!event.shiftKey) {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
|
||||||
|
name: action.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
|
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
|
||||||
});
|
});
|
||||||
|
|
@ -148,43 +175,49 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
* Add a new feature to the item, prompting the user for its type.
|
* Add a new feature to the item, prompting the user for its type.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #addFeature(_event, _button) {
|
static async #addFeature(_, target) {
|
||||||
const feature = await game.items.documentClass.create({
|
const { type } = target.dataset;
|
||||||
type: 'feature',
|
const cls = foundry.documents.Item.implementation;
|
||||||
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') })
|
const feature = await cls.create({
|
||||||
|
'type': 'feature',
|
||||||
|
'name': cls.defaultName({ type: 'feature' }),
|
||||||
|
'system.subType': CONFIG.DH.ITEM.featureSubTypes[type]
|
||||||
});
|
});
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.features': [...this.document.system.features.filter(x => x).map(x => x.uuid), feature.uuid]
|
'system.features': [...this.document.system.features, feature].map(f => f.uuid)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit an existing feature on the item
|
|
||||||
* @type {ApplicationClickAction}
|
|
||||||
*/
|
|
||||||
static async #editFeature(_event, button) {
|
|
||||||
const target = button.closest('.feature-item');
|
|
||||||
const feature = this.document.system.features.find(x => x?.id === target.id);
|
|
||||||
if (!feature) {
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.sheet.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a feature from the item.
|
* Remove a feature from the item.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #removeFeature(event, button) {
|
static async #deleteFeature(_, target) {
|
||||||
event.stopPropagation();
|
const feature = getDocFromElement(target);
|
||||||
const target = button.closest('.feature-item');
|
if (!feature) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||||
|
await feature.update({ 'system.subType': null });
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.features': this.document.system.features
|
'system.features': this.document.system.features.map(x => x.uuid).filter(uuid => uuid !== feature.uuid)
|
||||||
.filter(feature => feature && feature.id !== target.id)
|
});
|
||||||
.map(x => x.uuid)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a resource to the item.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #addResource() {
|
||||||
|
await this.document.update({
|
||||||
|
'system.resource': { type: 'simple', value: 0 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the resource from the item.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #removeResource() {
|
||||||
|
await this.document.update({
|
||||||
|
'system.resource': null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,13 +235,30 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
if (featureItem) {
|
if (featureItem) {
|
||||||
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
|
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
|
||||||
if (!feature) {
|
if (!feature) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
|
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
||||||
|
} else {
|
||||||
|
const actionItem = event.currentTarget.closest('.action-item');
|
||||||
|
if (actionItem) {
|
||||||
|
const action = this.document.system.actions[actionItem.dataset.index];
|
||||||
|
if (!action) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionData = {
|
||||||
|
type: 'Action',
|
||||||
|
data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid },
|
||||||
|
fromInternal: true
|
||||||
|
};
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(actionData));
|
||||||
|
event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ export default class DHHeritageSheet extends DHBaseItemSheet {
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
|
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
|
||||||
feature: {
|
|
||||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
|
|
||||||
scrollable: ['.feature']
|
|
||||||
},
|
|
||||||
effects: {
|
effects: {
|
||||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
scrollable: ['.effects']
|
scrollable: ['.effects']
|
||||||
|
|
|
||||||
85
module/applications/sheets/api/item-attachment-sheet.mjs
Normal file
85
module/applications/sheets/api/item-attachment-sheet.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
export default function ItemAttachmentSheet(Base) {
|
||||||
|
return class extends Base {
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
...super.DEFAULT_OPTIONS,
|
||||||
|
dragDrop: [
|
||||||
|
...(super.DEFAULT_OPTIONS.dragDrop || []),
|
||||||
|
{ dragSelector: null, dropSelector: '.attachments-section' }
|
||||||
|
],
|
||||||
|
actions: {
|
||||||
|
...super.DEFAULT_OPTIONS.actions,
|
||||||
|
removeAttachment: this.#removeAttachment
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
...super.PARTS,
|
||||||
|
attachments: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-attachments.hbs',
|
||||||
|
scrollable: ['.attachments']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static TABS = {
|
||||||
|
...super.TABS,
|
||||||
|
primary: {
|
||||||
|
...super.TABS?.primary,
|
||||||
|
tabs: [...(super.TABS?.primary?.tabs || []), { id: 'attachments' }],
|
||||||
|
initial: super.TABS?.primary?.initial || 'description',
|
||||||
|
labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
await super._preparePartContext(partId, context);
|
||||||
|
|
||||||
|
if (partId === 'attachments') {
|
||||||
|
context.attachedItems = await prepareAttachmentContext(this.document);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
const data = TextEditor.getDragEventData(event);
|
||||||
|
|
||||||
|
const attachmentsSection = event.target.closest('.attachments-section');
|
||||||
|
if (!attachmentsSection) return super._onDrop(event);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const item = await Item.implementation.fromDropData(data);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
// Call the data model's public method
|
||||||
|
await this.document.system.addAttachment(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #removeAttachment(event, target) {
|
||||||
|
// Call the data model's public method
|
||||||
|
await this.document.system.removeAttachment(target.dataset.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
await super._preparePartContext(partId, context);
|
||||||
|
|
||||||
|
if (partId === 'attachments') {
|
||||||
|
// Keep this simple UI preparation in the mixin
|
||||||
|
const attachedUUIDs = this.document.system.attached;
|
||||||
|
context.attachedItems = await Promise.all(
|
||||||
|
attachedUUIDs.map(async uuid => {
|
||||||
|
const item = await fromUuid(uuid);
|
||||||
|
return {
|
||||||
|
uuid: uuid,
|
||||||
|
name: item?.name || 'Unknown Item',
|
||||||
|
img: item?.img || 'icons/svg/item-bag.svg'
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,80 @@ import DHHeritageSheet from '../api/heritage-sheet.mjs';
|
||||||
export default class AncestrySheet extends DHHeritageSheet {
|
export default class AncestrySheet extends DHHeritageSheet {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['ancestry']
|
classes: ['ancestry'],
|
||||||
|
actions: {
|
||||||
|
editFeature: AncestrySheet.#editFeature,
|
||||||
|
removeFeature: AncestrySheet.#removeFeature
|
||||||
|
},
|
||||||
|
dragDrop: [{ dragSelector: null, dropSelector: '.tab.features .drop-section' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' },
|
||||||
...super.PARTS
|
...super.PARTS,
|
||||||
|
features: { template: 'systems/daggerheart/templates/sheets/items/ancestry/features.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Clicks Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit an existing feature on the item
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #editFeature(_event, button) {
|
||||||
|
const target = button.closest('.feature-item');
|
||||||
|
const feature = this.document.system[`${target.dataset.type}Feature`];
|
||||||
|
if (!feature || Object.keys(feature).length === 0) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
feature.sheet.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a feature from the item.
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #removeFeature(event, button) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const target = button.closest('.feature-item');
|
||||||
|
const feature = this.document.system[`${target.dataset.type}Feature`];
|
||||||
|
|
||||||
|
if (feature) await feature.update({ 'system.subType': null });
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Drag/Drop */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On drop on the item.
|
||||||
|
* @param {DragEvent} event - The drag event
|
||||||
|
*/
|
||||||
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
|
const item = await fromUuid(data.uuid);
|
||||||
|
if (item?.type === 'feature') {
|
||||||
|
const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary';
|
||||||
|
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes[subType]) {
|
||||||
|
const error = subType === 'primary' ? 'featureNotPrimary' : 'featureNotSecondary';
|
||||||
|
ui.notifications.warn(game.i18n.localize(`DAGGERHEART.UI.Notifications.${error}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.update({ 'system.subType': subType });
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import DHBaseItemSheet from '../api/base-item.mjs';
|
import DHBaseItemSheet from '../api/base-item.mjs';
|
||||||
|
import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
|
||||||
|
|
||||||
export default class ArmorSheet extends DHBaseItemSheet {
|
export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['armor'],
|
classes: ['armor'],
|
||||||
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
|
||||||
tagifyConfigs: [
|
tagifyConfigs: [
|
||||||
{
|
{
|
||||||
selector: '.features-input',
|
selector: '.features-input',
|
||||||
|
|
@ -26,7 +26,12 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
||||||
settings: {
|
settings: {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
}
|
},
|
||||||
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
|
scrollable: ['.effects']
|
||||||
|
},
|
||||||
|
...super.PARTS
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -35,7 +40,7 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
||||||
|
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'settings':
|
case 'settings':
|
||||||
context.features = this.document.system.features.map(x => x.value);
|
context.features = this.document.system.armorFeatures.map(x => x.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +52,6 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
||||||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||||
*/
|
*/
|
||||||
static async #onFeatureSelect(selectedOptions) {
|
static async #onFeatureSelect(selectedOptions) {
|
||||||
await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) });
|
await this.document.update({ 'system.armorFeatures': selectedOptions.map(x => ({ value: x.value })) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import DHBaseItemSheet from '../api/base-item.mjs';
|
import DHBaseItemSheet from '../api/base-item.mjs';
|
||||||
|
import Tagify from '@yaireo/tagify';
|
||||||
|
|
||||||
export default class BeastformSheet extends DHBaseItemSheet {
|
export default class BeastformSheet extends DHBaseItemSheet {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -15,6 +16,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
||||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
|
||||||
scrollable: ['.features']
|
scrollable: ['.features']
|
||||||
},
|
},
|
||||||
|
advanced: { template: 'systems/daggerheart/templates/sheets/items/beastform/advanced.hbs' },
|
||||||
effects: {
|
effects: {
|
||||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
scrollable: ['.effects']
|
scrollable: ['.effects']
|
||||||
|
|
@ -23,16 +25,77 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
||||||
|
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'settings' }, { id: 'features' }, { id: 'effects' }],
|
tabs: [{ id: 'settings' }, { id: 'features' }, { id: 'advanced' }, { id: 'effects' }],
|
||||||
initial: 'settings',
|
initial: 'settings',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
const advantageOnInput = htmlElement.querySelector('.advantageon-input');
|
||||||
|
if (advantageOnInput) {
|
||||||
|
const tagifyElement = new Tagify(advantageOnInput, {
|
||||||
|
tagTextProp: 'name',
|
||||||
|
templates: {
|
||||||
|
tag(tagData) {
|
||||||
|
return `<tag
|
||||||
|
contenteditable='false'
|
||||||
|
spellcheck='false'
|
||||||
|
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
||||||
|
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
||||||
|
${this.getAttributes(tagData)}>
|
||||||
|
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
||||||
|
<div>
|
||||||
|
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
|
||||||
|
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
|
||||||
|
</div>
|
||||||
|
</tag>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tagifyElement.on('add', this.advantageOnAdd.bind(this));
|
||||||
|
tagifyElement.on('remove', this.advantageOnRemove.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context, options) {
|
||||||
await super._preparePartContext(partId, context);
|
await super._preparePartContext(partId, context, options);
|
||||||
|
|
||||||
|
switch (partId) {
|
||||||
|
case 'settings':
|
||||||
|
context.advantageOn = JSON.stringify(
|
||||||
|
Object.keys(context.document.system.advantageOn).map(key => ({
|
||||||
|
value: key,
|
||||||
|
name: context.document.system.advantageOn[key].value
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'effects':
|
||||||
|
context.effects.actives = context.effects.actives.map(effect => {
|
||||||
|
const data = effect.toObject();
|
||||||
|
data.id = effect.id;
|
||||||
|
if (effect.type === 'beastform') data.mandatory = true;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async advantageOnAdd(event) {
|
||||||
|
await this.document.update({
|
||||||
|
[`system.advantageOn.${foundry.utils.randomID()}`]: { value: event.detail.data.value }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async advantageOnRemove(event) {
|
||||||
|
await this.document.update({
|
||||||
|
[`system.advantageOn.-=${event.detail.data.value}`]: null
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,7 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
position: { width: 700 },
|
position: { width: 700 },
|
||||||
actions: {
|
actions: {
|
||||||
removeItemFromCollection: ClassSheet.#removeItemFromCollection,
|
removeItemFromCollection: ClassSheet.#removeItemFromCollection,
|
||||||
removeSuggestedItem: ClassSheet.#removeSuggestedItem,
|
removeSuggestedItem: ClassSheet.#removeSuggestedItem
|
||||||
viewDoc: ClassSheet.#viewDoc,
|
|
||||||
addFeature: this.addFeature,
|
|
||||||
editFeature: this.editFeature,
|
|
||||||
deleteFeature: this.deleteFeature
|
|
||||||
},
|
},
|
||||||
tagifyConfigs: [
|
tagifyConfigs: [
|
||||||
{
|
{
|
||||||
|
|
@ -46,13 +42,17 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
settings: {
|
settings: {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
|
scrollable: ['.effects']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }],
|
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }, { id: 'effects' }],
|
||||||
initial: 'description',
|
initial: 'description',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +78,7 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
const data = TextEditor.getDragEventData(event);
|
const data = TextEditor.getDragEventData(event);
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
|
|
@ -85,6 +86,28 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
||||||
});
|
});
|
||||||
|
} else if (item.type === 'feature') {
|
||||||
|
if (target.classList.contains('hope-feature')) {
|
||||||
|
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.hope) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotHope'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.hope });
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||||
|
});
|
||||||
|
} else if (target.classList.contains('class-feature')) {
|
||||||
|
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.class) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotClass'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.class });
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (item.type === 'weapon') {
|
} else if (item.type === 'weapon') {
|
||||||
if (target.classList.contains('primary-weapon-section')) {
|
if (target.classList.contains('primary-weapon-section')) {
|
||||||
if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary)
|
if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary)
|
||||||
|
|
@ -144,7 +167,7 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
static async #removeItemFromCollection(_event, element) {
|
static async #removeItemFromCollection(_event, element) {
|
||||||
const { uuid, target } = element.dataset;
|
const { uuid, target } = element.dataset;
|
||||||
const prop = foundry.utils.getProperty(this.document.system, target);
|
const prop = foundry.utils.getProperty(this.document.system, target);
|
||||||
await this.document.update({ [target]: prop.filter(i => i.uuid !== uuid) });
|
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -156,56 +179,4 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
const { target } = element.dataset;
|
const { target } = element.dataset;
|
||||||
await this.document.update({ [`system.characterGuide.${target}`]: null });
|
await this.document.update({ [`system.characterGuide.${target}`]: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the sheet of a item by UUID.
|
|
||||||
* @param {PointerEvent} _event -
|
|
||||||
* @param {HTMLElement} button
|
|
||||||
*/
|
|
||||||
static async #viewDoc(_event, button) {
|
|
||||||
const doc = await fromUuid(button.dataset.uuid);
|
|
||||||
doc.sheet.render({ force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
getActionPath(type) {
|
|
||||||
return type === 'hope' ? 'hopeFeatures' : 'classFeatures';
|
|
||||||
}
|
|
||||||
|
|
||||||
static async addFeature(_, target) {
|
|
||||||
const actionPath = this.getActionPath(target.dataset.type);
|
|
||||||
const feature = await game.items.documentClass.create({
|
|
||||||
type: 'feature',
|
|
||||||
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') })
|
|
||||||
});
|
|
||||||
await this.document.update({
|
|
||||||
[`system.${actionPath}`]: [
|
|
||||||
...this.document.system[actionPath].filter(x => x).map(x => x.uuid),
|
|
||||||
feature.uuid
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async editFeature(_, button) {
|
|
||||||
const target = button.closest('.feature-item');
|
|
||||||
const actionPath = this.getActionPath(button.dataset.type);
|
|
||||||
const feature = this.document.system[actionPath].find(x => x?.id === target.dataset.featureId);
|
|
||||||
if (!feature) {
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.sheet.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async deleteFeature(event, button) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const target = button.closest('.feature-item');
|
|
||||||
const actionPath = this.getActionPath(button.dataset.type);
|
|
||||||
|
|
||||||
await this.document.update({
|
|
||||||
[`system.${actionPath}`]: this.document.system[actionPath]
|
|
||||||
.filter(feature => feature && feature.id !== target.dataset.featureId)
|
|
||||||
.map(x => x.uuid)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ export default class CommunitySheet extends DHHeritageSheet {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
|
||||||
...super.PARTS
|
...super.PARTS,
|
||||||
|
features: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
|
||||||
|
scrollable: ['.feature']
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ export default class ConsumableSheet extends DHBaseItemSheet {
|
||||||
settings: {
|
settings: {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/consumable/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/consumable/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
|
scrollable: ['.effects']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { actionsTypes } from '../../../data/action/_module.mjs';
|
|
||||||
import DHActionConfig from '../../sheets-configs/action-config.mjs';
|
|
||||||
import DHBaseItemSheet from '../api/base-item.mjs';
|
import DHBaseItemSheet from '../api/base-item.mjs';
|
||||||
|
|
||||||
export default class FeatureSheet extends DHBaseItemSheet {
|
export default class FeatureSheet extends DHBaseItemSheet {
|
||||||
|
|
@ -7,13 +5,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
id: 'daggerheart-feature',
|
id: 'daggerheart-feature',
|
||||||
classes: ['feature'],
|
classes: ['feature'],
|
||||||
position: { height: 600 },
|
actions: {}
|
||||||
window: { resizable: true },
|
|
||||||
actions: {
|
|
||||||
addAction: FeatureSheet.#addAction,
|
|
||||||
editAction: FeatureSheet.#editAction,
|
|
||||||
removeAction: FeatureSheet.#removeAction
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
@ -21,6 +13,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' },
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
|
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
|
||||||
|
settings: { template: 'systems/daggerheart/templates/sheets/items/feature/settings.hbs' },
|
||||||
actions: {
|
actions: {
|
||||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
|
||||||
scrollable: ['.actions']
|
scrollable: ['.actions']
|
||||||
|
|
@ -34,97 +27,9 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
||||||
/**@override */
|
/**@override */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }],
|
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }, { id: 'effects' }],
|
||||||
initial: 'description',
|
initial: 'description',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/* Application Clicks Actions */
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render a dialog prompting the user to select an action type.
|
|
||||||
*
|
|
||||||
* @returns {Promise<object>} An object containing the selected action type.
|
|
||||||
*/
|
|
||||||
static async selectActionType() {
|
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
|
||||||
{ types: CONFIG.DH.ACTIONS.actionTypes }
|
|
||||||
),
|
|
||||||
title = 'Select Action Type';
|
|
||||||
|
|
||||||
return foundry.applications.api.DialogV2.prompt({
|
|
||||||
window: { title },
|
|
||||||
content,
|
|
||||||
ok: {
|
|
||||||
label: title,
|
|
||||||
callback: (event, button, dialog) => button.form.elements.type.value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new action to the item, prompting the user for its type.
|
|
||||||
* @param {PointerEvent} _event - The originating click event
|
|
||||||
* @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"]
|
|
||||||
*/
|
|
||||||
static async #addAction(_event, _button) {
|
|
||||||
const actionType = await FeatureSheet.selectActionType();
|
|
||||||
if (!actionType) return;
|
|
||||||
try {
|
|
||||||
const cls = actionsTypes[actionType] ?? actionsTypes.attack,
|
|
||||||
action = new cls(
|
|
||||||
{
|
|
||||||
_id: foundry.utils.randomID(),
|
|
||||||
type: actionType,
|
|
||||||
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
|
|
||||||
...cls.getSourceConfig(this.document)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parent: this.document
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
|
|
||||||
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit an existing action on the item
|
|
||||||
* @param {PointerEvent} _event - The originating click event
|
|
||||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editAction"]
|
|
||||||
*/
|
|
||||||
static async #editAction(_event, button) {
|
|
||||||
const action = this.document.system.actions[button.dataset.index];
|
|
||||||
await new DHActionConfig(action).render({ force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an action from the item.
|
|
||||||
* @param {PointerEvent} event - The originating click event
|
|
||||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
|
|
||||||
*/
|
|
||||||
static async #removeAction(event, button) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const actionIndex = button.closest('[data-index]').dataset.index;
|
|
||||||
await this.document.update({
|
|
||||||
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ export default class MiscellaneousSheet extends DHBaseItemSheet {
|
||||||
settings: {
|
settings: {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/miscellaneous/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/miscellaneous/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
|
scrollable: ['.effects']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,7 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
||||||
classes: ['subclass'],
|
classes: ['subclass'],
|
||||||
position: { width: 600 },
|
position: { width: 600 },
|
||||||
window: { resizable: false },
|
window: { resizable: false },
|
||||||
actions: {
|
actions: {}
|
||||||
addFeature: this.addFeature,
|
|
||||||
editFeature: this.editFeature,
|
|
||||||
deleteFeature: this.deleteFeature
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
@ -25,53 +21,29 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
||||||
settings: {
|
settings: {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/subclass/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/subclass/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
|
},
|
||||||
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
|
scrollable: ['.effects']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TABS = {
|
static TABS = {
|
||||||
primary: {
|
primary: {
|
||||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }],
|
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }, { id: 'effects' }],
|
||||||
initial: 'description',
|
initial: 'description',
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static async addFeature(_, target) {
|
|
||||||
const feature = await game.items.documentClass.create({
|
|
||||||
type: 'feature',
|
|
||||||
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') })
|
|
||||||
});
|
|
||||||
await this.document.update({
|
|
||||||
[`system.${target.dataset.type}`]: feature.uuid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async editFeature(_, button) {
|
|
||||||
const feature = this.document.system[button.dataset.type];
|
|
||||||
if (!feature) {
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.sheet.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async deleteFeature(event, button) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
await this.document.update({
|
|
||||||
[`system.${button.dataset.type}`]: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const featureItem = event.currentTarget.closest('.drop-section');
|
const featureItem = event.currentTarget.closest('.drop-section');
|
||||||
|
|
||||||
if (featureItem) {
|
if (featureItem) {
|
||||||
const feature = this.document.system[featureItem.dataset.type];
|
const feature = this.document.system[featureItem.dataset.type];
|
||||||
if (!feature) {
|
if (!feature) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,18 +54,45 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (data.fromInternal) return;
|
if (data.fromInternal) return;
|
||||||
|
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
if (item?.type === 'feature') {
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
const dropSection = event.target.closest('.drop-section');
|
if (item.type === 'feature') {
|
||||||
if (this.document.system[dropSection.dataset.type]) {
|
if (target.dataset.type === 'foundation') {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsFull'));
|
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.foundation) {
|
||||||
return;
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotFoundation'));
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.document.update({ [`system.${dropSection.dataset.type}`]: item.uuid });
|
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.foundation });
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||||
|
});
|
||||||
|
} else if (target.dataset.type === 'specialization') {
|
||||||
|
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.specialization) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotSpecialization'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.specialization });
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||||
|
});
|
||||||
|
} else if (target.dataset.type === 'mastery') {
|
||||||
|
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.mastery) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotMastery'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.mastery });
|
||||||
|
await this.document.update({
|
||||||
|
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import DHBaseItemSheet from '../api/base-item.mjs';
|
import DHBaseItemSheet from '../api/base-item.mjs';
|
||||||
|
import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
|
||||||
|
|
||||||
export default class WeaponSheet extends DHBaseItemSheet {
|
export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['weapon'],
|
classes: ['weapon'],
|
||||||
|
|
@ -25,15 +26,20 @@ export default class WeaponSheet extends DHBaseItemSheet {
|
||||||
settings: {
|
settings: {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
}
|
},
|
||||||
|
effects: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||||
|
scrollable: ['.effects']
|
||||||
|
},
|
||||||
|
...super.PARTS
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context) {
|
||||||
super._preparePartContext(partId, context);
|
await super._preparePartContext(partId, context);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'settings':
|
case 'settings':
|
||||||
context.features = this.document.system.features.map(x => x.value);
|
context.features = this.document.system.weaponFeatures.map(x => x.value);
|
||||||
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
|
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +51,6 @@ export default class WeaponSheet extends DHBaseItemSheet {
|
||||||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||||
*/
|
*/
|
||||||
static async #onFeatureSelect(selectedOptions) {
|
static async #onFeatureSelect(selectedOptions) {
|
||||||
await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) });
|
await this.document.update({ 'system.weaponFeatures': selectedOptions.map(x => ({ value: x.value })) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ export { default as DhChatLog } from './chatLog.mjs';
|
||||||
export { default as DhCombatTracker } from './combatTracker.mjs';
|
export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||||
export * as DhCountdowns from './countdowns.mjs';
|
export * as DhCountdowns from './countdowns.mjs';
|
||||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||||
|
export { default as DhHotbar } from './hotbar.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||||
constructor() {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
|
|
||||||
this.targetTemplate = {
|
this.targetTemplate = {
|
||||||
activeLayer: undefined,
|
activeLayer: undefined,
|
||||||
|
|
@ -83,15 +83,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
actor.system.attack?._id === actionId
|
actor.system.attack?._id === actionId
|
||||||
? actor.system.attack
|
? actor.system.attack
|
||||||
: item.system.attack?._id === actionId
|
: item.system.attack?._id === actionId
|
||||||
? item.system.attack
|
? item.system.attack
|
||||||
: item?.system?.actions?.find(a => a._id === actionId);
|
: item?.system?.actions?.find(a => a._id === actionId);
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
onRollDamage = async (event, message) => {
|
onRollDamage = async (event, message) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const actor = await this.getActor(message.system.source.actor);
|
const actor = await this.getActor(message.system.source.actor);
|
||||||
if (!actor || !game.user.isGM) return true;
|
if (game.user.character?.id !== actor.id && !game.user.isGM) return true;
|
||||||
if (message.system.source.item && message.system.source.action) {
|
if (message.system.source.item && message.system.source.action) {
|
||||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||||
if (!action || !action?.rollDamage) return;
|
if (!action || !action?.rollDamage) return;
|
||||||
|
|
@ -190,7 +190,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
|
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game.canvas.pan(token);
|
game.canvas.pan(token);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -212,13 +211,25 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targets.length === 0)
|
if (targets.length === 0)
|
||||||
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) {
|
|
||||||
let damage = message.system.roll.total;
|
|
||||||
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
|
|
||||||
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
|
|
||||||
|
|
||||||
await target.actor.takeDamage(damage, message.system.roll.type);
|
for (let target of targets) {
|
||||||
|
let damages = foundry.utils.deepClone(message.system.damage?.roll ?? message.system.roll);
|
||||||
|
if (
|
||||||
|
message.system.onSave &&
|
||||||
|
message.system.targets.find(t => t.id === target.id)?.saved?.success === true
|
||||||
|
) {
|
||||||
|
const mod = CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1;
|
||||||
|
Object.entries(damages).forEach(([k, v]) => {
|
||||||
|
v.total = 0;
|
||||||
|
v.parts.forEach(part => {
|
||||||
|
part.total = Math.ceil(part.total * mod);
|
||||||
|
v.total += part.total;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
target.actor.takeDamage(damages);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -227,10 +238,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
const targets = Array.from(game.user.targets);
|
const targets = Array.from(game.user.targets);
|
||||||
|
|
||||||
if (targets.length === 0)
|
if (targets.length === 0)
|
||||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||||
|
|
||||||
for (var target of targets) {
|
for (var target of targets) {
|
||||||
await target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]);
|
target.actor.takeHealing(message.system.roll);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -289,53 +300,54 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
await actor.useAction(action);
|
await actor.useAction(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
actionUseButton = async (_, message) => {
|
actionUseButton = async (event, message) => {
|
||||||
|
const { moveIndex, actionIndex } = event.currentTarget.dataset;
|
||||||
const parent = await foundry.utils.fromUuid(message.system.actor);
|
const parent = await foundry.utils.fromUuid(message.system.actor);
|
||||||
const actionType = Object.values(message.system.moves)[0].actions[0];
|
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
||||||
const cls = CONFIG.DH.ACTIONS.actionTypes[actionType.type];
|
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
||||||
const action = new cls(
|
const action = new cls(
|
||||||
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
|
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
|
||||||
{ parent: parent }
|
{ parent: parent.system }
|
||||||
);
|
);
|
||||||
|
|
||||||
action.use();
|
action.use(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Reroll Functionality
|
//Reroll Functionality
|
||||||
rerollEvent = async(event,message)=> {
|
rerollEvent = async (event, message) => {
|
||||||
let DieTerm=foundry.dice.terms.Die;
|
let DieTerm = foundry.dice.terms.Die;
|
||||||
let dicetype = event.target.value;
|
let dicetype = event.target.value;
|
||||||
let originalRoll_parsed=message.rolls.map(roll => JSON.parse(roll))[0];
|
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
||||||
console.log("Parsed Map:",originalRoll_parsed);
|
console.log('Parsed Map:', originalRoll_parsed);
|
||||||
let originalRoll=Roll.fromData(originalRoll_parsed);
|
let originalRoll = Roll.fromData(originalRoll_parsed);
|
||||||
let diceIndex;
|
let diceIndex;
|
||||||
console.log("Dice to reroll is:",dicetype,", and the message id is:",message._id,originalRoll_parsed);
|
console.log('Dice to reroll is:', dicetype, ', and the message id is:', message._id, originalRoll_parsed);
|
||||||
console.log("Original Roll Terms:",originalRoll.terms);
|
console.log('Original Roll Terms:', originalRoll.terms);
|
||||||
switch(dicetype){
|
switch (dicetype) {
|
||||||
case "hope": {
|
case 'hope': {
|
||||||
diceIndex=0; //Hope Die
|
diceIndex = 0; //Hope Die
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
case "fear" :{
|
case 'fear': {
|
||||||
diceIndex=2; //Fear Die
|
diceIndex = 2; //Fear Die
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
default:
|
default:
|
||||||
ui.notifications.warn("Invalid Dice type selected.");
|
ui.notifications.warn('Invalid Dice type selected.');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let rollClone=originalRoll.clone();
|
let rollClone = originalRoll.clone();
|
||||||
let rerolledTerm=originalRoll.terms[diceIndex];
|
let rerolledTerm = originalRoll.terms[diceIndex];
|
||||||
console.log("originalRoll:",originalRoll,"rerolledTerm",rerolledTerm);
|
console.log('originalRoll:', originalRoll, 'rerolledTerm', rerolledTerm);
|
||||||
if (!(rerolledTerm instanceof DieTerm)) {
|
if (!(rerolledTerm instanceof DieTerm)) {
|
||||||
ui.notifications.error("Selected term is not a die.");
|
ui.notifications.error('Selected term is not a die.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await rollClone.reroll({allowStrings:true})[diceIndex];
|
await rollClone.reroll({ allowStrings: true })[diceIndex];
|
||||||
console.log(rollClone);
|
console.log(rollClone);
|
||||||
await rollClone.evaluate({allowStrings:true});
|
await rollClone.evaluate({ allowStrings: true });
|
||||||
console.log(rollClone.result);
|
console.log(rollClone.result);
|
||||||
/*
|
/*
|
||||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: { title: 'Confirm Reroll' },
|
window: { title: 'Confirm Reroll' },
|
||||||
content: `<p>You have rerolled your <strong>${dicetype}</strong> die to <strong>${rollClone.result}</strong>.</p><p>Apply this new roll?</p>`
|
content: `<p>You have rerolled your <strong>${dicetype}</strong> die to <strong>${rollClone.result}</strong>.</p><p>Apply this new roll?</p>`
|
||||||
|
|
@ -343,5 +355,5 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
if (!confirm) return;
|
if (!confirm) return;
|
||||||
rollClone.toMessage({flavor: 'Selective reroll applied for ${dicetype}.'});
|
rollClone.toMessage({flavor: 'Selective reroll applied for ${dicetype}.'});
|
||||||
console.log("Updated Roll",rollClone);*/
|
console.log("Updated Roll",rollClone);*/
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,11 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCombatantSpotlight(combatantId) {
|
async setCombatantSpotlight(combatantId) {
|
||||||
|
const update = {
|
||||||
|
system: {
|
||||||
|
'spotlight.requesting': false
|
||||||
|
}
|
||||||
|
};
|
||||||
const combatant = this.viewed.combatants.get(combatantId);
|
const combatant = this.viewed.combatants.get(combatantId);
|
||||||
|
|
||||||
const toggleTurn = this.viewed.combatants.contents
|
const toggleTurn = this.viewed.combatants.contents
|
||||||
|
|
@ -73,10 +78,18 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
.map(x => x.id)
|
.map(x => x.id)
|
||||||
.indexOf(combatantId);
|
.indexOf(combatantId);
|
||||||
|
|
||||||
if (this.viewed.turn !== toggleTurn) Hooks.callAll(CONFIG.DH.HOOKS.spotlight, {});
|
if (this.viewed.turn !== toggleTurn) {
|
||||||
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.spotlight.id);
|
||||||
|
|
||||||
|
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
|
||||||
|
if (autoPoints) {
|
||||||
|
update.system.actionTokens = Math.max(combatant.system.actionTokens - 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
|
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
|
||||||
await combatant.update({ 'system.spotlight.requesting': false });
|
await combatant.update(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async requestSpotlight(_, target) {
|
static async requestSpotlight(_, target) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { countdownTypes } from '../../config/generalConfig.mjs';
|
|
||||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
import constructHTMLButton from '../../helpers/utils.mjs';
|
import constructHTMLButton from '../../helpers/utils.mjs';
|
||||||
import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
|
import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
|
||||||
|
|
@ -328,43 +327,29 @@ export class EncounterCountdowns extends Countdowns {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerCountdownApplicationHooks = () => {
|
export async function updateCountdowns(progressType) {
|
||||||
const updateCountdowns = async shouldProgress => {
|
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).countdowns) {
|
const update = Object.keys(countdownSetting).reduce((update, typeKey) => {
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
return foundry.utils.mergeObject(
|
||||||
for (let countdownCategoryKey in countdownSetting) {
|
update,
|
||||||
const countdownCategory = countdownSetting[countdownCategoryKey];
|
Object.keys(countdownSetting[typeKey].countdowns).reduce((acc, countdownKey) => {
|
||||||
for (let countdownKey in countdownCategory.countdowns) {
|
const countdown = countdownSetting[typeKey].countdowns[countdownKey];
|
||||||
const countdown = countdownCategory.countdowns[countdownKey];
|
if (countdown.progress.current > 0 && countdown.progress.type.value === progressType) {
|
||||||
|
acc[`${typeKey}.countdowns.${countdownKey}.progress.current`] = countdown.progress.current - 1;
|
||||||
if (shouldProgress(countdown)) {
|
|
||||||
await countdownSetting.updateSource({
|
|
||||||
[`${countdownCategoryKey}.countdowns.${countdownKey}.progress.current`]:
|
|
||||||
countdown.progress.current - 1
|
|
||||||
});
|
|
||||||
await game.settings.set(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
|
||||||
countdownSetting
|
|
||||||
);
|
|
||||||
foundry.applications.instances.get(`${countdownCategoryKey}-countdowns`)?.render();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Hooks.on(CONFIG.DH.HOOKS.characterAttack, async () => {
|
return acc;
|
||||||
updateCountdowns(countdown => {
|
}, {})
|
||||||
return (
|
);
|
||||||
countdown.progress.type.value === countdownTypes.characterAttack.id && countdown.progress.current > 0
|
}, {});
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Hooks.on(CONFIG.DH.HOOKS.spotlight, async () => {
|
await countdownSetting.updateSource(update);
|
||||||
updateCountdowns(countdown => {
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting);
|
||||||
return countdown.progress.type.value === countdownTypes.spotlight.id && countdown.progress.current > 0;
|
|
||||||
});
|
const data = { refreshType: RefreshType.Countdown };
|
||||||
|
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data
|
||||||
});
|
});
|
||||||
};
|
Hooks.callAll(socketEvent.Refresh, data);
|
||||||
|
}
|
||||||
|
|
|
||||||
129
module/applications/ui/hotbar.mjs
Normal file
129
module/applications/ui/hotbar.mjs
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
export default class DhHotbar extends foundry.applications.ui.Hotbar {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.setupHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async useItem(uuid) {
|
||||||
|
const item = await fromUuid(uuid);
|
||||||
|
if (!item) {
|
||||||
|
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
|
||||||
|
format: {
|
||||||
|
name: game.i18n.localize('Document'),
|
||||||
|
identifier: uuid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.use({});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async useAction(itemUuid, actionId) {
|
||||||
|
const item = await foundry.utils.fromUuid(itemUuid);
|
||||||
|
if (!item) {
|
||||||
|
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
|
||||||
|
format: {
|
||||||
|
name: game.i18n.localize('Document'),
|
||||||
|
identifier: itemUuid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = item.system.actions.find(x => x.id === actionId);
|
||||||
|
if (!action) {
|
||||||
|
return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing');
|
||||||
|
}
|
||||||
|
|
||||||
|
await action.use({});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async useAttack(actorUuid) {
|
||||||
|
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||||
|
if (!actor) {
|
||||||
|
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
|
||||||
|
format: {
|
||||||
|
name: game.i18n.localize('Document'),
|
||||||
|
identifier: actorUuid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const attack = actor.system.attack;
|
||||||
|
if (!attack) {
|
||||||
|
return ui.notifications.warn('DAGGERHEART.UI.Notifications.attackIsMissing');
|
||||||
|
}
|
||||||
|
|
||||||
|
await attack.use({});
|
||||||
|
}
|
||||||
|
|
||||||
|
setupHooks() {
|
||||||
|
Hooks.on('hotbarDrop', (bar, data, slot) => {
|
||||||
|
if (data.type === 'Item') {
|
||||||
|
const item = foundry.utils.fromUuidSync(data.uuid);
|
||||||
|
if (item.uuid.startsWith('Compendium') || !item.isOwned || !item.isOwner) return true;
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case 'ancestry':
|
||||||
|
case 'community':
|
||||||
|
case 'class':
|
||||||
|
case 'subclass':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
this.createItemMacro(item, slot);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (data.type === 'Action') {
|
||||||
|
const item = foundry.utils.fromUuidSync(data.data.itemUuid);
|
||||||
|
if (item.uuid.startsWith('Compendium')) return true;
|
||||||
|
if (!item.isOwned || !item.isOwner) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedActionMacro'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createActionMacro(data, slot);
|
||||||
|
return false;
|
||||||
|
} else if (data.type === 'Attack') {
|
||||||
|
const actor = foundry.utils.fromUuidSync(data.actorUuid);
|
||||||
|
if (actor.uuid.startsWith('Compendium')) return true;
|
||||||
|
if (!actor.isOwner) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedAttackMacro'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createAttackMacro(data, slot);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createItemMacro(data, slot) {
|
||||||
|
const macro = await Macro.implementation.create({
|
||||||
|
name: data.name,
|
||||||
|
type: CONST.MACRO_TYPES.SCRIPT,
|
||||||
|
img: data.img,
|
||||||
|
command: `await game.system.api.applications.ui.DhHotbar.useItem("${data.uuid}");`
|
||||||
|
});
|
||||||
|
await game.user.assignHotbarMacro(macro, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createActionMacro(data, slot) {
|
||||||
|
const macro = await Macro.implementation.create({
|
||||||
|
name: data.data.name,
|
||||||
|
type: CONST.MACRO_TYPES.SCRIPT,
|
||||||
|
img: data.data.img,
|
||||||
|
command: `await game.system.api.applications.ui.DhHotbar.useAction("${data.data.itemUuid}", "${data.data.id}");`
|
||||||
|
});
|
||||||
|
await game.user.assignHotbarMacro(macro, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAttackMacro(data, slot) {
|
||||||
|
const macro = await Macro.implementation.create({
|
||||||
|
name: data.name,
|
||||||
|
type: CONST.MACRO_TYPES.SCRIPT,
|
||||||
|
img: data.img,
|
||||||
|
command: `await game.system.api.applications.ui.DhHotbar.useAttack("${data.actorUuid}");`
|
||||||
|
});
|
||||||
|
await game.user.assignHotbarMacro(macro, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -100,7 +100,7 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const { clientX, clientY } = event;
|
const { clientX, clientY } = event;
|
||||||
const selector = '[data-item-id]';
|
const selector = '[data-item-uuid]';
|
||||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||||
target?.dispatchEvent(
|
target?.dispatchEvent(
|
||||||
new PointerEvent('contextmenu', {
|
new PointerEvent('contextmenu', {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
export { default as DhMeasuredTemplate } from './measuredTemplate.mjs';
|
||||||
export { default as DhRuler } from './ruler.mjs';
|
export { default as DhRuler } from './ruler.mjs';
|
||||||
|
export { default as DhTemplateLayer } from './templateLayer.mjs';
|
||||||
|
export { default as DhTokenPlaceable } from './token.mjs';
|
||||||
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
export { default as DhTokenRuler } from './tokenRuler.mjs';
|
||||||
|
|
|
||||||
116
module/canvas/placeables/templateLayer.mjs
Normal file
116
module/canvas/placeables/templateLayer.mjs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
export default class DhTemplateLayer extends foundry.canvas.layers.TemplateLayer {
|
||||||
|
static prepareSceneControls() {
|
||||||
|
const sc = foundry.applications.ui.SceneControls;
|
||||||
|
return {
|
||||||
|
name: 'templates',
|
||||||
|
order: 2,
|
||||||
|
title: 'CONTROLS.GroupMeasure',
|
||||||
|
icon: 'fa-solid fa-ruler-combined',
|
||||||
|
visible: game.user.can('TEMPLATE_CREATE'),
|
||||||
|
onChange: (event, active) => {
|
||||||
|
if (active) canvas.templates.activate();
|
||||||
|
},
|
||||||
|
onToolChange: () => canvas.templates.setAllRenderFlags({ refreshState: true }),
|
||||||
|
tools: {
|
||||||
|
circle: {
|
||||||
|
name: 'circle',
|
||||||
|
order: 1,
|
||||||
|
title: 'CONTROLS.MeasureCircle',
|
||||||
|
icon: 'fa-regular fa-circle',
|
||||||
|
toolclip: {
|
||||||
|
src: 'toolclips/tools/measure-circle.webm',
|
||||||
|
heading: 'CONTROLS.MeasureCircle',
|
||||||
|
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cone: {
|
||||||
|
name: 'cone',
|
||||||
|
order: 2,
|
||||||
|
title: 'CONTROLS.MeasureCone',
|
||||||
|
icon: 'fa-solid fa-angle-left',
|
||||||
|
toolclip: {
|
||||||
|
src: 'toolclips/tools/measure-cone.webm',
|
||||||
|
heading: 'CONTROLS.MeasureCone',
|
||||||
|
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inFront: {
|
||||||
|
name: 'inFront',
|
||||||
|
order: 3,
|
||||||
|
title: 'CONTROLS.inFront',
|
||||||
|
icon: 'fa-solid fa-eye',
|
||||||
|
toolclip: {
|
||||||
|
src: 'toolclips/tools/measure-cone.webm',
|
||||||
|
heading: 'CONTROLS.inFront',
|
||||||
|
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rect: {
|
||||||
|
name: 'rect',
|
||||||
|
order: 4,
|
||||||
|
title: 'CONTROLS.MeasureRect',
|
||||||
|
icon: 'fa-regular fa-square',
|
||||||
|
toolclip: {
|
||||||
|
src: 'toolclips/tools/measure-rect.webm',
|
||||||
|
heading: 'CONTROLS.MeasureRect',
|
||||||
|
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ray: {
|
||||||
|
name: 'ray',
|
||||||
|
order: 5,
|
||||||
|
title: 'CONTROLS.MeasureRay',
|
||||||
|
icon: 'fa-solid fa-up-down',
|
||||||
|
toolclip: {
|
||||||
|
src: 'toolclips/tools/measure-ray.webm',
|
||||||
|
heading: 'CONTROLS.MeasureRay',
|
||||||
|
items: sc.buildToolclipItems(['create', 'move', 'edit', 'hide', 'delete', 'rotate'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear: {
|
||||||
|
name: 'clear',
|
||||||
|
order: 6,
|
||||||
|
title: 'CONTROLS.MeasureClear',
|
||||||
|
icon: 'fa-solid fa-trash',
|
||||||
|
visible: game.user.isGM,
|
||||||
|
onChange: () => canvas.templates.deleteAll(),
|
||||||
|
button: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activeTool: 'circle'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDragLeftStart(event) {
|
||||||
|
const interaction = event.interactionData;
|
||||||
|
|
||||||
|
// Snap the origin to the grid
|
||||||
|
if (!event.shiftKey) interaction.origin = this.getSnappedPoint(interaction.origin);
|
||||||
|
|
||||||
|
// Create a pending MeasuredTemplateDocument
|
||||||
|
const tool = game.activeTool === 'inFront' ? 'cone' : game.activeTool;
|
||||||
|
const previewData = {
|
||||||
|
user: game.user.id,
|
||||||
|
t: tool,
|
||||||
|
x: interaction.origin.x,
|
||||||
|
y: interaction.origin.y,
|
||||||
|
sort: Math.max(this.getMaxSort() + 1, 0),
|
||||||
|
distance: 1,
|
||||||
|
direction: 0,
|
||||||
|
fillColor: game.user.color || '#FF0000',
|
||||||
|
hidden: event.altKey
|
||||||
|
};
|
||||||
|
const defaults = CONFIG.MeasuredTemplate.defaults;
|
||||||
|
if (game.activeTool === 'cone') previewData.angle = defaults.angle;
|
||||||
|
else if (game.activeTool === 'inFront') previewData.angle = 180;
|
||||||
|
else if (game.activeTool === 'ray') previewData.width = defaults.width * canvas.dimensions.distance;
|
||||||
|
const cls = foundry.utils.getDocumentClass('MeasuredTemplate');
|
||||||
|
const doc = new cls(previewData, { parent: canvas.scene });
|
||||||
|
|
||||||
|
// Create a preview MeasuredTemplate object
|
||||||
|
const template = new this.constructor.placeableClass(doc);
|
||||||
|
doc._object = template;
|
||||||
|
interaction.preview = this.preview.addChild(template);
|
||||||
|
template.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
36
module/canvas/placeables/token.mjs
Normal file
36
module/canvas/placeables/token.mjs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _drawEffects() {
|
||||||
|
this.effects.renderable = false;
|
||||||
|
|
||||||
|
// Clear Effects Container
|
||||||
|
this.effects.removeChildren().forEach(c => c.destroy());
|
||||||
|
this.effects.bg = this.effects.addChild(new PIXI.Graphics());
|
||||||
|
this.effects.bg.zIndex = -1;
|
||||||
|
this.effects.overlay = null;
|
||||||
|
|
||||||
|
// Categorize effects
|
||||||
|
const activeEffects = this.actor ? Array.from(this.actor.effects).filter(x => !x.disabled) : [];
|
||||||
|
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag('core', 'overlay'));
|
||||||
|
|
||||||
|
// Draw effects
|
||||||
|
const promises = [];
|
||||||
|
for (const [i, effect] of activeEffects.entries()) {
|
||||||
|
if (!effect.img) continue;
|
||||||
|
const promise =
|
||||||
|
effect === overlayEffect
|
||||||
|
? this._drawOverlay(effect.img, effect.tint)
|
||||||
|
: this._drawEffect(effect.img, effect.tint);
|
||||||
|
promises.push(
|
||||||
|
promise.then(e => {
|
||||||
|
if (e) e.zIndex = i;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
|
||||||
|
this.effects.sortChildren();
|
||||||
|
this.effects.renderable = true;
|
||||||
|
this.renderFlags.set({ refreshEffects: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ export * as domainConfig from './domainConfig.mjs';
|
||||||
export * as effectConfig from './effectConfig.mjs';
|
export * as effectConfig from './effectConfig.mjs';
|
||||||
export * as flagsConfig from './flagsConfig.mjs';
|
export * as flagsConfig from './flagsConfig.mjs';
|
||||||
export * as generalConfig from './generalConfig.mjs';
|
export * as generalConfig from './generalConfig.mjs';
|
||||||
export * as hooksConfig from './hooksConfig.mjs';
|
|
||||||
export * as itemConfig from './itemConfig.mjs';
|
export * as itemConfig from './itemConfig.mjs';
|
||||||
export * as settingsConfig from './settingsConfig.mjs';
|
export * as settingsConfig from './settingsConfig.mjs';
|
||||||
export * as systemConfig from './system.mjs';
|
export * as systemConfig from './system.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export const abilities = {
|
export const abilities = {
|
||||||
agility: {
|
agility: {
|
||||||
|
id: 'agility',
|
||||||
label: 'DAGGERHEART.CONFIG.Traits.agility.name',
|
label: 'DAGGERHEART.CONFIG.Traits.agility.name',
|
||||||
verbs: [
|
verbs: [
|
||||||
'DAGGERHEART.CONFIG.Traits.agility.verb.sprint',
|
'DAGGERHEART.CONFIG.Traits.agility.verb.sprint',
|
||||||
|
|
@ -8,6 +9,7 @@ export const abilities = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
strength: {
|
strength: {
|
||||||
|
id: 'strength',
|
||||||
label: 'DAGGERHEART.CONFIG.Traits.strength.name',
|
label: 'DAGGERHEART.CONFIG.Traits.strength.name',
|
||||||
verbs: [
|
verbs: [
|
||||||
'DAGGERHEART.CONFIG.Traits.strength.verb.lift',
|
'DAGGERHEART.CONFIG.Traits.strength.verb.lift',
|
||||||
|
|
@ -16,6 +18,7 @@ export const abilities = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
finesse: {
|
finesse: {
|
||||||
|
id: 'finesse',
|
||||||
label: 'DAGGERHEART.CONFIG.Traits.finesse.name',
|
label: 'DAGGERHEART.CONFIG.Traits.finesse.name',
|
||||||
verbs: [
|
verbs: [
|
||||||
'DAGGERHEART.CONFIG.Traits.finesse.verb.control',
|
'DAGGERHEART.CONFIG.Traits.finesse.verb.control',
|
||||||
|
|
@ -24,6 +27,7 @@ export const abilities = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
instinct: {
|
instinct: {
|
||||||
|
id: 'instinct',
|
||||||
label: 'DAGGERHEART.CONFIG.Traits.instinct.name',
|
label: 'DAGGERHEART.CONFIG.Traits.instinct.name',
|
||||||
verbs: [
|
verbs: [
|
||||||
'DAGGERHEART.CONFIG.Traits.instinct.verb.perceive',
|
'DAGGERHEART.CONFIG.Traits.instinct.verb.perceive',
|
||||||
|
|
@ -32,6 +36,7 @@ export const abilities = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
presence: {
|
presence: {
|
||||||
|
id: 'presence',
|
||||||
label: 'DAGGERHEART.CONFIG.Traits.presence.name',
|
label: 'DAGGERHEART.CONFIG.Traits.presence.name',
|
||||||
verbs: [
|
verbs: [
|
||||||
'DAGGERHEART.CONFIG.Traits.presence.verb.charm',
|
'DAGGERHEART.CONFIG.Traits.presence.verb.charm',
|
||||||
|
|
@ -40,6 +45,7 @@ export const abilities = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
knowledge: {
|
knowledge: {
|
||||||
|
id: 'knowledge',
|
||||||
label: 'DAGGERHEART.CONFIG.Traits.knowledge.name',
|
label: 'DAGGERHEART.CONFIG.Traits.knowledge.name',
|
||||||
verbs: [
|
verbs: [
|
||||||
'DAGGERHEART.CONFIG.Traits.knowledge.verb.recall',
|
'DAGGERHEART.CONFIG.Traits.knowledge.verb.recall',
|
||||||
|
|
@ -113,7 +119,7 @@ export const adversaryTypes = {
|
||||||
},
|
},
|
||||||
social: {
|
social: {
|
||||||
id: 'social',
|
id: 'social',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryTypee.social.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
|
||||||
},
|
},
|
||||||
solo: {
|
solo: {
|
||||||
|
|
@ -411,7 +417,7 @@ export const levelupData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subclassFeatureLabels = {
|
export const subclassFeatureLabels = {
|
||||||
1: 'DAGGERHEART.ITEMS.DomainCard.foundation',
|
1: 'DAGGERHEART.ITEMS.DomainCard.foundationTitle',
|
||||||
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
|
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
|
||||||
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
|
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,55 +3,55 @@ export const domains = {
|
||||||
id: 'arcana',
|
id: 'arcana',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.arcana.label',
|
label: 'DAGGERHEART.GENERAL.Domain.arcana.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/arcana.svg',
|
src: 'systems/daggerheart/assets/icons/domains/arcana.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Arcana'
|
description: 'DAGGERHEART.GENERAL.Domain.arcana.description'
|
||||||
},
|
},
|
||||||
blade: {
|
blade: {
|
||||||
id: 'blade',
|
id: 'blade',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.blade.label',
|
label: 'DAGGERHEART.GENERAL.Domain.blade.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/blade.svg',
|
src: 'systems/daggerheart/assets/icons/domains/blade.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Blade'
|
description: 'DAGGERHEART.GENERAL.Domain.blade.description'
|
||||||
},
|
},
|
||||||
bone: {
|
bone: {
|
||||||
id: 'bone',
|
id: 'bone',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.bone.label',
|
label: 'DAGGERHEART.GENERAL.Domain.bone.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/bone.svg',
|
src: 'systems/daggerheart/assets/icons/domains/bone.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Bone'
|
description: 'DAGGERHEART.GENERAL.Domain.bone.description'
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
id: 'codex',
|
id: 'codex',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.codex.label',
|
label: 'DAGGERHEART.GENERAL.Domain.codex.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/codex.svg',
|
src: 'systems/daggerheart/assets/icons/domains/codex.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Codex'
|
description: 'DAGGERHEART.GENERAL.Domain.codex.description'
|
||||||
},
|
},
|
||||||
grace: {
|
grace: {
|
||||||
id: 'grace',
|
id: 'grace',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.grace.label',
|
label: 'DAGGERHEART.GENERAL.Domain.grace.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/grace.svg',
|
src: 'systems/daggerheart/assets/icons/domains/grace.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Grace'
|
description: 'DAGGERHEART.GENERAL.Domain.grace.description'
|
||||||
},
|
},
|
||||||
midnight: {
|
midnight: {
|
||||||
id: 'midnight',
|
id: 'midnight',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.midnight.label',
|
label: 'DAGGERHEART.GENERAL.Domain.midnight.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/midnight.svg',
|
src: 'systems/daggerheart/assets/icons/domains/midnight.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Midnight'
|
description: 'DAGGERHEART.GENERAL.Domain.midnight.description'
|
||||||
},
|
},
|
||||||
sage: {
|
sage: {
|
||||||
id: 'sage',
|
id: 'sage',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.sage.label',
|
label: 'DAGGERHEART.GENERAL.Domain.sage.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/sage.svg',
|
src: 'systems/daggerheart/assets/icons/domains/sage.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Sage'
|
description: 'DAGGERHEART.GENERAL.Domain.sage.description'
|
||||||
},
|
},
|
||||||
splendor: {
|
splendor: {
|
||||||
id: 'splendor',
|
id: 'splendor',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.splendor.label',
|
label: 'DAGGERHEART.GENERAL.Domain.splendor.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/splendor.svg',
|
src: 'systems/daggerheart/assets/icons/domains/splendor.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Splendor'
|
description: 'DAGGERHEART.GENERAL.Domain.splendor.description'
|
||||||
},
|
},
|
||||||
valor: {
|
valor: {
|
||||||
id: 'valor',
|
id: 'valor',
|
||||||
label: 'DAGGERHEART.GENERAL.Domain.valor.label',
|
label: 'DAGGERHEART.GENERAL.Domain.valor.label',
|
||||||
src: 'systems/daggerheart/assets/icons/domains/valor.svg',
|
src: 'systems/daggerheart/assets/icons/domains/valor.svg',
|
||||||
description: 'DAGGERHEART.GENERAL.Domain.Valor'
|
description: 'DAGGERHEART.GENERAL.Domain.valor.description'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,5 @@ export const encounterCountdown = {
|
||||||
simple: 'countdown-encounter-simple',
|
simple: 'countdown-encounter-simple',
|
||||||
position: 'countdown-encounter-position'
|
position: 'countdown-encounter-position'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const itemAttachmentSource = 'attachmentSource';
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,13 @@ export const damageTypes = {
|
||||||
id: 'physical',
|
id: 'physical',
|
||||||
label: 'DAGGERHEART.CONFIG.DamageType.physical.name',
|
label: 'DAGGERHEART.CONFIG.DamageType.physical.name',
|
||||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation',
|
abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation',
|
||||||
icon: ["fa-hand-fist"]
|
icon: 'fa-hand-fist'
|
||||||
},
|
},
|
||||||
magical: {
|
magical: {
|
||||||
id: 'magical',
|
id: 'magical',
|
||||||
label: 'DAGGERHEART.CONFIG.DamageType.magical.name',
|
label: 'DAGGERHEART.CONFIG.DamageType.magical.name',
|
||||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation',
|
abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation',
|
||||||
icon: ["fa-wand-sparkles"]
|
icon: 'fa-wand-sparkles'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -89,6 +89,11 @@ export const healingTypes = {
|
||||||
id: 'armorStack',
|
id: 'armorStack',
|
||||||
label: 'DAGGERHEART.CONFIG.HealingType.armorStack.name',
|
label: 'DAGGERHEART.CONFIG.HealingType.armorStack.name',
|
||||||
abbreviation: 'DAGGERHEART.CONFIG.HealingType.armorStack.abbreviation'
|
abbreviation: 'DAGGERHEART.CONFIG.HealingType.armorStack.abbreviation'
|
||||||
|
},
|
||||||
|
fear: {
|
||||||
|
id: 'fear',
|
||||||
|
label: 'DAGGERHEART.CONFIG.HealingType.fear.name',
|
||||||
|
abbreviation: 'DAGGERHEART.CONFIG.HealingType.fear.abbreviation'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -110,6 +115,18 @@ export const conditions = {
|
||||||
name: 'DAGGERHEART.CONFIG.Condition.restrained.name',
|
name: 'DAGGERHEART.CONFIG.Condition.restrained.name',
|
||||||
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
||||||
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
|
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
|
||||||
|
},
|
||||||
|
unconcious: {
|
||||||
|
id: 'unconcious',
|
||||||
|
name: 'DAGGERHEART.CONFIG.Condition.unconcious.name',
|
||||||
|
icon: 'icons/magic/control/sleep-bubble-purple.webp',
|
||||||
|
description: 'DAGGERHEART.CONFIG.Condition.unconcious.description'
|
||||||
|
},
|
||||||
|
dead: {
|
||||||
|
id: 'dead',
|
||||||
|
name: 'DAGGERHEART.CONFIG.Condition.dead.name',
|
||||||
|
icon: 'icons/magic/death/grave-tombstone-glow-teal.webp',
|
||||||
|
description: 'DAGGERHEART.CONFIG.Condition.dead.description'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -118,6 +135,7 @@ export const defaultRestOptions = {
|
||||||
tendToWounds: {
|
tendToWounds: {
|
||||||
id: 'tendToWounds',
|
id: 'tendToWounds',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.name'),
|
||||||
|
icon: 'fa-solid fa-bandage',
|
||||||
img: 'icons/magic/life/cross-worn-green.webp',
|
img: 'icons/magic/life/cross-worn-green.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.description'),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -127,11 +145,11 @@ export const defaultRestOptions = {
|
||||||
img: 'icons/magic/life/cross-worn-green.webp',
|
img: 'icons/magic/life/cross-worn-green.webp',
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
healing: {
|
healing: {
|
||||||
type: 'health',
|
applyTo: healingTypes.hitPoints.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
formula: '1d4 + 1' // should be 1d4 + {tier}. How to use the roll param?
|
formula: '1d4 + @tier'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +159,7 @@ export const defaultRestOptions = {
|
||||||
clearStress: {
|
clearStress: {
|
||||||
id: 'clearStress',
|
id: 'clearStress',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.name'),
|
||||||
|
icon: 'fa-regular fa-face-surprise',
|
||||||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.description'),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -150,11 +169,11 @@ export const defaultRestOptions = {
|
||||||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
healing: {
|
healing: {
|
||||||
type: 'stress',
|
applyTo: healingTypes.stress.id,
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
formula: '1d4 + 1' // should be 1d4 + {tier}. How to use the roll param?
|
formula: '1d4 + @tier'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,13 +183,31 @@ export const defaultRestOptions = {
|
||||||
repairArmor: {
|
repairArmor: {
|
||||||
id: 'repairArmor',
|
id: 'repairArmor',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'),
|
||||||
|
icon: 'fa-solid fa-hammer',
|
||||||
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.description'),
|
||||||
actions: []
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'healing',
|
||||||
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'),
|
||||||
|
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
||||||
|
actionType: 'action',
|
||||||
|
healing: {
|
||||||
|
applyTo: healingTypes.armorStack.id,
|
||||||
|
value: {
|
||||||
|
custom: {
|
||||||
|
enabled: true,
|
||||||
|
formula: '1d4 + @tier'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
prepare: {
|
prepare: {
|
||||||
id: 'prepare',
|
id: 'prepare',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'),
|
||||||
|
icon: 'fa-solid fa-dumbbell',
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
|
||||||
actions: []
|
actions: []
|
||||||
|
|
@ -180,6 +217,7 @@ export const defaultRestOptions = {
|
||||||
tendToWounds: {
|
tendToWounds: {
|
||||||
id: 'tendToWounds',
|
id: 'tendToWounds',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.name'),
|
||||||
|
icon: 'fa-solid fa-bandage',
|
||||||
img: 'icons/magic/life/cross-worn-green.webp',
|
img: 'icons/magic/life/cross-worn-green.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.description'),
|
||||||
actions: []
|
actions: []
|
||||||
|
|
@ -187,6 +225,7 @@ export const defaultRestOptions = {
|
||||||
clearStress: {
|
clearStress: {
|
||||||
id: 'clearStress',
|
id: 'clearStress',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.name'),
|
||||||
|
icon: 'fa-regular fa-face-surprise',
|
||||||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.description'),
|
||||||
actions: []
|
actions: []
|
||||||
|
|
@ -194,6 +233,7 @@ export const defaultRestOptions = {
|
||||||
repairArmor: {
|
repairArmor: {
|
||||||
id: 'repairArmor',
|
id: 'repairArmor',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.name'),
|
||||||
|
icon: 'fa-solid fa-hammer',
|
||||||
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.description'),
|
||||||
actions: []
|
actions: []
|
||||||
|
|
@ -201,6 +241,7 @@ export const defaultRestOptions = {
|
||||||
prepare: {
|
prepare: {
|
||||||
id: 'prepare',
|
id: 'prepare',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'),
|
||||||
|
icon: 'fa-solid fa-dumbbell',
|
||||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
|
||||||
actions: []
|
actions: []
|
||||||
|
|
@ -208,19 +249,12 @@ export const defaultRestOptions = {
|
||||||
workOnAProject: {
|
workOnAProject: {
|
||||||
id: 'workOnAProject',
|
id: 'workOnAProject',
|
||||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.name'),
|
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.name'),
|
||||||
|
icon: 'fa-solid fa-diagram-project',
|
||||||
img: 'icons/skills/social/thumbsup-approval-like.webp',
|
img: 'icons/skills/social/thumbsup-approval-like.webp',
|
||||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
|
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
|
||||||
actions: []
|
actions: []
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
custom: {
|
|
||||||
id: 'customActivity',
|
|
||||||
name: '',
|
|
||||||
img: 'icons/skills/trades/academics-investigation-puzzles.webp',
|
|
||||||
description: '',
|
|
||||||
namePlaceholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.namePlaceholder',
|
|
||||||
placeholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.placeholder'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deathMoves = {
|
export const deathMoves = {
|
||||||
|
|
@ -245,25 +279,21 @@ export const deathMoves = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tiers = {
|
export const tiers = {
|
||||||
tier1: {
|
1: {
|
||||||
id: 'tier1',
|
id: 1,
|
||||||
label: 'DAGGERHEART.GENERAL.Tiers.tier1',
|
label: 'DAGGERHEART.GENERAL.Tiers.1'
|
||||||
value: 1
|
|
||||||
},
|
},
|
||||||
tier2: {
|
2: {
|
||||||
id: 'tier2',
|
id: 2,
|
||||||
label: 'DAGGERHEART.GENERAL.Tiers.tier2',
|
label: 'DAGGERHEART.GENERAL.Tiers.2'
|
||||||
value: 2
|
|
||||||
},
|
},
|
||||||
tier3: {
|
3: {
|
||||||
id: 'tier3',
|
id: 3,
|
||||||
label: 'DAGGERHEART.GENERAL.Tiers.tier3',
|
label: 'DAGGERHEART.GENERAL.Tiers.3'
|
||||||
value: 3
|
|
||||||
},
|
},
|
||||||
tier4: {
|
4: {
|
||||||
id: 'tier4',
|
id: 4,
|
||||||
label: 'DAGGERHEART.GENERAL.Tiers.tier4',
|
label: 'DAGGERHEART.GENERAL.Tiers.4'
|
||||||
value: 4
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -366,31 +396,6 @@ export const abilityCosts = {
|
||||||
label: 'Armor Stack',
|
label: 'Armor Stack',
|
||||||
group: 'TYPES.Actor.character'
|
group: 'TYPES.Actor.character'
|
||||||
},
|
},
|
||||||
prayer: {
|
|
||||||
id: 'prayer',
|
|
||||||
label: 'Prayer Dice',
|
|
||||||
group: 'TYPES.Actor.character'
|
|
||||||
},
|
|
||||||
favor: {
|
|
||||||
id: 'favor',
|
|
||||||
label: 'Favor Points',
|
|
||||||
group: 'TYPES.Actor.character'
|
|
||||||
},
|
|
||||||
slayer: {
|
|
||||||
id: 'slayer',
|
|
||||||
label: 'Slayer Dice',
|
|
||||||
group: 'TYPES.Actor.character'
|
|
||||||
},
|
|
||||||
tide: {
|
|
||||||
id: 'tide',
|
|
||||||
label: 'Tide',
|
|
||||||
group: 'TYPES.Actor.character'
|
|
||||||
},
|
|
||||||
chaos: {
|
|
||||||
id: 'chaos',
|
|
||||||
label: 'Chaos',
|
|
||||||
group: 'TYPES.Actor.character'
|
|
||||||
},
|
|
||||||
fear: {
|
fear: {
|
||||||
id: 'fear',
|
id: 'fear',
|
||||||
label: 'Fear',
|
label: 'Fear',
|
||||||
|
|
@ -401,29 +406,29 @@ export const abilityCosts = {
|
||||||
export const countdownTypes = {
|
export const countdownTypes = {
|
||||||
spotlight: {
|
spotlight: {
|
||||||
id: 'spotlight',
|
id: 'spotlight',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.Spotlight'
|
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
||||||
},
|
},
|
||||||
characterAttack: {
|
characterAttack: {
|
||||||
id: 'characterAttack',
|
id: 'characterAttack',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.CharacterAttack'
|
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
||||||
},
|
},
|
||||||
custom: {
|
custom: {
|
||||||
id: 'custom',
|
id: 'custom',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.Custom'
|
label: 'DAGGERHEART.CONFIG.CountdownType.custom'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const rollTypes = {
|
export const rollTypes = {
|
||||||
weapon: {
|
attack: {
|
||||||
id: 'weapon',
|
id: 'attack',
|
||||||
label: 'DAGGERHEART.CONFIG.RollTypes.weapon.name'
|
label: 'DAGGERHEART.CONFIG.RollTypes.attack.name'
|
||||||
},
|
},
|
||||||
spellcast: {
|
spellcast: {
|
||||||
id: 'spellcast',
|
id: 'spellcast',
|
||||||
label: 'DAGGERHEART.CONFIG.RollTypes.spellcast.name'
|
label: 'DAGGERHEART.CONFIG.RollTypes.spellcast.name'
|
||||||
},
|
},
|
||||||
ability: {
|
trait: {
|
||||||
id: 'ability',
|
id: 'trait',
|
||||||
label: 'DAGGERHEART.CONFIG.RollTypes.ability.name'
|
label: 'DAGGERHEART.CONFIG.RollTypes.trait.name'
|
||||||
},
|
},
|
||||||
diceSet: {
|
diceSet: {
|
||||||
id: 'diceSet',
|
id: 'diceSet',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export const hooks = {
|
|
||||||
characterAttack: 'characterAttackHook',
|
|
||||||
spotlight: 'spotlightHook'
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,6 @@ import * as DOMAIN from './domainConfig.mjs';
|
||||||
import * as ACTOR from './actorConfig.mjs';
|
import * as ACTOR from './actorConfig.mjs';
|
||||||
import * as ITEM from './itemConfig.mjs';
|
import * as ITEM from './itemConfig.mjs';
|
||||||
import * as SETTINGS from './settingsConfig.mjs';
|
import * as SETTINGS from './settingsConfig.mjs';
|
||||||
import { hooks as HOOKS } from './hooksConfig.mjs';
|
|
||||||
import * as EFFECTS from './effectConfig.mjs';
|
import * as EFFECTS from './effectConfig.mjs';
|
||||||
import * as ACTIONS from './actionConfig.mjs';
|
import * as ACTIONS from './actionConfig.mjs';
|
||||||
import * as FLAGS from './flagsConfig.mjs';
|
import * as FLAGS from './flagsConfig.mjs';
|
||||||
|
|
@ -17,7 +16,6 @@ export const SYSTEM = {
|
||||||
ACTOR,
|
ACTOR,
|
||||||
ITEM,
|
ITEM,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
HOOKS,
|
|
||||||
EFFECTS,
|
EFFECTS,
|
||||||
ACTIONS,
|
ACTIONS,
|
||||||
FLAGS
|
FLAGS
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormula(actor) {
|
getFormula() {
|
||||||
/* const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : actor.system[this.multiplier]?.total;
|
|
||||||
return this.custom.enabled
|
|
||||||
? this.custom.formula
|
|
||||||
: `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; */
|
|
||||||
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
||||||
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
||||||
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
||||||
|
|
@ -91,25 +87,25 @@ export class DHDamageField extends fields.SchemaField {
|
||||||
constructor(options, context = {}) {
|
constructor(options, context = {}) {
|
||||||
const damageFields = {
|
const damageFields = {
|
||||||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
||||||
includeBase: new fields.BooleanField({ initial: false })
|
includeBase: new fields.BooleanField({
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||||
|
})
|
||||||
};
|
};
|
||||||
// if (hasBase) damageFields.includeBase = new fields.BooleanField({ initial: true });
|
|
||||||
super(damageFields, options, context);
|
super(damageFields, options, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DHDamageData extends foundry.abstract.DataModel {
|
export class DHResourceData extends foundry.abstract.DataModel {
|
||||||
/** @override */
|
/** @override */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
// ...super.defineSchema(),
|
applyTo: new fields.StringField({
|
||||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
choices: CONFIG.DH.GENERAL.healingTypes,
|
||||||
type: new fields.StringField({
|
required: true,
|
||||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
blank: false,
|
||||||
initial: 'physical',
|
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
||||||
label: 'Type',
|
label: 'DAGGERHEART.ACTIONS.Settings.applyTo.label'
|
||||||
nullable: false,
|
|
||||||
required: true
|
|
||||||
}),
|
}),
|
||||||
resultBased: new fields.BooleanField({
|
resultBased: new fields.BooleanField({
|
||||||
initial: false,
|
initial: false,
|
||||||
|
|
@ -120,3 +116,24 @@ export class DHDamageData extends foundry.abstract.DataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class DHDamageData extends DHResourceData {
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
|
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||||
|
type: new fields.SetField(
|
||||||
|
new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||||
|
initial: 'physical',
|
||||||
|
nullable: false,
|
||||||
|
required: true
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
label: 'Type'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
static extraSchemas = [...super.extraSchemas, ...['roll', 'save']];
|
static extraSchemas = [...super.extraSchemas, ...['roll', 'save']];
|
||||||
|
|
||||||
static getRollType(parent) {
|
static getRollType(parent) {
|
||||||
return parent.type === 'weapon' ? 'weapon' : 'spellcast';
|
return parent.type === 'weapon' ? 'attack' : 'spellcast';
|
||||||
}
|
}
|
||||||
|
|
||||||
get chatTemplate() {
|
get chatTemplate() {
|
||||||
|
|
@ -14,14 +14,14 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
|
|
||||||
prepareData() {
|
prepareData() {
|
||||||
super.prepareData();
|
super.prepareData();
|
||||||
if(!!this.item?.system?.attack) {
|
if (!!this.item?.system?.attack) {
|
||||||
if (this.damage.includeBase) {
|
if (this.damage.includeBase) {
|
||||||
const baseDamage = this.getParentDamage();
|
const baseDamage = this.getParentDamage();
|
||||||
this.damage.parts.unshift(new DHDamageData(baseDamage));
|
this.damage.parts.unshift(new DHDamageData(baseDamage));
|
||||||
}
|
}
|
||||||
if(this.roll.useDefault) {
|
if (this.roll.useDefault) {
|
||||||
this.roll.trait = this.item.system.attack.roll.trait;
|
this.roll.trait = this.item.system.attack.roll.trait;
|
||||||
this.roll.type = 'weapon';
|
this.roll.type = 'attack';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,4 +37,17 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
base: true
|
base: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async use(event, ...args) {
|
||||||
|
const result = await super.use(event, args);
|
||||||
|
|
||||||
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get modifiers() {
|
||||||
|
// return [];
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField } from './actionDice.mjs';
|
import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField, DHResourceData } from './actionDice.mjs';
|
||||||
import DhpActor from '../../documents/actor.mjs';
|
import DhpActor from '../../documents/actor.mjs';
|
||||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||||
|
|
||||||
|
|
@ -35,12 +35,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}),
|
}),
|
||||||
cost: new fields.ArrayField(
|
cost: new fields.ArrayField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
type: new fields.StringField({
|
key: new fields.StringField({
|
||||||
choices: CONFIG.DH.GENERAL.abilityCosts,
|
|
||||||
nullable: false,
|
nullable: false,
|
||||||
required: true,
|
required: true,
|
||||||
initial: 'hope'
|
initial: 'hope'
|
||||||
}),
|
}),
|
||||||
|
keyIsID: new fields.BooleanField(),
|
||||||
value: new fields.NumberField({ nullable: true, initial: 1 }),
|
value: new fields.NumberField({ nullable: true, initial: 1 }),
|
||||||
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 })
|
||||||
|
|
@ -96,21 +96,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
onSave: new fields.BooleanField({ initial: false })
|
onSave: new fields.BooleanField({ initial: false })
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
healing: new fields.SchemaField({
|
healing: new fields.EmbeddedDataField(DHResourceData),
|
||||||
type: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.healingTypes,
|
|
||||||
required: true,
|
|
||||||
blank: false,
|
|
||||||
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
|
||||||
label: 'Healing'
|
|
||||||
}),
|
|
||||||
resultBased: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
|
|
||||||
}),
|
|
||||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
|
||||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
|
||||||
}),
|
|
||||||
beastform: new fields.SchemaField({
|
beastform: new fields.SchemaField({
|
||||||
tierAccess: new fields.SchemaField({
|
tierAccess: new fields.SchemaField({
|
||||||
exact: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
exact: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||||
|
|
@ -150,18 +136,18 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getRollType(parent) {
|
static getRollType(parent) {
|
||||||
return 'ability';
|
return 'trait';
|
||||||
}
|
}
|
||||||
|
|
||||||
static getSourceConfig(parent) {
|
static getSourceConfig(parent) {
|
||||||
const updateSource = {};
|
const updateSource = {};
|
||||||
updateSource.img ??= parent?.img ?? parent?.system?.img;
|
updateSource.img ??= parent?.img ?? parent?.system?.img;
|
||||||
if (parent?.type === 'weapon') {
|
if (parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
||||||
updateSource['damage'] = { includeBase: true };
|
updateSource['damage'] = { includeBase: true };
|
||||||
updateSource['range'] = parent?.system?.attack?.range;
|
updateSource['range'] = parent?.system?.attack?.range;
|
||||||
updateSource['roll'] = {
|
updateSource['roll'] = {
|
||||||
useDefault: true
|
useDefault: true
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
if (parent?.system?.trait) {
|
if (parent?.system?.trait) {
|
||||||
updateSource['roll'] = {
|
updateSource['roll'] = {
|
||||||
|
|
@ -177,18 +163,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
getRollData(data = {}) {
|
getRollData(data = {}) {
|
||||||
|
if (!this.actor) return null;
|
||||||
const actorData = this.actor.getRollData(false);
|
const actorData = this.actor.getRollData(false);
|
||||||
|
|
||||||
// Remove when included directly in Actor getRollData
|
// Add Roll results to RollDatas
|
||||||
actorData.prof = actorData.proficiency?.value ?? 1;
|
|
||||||
actorData.cast = actorData.spellcast?.value ?? 1;
|
|
||||||
actorData.result = data.roll?.total ?? 1;
|
actorData.result = data.roll?.total ?? 1;
|
||||||
/* actorData.scale = data.costs?.length
|
|
||||||
? data.costs.reduce((a, c) => {
|
|
||||||
a[c.type] = c.value;
|
|
||||||
return a;
|
|
||||||
}, {})
|
|
||||||
: 1; */
|
|
||||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||||
: 1;
|
: 1;
|
||||||
|
|
@ -198,6 +178,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event, ...args) {
|
async use(event, ...args) {
|
||||||
|
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
|
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
|
||||||
// Prepare base Config
|
// Prepare base Config
|
||||||
const initConfig = this.initActionConfig(event);
|
const initConfig = this.initActionConfig(event);
|
||||||
|
|
@ -211,7 +193,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
|
|
||||||
// Prepare Costs
|
// Prepare Costs
|
||||||
const costsConfig = this.prepareCost();
|
const costsConfig = this.prepareCost();
|
||||||
if (isFastForward && !this.hasCost(costsConfig))
|
if (isFastForward && !(await this.hasCost(costsConfig)))
|
||||||
return ui.notifications.warn("You don't have the resources to use that action.");
|
return ui.notifications.warn("You don't have the resources to use that action.");
|
||||||
|
|
||||||
// Prepare Uses
|
// Prepare Uses
|
||||||
|
|
@ -234,7 +216,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
// Display configuration window if necessary
|
// Display configuration window if necessary
|
||||||
// if (config.dialog?.configure && this.requireConfigurationDialog(config)) {
|
|
||||||
if (this.requireConfigurationDialog(config)) {
|
if (this.requireConfigurationDialog(config)) {
|
||||||
config = await D20RollDialog.configure(null, config);
|
config = await D20RollDialog.configure(null, config);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
@ -275,7 +256,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
hasDamage: !!this.damage?.parts?.length,
|
hasDamage: !!this.damage?.parts?.length,
|
||||||
hasHealing: !!this.healing,
|
hasHealing: !!this.healing,
|
||||||
hasEffect: !!this.effects?.length,
|
hasEffect: !!this.effects?.length,
|
||||||
hasSave: this.hasSave
|
hasSave: this.hasSave,
|
||||||
|
selectedRollMode: game.settings.get('core', 'rollMode')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +267,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
|
|
||||||
prepareCost() {
|
prepareCost() {
|
||||||
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
|
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
|
||||||
return costs;
|
return this.calcCosts(costs);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareUse() {
|
prepareUse() {
|
||||||
|
|
@ -295,7 +277,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareTarget() {
|
prepareTarget() {
|
||||||
if(!this.target?.type) return [];
|
if (!this.target?.type) return [];
|
||||||
let targets;
|
let targets;
|
||||||
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
|
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
|
||||||
targets = this.constructor.formatTarget(this.actor.token ?? this.actor.prototypeToken);
|
targets = this.constructor.formatTarget(this.actor.token ?? this.actor.prototypeToken);
|
||||||
|
|
@ -315,7 +297,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
|
|
||||||
prepareRoll() {
|
prepareRoll() {
|
||||||
const roll = {
|
const roll = {
|
||||||
modifiers: [],
|
modifiers: this.modifiers,
|
||||||
trait: this.roll?.trait,
|
trait: this.roll?.trait,
|
||||||
label: 'Attack',
|
label: 'Attack',
|
||||||
type: this.actionType,
|
type: this.actionType,
|
||||||
|
|
@ -334,10 +316,26 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async consume(config) {
|
async consume(config) {
|
||||||
|
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||||
|
for (var cost of config.costs) {
|
||||||
|
if (cost.keyIsID) {
|
||||||
|
usefulResources[cost.key] = {
|
||||||
|
value: cost.value,
|
||||||
|
target: this.parent.parent,
|
||||||
|
keyIsID: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
const resources = config.costs
|
const resources = config.costs
|
||||||
.filter(c => c.enabled !== false)
|
.filter(c => c.enabled !== false)
|
||||||
.map(c => {
|
.map(c => {
|
||||||
return { type: c.type, value: (c.total ?? c.value) * -1 };
|
const resource = usefulResources[c.key];
|
||||||
|
return {
|
||||||
|
key: c.key,
|
||||||
|
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||||
|
target: resource.target,
|
||||||
|
keyIsID: resource.keyIsID
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.actor.modifyResource(resources);
|
await this.actor.modifyResource(resources);
|
||||||
|
|
@ -353,6 +351,13 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
get hasRoll() {
|
get hasRoll() {
|
||||||
return !!this.roll?.type || !!this.roll?.bonus;
|
return !!this.roll?.type || !!this.roll?.bonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
if (!this.actor) return [];
|
||||||
|
const modifiers = [];
|
||||||
|
/** Placeholder for specific bonuses **/
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
/* ROLL */
|
/* ROLL */
|
||||||
|
|
||||||
/* SAVE */
|
/* SAVE */
|
||||||
|
|
@ -378,23 +383,46 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCost(costs) {
|
async getResources(costs) {
|
||||||
|
const actorResources = this.actor.system.resources;
|
||||||
|
const itemResources = {};
|
||||||
|
for (var itemResource of costs) {
|
||||||
|
if (itemResource.keyIsID) {
|
||||||
|
itemResources[itemResource.key] = {
|
||||||
|
value: this.parent.resource.value ?? 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actorResources,
|
||||||
|
...itemResources
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* COST */
|
||||||
|
async hasCost(costs) {
|
||||||
const realCosts = this.getRealCosts(costs),
|
const realCosts = this.getRealCosts(costs),
|
||||||
hasFearCost = realCosts.findIndex(c => c.type === 'fear');
|
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
||||||
if (hasFearCost > -1) {
|
if (hasFearCost > -1) {
|
||||||
const fearCost = realCosts.splice(hasFearCost, 1);
|
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
||||||
if (
|
if (
|
||||||
!game.user.isGM ||
|
!game.user.isGM ||
|
||||||
fearCost[0].total > game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
fearCost.total > game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */
|
||||||
|
const resources = await this.getResources(realCosts);
|
||||||
return realCosts.reduce(
|
return realCosts.reduce(
|
||||||
(a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value),
|
(a, c) =>
|
||||||
|
a && resources[c.key].isReversed
|
||||||
|
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
|
||||||
|
: resources[c.key]?.value >= (c.total ?? c.value),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/* COST */
|
|
||||||
|
|
||||||
/* USES */
|
/* USES */
|
||||||
calcUses(uses) {
|
calcUses(uses) {
|
||||||
|
|
@ -409,7 +437,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
if (!uses) return true;
|
if (!uses) return true;
|
||||||
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
|
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
|
||||||
}
|
}
|
||||||
/* USES */
|
|
||||||
|
|
||||||
/* TARGET */
|
/* TARGET */
|
||||||
isTargetFriendly(target) {
|
isTargetFriendly(target) {
|
||||||
|
|
@ -432,7 +459,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
name: actor.actor.name,
|
name: actor.actor.name,
|
||||||
img: actor.actor.img,
|
img: actor.actor.img,
|
||||||
difficulty: actor.actor.system.difficulty,
|
difficulty: actor.actor.system.difficulty,
|
||||||
evasion: actor.actor.system.evasion?.total
|
evasion: actor.actor.system.evasion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/* TARGET */
|
/* TARGET */
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@ export default class DhBeastformAction extends DHBaseAction {
|
||||||
const abort = await this.handleActiveTransformations();
|
const abort = await this.handleActiveTransformations();
|
||||||
if (abort) return;
|
if (abort) return;
|
||||||
|
|
||||||
const beastformUuid = await BeastformDialog.configure(beastformConfig);
|
const item = args[0];
|
||||||
if (!beastformUuid) return;
|
|
||||||
|
|
||||||
await this.transform(beastformUuid);
|
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, item);
|
||||||
|
if (!selected) return;
|
||||||
|
|
||||||
|
await this.transform(selected, evolved, hybrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareBeastformConfig(config) {
|
prepareBeastformConfig(config) {
|
||||||
|
|
@ -29,21 +31,48 @@ export default class DhBeastformAction extends DHBaseAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async transform(beastformUuid) {
|
async transform(selectedForm, evolvedData, hybridData) {
|
||||||
const beastform = await foundry.utils.fromUuid(beastformUuid);
|
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject();
|
||||||
this.actor.createEmbeddedDocuments('Item', [beastform.toObject()]);
|
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
||||||
|
if (!beastformEffect) {
|
||||||
|
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evolvedData?.form) {
|
||||||
|
const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform');
|
||||||
|
if (!evolvedForm) {
|
||||||
|
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
|
||||||
|
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
|
||||||
|
formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
|
||||||
|
Object.keys(formCategory).forEach(advantageKey => {
|
||||||
|
advantages[advantageKey] = formCategory[advantageKey];
|
||||||
|
});
|
||||||
|
return advantages;
|
||||||
|
}, {});
|
||||||
|
formData.system.features = [
|
||||||
|
...formData.system.features,
|
||||||
|
...Object.values(hybridData.features).flatMap(x => Object.keys(x))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actor.createEmbeddedDocuments('Item', [formData]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleActiveTransformations() {
|
async handleActiveTransformations() {
|
||||||
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
|
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
|
||||||
if (beastformEffects.length > 0) {
|
const existingEffects = beastformEffects.length > 0;
|
||||||
for (let effect of beastformEffects) {
|
await this.actor.deleteEmbeddedDocuments(
|
||||||
await effect.delete();
|
'ActiveEffect',
|
||||||
}
|
beastformEffects.map(x => x.id)
|
||||||
|
);
|
||||||
return true;
|
return existingEffects;
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { setsEqual } from '../../helpers/utils.mjs';
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHDamageAction extends DHBaseAction {
|
export default class DHDamageAction extends DHBaseAction {
|
||||||
|
|
@ -6,33 +7,63 @@ export default class DHDamageAction extends DHBaseAction {
|
||||||
getFormulaValue(part, data) {
|
getFormulaValue(part, data) {
|
||||||
let formulaValue = part.value;
|
let formulaValue = part.value;
|
||||||
if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
||||||
|
|
||||||
|
const isAdversary = this.actor.type === 'adversary';
|
||||||
|
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||||
|
const hasHordeDamage = this.actor.effects.find(
|
||||||
|
x => x.name === game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label')
|
||||||
|
);
|
||||||
|
if (hasHordeDamage) return part.valueAlt;
|
||||||
|
}
|
||||||
|
|
||||||
return formulaValue;
|
return formulaValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatFormulas(formulas, systemData) {
|
||||||
|
const formattedFormulas = [];
|
||||||
|
formulas.forEach(formula => {
|
||||||
|
if (isNaN(formula.formula))
|
||||||
|
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
|
||||||
|
const same = formattedFormulas.find(
|
||||||
|
f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
|
||||||
|
);
|
||||||
|
if (same) same.formula += ` + ${formula.formula}`;
|
||||||
|
else formattedFormulas.push(formula);
|
||||||
|
});
|
||||||
|
return formattedFormulas;
|
||||||
|
}
|
||||||
|
|
||||||
async rollDamage(event, data) {
|
async rollDamage(event, data) {
|
||||||
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + ');
|
const systemData = data.system ?? data;
|
||||||
|
|
||||||
if (!formula || formula == '') return;
|
let formulas = this.damage.parts.map(p => ({
|
||||||
let roll = { formula: formula, total: formula },
|
formula: this.getFormulaValue(p, data).getFormula(this.actor),
|
||||||
bonusDamage = [];
|
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
||||||
|
applyTo: p.applyTo
|
||||||
|
}));
|
||||||
|
|
||||||
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
|
if (!formulas.length) return;
|
||||||
|
|
||||||
|
formulas = this.formatFormulas(formulas, systemData);
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
|
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: game.i18n.localize(this.name) }),
|
||||||
roll: { formula },
|
roll: formulas,
|
||||||
targets: data.system?.targets.filter(t => t.hit) ?? data.targets,
|
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
isCritical: data.system?.roll?.isCritical ?? false,
|
isCritical: systemData.roll?.isCritical ?? false,
|
||||||
source: data.system?.source,
|
source: systemData.source,
|
||||||
|
data: this.getRollData(),
|
||||||
event
|
event
|
||||||
};
|
};
|
||||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||||
if (data.system) {
|
if (data.system) {
|
||||||
config.source.message = data._id;
|
config.source.message = data._id;
|
||||||
config.directDamage = false;
|
config.directDamage = false;
|
||||||
|
} else {
|
||||||
|
config.directDamage = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
return CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,28 +15,34 @@ export default class DHHealingAction extends DHBaseAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollHealing(event, data) {
|
async rollHealing(event, data) {
|
||||||
let formulaValue = this.getFormulaValue(data),
|
const systemData = data.system ?? data;
|
||||||
formula = formulaValue.getFormula(this.actor);
|
let formulas = [
|
||||||
|
{
|
||||||
if (!formula || formula == '') return;
|
formula: this.getFormulaValue(data).getFormula(this.actor),
|
||||||
let roll = { formula: formula, total: formula },
|
applyTo: this.healing.applyTo
|
||||||
bonusDamage = [];
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.healingRoll.title', {
|
title: game.i18n.format('DAGGERHEART.UI.Chat.healingRoll.title', {
|
||||||
healing: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[this.healing.type].label)
|
healing: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[this.healing.applyTo].label)
|
||||||
}),
|
}),
|
||||||
roll: { formula },
|
roll: formulas,
|
||||||
targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
|
targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
|
||||||
messageType: 'healing',
|
messageType: 'healing',
|
||||||
type: this.healing.type,
|
source: systemData.source,
|
||||||
|
data: this.getRollData(),
|
||||||
event
|
event
|
||||||
};
|
};
|
||||||
|
|
||||||
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
return CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
get chatTemplate() {
|
get chatTemplate() {
|
||||||
return 'systems/daggerheart/templates/ui/chat/healing-roll.hbs';
|
return 'systems/daggerheart/templates/ui/chat/healing-roll.hbs';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
||||||
base64: false,
|
base64: false,
|
||||||
nullable: true
|
nullable: true
|
||||||
}),
|
}),
|
||||||
|
tokenRingImg: new fields.FilePathField({
|
||||||
|
initial: 'icons/svg/mystery-man.svg',
|
||||||
|
categories: ['IMAGE'],
|
||||||
|
base64: false
|
||||||
|
}),
|
||||||
tokenSize: new fields.SchemaField({
|
tokenSize: new fields.SchemaField({
|
||||||
height: new fields.NumberField({ integer: true, nullable: true }),
|
height: new fields.NumberField({ integer: true, nullable: true }),
|
||||||
width: new fields.NumberField({ integer: true, nullable: true })
|
width: new fields.NumberField({ integer: true, nullable: true })
|
||||||
|
|
@ -21,6 +26,13 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _onCreate() {
|
||||||
|
if (this.parent.parent?.type === 'character') {
|
||||||
|
this.parent.parent.system.primaryWeapon?.update?.({ 'system.equipped': false });
|
||||||
|
this.parent.parent.system.secondayWeapon?.update?.({ 'system.equipped': false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete() {
|
||||||
if (this.parent.parent.type === 'character') {
|
if (this.parent.parent.type === 'character') {
|
||||||
const update = {
|
const update = {
|
||||||
|
|
@ -28,6 +40,11 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
||||||
width: this.characterTokenData.tokenSize.width,
|
width: this.characterTokenData.tokenSize.width,
|
||||||
texture: {
|
texture: {
|
||||||
src: this.characterTokenData.tokenImg
|
src: this.characterTokenData.tokenImg
|
||||||
|
},
|
||||||
|
ring: {
|
||||||
|
subject: {
|
||||||
|
texture: this.characterTokenData.tokenRingImg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import ActionField from '../fields/actionField.mjs';
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
const resourceField = () =>
|
|
||||||
new foundry.data.fields.SchemaField({
|
|
||||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
max: new foundry.data.fields.NumberField({ initial: 0, integer: true })
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class DhpAdversary extends BaseDataActor {
|
export default class DhpAdversary extends BaseDataActor {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
||||||
|
|
@ -22,28 +17,44 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
tier: new fields.StringField({
|
...super.defineSchema(),
|
||||||
|
tier: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
integer: true,
|
||||||
choices: CONFIG.DH.GENERAL.tiers,
|
choices: CONFIG.DH.GENERAL.tiers,
|
||||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||||
}),
|
}),
|
||||||
type: new fields.StringField({
|
type: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
choices: CONFIG.DH.ACTOR.adversaryTypes,
|
choices: CONFIG.DH.ACTOR.adversaryTypes,
|
||||||
initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id
|
initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id
|
||||||
}),
|
}),
|
||||||
description: new fields.StringField(),
|
|
||||||
motivesAndTactics: new fields.StringField(),
|
motivesAndTactics: new fields.StringField(),
|
||||||
notes: new fields.HTMLField(),
|
notes: new fields.HTMLField(),
|
||||||
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
||||||
hordeHp: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
hordeHp: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
initial: 1,
|
||||||
|
integer: true,
|
||||||
|
label: 'DAGGERHEART.GENERAL.hordeHp'
|
||||||
|
}),
|
||||||
damageThresholds: new fields.SchemaField({
|
damageThresholds: new fields.SchemaField({
|
||||||
major: new fields.NumberField({ required: true, initial: 0, integer: true }),
|
major: new fields.NumberField({
|
||||||
severe: new fields.NumberField({ required: true, initial: 0, integer: true })
|
required: true,
|
||||||
|
initial: 0,
|
||||||
|
integer: true,
|
||||||
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
||||||
|
}),
|
||||||
|
severe: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
initial: 0,
|
||||||
|
integer: true,
|
||||||
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
resources: new fields.SchemaField({
|
resources: new fields.SchemaField({
|
||||||
hitPoints: resourceField(),
|
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||||
stress: resourceField()
|
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
|
||||||
}),
|
}),
|
||||||
attack: new ActionField({
|
attack: new ActionField({
|
||||||
initial: {
|
initial: {
|
||||||
|
|
@ -58,11 +69,12 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
amount: 1
|
amount: 1
|
||||||
},
|
},
|
||||||
roll: {
|
roll: {
|
||||||
type: 'weapon'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
multiplier: 'flat'
|
multiplier: 'flat'
|
||||||
}
|
}
|
||||||
|
|
@ -74,13 +86,18 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
experiences: new fields.TypedObjectField(
|
experiences: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
name: new fields.StringField(),
|
name: new fields.StringField(),
|
||||||
total: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
value: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
bonuses: new fields.SchemaField({
|
bonuses: new fields.SchemaField({
|
||||||
difficulty: new fields.SchemaField({
|
roll: new fields.SchemaField({
|
||||||
all: new fields.NumberField({ integer: true, initial: 0 }),
|
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
|
||||||
reaction: new fields.NumberField({ integer: true, initial: 0 })
|
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
|
||||||
|
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction')
|
||||||
|
}),
|
||||||
|
damage: new fields.SchemaField({
|
||||||
|
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
||||||
|
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
@ -93,4 +110,37 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
get features() {
|
get features() {
|
||||||
return this.parent.items.filter(x => x.type === 'feature');
|
return this.parent.items.filter(x => x.type === 'feature');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _preUpdate(changes, options, user) {
|
||||||
|
const allowed = await super._preUpdate(changes, options, user);
|
||||||
|
if (allowed === false) return false;
|
||||||
|
|
||||||
|
if (this.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||||
|
if (changes.system?.resources?.hitPoints?.value) {
|
||||||
|
const halfHP = Math.ceil(this.resources.hitPoints.max / 2);
|
||||||
|
const newHitPoints = changes.system.resources.hitPoints.value;
|
||||||
|
const previouslyAboveHalf = this.resources.hitPoints.value < halfHP;
|
||||||
|
const loweredBelowHalf = previouslyAboveHalf && newHitPoints >= halfHP;
|
||||||
|
const raisedAboveHalf = !previouslyAboveHalf && newHitPoints < halfHP;
|
||||||
|
if (loweredBelowHalf) {
|
||||||
|
await this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||||
|
{
|
||||||
|
name: game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label'),
|
||||||
|
img: 'icons/magic/movement/chevrons-down-yellow.webp',
|
||||||
|
disabled: !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation)
|
||||||
|
.hordeDamage
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
} else if (raisedAboveHalf) {
|
||||||
|
const hordeEffects = this.parent.effects.filter(
|
||||||
|
x => x.name === game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label')
|
||||||
|
);
|
||||||
|
await this.parent.deleteEmbeddedDocuments(
|
||||||
|
'ActiveEffect',
|
||||||
|
hordeEffects.map(x => x.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
import DHBaseActorSettings from "../../applications/sheets/api/actor-setting.mjs";
|
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
|
||||||
|
|
||||||
|
const resistanceField = reductionLabel =>
|
||||||
|
new foundry.data.fields.SchemaField({
|
||||||
|
resistance: new foundry.data.fields.BooleanField({ initial: false }),
|
||||||
|
immunity: new foundry.data.fields.BooleanField({ initial: false }),
|
||||||
|
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0, label: reductionLabel })
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes metadata about the actor data model type
|
* Describes metadata about the actor data model type
|
||||||
|
|
@ -16,6 +23,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
type: 'base',
|
type: 'base',
|
||||||
isNPC: true,
|
isNPC: true,
|
||||||
settingSheet: null,
|
settingSheet: null,
|
||||||
|
hasResistances: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,10 +35,15 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
const schema = {};
|
||||||
|
|
||||||
return {
|
if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||||
description: new fields.HTMLField({ required: true, nullable: true })
|
if (this.metadata.hasResistances)
|
||||||
};
|
schema.resistance = new fields.SchemaField({
|
||||||
|
physical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.physicalReduction'),
|
||||||
|
magical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.magicalReduction')
|
||||||
|
});
|
||||||
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,18 @@
|
||||||
import { burden } from '../../config/generalConfig.mjs';
|
import { burden } from '../../config/generalConfig.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
|
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||||
const attributeField = () =>
|
import ActionField from '../fields/actionField.mjs';
|
||||||
new foundry.data.fields.SchemaField({
|
|
||||||
value: new foundry.data.fields.NumberField({ initial: null, integer: true }),
|
|
||||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
|
|
||||||
});
|
|
||||||
|
|
||||||
const resourceField = max =>
|
|
||||||
new foundry.data.fields.SchemaField({
|
|
||||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
max: new foundry.data.fields.NumberField({ initial: max, integer: true })
|
|
||||||
});
|
|
||||||
|
|
||||||
const stressDamageReductionRule = () =>
|
|
||||||
new foundry.data.fields.SchemaField({
|
|
||||||
enabled: new foundry.data.fields.BooleanField({ required: true, initial: false }),
|
|
||||||
cost: new foundry.data.fields.NumberField({ integer: true })
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class DhCharacter extends BaseDataActor {
|
export default class DhCharacter extends BaseDataActor {
|
||||||
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||||
|
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: 'TYPES.Actor.character',
|
label: 'TYPES.Actor.character',
|
||||||
type: 'character',
|
type: 'character',
|
||||||
isNPC: false,
|
isNPC: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,36 +20,43 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
resources: new fields.SchemaField({
|
resources: new fields.SchemaField({
|
||||||
hitPoints: new fields.SchemaField({
|
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
|
||||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true })
|
hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
|
||||||
}),
|
|
||||||
stress: resourceField(6),
|
|
||||||
hope: resourceField(6),
|
|
||||||
tokens: new fields.ObjectField(),
|
|
||||||
dice: new fields.ObjectField()
|
|
||||||
}),
|
}),
|
||||||
traits: new fields.SchemaField({
|
traits: new fields.SchemaField({
|
||||||
agility: attributeField(),
|
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||||
strength: attributeField(),
|
strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'),
|
||||||
finesse: attributeField(),
|
finesse: attributeField('DAGGERHEART.CONFIG.Traits.finesse.name'),
|
||||||
instinct: attributeField(),
|
instinct: attributeField('DAGGERHEART.CONFIG.Traits.instinct.name'),
|
||||||
presence: attributeField(),
|
presence: attributeField('DAGGERHEART.CONFIG.Traits.presence.name'),
|
||||||
knowledge: attributeField()
|
knowledge: attributeField('DAGGERHEART.CONFIG.Traits.knowledge.name')
|
||||||
}),
|
}),
|
||||||
proficiency: new fields.SchemaField({
|
proficiency: new fields.NumberField({
|
||||||
value: new fields.NumberField({ initial: 1, integer: true }),
|
initial: 1,
|
||||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
integer: true,
|
||||||
|
label: 'DAGGERHEART.GENERAL.proficiency'
|
||||||
}),
|
}),
|
||||||
evasion: new fields.SchemaField({
|
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }),
|
||||||
|
damageThresholds: new fields.SchemaField({
|
||||||
|
severe: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
|
||||||
|
}),
|
||||||
|
major: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
experiences: new fields.TypedObjectField(
|
experiences: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
name: new fields.StringField(),
|
name: new fields.StringField(),
|
||||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
value: new fields.NumberField({ integer: true, initial: 0 })
|
||||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
gold: new fields.SchemaField({
|
gold: new fields.SchemaField({
|
||||||
|
|
@ -78,7 +68,7 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
scars: new fields.TypedObjectField(
|
scars: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
name: new fields.StringField({}),
|
name: new fields.StringField({}),
|
||||||
description: new fields.HTMLField()
|
description: new fields.StringField()
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
biography: new fields.SchemaField({
|
biography: new fields.SchemaField({
|
||||||
|
|
@ -98,43 +88,174 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||||
}),
|
}),
|
||||||
|
attack: new ActionField({
|
||||||
|
initial: {
|
||||||
|
name: 'Attack',
|
||||||
|
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
|
||||||
|
_id: foundry.utils.randomID(),
|
||||||
|
systemPath: 'attack',
|
||||||
|
type: 'attack',
|
||||||
|
range: 'melee',
|
||||||
|
target: {
|
||||||
|
type: 'any',
|
||||||
|
amount: 1
|
||||||
|
},
|
||||||
|
roll: {
|
||||||
|
type: 'attack',
|
||||||
|
trait: 'strength'
|
||||||
|
},
|
||||||
|
damage: {
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: ['physical'],
|
||||||
|
value: {
|
||||||
|
custom: {
|
||||||
|
enabled: true,
|
||||||
|
formula: '@system.rules.attack.damage.value'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||||
|
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||||
|
}),
|
||||||
|
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||||
|
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
||||||
|
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
||||||
|
}),
|
||||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||||
bonuses: new fields.SchemaField({
|
bonuses: new fields.SchemaField({
|
||||||
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
|
|
||||||
damageThresholds: new fields.SchemaField({
|
|
||||||
severe: new fields.NumberField({ integer: true, initial: 0 }),
|
|
||||||
major: new fields.NumberField({ integer: true, initial: 0 })
|
|
||||||
}),
|
|
||||||
roll: new fields.SchemaField({
|
roll: new fields.SchemaField({
|
||||||
attack: new fields.NumberField({ integer: true, initial: 0 }),
|
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
|
||||||
spellcast: new fields.NumberField({ integer: true, initial: 0 }),
|
spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'),
|
||||||
action: new fields.NumberField({ integer: true, initial: 0 }),
|
trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'),
|
||||||
hopeOrFear: new fields.NumberField({ integer: true, initial: 0 })
|
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
|
||||||
|
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction'),
|
||||||
|
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.primaryWeaponAttack'),
|
||||||
|
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.secondaryWeaponAttack')
|
||||||
}),
|
}),
|
||||||
damage: new fields.SchemaField({
|
damage: new fields.SchemaField({
|
||||||
all: new fields.NumberField({ integer: true, initial: 0 }),
|
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
||||||
physical: new fields.NumberField({ integer: true, initial: 0 }),
|
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
|
||||||
magic: new fields.NumberField({ integer: true, initial: 0 })
|
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'),
|
||||||
|
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.secondaryWeapon')
|
||||||
|
}),
|
||||||
|
healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'),
|
||||||
|
range: new fields.SchemaField({
|
||||||
|
weapon: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Range.weapon'
|
||||||
|
}),
|
||||||
|
spell: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Range.spell'
|
||||||
|
}),
|
||||||
|
other: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Range.other'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
rally: new fields.ArrayField(new fields.StringField(), {
|
||||||
|
label: 'DAGGERHEART.CLASS.Feature.rallyDice'
|
||||||
|
}),
|
||||||
|
rest: new fields.SchemaField({
|
||||||
|
shortRest: new fields.SchemaField({
|
||||||
|
shortMoves: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
min: 0,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
|
||||||
|
}),
|
||||||
|
longMoves: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
min: 0,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
longRest: new fields.SchemaField({
|
||||||
|
shortMoves: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
min: 0,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
|
||||||
|
}),
|
||||||
|
longMoves: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
min: 0,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
maxArmorMarked: new fields.SchemaField({
|
damageReduction: new fields.SchemaField({
|
||||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
maxArmorMarked: new fields.SchemaField({
|
||||||
bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }),
|
value: new fields.NumberField({
|
||||||
stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 })
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
initial: 1,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
||||||
|
}),
|
||||||
|
stressExtra: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
stressDamageReduction: new fields.SchemaField({
|
||||||
|
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
|
||||||
|
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
||||||
|
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor')
|
||||||
|
}),
|
||||||
|
increasePerArmorMark: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
initial: 1,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||||
|
}),
|
||||||
|
magical: new fields.BooleanField({ initial: false }),
|
||||||
|
physical: new fields.BooleanField({ initial: false })
|
||||||
}),
|
}),
|
||||||
stressDamageReduction: new fields.SchemaField({
|
attack: new fields.SchemaField({
|
||||||
severe: stressDamageReductionRule(),
|
damage: new fields.SchemaField({
|
||||||
major: stressDamageReductionRule(),
|
value: new fields.StringField({
|
||||||
minor: stressDamageReductionRule()
|
required: true,
|
||||||
|
initial: '@profd4',
|
||||||
|
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.value.label'
|
||||||
|
})
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
strangePatterns: new fields.NumberField({
|
weapon: new fields.SchemaField({
|
||||||
integer: true,
|
/* Unimplemented
|
||||||
min: 1,
|
-> Should remove the lowest damage dice from weapon damage
|
||||||
max: 12,
|
-> Reflect this in the chat message somehow so players get feedback that their choice is helping them.
|
||||||
nullable: true,
|
*/
|
||||||
initial: null
|
dropLowestDamageDice: new fields.BooleanField({ initial: false }),
|
||||||
|
/* Unimplemented
|
||||||
|
-> Should flip any lowest possible dice rolls for weapon damage to highest
|
||||||
|
-> Reflect this in the chat message somehow so players get feedback that their choice is helping them.
|
||||||
|
*/
|
||||||
|
flipMinDiceValue: new fields.BooleanField({ intial: false })
|
||||||
}),
|
}),
|
||||||
runeWard: new fields.BooleanField({ initial: false })
|
runeWard: new fields.BooleanField({ initial: false })
|
||||||
})
|
})
|
||||||
|
|
@ -169,6 +290,11 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
return !this.class.value || !this.class.subclass;
|
return !this.class.value || !this.class.subclass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get spellcastModifier() {
|
||||||
|
const subClasses = this.parent.items.filter(x => x.type === 'subclass') ?? [];
|
||||||
|
return Math.max(subClasses?.map(sc => this.traits[sc.system.spellcastingTrait]?.value));
|
||||||
|
}
|
||||||
|
|
||||||
get spellcastingModifiers() {
|
get spellcastingModifiers() {
|
||||||
return {
|
return {
|
||||||
main: this.class.subclass?.system?.spellcastingTrait,
|
main: this.class.subclass?.system?.spellcastingTrait,
|
||||||
|
|
@ -198,6 +324,24 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
|
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get activeBeastform() {
|
||||||
|
return this.parent.effects.find(x => x.type === 'beastform');
|
||||||
|
}
|
||||||
|
|
||||||
|
get usedUnarmed() {
|
||||||
|
const primaryWeaponEquipped = this.primaryWeapon?.system?.equipped;
|
||||||
|
const secondaryWeaponEquipped = this.secondaryWeapon?.system?.equipped;
|
||||||
|
return !primaryWeaponEquipped && !secondaryWeaponEquipped
|
||||||
|
? {
|
||||||
|
...this.attack,
|
||||||
|
id: this.attack.id,
|
||||||
|
name: this.activeBeastform ? 'DAGGERHEART.ITEMS.Beastform.attackName' : this.attack.name,
|
||||||
|
img: this.activeBeastform ? 'icons/creatures/claws/claw-straight-brown.webp' : this.attack.img,
|
||||||
|
actor: this.parent
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
get sheetLists() {
|
get sheetLists() {
|
||||||
const ancestryFeatures = [],
|
const ancestryFeatures = [],
|
||||||
communityFeatures = [],
|
communityFeatures = [],
|
||||||
|
|
@ -207,23 +351,23 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
features = [];
|
features = [];
|
||||||
|
|
||||||
for (let item of this.parent.items) {
|
for (let item of this.parent.items) {
|
||||||
if (item.system.type === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
|
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
|
||||||
ancestryFeatures.push(item);
|
ancestryFeatures.push(item);
|
||||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.community.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
|
||||||
communityFeatures.push(item);
|
communityFeatures.push(item);
|
||||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.class.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
|
||||||
classFeatures.push(item);
|
classFeatures.push(item);
|
||||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
||||||
const subclassState = this.class.subclass.system.featureState;
|
const subclassState = this.class.subclass.system.featureState;
|
||||||
const identifier = item.system.identifier;
|
const subType = item.system.subType;
|
||||||
if (
|
if (
|
||||||
identifier === 'foundationFeature' ||
|
subType === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
||||||
(identifier === 'specializationFeature' && subclassState >= 2) ||
|
(subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
|
||||||
(identifier === 'masterFeature' && subclassState >= 3)
|
(subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
|
||||||
) {
|
) {
|
||||||
subclassFeatures.push(item);
|
subclassFeatures.push(item);
|
||||||
}
|
}
|
||||||
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.companion.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
|
||||||
companionFeatures.push(item);
|
companionFeatures.push(item);
|
||||||
} else if (item.type === 'feature' && !item.system.type) {
|
} else if (item.type === 'feature' && !item.system.type) {
|
||||||
features.push(item);
|
features.push(item);
|
||||||
|
|
@ -278,9 +422,14 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
get deathMoveViable() {
|
get deathMoveViable() {
|
||||||
return (
|
return this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
|
||||||
this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal
|
}
|
||||||
);
|
|
||||||
|
get armorApplicableDamageTypes() {
|
||||||
|
return {
|
||||||
|
physical: !this.rules.damageReduction.magical,
|
||||||
|
magical: !this.rules.damageReduction.physical
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static async unequipBeforeEquip(itemToEquip) {
|
static async unequipBeforeEquip(itemToEquip) {
|
||||||
|
|
@ -306,6 +455,8 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
|
this.evasion = this.class.value?.system?.evasion ?? 0;
|
||||||
|
|
||||||
const currentLevel = this.levelData.level.current;
|
const currentLevel = this.levelData.level.current;
|
||||||
const currentTier =
|
const currentTier =
|
||||||
currentLevel === 1
|
currentLevel === 1
|
||||||
|
|
@ -316,32 +467,32 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
for (let levelKey in this.levelData.levelups) {
|
for (let levelKey in this.levelData.levelups) {
|
||||||
const level = this.levelData.levelups[levelKey];
|
const level = this.levelData.levelups[levelKey];
|
||||||
|
|
||||||
this.proficiency.bonus += level.achievements.proficiency;
|
this.proficiency += level.achievements.proficiency;
|
||||||
|
|
||||||
for (let selection of level.selections) {
|
for (let selection of level.selections) {
|
||||||
switch (selection.type) {
|
switch (selection.type) {
|
||||||
case 'trait':
|
case 'trait':
|
||||||
selection.data.forEach(data => {
|
selection.data.forEach(data => {
|
||||||
this.traits[data].bonus += 1;
|
this.traits[data].value += 1;
|
||||||
this.traits[data].tierMarked = selection.tier === currentTier;
|
this.traits[data].tierMarked = selection.tier === currentTier;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'hitPoint':
|
case 'hitPoint':
|
||||||
this.resources.hitPoints.bonus += selection.value;
|
this.resources.hitPoints.max += selection.value;
|
||||||
break;
|
break;
|
||||||
case 'stress':
|
case 'stress':
|
||||||
this.resources.stress.bonus += selection.value;
|
this.resources.stress.max += selection.value;
|
||||||
break;
|
break;
|
||||||
case 'evasion':
|
case 'evasion':
|
||||||
this.evasion.bonus += selection.value;
|
this.evasion += selection.value;
|
||||||
break;
|
break;
|
||||||
case 'proficiency':
|
case 'proficiency':
|
||||||
this.proficiency.bonus = selection.value;
|
this.proficiency = selection.value;
|
||||||
break;
|
break;
|
||||||
case 'experience':
|
case 'experience':
|
||||||
Object.keys(this.experiences).forEach(key => {
|
Object.keys(this.experiences).forEach(key => {
|
||||||
const experience = this.experiences[key];
|
const experience = this.experiences[key];
|
||||||
experience.bonus += selection.value;
|
experience.value += selection.value;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -349,6 +500,7 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
const armor = this.armor;
|
const armor = this.armor;
|
||||||
|
this.armorScore = armor ? armor.system.baseScore : 0;
|
||||||
this.damageThresholds = {
|
this.damageThresholds = {
|
||||||
major: armor
|
major: armor
|
||||||
? armor.system.baseThresholds.major + this.levelData.level.current
|
? armor.system.baseThresholds.major + this.levelData.level.current
|
||||||
|
|
@ -357,38 +509,19 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
? armor.system.baseThresholds.severe + this.levelData.level.current
|
||||||
: this.levelData.level.current * 2
|
: this.levelData.level.current * 2
|
||||||
};
|
};
|
||||||
|
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||||
|
this.resources.hitPoints.max = this.class.value?.system?.hitPoints ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
const baseHope = this.resources.hope.value + (this.companion?.system?.resources?.hope ?? 0);
|
||||||
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
|
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||||
|
|
||||||
for (var traitKey in this.traits) {
|
|
||||||
var trait = this.traits[traitKey];
|
|
||||||
trait.total = (trait.value ?? 0) + trait.bonus;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var experienceKey in this.experiences) {
|
|
||||||
var experience = this.experiences[experienceKey];
|
|
||||||
experience.total = experience.value + experience.bonus;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rules.maxArmorMarked.total = this.rules.maxArmorMarked.value + this.rules.maxArmorMarked.bonus;
|
|
||||||
|
|
||||||
this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0;
|
|
||||||
this.resources.hitPoints.maxTotal = (this.class.value?.system?.hitPoints ?? 0) + this.resources.hitPoints.bonus;
|
|
||||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
|
||||||
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
|
|
||||||
this.proficiency.total = this.proficiency.value + this.proficiency.bonus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRollData() {
|
getRollData() {
|
||||||
const data = super.getRollData();
|
const data = super.getRollData();
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
...this.resources.tokens,
|
|
||||||
...this.resources.dice,
|
|
||||||
...this.bonuses,
|
|
||||||
tier: this.tier,
|
tier: this.tier,
|
||||||
level: this.levelData.level.current
|
level: this.levelData.level.current
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import ActionField from '../fields/actionField.mjs';
|
||||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||||
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
|
|
||||||
export default class DhCompanion extends BaseDataActor {
|
export default class DhCompanion extends BaseDataActor {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
||||||
|
|
@ -12,6 +13,7 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: 'TYPES.Actor.companion',
|
label: 'TYPES.Actor.companion',
|
||||||
type: 'companion',
|
type: 'companion',
|
||||||
|
isNPC: false,
|
||||||
settingSheet: DHCompanionSettings
|
settingSheet: DHCompanionSettings
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -20,24 +22,23 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
|
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
|
||||||
resources: new fields.SchemaField({
|
resources: new fields.SchemaField({
|
||||||
stress: new fields.SchemaField({
|
stress: resourceField(3, 'DAGGERHEART.GENERAL.stress', true),
|
||||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' })
|
||||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
|
||||||
max: new fields.NumberField({ initial: 3, integer: true })
|
|
||||||
}),
|
|
||||||
hope: new fields.NumberField({ initial: 0, integer: true })
|
|
||||||
}),
|
}),
|
||||||
evasion: new fields.SchemaField({
|
evasion: new fields.NumberField({
|
||||||
value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
|
required: true,
|
||||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
min: 1,
|
||||||
|
initial: 10,
|
||||||
|
integer: true,
|
||||||
|
label: 'DAGGERHEART.GENERAL.evasion'
|
||||||
}),
|
}),
|
||||||
experiences: new fields.TypedObjectField(
|
experiences: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
name: new fields.StringField({}),
|
name: new fields.StringField({}),
|
||||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
value: new fields.NumberField({ integer: true, initial: 0 })
|
||||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
initial: {
|
initial: {
|
||||||
|
|
@ -59,17 +60,16 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
amount: 1
|
amount: 1
|
||||||
},
|
},
|
||||||
roll: {
|
roll: {
|
||||||
type: 'weapon',
|
type: 'attack',
|
||||||
bonus: 0,
|
bonus: 0
|
||||||
trait: 'instinct'
|
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
multiplier: 'flat',
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
dice: 'd6',
|
dice: 'd6',
|
||||||
multiplier: 'flat'
|
multiplier: 'prof'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -77,20 +77,22 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
actions: new fields.ArrayField(new ActionField()),
|
actions: new fields.ArrayField(new ActionField()),
|
||||||
levelData: new fields.EmbeddedDataField(DhLevelData)
|
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||||
|
bonuses: new fields.SchemaField({
|
||||||
|
damage: new fields.SchemaField({
|
||||||
|
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
||||||
|
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
|
||||||
|
})
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get traits() {
|
get proficiency() {
|
||||||
return {
|
return this.partner?.system?.proficiency ?? 1;
|
||||||
instinct: { total: this.attack.roll.bonus }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main;
|
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||||
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.total;
|
|
||||||
this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing;
|
|
||||||
|
|
||||||
for (let levelKey in this.levelData.levelups) {
|
for (let levelKey in this.levelData.levelups) {
|
||||||
const level = this.levelData.levelups[levelKey];
|
const level = this.levelData.levelups[levelKey];
|
||||||
|
|
@ -107,15 +109,15 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'stress':
|
case 'stress':
|
||||||
this.resources.stress.bonus += selection.value;
|
this.resources.stress.max += selection.value;
|
||||||
break;
|
break;
|
||||||
case 'evasion':
|
case 'evasion':
|
||||||
this.evasion.bonus += selection.value;
|
this.evasion += selection.value;
|
||||||
break;
|
break;
|
||||||
case 'experience':
|
case 'experience':
|
||||||
Object.keys(this.experiences).forEach(key => {
|
Object.keys(this.experiences).forEach(key => {
|
||||||
const experience = this.experiences[key];
|
const experience = this.experiences[key];
|
||||||
experience.bonus += selection.value;
|
experience.value += selection.value;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -123,20 +125,6 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareDerivedData() {
|
|
||||||
for (var experienceKey in this.experiences) {
|
|
||||||
var experience = this.experiences[experienceKey];
|
|
||||||
experience.total = experience.value + experience.bonus;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.partner) {
|
|
||||||
this.partner.system.resources.hope.max += this.resources.hope;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
|
||||||
this.evasion.total = this.evasion.value + this.evasion.bonus;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete() {
|
||||||
if (this.partner) {
|
if (this.partner) {
|
||||||
await this.partner.update({ 'system.companion': null });
|
await this.partner.update({ 'system.companion': null });
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,22 @@ export default class DhEnvironment extends BaseDataActor {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: 'TYPES.Actor.environment',
|
label: 'TYPES.Actor.environment',
|
||||||
type: 'environment',
|
type: 'environment',
|
||||||
settingSheet: DHEnvironmentSettings
|
settingSheet: DHEnvironmentSettings,
|
||||||
|
hasResistances: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
tier: new fields.StringField({
|
...super.defineSchema(),
|
||||||
|
tier: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
integer: true,
|
||||||
choices: CONFIG.DH.GENERAL.tiers,
|
choices: CONFIG.DH.GENERAL.tiers,
|
||||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||||
}),
|
}),
|
||||||
type: new fields.StringField({ choices: CONFIG.DH.ACTOR.environmentTypes }),
|
type: new fields.StringField({ choices: CONFIG.DH.ACTOR.environmentTypes }),
|
||||||
description: new fields.StringField(),
|
|
||||||
impulses: new fields.StringField(),
|
impulses: new fields.StringField(),
|
||||||
difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }),
|
difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }),
|
||||||
potentialAdversaries: new fields.TypedObjectField(
|
potentialAdversaries: new fields.TypedObjectField(
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ class DhCountdown extends foundry.abstract.DataModel {
|
||||||
value: new fields.StringField({
|
value: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
choices: CONFIG.DH.GENERAL.countdownTypes,
|
||||||
initial: CONFIG.DH.GENERAL.countdownTypes.spotlight.id,
|
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label'
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label'
|
||||||
}),
|
}),
|
||||||
label: new fields.StringField({
|
label: new fields.StringField({
|
||||||
|
|
@ -132,7 +132,13 @@ class DhCountdown extends foundry.abstract.DataModel {
|
||||||
export const registerCountdownHooks = () => {
|
export const registerCountdownHooks = () => {
|
||||||
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
|
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
|
||||||
if (refreshType === RefreshType.Countdown) {
|
if (refreshType === RefreshType.Countdown) {
|
||||||
foundry.applications.instances.get(application)?.render();
|
if (application) {
|
||||||
|
foundry.applications.instances.get(application)?.render();
|
||||||
|
} else {
|
||||||
|
foundry.applications.instances.get('narrative-countdowns')?.render();
|
||||||
|
foundry.applications.instances.get('encounter-countdowns')?.render();
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
32
module/data/fields/actorField.mjs
Normal file
32
module/data/fields/actorField.mjs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
const attributeField = label =>
|
||||||
|
new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ initial: 0, integer: true, label }),
|
||||||
|
tierMarked: new fields.BooleanField({ initial: false })
|
||||||
|
});
|
||||||
|
|
||||||
|
const resourceField = (max = 0, label, reverse = false) =>
|
||||||
|
new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ initial: 0, integer: true, label }),
|
||||||
|
max: new fields.NumberField({ initial: max, integer: true }),
|
||||||
|
isReversed: new fields.BooleanField({ initial: reverse })
|
||||||
|
});
|
||||||
|
|
||||||
|
const stressDamageReductionRule = localizationPath =>
|
||||||
|
new fields.SchemaField({
|
||||||
|
enabled: new fields.BooleanField({ required: true, initial: false }),
|
||||||
|
cost: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
label: `${localizationPath}.label`,
|
||||||
|
hint: `${localizationPath}.hint`
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const bonusField = label =>
|
||||||
|
new fields.SchemaField({
|
||||||
|
bonus: new fields.NumberField({ integer: true, initial: 0, label: `${game.i18n.localize(label)} Value` }),
|
||||||
|
dice: new fields.ArrayField(new fields.StringField(), { label: `${game.i18n.localize(label)} Dice` })
|
||||||
|
});
|
||||||
|
|
||||||
|
export { attributeField, resourceField, stressDamageReductionRule, bonusField };
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import DHAncestry from './ancestry.mjs';
|
import DHAncestry from './ancestry.mjs';
|
||||||
import DHArmor from './armor.mjs';
|
import DHArmor from './armor.mjs';
|
||||||
|
import DHAttachableItem from './attachableItem.mjs';
|
||||||
import DHClass from './class.mjs';
|
import DHClass from './class.mjs';
|
||||||
import DHCommunity from './community.mjs';
|
import DHCommunity from './community.mjs';
|
||||||
import DHConsumable from './consumable.mjs';
|
import DHConsumable from './consumable.mjs';
|
||||||
|
|
@ -13,6 +14,7 @@ import DHBeastform from './beastform.mjs';
|
||||||
export {
|
export {
|
||||||
DHAncestry,
|
DHAncestry,
|
||||||
DHArmor,
|
DHArmor,
|
||||||
|
DHAttachableItem,
|
||||||
DHClass,
|
DHClass,
|
||||||
DHCommunity,
|
DHCommunity,
|
||||||
DHConsumable,
|
DHConsumable,
|
||||||
|
|
@ -27,6 +29,7 @@ export {
|
||||||
export const config = {
|
export const config = {
|
||||||
ancestry: DHAncestry,
|
ancestry: DHAncestry,
|
||||||
armor: DHArmor,
|
armor: DHArmor,
|
||||||
|
attachableItem: DHAttachableItem,
|
||||||
class: DHClass,
|
class: DHClass,
|
||||||
community: DHCommunity,
|
community: DHCommunity,
|
||||||
consumable: DHConsumable,
|
consumable: DHConsumable,
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,20 @@ export default class DHAncestry extends BaseDataItem {
|
||||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
|
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get primaryFeature() {
|
||||||
|
return (
|
||||||
|
this.features.find(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.primary) ??
|
||||||
|
(this.features.filter(x => !x).length > 0 ? {} : null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get secondaryFeature() {
|
||||||
|
return (
|
||||||
|
this.features.find(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.secondary) ??
|
||||||
|
(this.features.filter(x => !x || x.system.subType === CONFIG.DH.ITEM.featureSubTypes.primary).length > 1
|
||||||
|
? {}
|
||||||
|
: null)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import AttachableItem from './attachableItem.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import ActionField from '../fields/actionField.mjs';
|
||||||
import { armorFeatures } from '../../config/itemConfig.mjs';
|
import { armorFeatures } from '../../config/itemConfig.mjs';
|
||||||
|
import { actionsTypes } from '../action/_module.mjs';
|
||||||
|
|
||||||
export default class DHArmor extends BaseDataItem {
|
export default class DHArmor extends AttachableItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: 'TYPES.Item.armor',
|
label: 'TYPES.Item.armor',
|
||||||
type: 'armor',
|
type: 'armor',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true,
|
|
||||||
isInventoryItem: true
|
isInventoryItem: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export default class DHArmor extends BaseDataItem {
|
||||||
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }),
|
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }),
|
||||||
equipped: new fields.BooleanField({ initial: false }),
|
equipped: new fields.BooleanField({ initial: false }),
|
||||||
baseScore: new fields.NumberField({ integer: true, initial: 0 }),
|
baseScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||||
features: new fields.ArrayField(
|
armorFeatures: new fields.ArrayField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
value: new fields.StringField({
|
value: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -44,25 +44,28 @@ export default class DHArmor extends BaseDataItem {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get featureInfo() {
|
get customActions() {
|
||||||
return this.feature ? CONFIG.DH.ITEM.armorFeatures[this.feature] : null;
|
return this.actions.filter(
|
||||||
|
action => !this.armorFeatures.some(feature => feature.actionIds.includes(action.id))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preUpdate(changes, options, user) {
|
async _preUpdate(changes, options, user) {
|
||||||
const allowed = await super._preUpdate(changes, options, user);
|
const allowed = await super._preUpdate(changes, options, user);
|
||||||
if (allowed === false) return false;
|
if (allowed === false) return false;
|
||||||
|
|
||||||
if (changes.system.features) {
|
if (changes.system.armorFeatures) {
|
||||||
const removed = this.features.filter(x => !changes.system.features.includes(x));
|
const removed = this.armorFeatures.filter(x => !changes.system.armorFeatures.includes(x));
|
||||||
const added = changes.system.features.filter(x => !this.features.includes(x));
|
const added = changes.system.armorFeatures.filter(x => !this.armorFeatures.includes(x));
|
||||||
|
|
||||||
|
const effectIds = [];
|
||||||
|
const actionIds = [];
|
||||||
for (var feature of removed) {
|
for (var feature of removed) {
|
||||||
for (var effectId of feature.effectIds) {
|
effectIds.push(...feature.effectIds);
|
||||||
await this.parent.effects.get(effectId).delete();
|
actionIds.push(...feature.actionIds);
|
||||||
}
|
|
||||||
|
|
||||||
changes.system.actions = this.actions.filter(x => !feature.actionIds.includes(x._id));
|
|
||||||
}
|
}
|
||||||
|
await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
||||||
|
changes.system.actions = this.actions.filter(x => !actionIds.includes(x._id));
|
||||||
|
|
||||||
for (var feature of added) {
|
for (var feature of added) {
|
||||||
const featureData = armorFeatures[feature.value];
|
const featureData = armorFeatures[feature.value];
|
||||||
|
|
|
||||||
160
module/data/item/attachableItem.mjs
Normal file
160
module/data/item/attachableItem.mjs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import BaseDataItem from './base.mjs';
|
||||||
|
|
||||||
|
export default class AttachableItem extends BaseDataItem {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
|
attached: new fields.ArrayField(new fields.DocumentUUIDField({ type: 'Item', nullable: true }))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preUpdate(changes, options, user) {
|
||||||
|
const allowed = await super._preUpdate(changes, options, user);
|
||||||
|
if (allowed === false) return false;
|
||||||
|
|
||||||
|
// Handle equipped status changes for attachment effects
|
||||||
|
if (changes.system?.equipped !== undefined && changes.system.equipped !== this.equipped) {
|
||||||
|
await this.#handleAttachmentEffectsOnEquipChange(changes.system.equipped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleAttachmentEffectsOnEquipChange(newEquippedStatus) {
|
||||||
|
const actor = this.parent.parent?.type === 'character' ? this.parent.parent : this.parent.parent?.parent;
|
||||||
|
const parentType = this.parent.type;
|
||||||
|
|
||||||
|
if (!actor || !this.attached?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newEquippedStatus) {
|
||||||
|
// Item is being equipped - add attachment effects
|
||||||
|
for (const attachedUuid of this.attached) {
|
||||||
|
const attachedItem = await fromUuid(attachedUuid);
|
||||||
|
if (attachedItem && attachedItem.effects.size > 0) {
|
||||||
|
await this.#copyAttachmentEffectsToActor({
|
||||||
|
attachedItem,
|
||||||
|
attachedUuid,
|
||||||
|
parentType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Item is being unequipped - remove attachment effects
|
||||||
|
await this.#removeAllAttachmentEffects(parentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #copyAttachmentEffectsToActor({ attachedItem, attachedUuid, parentType }) {
|
||||||
|
const actor = this.parent.parent;
|
||||||
|
if (!actor || !attachedItem.effects.size > 0 || !this.equipped) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectsToCreate = [];
|
||||||
|
for (const effect of attachedItem.effects) {
|
||||||
|
const effectData = effect.toObject();
|
||||||
|
effectData.origin = `${this.parent.uuid}:${attachedUuid}`;
|
||||||
|
|
||||||
|
const attachmentSource = {
|
||||||
|
itemUuid: attachedUuid,
|
||||||
|
originalEffectId: effect.id
|
||||||
|
};
|
||||||
|
attachmentSource[`${parentType}Uuid`] = this.parent.uuid;
|
||||||
|
|
||||||
|
effectData.flags = {
|
||||||
|
...effectData.flags,
|
||||||
|
[CONFIG.DH.id]: {
|
||||||
|
...effectData.flags?.[CONFIG.DH.id],
|
||||||
|
[CONFIG.DH.FLAGS.itemAttachmentSource]: attachmentSource
|
||||||
|
}
|
||||||
|
};
|
||||||
|
effectsToCreate.push(effectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectsToCreate.length > 0) {
|
||||||
|
return await actor.createEmbeddedDocuments('ActiveEffect', effectsToCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async #removeAllAttachmentEffects(parentType) {
|
||||||
|
const actor = this.parent.parent;
|
||||||
|
if (!actor) return;
|
||||||
|
|
||||||
|
const parentUuidProperty = `${parentType}Uuid`;
|
||||||
|
const effectsToRemove = actor.effects.filter(effect => {
|
||||||
|
const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource);
|
||||||
|
return attachmentSource && attachmentSource[parentUuidProperty] === this.parent.uuid;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (effectsToRemove.length > 0) {
|
||||||
|
await actor.deleteEmbeddedDocuments(
|
||||||
|
'ActiveEffect',
|
||||||
|
effectsToRemove.map(e => e.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public method for adding an attachment
|
||||||
|
*/
|
||||||
|
async addAttachment(droppedItem) {
|
||||||
|
const newUUID = droppedItem.uuid;
|
||||||
|
|
||||||
|
if (this.attached.includes(newUUID)) {
|
||||||
|
ui.notifications.warn(`${droppedItem.name} is already attached to this ${this.parent.type}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedAttached = [...this.attached, newUUID];
|
||||||
|
await this.parent.update({
|
||||||
|
'system.attached': updatedAttached
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy effects if equipped
|
||||||
|
if (this.equipped && droppedItem.effects.size > 0) {
|
||||||
|
await this.#copyAttachmentEffectsToActor({
|
||||||
|
attachedItem: droppedItem,
|
||||||
|
attachedUuid: newUUID,
|
||||||
|
parentType: this.parent.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public method for removing an attachment
|
||||||
|
*/
|
||||||
|
async removeAttachment(attachedUuid) {
|
||||||
|
await this.parent.update({
|
||||||
|
'system.attached': this.attached.filter(uuid => uuid !== attachedUuid)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove effects
|
||||||
|
await this.#removeAttachmentEffects(attachedUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
async #removeAttachmentEffects(attachedUuid) {
|
||||||
|
const actor = this.parent.parent;
|
||||||
|
if (!actor) return;
|
||||||
|
|
||||||
|
const parentType = this.parent.type;
|
||||||
|
const parentUuidProperty = `${parentType}Uuid`;
|
||||||
|
const effectsToRemove = actor.effects.filter(effect => {
|
||||||
|
const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource);
|
||||||
|
return (
|
||||||
|
attachmentSource &&
|
||||||
|
attachmentSource[parentUuidProperty] === this.parent.uuid &&
|
||||||
|
attachmentSource.itemUuid === attachedUuid
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (effectsToRemove.length > 0) {
|
||||||
|
await actor.deleteEmbeddedDocuments(
|
||||||
|
'ActiveEffect',
|
||||||
|
effectsToRemove.map(e => e.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,12 +11,15 @@
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ITEMS'];
|
||||||
|
|
||||||
/** @returns {ItemDataModelMetadata}*/
|
/** @returns {ItemDataModelMetadata}*/
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
return {
|
return {
|
||||||
label: 'Base Item',
|
label: 'Base Item',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
hasDescription: false,
|
hasDescription: false,
|
||||||
|
hasResource: false,
|
||||||
isQuantifiable: false,
|
isQuantifiable: false,
|
||||||
isInventoryItem: false
|
isInventoryItem: false
|
||||||
};
|
};
|
||||||
|
|
@ -33,6 +36,36 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||||
|
|
||||||
|
if (this.metadata.hasResource) {
|
||||||
|
schema.resource = new fields.SchemaField(
|
||||||
|
{
|
||||||
|
type: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.ITEM.itemResourceTypes,
|
||||||
|
initial: CONFIG.DH.ITEM.itemResourceTypes.simple
|
||||||
|
}),
|
||||||
|
value: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
|
||||||
|
max: new fields.StringField({ nullable: true, initial: null }),
|
||||||
|
icon: new fields.StringField(),
|
||||||
|
recovery: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.refreshTypes,
|
||||||
|
initial: null,
|
||||||
|
nullable: true
|
||||||
|
}),
|
||||||
|
diceStates: new fields.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ integer: true, initial: 1, min: 1 }),
|
||||||
|
used: new fields.BooleanField({ initial: false })
|
||||||
|
})
|
||||||
|
),
|
||||||
|
dieFaces: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.diceTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.diceTypes.d4
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ nullable: true, initial: null }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.metadata.isQuantifiable)
|
if (this.metadata.isQuantifiable)
|
||||||
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
||||||
|
|
||||||
|
|
@ -62,28 +95,27 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
// Skip if no initial action is required or actions already exist
|
// Skip if no initial action is required or actions already exist
|
||||||
if (!this.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return;
|
if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) {
|
||||||
|
const metadataType = this.metadata.type;
|
||||||
|
const actionType = { weapon: 'attack' }[metadataType];
|
||||||
|
const ActionClass = game.system.api.models.actions.actionsTypes[actionType];
|
||||||
|
|
||||||
const metadataType = this.metadata.type;
|
const action = new ActionClass(
|
||||||
const actionType = { weapon: 'attack' }[metadataType];
|
{
|
||||||
const ActionClass = game.system.api.models.actions.actionsTypes[actionType];
|
_id: foundry.utils.randomID(),
|
||||||
|
type: actionType,
|
||||||
|
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
|
||||||
|
...ActionClass.getSourceConfig(this.parent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parent: this.parent
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const action = new ActionClass(
|
this.updateSource({ actions: [action] });
|
||||||
{
|
}
|
||||||
_id: foundry.utils.randomID(),
|
|
||||||
type: actionType,
|
|
||||||
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
|
|
||||||
...ActionClass.getSourceConfig(this.parent)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parent: this.parent
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.updateSource({ actions: [action] });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCreate(data) {
|
_onCreate(data) {
|
||||||
|
|
@ -95,7 +127,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
...feature,
|
...feature,
|
||||||
system: {
|
system: {
|
||||||
...feature.system,
|
...feature.system,
|
||||||
type: this.parent.type,
|
originItemType: this.parent.type,
|
||||||
originId: data._id,
|
originId: data._id,
|
||||||
identifier: feature.identifier
|
identifier: feature.identifier
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayFie
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
|
|
||||||
export default class DHBeastform extends BaseDataItem {
|
export default class DHBeastform extends BaseDataItem {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Beastform'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ITEMS.Beastform'];
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
|
|
@ -19,23 +19,65 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
tier: new fields.StringField({
|
beastformType: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
|
choices: CONFIG.DH.ITEM.beastformTypes,
|
||||||
|
initial: CONFIG.DH.ITEM.beastformTypes.normal.id
|
||||||
|
}),
|
||||||
|
tier: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
choices: CONFIG.DH.GENERAL.tiers,
|
choices: CONFIG.DH.GENERAL.tiers,
|
||||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||||
}),
|
}),
|
||||||
tokenImg: new fields.FilePathField({
|
tokenImg: new fields.FilePathField({
|
||||||
initial: 'icons/svg/mystery-man.svg',
|
initial: 'icons/svg/mystery-man.svg',
|
||||||
categories: ['IMAGE'],
|
categories: ['IMAGE'],
|
||||||
base64: false
|
base64: false
|
||||||
}),
|
}),
|
||||||
|
tokenRingImg: new fields.FilePathField({
|
||||||
|
initial: 'icons/svg/mystery-man.svg',
|
||||||
|
categories: ['IMAGE'],
|
||||||
|
base64: false
|
||||||
|
}),
|
||||||
tokenSize: new fields.SchemaField({
|
tokenSize: new fields.SchemaField({
|
||||||
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||||
}),
|
}),
|
||||||
|
mainTrait: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.ACTOR.abilities,
|
||||||
|
initial: CONFIG.DH.ACTOR.abilities.agility.id
|
||||||
|
}),
|
||||||
examples: new fields.StringField(),
|
examples: new fields.StringField(),
|
||||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
advantageOn: new fields.TypedObjectField(
|
||||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
|
new fields.SchemaField({
|
||||||
|
value: new fields.StringField()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||||
|
evolved: new fields.SchemaField({
|
||||||
|
maximumTier: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.tiers
|
||||||
|
}),
|
||||||
|
mainTraitBonus: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
min: 0,
|
||||||
|
initial: 0
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
hybrid: new fields.SchemaField({
|
||||||
|
maximumTier: new fields.NumberField({
|
||||||
|
integer: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.tiers,
|
||||||
|
label: 'DAGGERHEART.ITEMS.Beastform.FIELDS.evolved.maximumTier.label'
|
||||||
|
}),
|
||||||
|
beastformOptions: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 }),
|
||||||
|
advantages: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 }),
|
||||||
|
features: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 })
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,40 +98,64 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
'Item',
|
'Item',
|
||||||
this.features.map(x => x.toObject())
|
this.features.map(x => x.toObject())
|
||||||
);
|
);
|
||||||
const effects = await this.parent.parent.createEmbeddedDocuments(
|
|
||||||
|
const extraEffects = await this.parent.parent.createEmbeddedDocuments(
|
||||||
'ActiveEffect',
|
'ActiveEffect',
|
||||||
this.parent.effects.map(x => x.toObject())
|
this.parent.effects.filter(x => x.type !== 'beastform').map(x => x.toObject())
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [
|
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
||||||
{
|
await beastformEffect.updateSource({
|
||||||
type: 'beastform',
|
changes: [
|
||||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
...beastformEffect.changes,
|
||||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp',
|
{
|
||||||
system: {
|
key: 'system.advantageSources',
|
||||||
isBeastform: true,
|
mode: 2,
|
||||||
characterTokenData: {
|
value: Object.values(this.advantageOn)
|
||||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
.map(x => x.value)
|
||||||
tokenSize: {
|
.join(', ')
|
||||||
height: this.parent.parent.prototypeToken.height,
|
|
||||||
width: this.parent.parent.prototypeToken.width
|
|
||||||
}
|
|
||||||
},
|
|
||||||
advantageOn: this.advantageOn,
|
|
||||||
featureIds: features.map(x => x.id),
|
|
||||||
effectIds: effects.map(x => x.id)
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
system: {
|
||||||
|
characterTokenData: {
|
||||||
|
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||||
|
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
|
||||||
|
tokenSize: {
|
||||||
|
height: this.parent.parent.prototypeToken.height,
|
||||||
|
width: this.parent.parent.prototypeToken.width
|
||||||
|
}
|
||||||
|
},
|
||||||
|
advantageOn: this.advantageOn,
|
||||||
|
featureIds: features.map(x => x.id),
|
||||||
|
effectIds: extraEffects.map(x => x.id)
|
||||||
}
|
}
|
||||||
]);
|
});
|
||||||
|
|
||||||
|
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
|
||||||
|
|
||||||
await updateActorTokens(this.parent.parent, {
|
await updateActorTokens(this.parent.parent, {
|
||||||
height: this.tokenSize.height,
|
height: this.tokenSize.height,
|
||||||
width: this.tokenSize.width,
|
width: this.tokenSize.width,
|
||||||
texture: {
|
texture: {
|
||||||
src: this.tokenImg
|
src: this.tokenImg
|
||||||
|
},
|
||||||
|
ring: {
|
||||||
|
subject: {
|
||||||
|
texture: this.tokenRingImg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onCreate() {
|
||||||
|
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||||
|
{
|
||||||
|
type: 'beastform',
|
||||||
|
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
||||||
|
img: 'icons/creatures/abilities/paw-print-pair-purple.webp'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,10 @@ export default class DHClass extends BaseDataItem {
|
||||||
integer: true,
|
integer: true,
|
||||||
min: 1,
|
min: 1,
|
||||||
initial: 5,
|
initial: 5,
|
||||||
label: 'DAGGERHEART.GENERAL.hitPoints'
|
label: 'DAGGERHEART.GENERAL.HitPoints.plural'
|
||||||
}),
|
}),
|
||||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||||
hopeFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||||
classFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
|
||||||
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||||
inventory: new fields.SchemaField({
|
inventory: new fields.SchemaField({
|
||||||
take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||||
|
|
@ -52,12 +51,18 @@ export default class DHClass extends BaseDataItem {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get hopeFeature() {
|
get hopeFeatures() {
|
||||||
return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null;
|
return (
|
||||||
|
this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.hope) ??
|
||||||
|
(this.features.filter(x => !x).length > 0 ? {} : null)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get features() {
|
get classFeatures() {
|
||||||
return [...this.hopeFeatures.filter(x => x), ...this.classFeatures.filter(x => x)];
|
return (
|
||||||
|
this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.class) ??
|
||||||
|
(this.features.filter(x => !x).length > 0 ? {} : null)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: 'TYPES.Item.domainCard',
|
label: 'TYPES.Item.domainCard',
|
||||||
type: 'domainCard',
|
type: 'domainCard',
|
||||||
hasDescription: true
|
hasDescription: true,
|
||||||
|
hasResource: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +29,6 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
required: true,
|
required: true,
|
||||||
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
||||||
}),
|
}),
|
||||||
foundation: new fields.BooleanField({ initial: false }),
|
|
||||||
inVault: new fields.BooleanField({ initial: false }),
|
inVault: new fields.BooleanField({ initial: false }),
|
||||||
actions: new fields.ArrayField(new ActionField())
|
actions: new fields.ArrayField(new ActionField())
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ export default class DHFeature extends BaseDataItem {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: 'TYPES.Item.feature',
|
label: 'TYPES.Item.feature',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
hasDescription: true
|
hasDescription: true,
|
||||||
|
hasResource: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,10 +17,33 @@ export default class DHFeature extends BaseDataItem {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
type: new fields.StringField({ choices: CONFIG.DH.ITEM.featureTypes, nullable: true, initial: null }),
|
originItemType: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.ITEM.featureTypes,
|
||||||
|
nullable: true,
|
||||||
|
initial: null
|
||||||
|
}),
|
||||||
|
subType: new fields.StringField({ choices: CONFIG.DH.ITEM.featureSubTypes, nullable: true, initial: null }),
|
||||||
originId: new fields.StringField({ nullable: true, initial: null }),
|
originId: new fields.StringField({ nullable: true, initial: null }),
|
||||||
identifier: new fields.StringField(),
|
identifier: new fields.StringField(),
|
||||||
actions: new fields.ArrayField(new ActionField())
|
actions: new fields.ArrayField(new ActionField())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get spellcastingModifier() {
|
||||||
|
let traitValue = 0;
|
||||||
|
if (this.actor && this.originId && ['class', 'subclass'].includes(this.originItemType)) {
|
||||||
|
if (this.originItemType === 'subclass') {
|
||||||
|
traitValue =
|
||||||
|
this.actor.system.traits[this.actor.items.get(this.originId).system.spellcastingTrait]?.value ?? 0;
|
||||||
|
} else {
|
||||||
|
const subclass =
|
||||||
|
this.actor.system.multiclass.value?.id === this.originId
|
||||||
|
? this.actor.system.multiclass.subclass
|
||||||
|
: this.actor.system.class.subclass;
|
||||||
|
traitValue = this.actor.system.traits[subclass.system.spellcastingTrait]?.value ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return traitValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
|
|
||||||
export default class DHSubclass extends BaseDataItem {
|
export default class DHSubclass extends BaseDataItem {
|
||||||
|
|
@ -22,20 +22,22 @@ export default class DHSubclass extends BaseDataItem {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
initial: null
|
initial: null
|
||||||
}),
|
}),
|
||||||
foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||||
specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
|
||||||
masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
|
||||||
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
||||||
isMulticlass: new fields.BooleanField({ initial: false })
|
isMulticlass: new fields.BooleanField({ initial: false })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get features() {
|
get foundationFeatures() {
|
||||||
return [
|
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.foundation);
|
||||||
{ ...this.foundationFeature.toObject(), identifier: 'foundationFeature' },
|
}
|
||||||
{ ...this.specializationFeature.toObject(), identifier: 'specializationFeature' },
|
|
||||||
{ ...this.masteryFeature.toObject(), identifier: 'masteryFeature' }
|
get specializationFeatures() {
|
||||||
];
|
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.specialization);
|
||||||
|
}
|
||||||
|
|
||||||
|
get masteryFeatures() {
|
||||||
|
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.mastery);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import AttachableItem from './attachableItem.mjs';
|
||||||
import { actionsTypes } from '../action/_module.mjs';
|
import { actionsTypes } from '../action/_module.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import ActionField from '../fields/actionField.mjs';
|
||||||
|
|
||||||
export default class DHWeapon extends BaseDataItem {
|
export default class DHWeapon extends AttachableItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: 'TYPES.Item.weapon',
|
label: 'TYPES.Item.weapon',
|
||||||
type: 'weapon',
|
type: 'weapon',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true,
|
isInventoryItem: true
|
||||||
isInventoryItem: true,
|
|
||||||
// hasInitialAction: true
|
// hasInitialAction: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -26,8 +25,7 @@ export default class DHWeapon extends BaseDataItem {
|
||||||
//SETTINGS
|
//SETTINGS
|
||||||
secondary: new fields.BooleanField({ initial: false }),
|
secondary: new fields.BooleanField({ initial: false }),
|
||||||
burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded' }),
|
burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded' }),
|
||||||
|
weaponFeatures: new fields.ArrayField(
|
||||||
features: new fields.ArrayField(
|
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
value: new fields.StringField({
|
value: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -52,14 +50,15 @@ export default class DHWeapon extends BaseDataItem {
|
||||||
},
|
},
|
||||||
roll: {
|
roll: {
|
||||||
trait: 'agility',
|
trait: 'agility',
|
||||||
type: 'weapon'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
multiplier: 'prof',
|
multiplier: 'prof',
|
||||||
dice: "d8"
|
dice: 'd8'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -74,22 +73,30 @@ export default class DHWeapon extends BaseDataItem {
|
||||||
return [this.attack, ...this.actions];
|
return [this.attack, ...this.actions];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get customActions() {
|
||||||
|
return this.actions.filter(
|
||||||
|
action => !this.weaponFeatures.some(feature => feature.actionIds.includes(action.id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async _preUpdate(changes, options, user) {
|
async _preUpdate(changes, options, user) {
|
||||||
const allowed = await super._preUpdate(changes, options, user);
|
const allowed = await super._preUpdate(changes, options, user);
|
||||||
if (allowed === false) return false;
|
if (allowed === false) return false;
|
||||||
|
|
||||||
if (changes.system?.features) {
|
if (changes.system?.weaponFeatures) {
|
||||||
const removed = this.features.filter(x => !changes.system.features.includes(x));
|
const removed = this.weaponFeatures.filter(x => !changes.system.weaponFeatures.includes(x));
|
||||||
const added = changes.system.features.filter(x => !this.features.includes(x));
|
const added = changes.system.weaponFeatures.filter(x => !this.weaponFeatures.includes(x));
|
||||||
|
|
||||||
|
const removedEffectsUpdate = [];
|
||||||
|
const removedActionsUpdate = [];
|
||||||
for (let weaponFeature of removed) {
|
for (let weaponFeature of removed) {
|
||||||
for (var effectId of weaponFeature.effectIds) {
|
removedEffectsUpdate.push(...weaponFeature.effectIds);
|
||||||
await this.parent.effects.get(effectId).delete();
|
removedActionsUpdate.push(...weaponFeature.actionIds);
|
||||||
}
|
|
||||||
|
|
||||||
changes.system.actions = this.actions.filter(x => !weaponFeature.actionIds.includes(x._id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate);
|
||||||
|
changes.system.actions = this.actions.filter(x => !removedActionsUpdate.includes(x._id));
|
||||||
|
|
||||||
for (let weaponFeature of added) {
|
for (let weaponFeature of added) {
|
||||||
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
|
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
|
||||||
if (featureData.effects?.length > 0) {
|
if (featureData.effects?.length > 0) {
|
||||||
|
|
@ -102,17 +109,37 @@ export default class DHWeapon extends BaseDataItem {
|
||||||
]);
|
]);
|
||||||
weaponFeature.effectIds = embeddedItems.map(x => x.id);
|
weaponFeature.effectIds = embeddedItems.map(x => x.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newActions = [];
|
||||||
if (featureData.actions?.length > 0) {
|
if (featureData.actions?.length > 0) {
|
||||||
const newActions = featureData.actions.map(action => {
|
for (let action of featureData.actions) {
|
||||||
const cls = actionsTypes[action.type];
|
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
||||||
return new cls(
|
'ActiveEffect',
|
||||||
{ ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) },
|
(action.effects ?? []).map(effect => ({
|
||||||
{ parent: this }
|
...effect,
|
||||||
|
transfer: false,
|
||||||
|
name: game.i18n.localize(effect.name),
|
||||||
|
description: game.i18n.localize(effect.description)
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
});
|
const cls = actionsTypes[action.type];
|
||||||
changes.system.actions = [...this.actions, ...newActions];
|
newActions.push(
|
||||||
weaponFeature.actionIds = newActions.map(x => x._id);
|
new cls(
|
||||||
|
{
|
||||||
|
...action,
|
||||||
|
_id: foundry.utils.randomID(),
|
||||||
|
name: game.i18n.localize(action.name),
|
||||||
|
description: game.i18n.localize(action.description),
|
||||||
|
effects: embeddedEffects.map(x => ({ _id: x.id }))
|
||||||
|
},
|
||||||
|
{ parent: this }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changes.system.actions = [...this.actions, ...newActions];
|
||||||
|
weaponFeature.actionIds = newActions.map(x => x._id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,12 @@ export default class DhLevelData extends foundry.abstract.DataModel {
|
||||||
data: new fields.ArrayField(new fields.StringField({ required: true })),
|
data: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||||
secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })),
|
secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })),
|
||||||
itemUuid: new fields.DocumentUUIDField({ required: true }),
|
itemUuid: new fields.DocumentUUIDField({ required: true }),
|
||||||
featureIds: new fields.ArrayField(new fields.StringField())
|
features: new fields.ArrayField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
onPartner: new fields.BooleanField(),
|
||||||
|
id: new fields.StringField()
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
@ -51,10 +56,6 @@ export default class DhLevelData extends foundry.abstract.DataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get actions() {
|
|
||||||
return Object.values(this.levelups).flatMap(level => level.selections.flatMap(s => s.actions));
|
|
||||||
}
|
|
||||||
|
|
||||||
get canLevelUp() {
|
get canLevelUp() {
|
||||||
return this.level.current < this.level.changed;
|
return this.level.current < this.level.changed;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,8 @@ export const CompanionLevelOptionType = {
|
||||||
{
|
{
|
||||||
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.name',
|
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.name',
|
||||||
img: 'icons/magic/life/heart-cross-purple-orange.webp',
|
img: 'icons/magic/life/heart-cross-purple-orange.webp',
|
||||||
description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.description'
|
description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.description',
|
||||||
|
toPartner: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -81,7 +82,8 @@ export const CompanionLevelOptionType = {
|
||||||
{
|
{
|
||||||
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.name',
|
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.name',
|
||||||
img: 'icons/equipment/shield/kite-wooden-oak-glow.webp',
|
img: 'icons/equipment/shield/kite-wooden-oak-glow.webp',
|
||||||
description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.description'
|
description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.description',
|
||||||
|
toPartner: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -100,7 +102,8 @@ export const CompanionLevelOptionType = {
|
||||||
{
|
{
|
||||||
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.name',
|
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.name',
|
||||||
img: 'icons/magic/life/heart-red-blue.webp',
|
img: 'icons/magic/life/heart-red-blue.webp',
|
||||||
description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.description'
|
description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.description',
|
||||||
|
toPartner: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,10 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
||||||
outline: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
outline: new fields.ColorField({ required: true, initial: '#ffffff' }),
|
||||||
edge: new fields.ColorField({ required: true, initial: '#000000' })
|
edge: new fields.ColorField({ required: true, initial: '#000000' })
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
showGenericStatusEffects: new fields.BooleanField({
|
||||||
|
initial: true,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label'
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,28 @@
|
||||||
export default class DhAutomation extends foundry.abstract.DataModel {
|
export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Automation']; // Doesn't work for some reason
|
|
||||||
|
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
hope: new fields.BooleanField({
|
hopeFear: new fields.SchemaField({
|
||||||
required: true,
|
gm: new fields.BooleanField({
|
||||||
initial: false,
|
required: true,
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hope.label'
|
initial: false,
|
||||||
}), // Label need to be updated into something like "Duality Roll Auto Gain" + a hint
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.gm.label'
|
||||||
|
}),
|
||||||
|
players: new fields.BooleanField({
|
||||||
|
required: true,
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
||||||
|
})
|
||||||
|
}),
|
||||||
actionPoints: new fields.BooleanField({
|
actionPoints: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
initial: false,
|
initial: false,
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label'
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label'
|
||||||
}),
|
}),
|
||||||
countdowns: new fields.BooleanField({
|
hordeDamage: new fields.BooleanField({
|
||||||
requireD: true,
|
required: true,
|
||||||
initial: false,
|
initial: true,
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdowns.label'
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hordeDamage.label'
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue