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
|
||||
|
||||
# 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
|
||||
- name: Update Release with Files
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
- [Overview](#overview)
|
||||
- [User Install Guide](#user-install)
|
||||
- [Developer Setup](#developer-setup)
|
||||
- [Developer Setup](#development-setup)
|
||||
- [Contribution Info](#contributing)
|
||||
|
||||
## 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 documents from './module/documents/_module.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 { 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 { 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 { registerCountdownHooks } from './module/data/countdowns.mjs';
|
||||
import {
|
||||
|
|
@ -18,6 +18,9 @@ import {
|
|||
} from './module/systemRegistration/_module.mjs';
|
||||
import { placeables } from './module/canvas/_module.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', () => {
|
||||
CONFIG.DH = SYSTEM;
|
||||
|
|
@ -27,23 +30,16 @@ Hooks.once('init', () => {
|
|||
documents
|
||||
};
|
||||
|
||||
CONFIG.TextEditor.enrichers.push(
|
||||
...[
|
||||
{
|
||||
pattern: /\[\[\/dr\s?(.*?)\]\]/g,
|
||||
enricher: DhDualityRollEnricher
|
||||
},
|
||||
{
|
||||
pattern: /^@Template\[(.*)\]$/g,
|
||||
enricher: DhTemplateEnricher
|
||||
}
|
||||
]
|
||||
);
|
||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||
|
||||
CONFIG.statusEffects = Object.values(SYSTEM.GENERAL.conditions).map(x => ({
|
||||
...x,
|
||||
name: game.i18n.localize(x.name)
|
||||
}));
|
||||
CONFIG.statusEffects = [
|
||||
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
||||
...Object.values(SYSTEM.GENERAL.conditions).map(x => ({
|
||||
...x,
|
||||
name: game.i18n.localize(x.name),
|
||||
systemEffect: true
|
||||
}))
|
||||
];
|
||||
|
||||
CONFIG.Dice.daggerheart = {
|
||||
DualityDie: DualityDie,
|
||||
|
|
@ -56,6 +52,14 @@ Hooks.once('init', () => {
|
|||
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]];
|
||||
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;
|
||||
|
||||
//Registering the Item DataModel
|
||||
|
|
@ -93,12 +97,12 @@ Hooks.once('init', () => {
|
|||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||
|
||||
foundry.applications.apps.DocumentSheetConfig.unregisterSheet(
|
||||
DocumentSheetConfig.unregisterSheet(
|
||||
CONFIG.ActiveEffect.documentClass,
|
||||
'core',
|
||||
foundry.applications.sheets.ActiveEffectConfig
|
||||
);
|
||||
foundry.applications.apps.DocumentSheetConfig.registerSheet(
|
||||
DocumentSheetConfig.registerSheet(
|
||||
CONFIG.ActiveEffect.documentClass,
|
||||
SYSTEM.id,
|
||||
applications.sheetConfigs.ActiveEffectConfig,
|
||||
|
|
@ -107,6 +111,8 @@ Hooks.once('init', () => {
|
|||
}
|
||||
);
|
||||
|
||||
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
||||
|
||||
CONFIG.Combat.dataModels = {
|
||||
base: models.DhCombat
|
||||
};
|
||||
|
|
@ -119,9 +125,12 @@ Hooks.once('init', () => {
|
|||
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
||||
|
||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||
|
||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||
|
|
@ -152,40 +161,22 @@ Hooks.on('ready', () => {
|
|||
|
||||
registerCountdownHooks();
|
||||
socketRegistration.registerSocketHooks();
|
||||
registerCountdownApplicationHooks();
|
||||
registerRollDiceHooks();
|
||||
registerDHActorHooks();
|
||||
});
|
||||
|
||||
Hooks.once('dicesoniceready', () => {});
|
||||
|
||||
Hooks.on('renderChatMessageHTML', (_, element) => {
|
||||
element
|
||||
.querySelectorAll('.duality-roll-button')
|
||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
||||
|
||||
element
|
||||
.querySelectorAll('.measured-template-button')
|
||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
||||
enricherRenderSetup(element);
|
||||
});
|
||||
|
||||
Hooks.on('renderJournalEntryPageProseMirrorSheet', (_, element) => {
|
||||
element
|
||||
.querySelectorAll('.duality-roll-button')
|
||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
||||
|
||||
element
|
||||
.querySelectorAll('.measured-template-button')
|
||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
||||
enricherRenderSetup(element);
|
||||
});
|
||||
|
||||
Hooks.on('renderHandlebarsApplication', (_, element) => {
|
||||
element
|
||||
.querySelectorAll('.duality-roll-button')
|
||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
||||
|
||||
element
|
||||
.querySelectorAll('.measured-template-button')
|
||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
||||
enricherRenderSetup(element);
|
||||
});
|
||||
|
||||
Hooks.on('chatMessage', (_, message) => {
|
||||
|
|
@ -197,49 +188,21 @@ Hooks.on('chatMessage', (_, message) => {
|
|||
}
|
||||
|
||||
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 = traitValue ? getCommandTarget() : undefined;
|
||||
if (target || !traitValue) {
|
||||
new Promise(async (resolve, reject) => {
|
||||
const trait = target ? target.system.traits[traitValue] : undefined;
|
||||
if (traitValue && !trait) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
const target = getCommandTarget();
|
||||
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');
|
||||
|
||||
enrichedDualityRoll({ traitValue, target, difficulty, title, label: 'test', actionType: null, advantage });
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
448
lang/en.json
448
lang/en.json
|
|
@ -13,6 +13,9 @@
|
|||
"armor": "Armor",
|
||||
"beastform": "Beastform"
|
||||
},
|
||||
"ActiveEffect": {
|
||||
"beastform": "Beastform"
|
||||
},
|
||||
"Actor": {
|
||||
"character": "Character",
|
||||
"companion": "Companion",
|
||||
|
|
@ -20,6 +23,9 @@
|
|||
"environment": "Environment"
|
||||
}
|
||||
},
|
||||
"CONTROLS": {
|
||||
"inFront": "In Front"
|
||||
},
|
||||
"DAGGERHEART": {
|
||||
"ACTIONS": {
|
||||
"Config": {
|
||||
|
|
@ -30,8 +36,15 @@
|
|||
}
|
||||
},
|
||||
"Settings": {
|
||||
"attackBonus": "Attack Bonus",
|
||||
"attackName": "Attack Name",
|
||||
"includeBase": { "label": "Include Item Damage" },
|
||||
"multiplier": "Multiplier",
|
||||
"resultBased": {
|
||||
"label": "Formula based on Hope/Fear result."
|
||||
},
|
||||
"applyTo": {
|
||||
"label": "Targeted Resource"
|
||||
}
|
||||
},
|
||||
"TYPES": {
|
||||
|
|
@ -96,9 +109,14 @@
|
|||
"tier": { "label": "Tier" },
|
||||
"type": { "label": "Type" }
|
||||
},
|
||||
"hordeDamage": "Horde Damage",
|
||||
"horderHp": "Horde/HP"
|
||||
},
|
||||
"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",
|
||||
"companionFeatures": "Companion Features",
|
||||
"contextMenu": {
|
||||
|
|
@ -107,7 +125,12 @@
|
|||
"sendToChat": "Send To Chat",
|
||||
"toLoadout": "Send to Loadout",
|
||||
"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",
|
||||
"levelUp": "You can level up",
|
||||
|
|
@ -129,7 +152,9 @@
|
|||
"partner": { "label": "Partner" },
|
||||
"resources": {
|
||||
"stress": {
|
||||
"value": { "label": "Stress" }
|
||||
"value": { "label": "Stress" },
|
||||
"currentStress": { "label": "Current Stress" },
|
||||
"maxStress": { "label": "Max Stress" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -158,12 +183,25 @@
|
|||
},
|
||||
"APPLICATIONS": {
|
||||
"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",
|
||||
"finishCreation": "Finish Character Setup",
|
||||
"heritage": "Heritage",
|
||||
"initialExperiences": "Initial Experiences",
|
||||
"mixedAncestry": "Mixed Ancestry",
|
||||
"newExperience": "New Experience..",
|
||||
"selectAncestry": "Select Ancestry",
|
||||
"selectPrimaryAncestry": "Select Primary Ancestry",
|
||||
"selectSecondaryAncestry": "Select Secondary Ancestry",
|
||||
"selectArmor": "Select Armor",
|
||||
"selectClass": "Select Class",
|
||||
"selectCommunity": "Select Community",
|
||||
|
|
@ -171,6 +209,7 @@
|
|||
"selectSecondaryWeapon": "Select Secondary Weapon",
|
||||
"selectSubclass": "Select Subclass",
|
||||
"startingItems": "Starting Items",
|
||||
"story": "Story",
|
||||
"suggestedArmor": "Suggested Armor",
|
||||
"suggestedPrimaryWeapon": "Suggested Primary Weapon",
|
||||
"suggestedSecondaryWeapon": "Suggested Secondary Weapon",
|
||||
|
|
@ -185,6 +224,16 @@
|
|||
"requestingSpotlight": "Requesting 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": {
|
||||
"addCountdown": "Add Countdown",
|
||||
"FIELDS": {
|
||||
|
|
@ -213,6 +262,10 @@
|
|||
"encounter": "Encounter"
|
||||
}
|
||||
},
|
||||
"DeleteConfirmation": {
|
||||
"title": "Delete {type} - {name}",
|
||||
"text": "Are you sure you want to delete {name}?"
|
||||
},
|
||||
"DamageReduction": {
|
||||
"armorMarks": "Armor Marks",
|
||||
"armorWithStress": "Spend 1 stress to use an extra mark",
|
||||
|
|
@ -227,8 +280,9 @@
|
|||
"title": "{actor} - Death Move"
|
||||
},
|
||||
"Downtime": {
|
||||
"downtimeHeader": "Downtime Moves ({current}/{max})",
|
||||
"longRest": {
|
||||
"title": "Long Rest",
|
||||
"moves": "Long Rest Moves ({current}/{max})",
|
||||
"clearStress": {
|
||||
"description": "Describe how you blow off steam or pull yourself together, and clear all marked 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.",
|
||||
"name": "Tend to Wounds"
|
||||
},
|
||||
"title": "Long Rest",
|
||||
"workOnAProject": {
|
||||
"description": "Establish or continue work on a project.",
|
||||
"name": "Work on a Project"
|
||||
|
|
@ -253,6 +306,7 @@
|
|||
},
|
||||
"shortRest": {
|
||||
"title": "Short Rest",
|
||||
"moves": "Short Rest Moves ({current}/{max})",
|
||||
"tendToWounds": {
|
||||
"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."
|
||||
|
|
@ -269,6 +323,12 @@
|
|||
"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."
|
||||
}
|
||||
},
|
||||
"takeDowntime": "Take Downtime"
|
||||
},
|
||||
"HUD": {
|
||||
"tokenHUD": {
|
||||
"genericEffects": "Foundry Effects"
|
||||
}
|
||||
},
|
||||
"Levelup": {
|
||||
|
|
@ -374,6 +434,15 @@
|
|||
"OwnershipSelection": {
|
||||
"title": "Ownership Selection - {name}",
|
||||
"default": "Default Ownership"
|
||||
},
|
||||
"ResourceDice": {
|
||||
"title": "{name} Resource",
|
||||
"rerollDice": "Reroll Dice"
|
||||
}
|
||||
},
|
||||
"CLASS": {
|
||||
"Feature": {
|
||||
"rallyDice": "Bardic Rally Dice"
|
||||
}
|
||||
},
|
||||
"CONFIG": {
|
||||
|
|
@ -478,14 +547,10 @@
|
|||
"name": "Impenetrable",
|
||||
"description": "Once per short rest, when you would mark your last Hit Point, you can instead mark a Stress."
|
||||
},
|
||||
"magic": {
|
||||
"name": "Magic",
|
||||
"magical": {
|
||||
"name": "Magical",
|
||||
"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": {
|
||||
"name": "Physical",
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"BeastformType": {
|
||||
"normal": "Normal",
|
||||
"evolved": "Evolved",
|
||||
"hybrid": "Hybrid"
|
||||
},
|
||||
"Burden": {
|
||||
"oneHanded": "One-Handed",
|
||||
"twoHanded": "Two-Handed"
|
||||
},
|
||||
"Condition": {
|
||||
"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."
|
||||
"dead": {
|
||||
"name": "Dead",
|
||||
"description": "The character is dead"
|
||||
},
|
||||
"hidden": {
|
||||
"name": "Hidden",
|
||||
|
|
@ -543,6 +613,14 @@
|
|||
"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."
|
||||
},
|
||||
"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": {
|
||||
|
|
@ -620,8 +698,16 @@
|
|||
"armorStack": {
|
||||
"name": "Armor Stack",
|
||||
"abbreviation": "AS"
|
||||
},
|
||||
"fear": {
|
||||
"name": "Fear",
|
||||
"abbreviation": "FR"
|
||||
}
|
||||
},
|
||||
"ItemResourceType": {
|
||||
"simple": "Simple",
|
||||
"diceValue": "Dice Value"
|
||||
},
|
||||
"Range": {
|
||||
"self": {
|
||||
"name": "Self",
|
||||
|
|
@ -655,11 +741,11 @@
|
|||
}
|
||||
},
|
||||
"RollTypes": {
|
||||
"ability": {
|
||||
"name": "Ability"
|
||||
"trait": {
|
||||
"name": "Trait"
|
||||
},
|
||||
"weapon": {
|
||||
"name": "Weapon"
|
||||
"attack": {
|
||||
"name": "Attack"
|
||||
},
|
||||
"spellcast": {
|
||||
"name": "SpellCast"
|
||||
|
|
@ -668,6 +754,10 @@
|
|||
"name": "Dice Set"
|
||||
}
|
||||
},
|
||||
"SelectAction": {
|
||||
"selectType": "Select Action Type",
|
||||
"selectAction": "Action Selection"
|
||||
},
|
||||
"Traits": {
|
||||
"agility": {
|
||||
"name": "Agility",
|
||||
|
|
@ -727,7 +817,7 @@
|
|||
"WeaponFeature": {
|
||||
"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": {
|
||||
"name": "Bonded",
|
||||
|
|
@ -843,7 +933,7 @@
|
|||
},
|
||||
"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": {
|
||||
"name": "Parry",
|
||||
|
|
@ -863,7 +953,7 @@
|
|||
},
|
||||
"protective": {
|
||||
"name": "Protective",
|
||||
"description": "+{tier} to Armor Score"
|
||||
"description": "Add your character's Tier to your Armor Score"
|
||||
},
|
||||
"quick": {
|
||||
"name": "Quick",
|
||||
|
|
@ -889,6 +979,10 @@
|
|||
"name": "Scary",
|
||||
"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": {
|
||||
"name": "Serrated",
|
||||
"description": "When you roll a 1 on a damage die, it deals 8 damage instead."
|
||||
|
|
@ -908,10 +1002,6 @@
|
|||
"timebending": {
|
||||
"name": "Timebending",
|
||||
"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": {
|
||||
"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": {
|
||||
|
|
@ -950,32 +1044,79 @@
|
|||
},
|
||||
"Advantage": {
|
||||
"full": "Advantage",
|
||||
"plural": "Advantages",
|
||||
"short": "Adv"
|
||||
},
|
||||
"Adversary": {
|
||||
"singular": "Adversary",
|
||||
"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": {
|
||||
"singular": "Character",
|
||||
"plural": "Characters"
|
||||
},
|
||||
"Cost": {
|
||||
"single": "Cost",
|
||||
"plural": "Costs"
|
||||
},
|
||||
"Damage": {
|
||||
"severe": "Severe",
|
||||
"major": "Major",
|
||||
"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": {
|
||||
"title": "Damage Thresholds",
|
||||
"minor": "Minor",
|
||||
"major": "Major",
|
||||
"severe": "Severe"
|
||||
"severe": "Severe",
|
||||
"majorThreshold": "Major Damage Threshold",
|
||||
"severeThreshold": "Severe Damage Threshold"
|
||||
},
|
||||
"Dice": {
|
||||
"single": "Die",
|
||||
"plural": "Dice"
|
||||
},
|
||||
"Difficulty": {
|
||||
"all": "Difficulty: all",
|
||||
"reaction": "Difficulty: reaction"
|
||||
},
|
||||
"Disadvantage": {
|
||||
"full": "Disadvantage",
|
||||
"short": "Dis"
|
||||
|
|
@ -985,39 +1126,39 @@
|
|||
"plural": "Domains",
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
"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": {
|
||||
|
|
@ -1028,15 +1169,73 @@
|
|||
"single": "Experience",
|
||||
"plural": "Experiences"
|
||||
},
|
||||
"Healing": {
|
||||
"healingAmount": "Healing Amount"
|
||||
},
|
||||
"Modifier": {
|
||||
"single": "Modifier",
|
||||
"plural": "Modifiers"
|
||||
},
|
||||
"Neutral": {
|
||||
"full": "None",
|
||||
"short": "no"
|
||||
},
|
||||
"Range": {
|
||||
"other": "Range Increase: Other",
|
||||
"spell": "Range Increase: Spell",
|
||||
"weapon": "Range Increase: Weapon"
|
||||
},
|
||||
"RefreshType": {
|
||||
"session": "Session",
|
||||
"shortrest": "Short 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": {
|
||||
"details": "Details",
|
||||
"attack": "Attack",
|
||||
|
|
@ -1065,52 +1264,124 @@
|
|||
"specialization": "Specialization",
|
||||
"mastery": "Mastery",
|
||||
"optional": "Optional",
|
||||
"recovery": "Recovery",
|
||||
"setup": "Setup",
|
||||
"equipment": "Equipment"
|
||||
},
|
||||
"Tiers": {
|
||||
"singular": "Tier",
|
||||
"equipment": "Equipment",
|
||||
"attachments": "Attachments",
|
||||
"advanced": "Advanced",
|
||||
"tier1": "Tier 1",
|
||||
"tier2": "Tier 2",
|
||||
"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": {
|
||||
"single": "Trait",
|
||||
"plural": "Traits"
|
||||
},
|
||||
"actorName": "Actor Name",
|
||||
"amount": "Amount",
|
||||
"armorScore": "Armor Score",
|
||||
"activeEffects": "Active Effects",
|
||||
"armorSlots": "Armor Slots",
|
||||
"attack": "Attack",
|
||||
"basics": "Basics",
|
||||
"bonus": "Bonus",
|
||||
"burden": "Burden",
|
||||
"check": "{check} Check",
|
||||
"continue": "Continue",
|
||||
"criticalSuccess": "Critical Success",
|
||||
"damage": "Damage",
|
||||
"damageType": "Damage Type",
|
||||
"description": "Description",
|
||||
"difficulty": "Difficulty",
|
||||
"dropActorsHere": "Drop Actors here",
|
||||
"duality": "Duality",
|
||||
"dualityRoll": "Duality Roll",
|
||||
"enabled": "Enabled",
|
||||
"evasion": "Evasion",
|
||||
"equipment": "Equipment",
|
||||
"experience": {
|
||||
"single": "Experience",
|
||||
"plural": "Experiences"
|
||||
},
|
||||
"failure": "Failure",
|
||||
"fear": "Fear",
|
||||
"features": "Features",
|
||||
"hitPoints": "Hit Points",
|
||||
"formula": "Formula",
|
||||
"healing": "Healing",
|
||||
"HitPoints": {
|
||||
"single": "Hit Point",
|
||||
"plural": "Hit Points",
|
||||
"short": "HP"
|
||||
},
|
||||
"hope": "Hope",
|
||||
"hordeHp": "Horde HP",
|
||||
"identify": "Identity",
|
||||
"imagePath": "Image Path",
|
||||
"inactiveEffects": "Inactive Effects",
|
||||
"inventory": "Inventory",
|
||||
"level": "Level",
|
||||
"modifier": "Modifier",
|
||||
"levelUp": "Level Up",
|
||||
"loadout": "Loadout",
|
||||
"max": "Max",
|
||||
"multiclass": "Multiclass",
|
||||
"newCategory": "New Category",
|
||||
"none": "None",
|
||||
"partner": "Partner",
|
||||
"proficiency": "Proficiency",
|
||||
"quantity": "Quantity",
|
||||
"range": "Range",
|
||||
"recovery": "Recovery",
|
||||
"resource": "Resource",
|
||||
"roll": "Roll",
|
||||
"rollAll": "Roll All",
|
||||
"rollDamage": "Roll Damage",
|
||||
"save": "Save",
|
||||
"scalable": "Scalable",
|
||||
"situationalBonus": "Situational Bonus",
|
||||
"stress": "Stress",
|
||||
"success": "Success",
|
||||
"take": "Take",
|
||||
"target": "Target",
|
||||
"Target": {
|
||||
"single": "Target",
|
||||
"plural": "Targets"
|
||||
},
|
||||
"title": "Title",
|
||||
"total": "Total",
|
||||
"true": "True",
|
||||
"type": "Type",
|
||||
"unarmed": "Unarmed",
|
||||
"unarmedStrike": "Unarmed Strike",
|
||||
"unarmored": "Unarmored",
|
||||
"use": "Use"
|
||||
"use": "Use",
|
||||
"used": "Used",
|
||||
"uses": "Uses",
|
||||
"value": "Value",
|
||||
"withThing": "With {thing}"
|
||||
},
|
||||
"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": {
|
||||
"baseScore": "Base Score",
|
||||
"baseThresholds": {
|
||||
|
|
@ -1121,20 +1392,39 @@
|
|||
},
|
||||
"Beastform": {
|
||||
"FIELDS": {
|
||||
"beastformType": { "label": "Beastform Type" },
|
||||
"tier": { "label": "Tier" },
|
||||
"mainTrait": { "label": "Main Trait" },
|
||||
"examples": { "label": "Examples" },
|
||||
"advantageOn": { "label": "Gain Advantage On" },
|
||||
"tokenImg": { "label": "Token Image" },
|
||||
"tokenRingImg": { "label": "Subject Texture" },
|
||||
"tokenSize": {
|
||||
"placeholder": "Using character dimensions",
|
||||
"height": { "label": "Height" },
|
||||
"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",
|
||||
"tokenTitle": "Beastform Token",
|
||||
"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": {
|
||||
"hopeFeatures": "Hope Features",
|
||||
|
|
@ -1155,8 +1445,8 @@
|
|||
},
|
||||
"DomainCard": {
|
||||
"type": "Type",
|
||||
"foundation": "Foundation",
|
||||
"recallCost": "Recall Cost",
|
||||
"foundationTitle": "Foundation",
|
||||
"specializationTitle": "Specialization",
|
||||
"masteryTitle": "Mastery"
|
||||
},
|
||||
|
|
@ -1164,6 +1454,7 @@
|
|||
"spellcastingTrait": "Spellcasting Trait"
|
||||
},
|
||||
"Weapon": {
|
||||
"weaponType": "Weapon Type",
|
||||
"primaryWeapon": "Primary Weapon",
|
||||
"secondaryWeapon": "Secondary Weapon"
|
||||
}
|
||||
|
|
@ -1171,7 +1462,8 @@
|
|||
"SETTINGS": {
|
||||
"Appearance": {
|
||||
"FIELDS": {
|
||||
"displayFear": { "label": "Fear Display" }
|
||||
"displayFear": { "label": "Fear Display" },
|
||||
"showGenericStatusEffects": { "label": "Show Foundry Status Effects" }
|
||||
},
|
||||
"fearDisplay": {
|
||||
"token": "Tokens",
|
||||
|
|
@ -1185,17 +1477,18 @@
|
|||
"hint": "Automatically increase the GM's fear pool on a fear duality roll result."
|
||||
},
|
||||
"FIELDS": {
|
||||
"hope": {
|
||||
"label": "Hope",
|
||||
"hint": "Automatically increase a character's hope on a hope duality roll result."
|
||||
"hopeFear": {
|
||||
"label": "Hope & Fear",
|
||||
"gm": { "label": "GM" },
|
||||
"players": { "label": "Players" }
|
||||
},
|
||||
"actionPoints": {
|
||||
"label": "Action Points",
|
||||
"hint": "Automatically give and take Action Points as combatants take their turns."
|
||||
},
|
||||
"countdowns": {
|
||||
"label": "Countdowns",
|
||||
"hint": "Automatically progress non-custom countdowns"
|
||||
"hordeDamage": {
|
||||
"label": "Automatic Horde Damage",
|
||||
"hint": "Automatically active horde effect to lower damage when reaching half or lower HP."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1226,6 +1519,7 @@
|
|||
}
|
||||
},
|
||||
"Menu": {
|
||||
"title": "Daggerheart Game Settings",
|
||||
"automation": {
|
||||
"name": "Automation Settings",
|
||||
"label": "Configure Automation",
|
||||
|
|
@ -1285,8 +1579,8 @@
|
|||
},
|
||||
"UI": {
|
||||
"Chat": {
|
||||
"dualityRoll": {
|
||||
"abilityCheckTitle": "{ability} Check"
|
||||
"applyEffect": {
|
||||
"title": "Apply Effects - {name}"
|
||||
},
|
||||
"attackRoll": {
|
||||
"title": "Attack - {attack}",
|
||||
|
|
@ -1302,25 +1596,28 @@
|
|||
"hitTarget": "Hit Targets",
|
||||
"selectedTarget": "Selected"
|
||||
},
|
||||
"applyEffect": {
|
||||
"title": "Apply Effects - {name}"
|
||||
},
|
||||
"healingRoll": {
|
||||
"title": "Heal - {healing}",
|
||||
"heal": "Heal"
|
||||
},
|
||||
"deathMove": {
|
||||
"title": "Death Move"
|
||||
},
|
||||
"domainCard": {
|
||||
"title": "Domain Card"
|
||||
},
|
||||
"dualityRoll": {
|
||||
"abilityCheckTitle": "{ability} Check"
|
||||
},
|
||||
"featureTitle": "Class Feature",
|
||||
"foundationCard": {
|
||||
"ancestryTitle": "Ancestry Card",
|
||||
"communityTitle": "Community Card",
|
||||
"subclassFeatureTitle": "Subclass Feature"
|
||||
},
|
||||
"featureTitle": "Class Feature"
|
||||
"healingRoll": {
|
||||
"title": "Heal - {healing}",
|
||||
"heal": "Heal"
|
||||
},
|
||||
"resourceRoll": {
|
||||
"playerMessage": "{user} rerolled their {name}"
|
||||
}
|
||||
},
|
||||
"Notifications": {
|
||||
"adversaryMissing": "The linked adversary doesn't exist in the world.",
|
||||
|
|
@ -1360,9 +1657,27 @@
|
|||
"damageAlreadyNone": "The damage has already been reduced to none",
|
||||
"noAvailableArmorMarks": "You have no more available armor marks",
|
||||
"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": {
|
||||
"disableEffect": "Disable Effect",
|
||||
"enableEffect": "Enable Effect",
|
||||
"openItemWorld": "Open Item World",
|
||||
"openActorWorld": "Open Actor World",
|
||||
"sendToChat": "Send to Chat",
|
||||
|
|
@ -1371,7 +1686,10 @@
|
|||
"unequip": "Unequip",
|
||||
"sendToVault": "Send to Vault",
|
||||
"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 dialogs from './dialogs/_module.mjs';
|
||||
export * as hud from './hud/_module.mjs';
|
||||
export * as levelup from './levelup/_module.mjs';
|
||||
export * as settings from './settings/_module.mjs';
|
||||
export * as sheets from './sheets/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
|
||||
this.setup = {
|
||||
traits: this.character.system.traits,
|
||||
ancestry: this.character.system.ancestry ?? {},
|
||||
ancestryName: '',
|
||||
mixedAncestry: false,
|
||||
primaryAncestry: this.character.system.ancestry ?? {},
|
||||
secondaryAncestry: {},
|
||||
community: this.character.system.community ?? {},
|
||||
class: this.character.system.class?.value ?? {},
|
||||
subclass: this.character.system.class?.subclass ?? {},
|
||||
experiences: {
|
||||
[foundry.utils.randomID()]: { description: '', value: 2 },
|
||||
[foundry.utils.randomID()]: { description: '', value: 2 }
|
||||
[foundry.utils.randomID()]: { name: '', value: 2 },
|
||||
[foundry.utils.randomID()]: { name: '', value: 2 }
|
||||
},
|
||||
domainCards: {
|
||||
[foundry.utils.randomID()]: {},
|
||||
|
|
@ -47,12 +50,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'],
|
||||
position: { width: 800, height: 'auto' },
|
||||
position: { width: 700, height: 'auto' },
|
||||
actions: {
|
||||
viewCompendium: this.viewCompendium,
|
||||
viewItem: this.viewItem,
|
||||
useSuggestedTraits: this.useSuggestedTraits,
|
||||
equipmentChoice: this.equipmentChoice,
|
||||
setupGoNext: this.setupGoNext,
|
||||
finish: this.finish
|
||||
},
|
||||
form: {
|
||||
|
|
@ -76,6 +80,12 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
static PARTS = {
|
||||
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.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' },
|
||||
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.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) {
|
||||
for (const v of Object.values(tabs)) {
|
||||
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) {
|
||||
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 heritageFinished = this.setup.ancestry.uuid && this.setup.community.uuid;
|
||||
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);
|
||||
v.finished =
|
||||
ancestryFinished &&
|
||||
communityFinished &&
|
||||
classFinished &&
|
||||
heritageFinished &&
|
||||
traitsFinished &&
|
||||
experiencesFinished &&
|
||||
domainCardsFinished;
|
||||
|
|
@ -146,15 +203,44 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
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) {
|
||||
super.changeTab(tab, group, options);
|
||||
|
||||
for (var listTab of Object.keys(this.constructor.TABS)) {
|
||||
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
|
||||
if (listTab === tab) {
|
||||
marker.classList.add('active');
|
||||
} else {
|
||||
marker.classList.remove('active');
|
||||
if (group === 'primary') {
|
||||
for (var listTab of Object.keys(this.constructor.TABS)) {
|
||||
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
|
||||
if (listTab === tab) {
|
||||
marker.classList.add('active');
|
||||
} else {
|
||||
marker.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,6 +249,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
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) {
|
||||
|
|
@ -174,7 +265,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
|
||||
async _preparePartContext(partId, context) {
|
||||
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':
|
||||
context.setupTabs = this._getSetupTabs(this.constructor.SETUPTABS);
|
||||
const availableTraitModifiers = game.settings
|
||||
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
|
||||
.traitArray.map(trait => ({ key: trait, name: trait }));
|
||||
|
|
@ -215,13 +329,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
context.experience = {
|
||||
values: this.setup.experiences,
|
||||
nrTotal: Object.keys(this.setup.experiences).length,
|
||||
nrSelected: Object.values(this.setup.experiences).reduce(
|
||||
(acc, exp) => acc + (exp.description ? 1 : 0),
|
||||
0
|
||||
)
|
||||
nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.name ? 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.class = { ...this.setup.class, compendium: 'classes' };
|
||||
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
|
||||
|
|
@ -278,18 +392,29 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
this.render();
|
||||
}
|
||||
|
||||
mixedAncestryToggle(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.setup.mixedAncestry = !this.setup.mixedAncestry;
|
||||
if (!this.setup.mixedAncestry) this.setup.secondaryAncestry = {};
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
getUpdateVisibility() {
|
||||
switch (this.setup.visibility) {
|
||||
case 6:
|
||||
return 6;
|
||||
case 5:
|
||||
return 5;
|
||||
return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5;
|
||||
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:
|
||||
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:
|
||||
return this.setup.ancestry.uuid && this.setup.community.uuid ? 3 : 2;
|
||||
return this.setup.community.uuid ? 3 : 2;
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
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.class]);
|
||||
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
|
||||
|
|
@ -396,8 +557,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
async _onDrop(event) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const item = await foundry.utils.fromUuid(data.uuid);
|
||||
if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) {
|
||||
this.setup.ancestry = {
|
||||
if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) {
|
||||
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,
|
||||
effects: Array.from(item.effects).map(x => x.toObject()),
|
||||
uuid: item.uuid
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
export { default as BeastformDialog } from './beastformDialog.mjs';
|
||||
export { default as costSelectionDialog } from './costSelectionDialog.mjs';
|
||||
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
||||
export { default as DamageDialog } from './damageDialog.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 Downtime } from './downtime.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;
|
||||
|
||||
export default class BeastformDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(configData) {
|
||||
constructor(configData, item) {
|
||||
super();
|
||||
|
||||
this.item = item;
|
||||
|
||||
this.configData = configData;
|
||||
this.selected = null;
|
||||
this.evolved = { form: null };
|
||||
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
||||
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'beastform-selection'],
|
||||
classes: ['daggerheart', 'views', 'dialog', 'dh-style', 'beastform-selection'],
|
||||
position: {
|
||||
width: 600,
|
||||
height: 'auto'
|
||||
},
|
||||
window: {
|
||||
icon: 'fa-solid fa-paw'
|
||||
},
|
||||
actions: {
|
||||
selectBeastform: this.selectBeastform,
|
||||
toggleHybridFeature: this.toggleHybridFeature,
|
||||
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
||||
submitBeastform: this.submitBeastform
|
||||
},
|
||||
form: {
|
||||
handler: this.updateBeastform,
|
||||
submitOnChange: true,
|
||||
submitOnClose: false
|
||||
}
|
||||
},
|
||||
dragDrop: [{ dragSelector: '.beastform-container', dropSelector: '.advanced-form-container' }]
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.ITEMS.Beastform.dialogTitle');
|
||||
return this.item.name;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
beastform: {
|
||||
template: 'systems/daggerheart/templates/dialogs/beastformDialog.hbs'
|
||||
header: { template: 'systems/daggerheart/templates/dialogs/beastform/header.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) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
||||
context.beastformTiers = game.items.reduce((acc, x) => {
|
||||
const tier = CONFIG.DH.GENERAL.tiers[x.system.tier];
|
||||
if (x.type !== 'beastform' || tier.value > this.configData.tierLimit) return acc;
|
||||
context.selected = this.selected;
|
||||
context.selectedBeastformEffect = this.selected?.effects?.find?.(x => x.type === 'beastform');
|
||||
|
||||
if (!acc[tier.value]) acc[tier.value] = { label: game.i18n.localize(tier.label), values: {} };
|
||||
acc[tier.value].values[x.uuid] = { selected: this.selected == x.uuid, value: x };
|
||||
context.evolved = this.evolved;
|
||||
|
||||
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;
|
||||
}, {}); // 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
this.selected = foundry.utils.mergeObject(this.selected, formData.object);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static selectBeastform(_, target) {
|
||||
this.selected = this.selected === target.dataset.uuid ? null : target.dataset.uuid;
|
||||
static async selectBeastform(_, target) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
@ -71,14 +265,61 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
|
||||
/** @override */
|
||||
_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 => {
|
||||
const app = new this(configData);
|
||||
app.addEventListener('close', () => resolve(app.selected), { once: true });
|
||||
const app = new this(configData, item);
|
||||
const featureItem = item;
|
||||
app.addEventListener(
|
||||
'close',
|
||||
() => resolve({ selected: app.selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem }),
|
||||
{ once: 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',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
||||
position: {
|
||||
width: 550
|
||||
width: 'auto'
|
||||
},
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
|
|
@ -52,10 +52,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
rollSelection: {
|
||||
id: 'rollSelection',
|
||||
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);
|
||||
context.rollConfig = this.config;
|
||||
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) {
|
||||
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);
|
||||
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.canRoll = context.canRoll && this.action.hasUses(context.uses);
|
||||
}
|
||||
if(this.roll) {
|
||||
if (this.roll) {
|
||||
context.roll = this.roll;
|
||||
context.rollType = this.roll?.constructor.name;
|
||||
context.rallyDie = this.roll.rallyChoices;
|
||||
context.experiences = Object.keys(this.config.data.experiences).map(id => ({
|
||||
id,
|
||||
...this.config.data.experiences[id]
|
||||
|
|
@ -84,7 +94,6 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.advantage = this.config.roll?.advantage;
|
||||
context.disadvantage = this.config.roll?.disadvantage;
|
||||
context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
|
||||
context.canRoll = true;
|
||||
context.isLite = this.config.roll?.lite;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
|
|
@ -94,6 +103,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
if (this.config.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) {
|
||||
/* 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.indexOf(button.dataset.key) > -1
|
||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'roll-selection',
|
||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
||||
position: {
|
||||
width: 400,
|
||||
height: 'auto'
|
||||
},
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
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) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.title = this.config.title;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.config = CONFIG.DH;
|
||||
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.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;
|
||||
}
|
||||
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
static updateRollConfiguration(_event, _, formData) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
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) {
|
||||
constructor(resolve, reject, actor, damage) {
|
||||
constructor(resolve, reject, actor, damage, damageType) {
|
||||
super({});
|
||||
|
||||
this.resolve = resolve;
|
||||
|
|
@ -11,37 +11,46 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.actor = actor;
|
||||
this.damage = damage;
|
||||
|
||||
const maxArmorMarks = Math.min(
|
||||
actor.system.armorScore - actor.system.armor.system.marks.value,
|
||||
actor.system.rules.maxArmorMarked.total
|
||||
);
|
||||
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
|
||||
const maxArmorMarks = canApplyArmor
|
||||
? 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, _) => {
|
||||
acc[foundry.utils.randomID()] = { selected: false };
|
||||
return acc;
|
||||
}, {});
|
||||
const stress = [...Array(actor.system.rules.maxArmorMarked.stressExtra ?? 0).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = { selected: false };
|
||||
return acc;
|
||||
}, {});
|
||||
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
||||
(acc, _) => {
|
||||
acc[foundry.utils.randomID()] = { selected: false };
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
this.marks = { armor, stress };
|
||||
|
||||
this.availableStressReductions = Object.keys(actor.system.rules.stressDamageReduction).reduce((acc, key) => {
|
||||
const dr = actor.system.rules.stressDamageReduction[key];
|
||||
if (dr.enabled) {
|
||||
if (acc === null) acc = {};
|
||||
this.availableStressReductions = Object.keys(actor.system.rules.damageReduction.stressDamageReduction).reduce(
|
||||
(acc, key) => {
|
||||
const dr = actor.system.rules.damageReduction.stressDamageReduction[key];
|
||||
if (dr.enabled) {
|
||||
if (acc === null) acc = {};
|
||||
|
||||
const damage = damageKeyToNumber(key);
|
||||
acc[damage] = {
|
||||
cost: dr.cost,
|
||||
selected: false,
|
||||
from: getDamageLabel(damage),
|
||||
to: getDamageLabel(damage - 1)
|
||||
};
|
||||
}
|
||||
const damage = damageKeyToNumber(key);
|
||||
acc[damage] = {
|
||||
cost: dr.cost,
|
||||
selected: false,
|
||||
from: getDamageLabel(damage),
|
||||
to: getDamageLabel(damage - 1)
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, null);
|
||||
return acc;
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -90,7 +99,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
context.armorScore = this.actor.system.armorScore;
|
||||
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
|
||||
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
||||
|
|
@ -100,7 +110,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
? {
|
||||
value:
|
||||
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
|
||||
maxTotal: this.actor.system.resources.stress.maxTotal
|
||||
max: this.actor.system.resources.stress.max
|
||||
}
|
||||
: null;
|
||||
|
||||
|
|
@ -122,12 +132,15 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
getDamageInfo = () => {
|
||||
const selectedArmorMarks = Object.values(this.marks.armor).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 =
|
||||
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
|
||||
|
||||
const currentDamage =
|
||||
this.damage - selectedArmorMarks.length - selectedStressMarks.length - stressReductions.length;
|
||||
const armorMarkReduction =
|
||||
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
||||
const currentDamage = this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length;
|
||||
|
||||
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
|
||||
};
|
||||
|
|
@ -184,7 +197,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
: 0;
|
||||
const currentStress =
|
||||
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'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -210,9 +223,17 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
async close(fromSave) {
|
||||
if (!fromSave) {
|
||||
this.reject();
|
||||
this.resolve();
|
||||
}
|
||||
|
||||
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 = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'damage-selection'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
||||
position: {
|
||||
width: 400,
|
||||
height: 'auto'
|
||||
|
|
|
|||
|
|
@ -7,8 +7,23 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
this.actor = actor;
|
||||
this.shortrest = shortrest;
|
||||
|
||||
const options = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves;
|
||||
this.moveData = shortrest ? options.shortRest : options.longRest;
|
||||
this.moveData = foundry.utils.deepClone(
|
||||
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() {
|
||||
|
|
@ -17,8 +32,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'downtime'],
|
||||
position: { width: 680, height: 'auto' },
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'downtime'],
|
||||
position: { width: 'auto', height: 'auto' },
|
||||
actions: {
|
||||
selectMove: this.selectMove,
|
||||
takeDowntime: this.takeDowntime
|
||||
|
|
@ -29,7 +44,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
static PARTS = {
|
||||
application: {
|
||||
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);
|
||||
|
||||
htmlElement
|
||||
.querySelectorAll('.activity-image')
|
||||
.querySelectorAll('.activity-container')
|
||||
.forEach(element => element.addEventListener('contextmenu', this.deselectMove.bind(this)));
|
||||
}
|
||||
|
||||
async _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.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;
|
||||
}
|
||||
|
||||
static selectMove(_, button) {
|
||||
const nrSelected = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
|
||||
if (nrSelected === this.moveData.nrChoices) {
|
||||
static selectMove(_, target) {
|
||||
const { category, move } = target.dataset;
|
||||
|
||||
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'));
|
||||
return;
|
||||
}
|
||||
|
||||
const move = button.dataset.move;
|
||||
this.moveData.moves[move].selected = this.moveData.moves[move].selected
|
||||
? 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
|
||||
: 1;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
deselectMove(event) {
|
||||
const move = event.currentTarget.dataset.move;
|
||||
this.moveData.moves[move].selected = this.moveData.moves[move].selected
|
||||
? this.moveData.moves[move].selected - 1
|
||||
const button = event.target.closest('.activity-container');
|
||||
const { move, category } = button.dataset;
|
||||
this.moveData[category].moves[move].selected = this.moveData[category].moves[move].selected
|
||||
? this.moveData[category].moves[move].selected - 1
|
||||
: 0;
|
||||
|
||||
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() {
|
||||
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 msg = new cls({
|
||||
|
|
@ -88,7 +142,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'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
|
||||
}
|
||||
)
|
||||
|
|
@ -96,11 +150,33 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
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) {
|
||||
this.customActivity = foundry.utils.mergeObject(this.customActivity, formData.object);
|
||||
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 = {
|
||||
proficiency: {
|
||||
old: this.actor.system.proficiency.total,
|
||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
||||
old: this.actor.system.proficiency,
|
||||
new: this.actor.system.proficiency + achivementProficiency,
|
||||
shown: achivementProficiency > 0
|
||||
},
|
||||
damageThresholds: {
|
||||
|
|
@ -332,16 +332,16 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||
},
|
||||
hitPoints: {
|
||||
old: this.actor.system.resources.hitPoints.maxTotal,
|
||||
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
||||
old: this.actor.system.resources.hitPoints.max,
|
||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||
},
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||
|
|
@ -349,8 +349,8 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
if (!acc) acc = {};
|
||||
acc[traitKey] = {
|
||||
label: game.i18n.localize(abilities[traitKey].label),
|
||||
old: this.actor.system.traits[traitKey].total,
|
||||
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
|
||||
old: this.actor.system.traits[traitKey].max,
|
||||
new: this.actor.system.traits[traitKey].max + advancement.trait[traitKey]
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
context.advancements = {
|
||||
statistics: {
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
experiences:
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
|
||||
context.showTabs = this.tabGroups.primary !== 'summary';
|
||||
break;
|
||||
const { current: currentActorLevel, changed: changedActorLevel } = this.actor.system.levelData.level;
|
||||
|
||||
const actorArmor = this.actor.system.armor;
|
||||
const levelKeys = Object.keys(this.levelup.levels);
|
||||
let achivementProficiency = 0;
|
||||
|
|
@ -157,8 +157,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
context.achievements = {
|
||||
proficiency: {
|
||||
old: this.actor.system.proficiency.total,
|
||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
||||
old: this.actor.system.proficiency,
|
||||
new: this.actor.system.proficiency + achivementProficiency,
|
||||
shown: achivementProficiency > 0
|
||||
},
|
||||
damageThresholds: {
|
||||
|
|
@ -265,16 +265,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
|
||||
},
|
||||
hitPoints: {
|
||||
old: this.actor.system.resources.hitPoints.maxTotal,
|
||||
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
|
||||
old: this.actor.system.resources.hitPoints.max,
|
||||
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
|
||||
},
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
old: this.actor.system.resources.stress.max,
|
||||
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
old: this.actor.system.evasion,
|
||||
new: this.actor.system.evasion + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||
|
|
@ -282,8 +282,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
if (!acc) acc = {};
|
||||
acc[traitKey] = {
|
||||
label: game.i18n.localize(abilities[traitKey].label),
|
||||
old: this.actor.system.traits[traitKey].total,
|
||||
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
|
||||
old: this.actor.system.traits[traitKey].value,
|
||||
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
|
|
|
|||
|
|
@ -12,14 +12,17 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.name');
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-appearance-settings',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-gears'
|
||||
},
|
||||
actions: {
|
||||
reset: this.reset,
|
||||
save: this.save
|
||||
|
|
|
|||
|
|
@ -12,14 +12,17 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.automation.name');
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-automation-settings',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'setting'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-gears'
|
||||
},
|
||||
actions: {
|
||||
reset: this.reset,
|
||||
save: this.save
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ import DHActionConfig from '../../sheets-configs/action-config.mjs';
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
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({});
|
||||
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.viewTitle = title;
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.img = img;
|
||||
this.description = description;
|
||||
this.actions = actions;
|
||||
|
|
@ -23,7 +24,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
position: { width: '400', height: 'auto' },
|
||||
position: { width: 440, height: 'auto' },
|
||||
actions: {
|
||||
editImage: this.onEditImage,
|
||||
addItem: this.addItem,
|
||||
|
|
@ -46,6 +47,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.name = this.name;
|
||||
context.icon = this.icon;
|
||||
context.img = this.img;
|
||||
context.description = this.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) {
|
||||
const { name, img, description } = foundry.utils.expandObject(formData.object);
|
||||
const { name, icon, description } = foundry.utils.expandObject(formData.object);
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.description = description;
|
||||
|
||||
this.render();
|
||||
|
|
@ -65,6 +68,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
static async saveForm(event) {
|
||||
this.resolve({
|
||||
name: this.name,
|
||||
icon: this.icon,
|
||||
img: this.img,
|
||||
description: this.description,
|
||||
actions: this.actions
|
||||
|
|
|
|||
|
|
@ -13,14 +13,17 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.homebrew.name');
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-homebrew-settings',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'setting'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-gears'
|
||||
},
|
||||
actions: {
|
||||
addItem: this.addItem,
|
||||
editItem: this.editItem,
|
||||
|
|
@ -76,6 +79,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
reject,
|
||||
game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves'),
|
||||
move.name,
|
||||
move.icon,
|
||||
move.img,
|
||||
move.description,
|
||||
move.actions
|
||||
|
|
@ -87,6 +91,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
await this.settings.updateSource({
|
||||
[`restMoves.${type}.moves.${id}`]: {
|
||||
name: data.name,
|
||||
icon: data.icon,
|
||||
img: data.img,
|
||||
description: data.description
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,14 +12,17 @@ export default class DhRangeMeasurementSettings extends HandlebarsApplicationMix
|
|||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.automation.name');
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-automation-settings',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-gears'
|
||||
},
|
||||
actions: {
|
||||
reset: this.reset,
|
||||
save: this.save
|
||||
|
|
|
|||
|
|
@ -12,14 +12,17 @@ export default class DHVariantRuleSettings extends HandlebarsApplicationMixin(Ap
|
|||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.variantRules.name');
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-appearance-settings',
|
||||
classes: ['daggerheart', 'setting', 'dh-style'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-gears'
|
||||
},
|
||||
actions: {
|
||||
reset: this.reset,
|
||||
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 EnvironmentSettings } from './environment-settings.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',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
||||
}
|
||||
/* form: {
|
||||
id: 'action',
|
||||
template: 'systems/daggerheart/templates/config/action.hbs'
|
||||
} */
|
||||
};
|
||||
|
||||
static TABS = {
|
||||
|
|
@ -111,13 +107,14 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
context.hasBaseDamage = !!this.action.parent.attack;
|
||||
context.getRealIndex = this.getRealIndex.bind(this);
|
||||
context.getEffectDetails = this.getEffectDetails.bind(this);
|
||||
context.costOptions = this.getCostOptions();
|
||||
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;
|
||||
|
||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
||||
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 }))
|
||||
];
|
||||
|
||||
|
|
@ -129,8 +126,21 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
this.render(true);
|
||||
}
|
||||
|
||||
disableOption(index, options, choices) {
|
||||
const filtered = foundry.utils.deepClone(options);
|
||||
getCostOptions() {
|
||||
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 => {
|
||||
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);
|
||||
}
|
||||
|
||||
_prepareSubmitData(event, formData) {
|
||||
_prepareSubmitData(_event, formData) {
|
||||
const submitData = foundry.utils.expandObject(formData.object);
|
||||
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
||||
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;
|
||||
}
|
||||
|
|
@ -161,7 +179,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
|
||||
let newActions;
|
||||
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);
|
||||
} else newActions = data;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
import autocomplete from 'autocompleter';
|
||||
|
||||
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 = {
|
||||
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) {
|
||||
const partContext = await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
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;
|
||||
}
|
||||
|
||||
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}
|
||||
*/
|
||||
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 });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getDocFromElement } from '../../helpers/utils.mjs';
|
||||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
|
@ -9,8 +10,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
actions: {
|
||||
addCategory: DHEnvironmentSettings.#addCategory,
|
||||
removeCategory: DHEnvironmentSettings.#removeCategory,
|
||||
viewAdversary: this.#viewAdversary,
|
||||
deleteAdversary: this.#deleteAdversary
|
||||
deleteAdversary: DHEnvironmentSettings.#deleteAdversary
|
||||
},
|
||||
dragDrop: [
|
||||
{ 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 });
|
||||
}
|
||||
|
||||
static async #viewAdversary(_, button) {
|
||||
const adversary = await foundry.utils.fromUuid(button.dataset.adversary);
|
||||
if (!adversary) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.adversaryMissing'));
|
||||
return;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @type {ApplicationClickAction}
|
||||
* @returns
|
||||
*/
|
||||
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) {
|
||||
const adversaryKey = target.dataset.adversary;
|
||||
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
|
||||
console.log(target.dataset.potentialAdversar);
|
||||
const newAdversaries = foundry.utils
|
||||
.getProperty(this.actor, path)
|
||||
.filter(x => x && (x?.uuid ?? x) !== adversaryKey);
|
||||
if (!confirmed) return;
|
||||
|
||||
const adversaries = foundry.utils.getProperty(this.actor, path);
|
||||
const newAdversaries = adversaries.filter(a => a.uuid !== doc.uuid);
|
||||
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 },
|
||||
window: { resizable: true },
|
||||
actions: {
|
||||
reactionRoll: AdversarySheet.#reactionRoll,
|
||||
useItem: this.useItem,
|
||||
toChat: this.toChat
|
||||
reactionRoll: AdversarySheet.#reactionRoll
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
|
|
@ -28,7 +26,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'features' }, { id: 'notes' }, { id: 'effects' }],
|
||||
tabs: [{ id: 'features' }, { id: 'effects' }, { id: 'notes' }],
|
||||
initial: 'features',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
@ -41,10 +39,63 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
return context;
|
||||
}
|
||||
|
||||
getItem(element) {
|
||||
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
|
||||
item = this.document.items.get(itemId);
|
||||
return item;
|
||||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||
import { getDocFromElement, itemAbleRollParse } from '../../../helpers/utils.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
|
|
@ -14,7 +15,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
classes: ['character'],
|
||||
position: { width: 850, height: 800 },
|
||||
actions: {
|
||||
triggerContextMenu: CharacterSheet.#triggerContextMenu,
|
||||
toggleVault: CharacterSheet.#toggleVault,
|
||||
rollAttribute: CharacterSheet.#rollAttribute,
|
||||
toggleHope: CharacterSheet.#toggleHope,
|
||||
|
|
@ -23,17 +23,39 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
makeDeathMove: CharacterSheet.#makeDeathMove,
|
||||
levelManagement: CharacterSheet.#levelManagement,
|
||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||
useItem: this.useItem, //TODO Fix this
|
||||
toChat: this.toChat
|
||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
useDowntime: this.useDowntime
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
},
|
||||
dragDrop: [],
|
||||
dragDrop: [
|
||||
{
|
||||
dragSelector: '[data-item-id][draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
handler: CharacterSheet._getContextMenuOptions,
|
||||
selector: '[data-item-id]',
|
||||
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||
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: {
|
||||
parentClassHooks: false,
|
||||
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 */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
|
@ -97,20 +135,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
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 */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -160,98 +184,135 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
case 'sidebar':
|
||||
await this._prepareSidebarContext(context, options);
|
||||
break;
|
||||
case 'biography':
|
||||
await this._prepareBiographyContext(context, options);
|
||||
break;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Loadout part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
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) {
|
||||
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 */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static _getContextMenuOptions() {
|
||||
/**
|
||||
* Get the item from the element.
|
||||
* @param {HTMLElement} el
|
||||
* @returns {foundry.documents.Item?}
|
||||
*/
|
||||
const getItem = el => this.actor.items.get(el.closest('[data-item-id]')?.dataset.itemId);
|
||||
|
||||
return [
|
||||
static #getDomainCardContextOptions() {
|
||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
const options = [
|
||||
{
|
||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem',
|
||||
icon: '<i class="fa-solid fa-burst"></i>',
|
||||
condition: el => {
|
||||
const item = getItem(el);
|
||||
return !['class', 'subclass'].includes(item.type);
|
||||
},
|
||||
callback: (button, event) => CharacterSheet.useItem.call(this, event, button)
|
||||
name: 'toLoadout',
|
||||
icon: 'fa-solid fa-arrow-up',
|
||||
condition: target => getDocFromElement(target).system.inVault,
|
||||
callback: target => getDocFromElement(target).update({ 'system.inVault': false })
|
||||
},
|
||||
{
|
||||
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip',
|
||||
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.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()
|
||||
name: 'toVault',
|
||||
icon: 'fa-solid fa-arrow-down',
|
||||
condition: target => !getDocFromElement(target).system.inVault,
|
||||
callback: target => getDocFromElement(target).update({ 'system.inVault': true })
|
||||
}
|
||||
];
|
||||
].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 */
|
||||
|
|
@ -345,7 +406,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
this.#filteredItems.inventory.search.clear();
|
||||
|
||||
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);
|
||||
if (matchesSearch) this.#filteredItems.inventory.search.add(item.id);
|
||||
const { menu } = this.#filteredItems.inventory;
|
||||
|
|
@ -365,14 +426,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
this.#filteredItems.loadout.search.clear();
|
||||
|
||||
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);
|
||||
if (matchesSearch) this.#filteredItems.loadout.search.add(item.id);
|
||||
const { menu } = this.#filteredItems.loadout;
|
||||
li.hidden = !(menu.has(item.id) && matchesSearch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Filter Menus */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -416,7 +477,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
this.#filteredItems.inventory.menu.clear();
|
||||
|
||||
for (const li of html.querySelectorAll('.inventory-item')) {
|
||||
const item = this.document.items.get(li.dataset.itemId);
|
||||
const item = getDocFromElement(li);
|
||||
|
||||
const matchesMenu =
|
||||
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();
|
||||
|
||||
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 =
|
||||
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 */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -495,7 +585,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const config = {
|
||||
event: event,
|
||||
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
|
||||
}),
|
||||
roll: {
|
||||
|
|
@ -505,13 +595,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
this.document.diceRoll(config);
|
||||
}
|
||||
|
||||
//TODO: redo toggleEquipItem method
|
||||
|
||||
/**
|
||||
* Toggles the equipped state of an item (armor or weapon).
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleEquipItem(_event, button) {
|
||||
//TODO: redo this
|
||||
const item = this.actor.items.get(button.closest('[data-item-id]')?.dataset.itemId);
|
||||
const item = getDocFromElement(button);
|
||||
if (!item) return;
|
||||
if (item.system.equipped) {
|
||||
await item.update({ 'system.equipped': false });
|
||||
|
|
@ -528,6 +619,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
await item.update({ 'system.equipped': true });
|
||||
break;
|
||||
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 item.update({ 'system.equipped': true });
|
||||
|
|
@ -559,76 +656,69 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* Toggles whether an item is stored in the vault.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleVault(event, button) {
|
||||
const docId = button.closest('[data-item-id]')?.dataset.itemId;
|
||||
const doc = this.document.items.get(docId);
|
||||
static async #toggleVault(_event, button) {
|
||||
const doc = getDocFromElement(button);
|
||||
await doc?.update({ 'system.inVault': !doc.system.inVault });
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the context menu.
|
||||
* Toggle the used state of a resource dice.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static #triggerContextMenu(event, _) {
|
||||
return CONFIG.ux.ContextMenu.triggerContextMenu(event);
|
||||
static async #toggleResourceDice(event, target) {
|
||||
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}
|
||||
*/
|
||||
static async useItem(event, button) {
|
||||
const item = this.getItem(button);
|
||||
static async #handleResourceDice(_, target) {
|
||||
const item = getDocFromElement(target);
|
||||
if (!item) return;
|
||||
|
||||
// Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board
|
||||
if (item.type === 'feature') {
|
||||
item.use(event);
|
||||
} else if (item instanceof ActiveEffect) {
|
||||
item.toChat(this);
|
||||
} else {
|
||||
const wasUsed = await item.use(event);
|
||||
if (wasUsed && item.type === 'weapon') {
|
||||
Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {});
|
||||
}
|
||||
}
|
||||
const rollValues = await game.system.api.applications.dialogs.ResourceDiceDialog.create(item, this.document);
|
||||
if (!rollValues) return;
|
||||
|
||||
await item.update({
|
||||
'system.resource.diceStates': rollValues.reduce((acc, state, index) => {
|
||||
acc[index] = { value: state.value, used: state.used };
|
||||
return acc;
|
||||
}, {})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send item to Chat
|
||||
* @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 < 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);
|
||||
}
|
||||
static useDowntime(_, button) {
|
||||
new game.system.api.applications.dialogs.Downtime(this.document, button.dataset.type === 'shortRest').render(
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
// Prevent event bubbling to avoid duplicate handling
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
super._onDrop(event);
|
||||
this._onDropItem(event, TextEditor.getDragEventData(event));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['actor', 'companion'],
|
||||
position: { width: 300 },
|
||||
actions: {
|
||||
viewActor: this.viewActor,
|
||||
useItem: this.useItem,
|
||||
toChat: this.toChat
|
||||
}
|
||||
actions: {}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
|
@ -29,52 +25,4 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
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: {
|
||||
width: 500
|
||||
},
|
||||
actions: {
|
||||
useItem: this.useItem,
|
||||
toChat: this.toChat
|
||||
},
|
||||
actions: {},
|
||||
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
|
||||
};
|
||||
|
||||
|
|
@ -35,47 +32,67 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
}
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
getItem(element) {
|
||||
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
|
||||
item = this.document.items.get(itemId);
|
||||
return item;
|
||||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'notes':
|
||||
await this._prepareNotesContext(context, options);
|
||||
break;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {ApplicationClickAction}
|
||||
* Prepare render context for the Biography part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async viewAdversary(_, button) {
|
||||
const target = button.closest('[data-item-uuid]');
|
||||
const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid);
|
||||
if (!adversary) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.adversaryMissing'));
|
||||
return;
|
||||
}
|
||||
async _prepareNotesContext(context, _options) {
|
||||
const { system } = this.document;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
adversary.sheet.render({ force: true });
|
||||
}
|
||||
const paths = {
|
||||
notes: 'notes'
|
||||
};
|
||||
|
||||
static async useItem(event, button) {
|
||||
const action = this.getItem(event);
|
||||
if (!action) {
|
||||
await this.viewAdversary(event, button);
|
||||
} else {
|
||||
action.use(event);
|
||||
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
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async toChat(event) {
|
||||
const item = this.getItem(event);
|
||||
item.toChat(this.document.id);
|
||||
/**
|
||||
* 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
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _onDragStart(event) {
|
||||
const item = event.currentTarget.closest('.inventory-item');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export { default as DHApplicationMixin } from './application-mixin.mjs';
|
||||
export { default as DHBaseItemSheet } from './base-item.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 DHBaseActorSettings } from './actor-setting.mjs';
|
||||
|
|
|
|||
|
|
@ -42,9 +42,12 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee
|
|||
/**@inheritdoc */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
||||
context.isNPC = this.actor.isNPC;
|
||||
|
||||
if (context.systemFields.attack) {
|
||||
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
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
|
||||
|
|
@ -71,11 +76,34 @@ export default function DHApplicationMixin(Base) {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'sheet', 'dh-style'],
|
||||
actions: {
|
||||
triggerContextMenu: DHSheetV2.#triggerContextMenu,
|
||||
createDoc: DHSheetV2.#createDoc,
|
||||
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: [],
|
||||
tagifyConfigs: []
|
||||
};
|
||||
|
|
@ -99,6 +127,27 @@ export default function DHApplicationMixin(Base) {
|
|||
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 */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -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.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]}
|
||||
* 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
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
_getEntryContextOptions() {
|
||||
return [];
|
||||
static #getEffectContextOptions() {
|
||||
/**@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;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* 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 */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create an embedded document.
|
||||
* @param {PointerEvent} event - The originating click event
|
||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #createDoc(event, button) {
|
||||
const { documentClass, type } = button.dataset;
|
||||
console.log(documentClass, type);
|
||||
const parent = this.document;
|
||||
static async #createDoc(event, target) {
|
||||
const { documentClass, type, inVault, disabled } = target.dataset;
|
||||
const parentIsItem = this.document.documentName === 'Item';
|
||||
const parent = parentIsItem && documentClass === 'Item' ? null : this.document;
|
||||
|
||||
const cls = getDocumentClass(documentClass);
|
||||
return await cls.createDocuments(
|
||||
[
|
||||
if (type === 'action') {
|
||||
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 }),
|
||||
type
|
||||
_id: foundry.utils.randomID(),
|
||||
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.
|
||||
* @param {PointerEvent} event - The originating click event
|
||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static #editDoc(_event, button) {
|
||||
const { type, docId } = button.dataset;
|
||||
this.document.getEmbeddedDocument(type, docId, { strict: true }).sheet.render({ force: true });
|
||||
static #editDoc(_event, target) {
|
||||
const doc = getDocFromElement(target);
|
||||
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.
|
||||
* @param {PointerEvent} _event - The originating click event
|
||||
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #deleteDoc(_event, button) {
|
||||
const { type, docId } = button.dataset;
|
||||
await this.document.getEmbeddedDocument(type, docId, { strict: true }).delete();
|
||||
static async #deleteDoc(event, target) {
|
||||
const doc = getDocFromElement(target);
|
||||
|
||||
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
|
||||
},
|
||||
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}*/
|
||||
#settingSheet;
|
||||
|
||||
|
|
@ -35,6 +48,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
return (this.#settingSheet ??= SheetClass ? new SheetClass({ document: this.document }) : null);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Prepare Context */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**@inheritdoc */
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
|
@ -42,6 +59,54 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
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
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -49,4 +114,50 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
static async #openSettings() {
|
||||
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';
|
||||
|
||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||
|
|
@ -15,20 +15,31 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['item'],
|
||||
position: { width: 600 },
|
||||
window: { resizable: true },
|
||||
form: {
|
||||
submitOnChange: true
|
||||
},
|
||||
actions: {
|
||||
addAction: DHBaseItemSheet.#addAction,
|
||||
editAction: DHBaseItemSheet.#editAction,
|
||||
removeAction: DHBaseItemSheet.#removeAction,
|
||||
addFeature: DHBaseItemSheet.#addFeature,
|
||||
editFeature: DHBaseItemSheet.#editFeature,
|
||||
removeFeature: DHBaseItemSheet.#removeFeature
|
||||
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
||||
addResource: DHBaseItemSheet.#addResource,
|
||||
removeResource: DHBaseItemSheet.#removeResource
|
||||
},
|
||||
dragDrop: [
|
||||
{ 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 */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }],
|
||||
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }, { id: 'effects' }],
|
||||
initial: 'description',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
@ -48,8 +59,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
async _preparePartContext(partId, context, options) {
|
||||
await super._preparePartContext(partId, context, options);
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
switch (partId) {
|
||||
|
|
@ -61,77 +72,78 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
secrets: this.item.isOwner
|
||||
});
|
||||
break;
|
||||
case 'effects':
|
||||
await this._prepareEffectsContext(context, options);
|
||||
break;
|
||||
case 'features':
|
||||
context.isGM = game.user.isGM;
|
||||
break;
|
||||
}
|
||||
|
||||
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.
|
||||
* Prepare render context for the Effect part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
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';
|
||||
async _prepareEffectsContext(context, _options) {
|
||||
context.effects = {
|
||||
actives: [],
|
||||
inactives: []
|
||||
};
|
||||
|
||||
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.
|
||||
* @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);
|
||||
for (const effect of this.item.effects) {
|
||||
const list = effect.active ? context.effects.actives : context.effects.inactives;
|
||||
list.push(effect);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Context Menu */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Edit an existing action on the item
|
||||
* @type {ApplicationClickAction}
|
||||
* 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 async #editAction(_event, button) {
|
||||
const action = this.document.system.actions[button.dataset.index];
|
||||
await new DHActionConfig(action).render({ force: true });
|
||||
static #getFeatureContextOptions() {
|
||||
const options = this._getContextMenuCommonOptions({ usable: true, toChat: true, deletable: false });
|
||||
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.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -139,6 +151,21 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
static async #removeAction(event, button) {
|
||||
event.stopPropagation();
|
||||
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({
|
||||
'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.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #addFeature(_event, _button) {
|
||||
const feature = await game.items.documentClass.create({
|
||||
type: 'feature',
|
||||
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') })
|
||||
static async #addFeature(_, target) {
|
||||
const { type } = target.dataset;
|
||||
const cls = foundry.documents.Item.implementation;
|
||||
const feature = await cls.create({
|
||||
'type': 'feature',
|
||||
'name': cls.defaultName({ type: 'feature' }),
|
||||
'system.subType': CONFIG.DH.ITEM.featureSubTypes[type]
|
||||
});
|
||||
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.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #removeFeature(event, button) {
|
||||
event.stopPropagation();
|
||||
const target = button.closest('.feature-item');
|
||||
|
||||
static async #deleteFeature(_, target) {
|
||||
const feature = getDocFromElement(target);
|
||||
if (!feature) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||
await feature.update({ 'system.subType': null });
|
||||
await this.document.update({
|
||||
'system.features': this.document.system.features
|
||||
.filter(feature => feature && feature.id !== target.id)
|
||||
.map(x => x.uuid)
|
||||
'system.features': this.document.system.features.map(x => x.uuid).filter(uuid => uuid !== feature.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) {
|
||||
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
|
||||
if (!feature) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||
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 = {
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
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 {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['ancestry']
|
||||
classes: ['ancestry'],
|
||||
actions: {
|
||||
editFeature: AncestrySheet.#editFeature,
|
||||
removeFeature: AncestrySheet.#removeFeature
|
||||
},
|
||||
dragDrop: [{ dragSelector: null, dropSelector: '.tab.features .drop-section' }]
|
||||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
static PARTS = {
|
||||
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 ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
|
||||
|
||||
export default class ArmorSheet extends DHBaseItemSheet {
|
||||
export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['armor'],
|
||||
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
||||
tagifyConfigs: [
|
||||
{
|
||||
selector: '.features-input',
|
||||
|
|
@ -26,7 +26,12 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
|||
settings: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs',
|
||||
scrollable: ['.settings']
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
},
|
||||
...super.PARTS
|
||||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
|
|
@ -35,7 +40,7 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
|||
|
||||
switch (partId) {
|
||||
case 'settings':
|
||||
context.features = this.document.system.features.map(x => x.value);
|
||||
context.features = this.document.system.armorFeatures.map(x => x.value);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +52,6 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
|||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||
*/
|
||||
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 Tagify from '@yaireo/tagify';
|
||||
|
||||
export default class BeastformSheet extends DHBaseItemSheet {
|
||||
/**@inheritdoc */
|
||||
|
|
@ -15,6 +16,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
|||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
|
||||
scrollable: ['.features']
|
||||
},
|
||||
advanced: { template: 'systems/daggerheart/templates/sheets/items/beastform/advanced.hbs' },
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
|
|
@ -23,16 +25,77 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
|||
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'settings' }, { id: 'features' }, { id: 'effects' }],
|
||||
tabs: [{ id: 'settings' }, { id: 'features' }, { id: 'advanced' }, { id: 'effects' }],
|
||||
initial: 'settings',
|
||||
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 */
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
async _preparePartContext(partId, context, options) {
|
||||
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;
|
||||
}
|
||||
|
||||
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 },
|
||||
actions: {
|
||||
removeItemFromCollection: ClassSheet.#removeItemFromCollection,
|
||||
removeSuggestedItem: ClassSheet.#removeSuggestedItem,
|
||||
viewDoc: ClassSheet.#viewDoc,
|
||||
addFeature: this.addFeature,
|
||||
editFeature: this.editFeature,
|
||||
deleteFeature: this.deleteFeature
|
||||
removeSuggestedItem: ClassSheet.#removeSuggestedItem
|
||||
},
|
||||
tagifyConfigs: [
|
||||
{
|
||||
|
|
@ -46,13 +42,17 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
settings: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
||||
scrollable: ['.settings']
|
||||
},
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }],
|
||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }, { id: 'effects' }],
|
||||
initial: 'description',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
@ -78,6 +78,7 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = TextEditor.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
|
|
@ -85,6 +86,28 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
await this.document.update({
|
||||
'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') {
|
||||
if (target.classList.contains('primary-weapon-section')) {
|
||||
if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary)
|
||||
|
|
@ -144,7 +167,7 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
static async #removeItemFromCollection(_event, element) {
|
||||
const { uuid, target } = element.dataset;
|
||||
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;
|
||||
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 */
|
||||
static PARTS = {
|
||||
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: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/consumable/settings.hbs',
|
||||
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';
|
||||
|
||||
export default class FeatureSheet extends DHBaseItemSheet {
|
||||
|
|
@ -7,13 +5,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
id: 'daggerheart-feature',
|
||||
classes: ['feature'],
|
||||
position: { height: 600 },
|
||||
window: { resizable: true },
|
||||
actions: {
|
||||
addAction: FeatureSheet.#addAction,
|
||||
editAction: FeatureSheet.#editAction,
|
||||
removeAction: FeatureSheet.#removeAction
|
||||
}
|
||||
actions: {}
|
||||
};
|
||||
|
||||
/**@override */
|
||||
|
|
@ -21,6 +13,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
|
||||
settings: { template: 'systems/daggerheart/templates/sheets/items/feature/settings.hbs' },
|
||||
actions: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
|
||||
scrollable: ['.actions']
|
||||
|
|
@ -34,97 +27,9 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
/**@override */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }],
|
||||
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }, { id: 'effects' }],
|
||||
initial: 'description',
|
||||
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: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/miscellaneous/settings.hbs',
|
||||
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'],
|
||||
position: { width: 600 },
|
||||
window: { resizable: false },
|
||||
actions: {
|
||||
addFeature: this.addFeature,
|
||||
editFeature: this.editFeature,
|
||||
deleteFeature: this.deleteFeature
|
||||
}
|
||||
actions: {}
|
||||
};
|
||||
|
||||
/**@override */
|
||||
|
|
@ -25,53 +21,29 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
settings: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/subclass/settings.hbs',
|
||||
scrollable: ['.settings']
|
||||
},
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }],
|
||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }, { id: 'effects' }],
|
||||
initial: 'description',
|
||||
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) {
|
||||
const featureItem = event.currentTarget.closest('.drop-section');
|
||||
|
||||
if (featureItem) {
|
||||
const feature = this.document.system[featureItem.dataset.type];
|
||||
if (!feature) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -82,18 +54,45 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
if (data.fromInternal) return;
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (item?.type === 'feature') {
|
||||
const dropSection = event.target.closest('.drop-section');
|
||||
if (this.document.system[dropSection.dataset.type]) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsFull'));
|
||||
return;
|
||||
}
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
if (item.type === 'feature') {
|
||||
if (target.dataset.type === 'foundation') {
|
||||
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.foundation) {
|
||||
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 ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
|
||||
|
||||
export default class WeaponSheet extends DHBaseItemSheet {
|
||||
export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['weapon'],
|
||||
|
|
@ -25,15 +26,20 @@ export default class WeaponSheet extends DHBaseItemSheet {
|
|||
settings: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs',
|
||||
scrollable: ['.settings']
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
},
|
||||
...super.PARTS
|
||||
};
|
||||
|
||||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context) {
|
||||
super._preparePartContext(partId, context);
|
||||
await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
|
@ -45,6 +51,6 @@ export default class WeaponSheet extends DHBaseItemSheet {
|
|||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||
*/
|
||||
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 * as DhCountdowns from './countdowns.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 {
|
||||
constructor() {
|
||||
super();
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.targetTemplate = {
|
||||
activeLayer: undefined,
|
||||
|
|
@ -83,15 +83,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
actor.system.attack?._id === actionId
|
||||
? actor.system.attack
|
||||
: item.system.attack?._id === actionId
|
||||
? item.system.attack
|
||||
: item?.system?.actions?.find(a => a._id === actionId);
|
||||
? item.system.attack
|
||||
: item?.system?.actions?.find(a => a._id === actionId);
|
||||
return action;
|
||||
}
|
||||
|
||||
onRollDamage = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
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) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
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'));
|
||||
return;
|
||||
}
|
||||
|
||||
game.canvas.pan(token);
|
||||
};
|
||||
|
||||
|
|
@ -212,13 +211,25 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
|
||||
if (targets.length === 0)
|
||||
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));
|
||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
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);
|
||||
};
|
||||
|
||||
actionUseButton = async (_, message) => {
|
||||
actionUseButton = async (event, message) => {
|
||||
const { moveIndex, actionIndex } = event.currentTarget.dataset;
|
||||
const parent = await foundry.utils.fromUuid(message.system.actor);
|
||||
const actionType = Object.values(message.system.moves)[0].actions[0];
|
||||
const cls = CONFIG.DH.ACTIONS.actionTypes[actionType.type];
|
||||
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
||||
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
||||
const action = new cls(
|
||||
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
|
||||
{ parent: parent }
|
||||
{ parent: parent.system }
|
||||
);
|
||||
|
||||
action.use();
|
||||
action.use(event);
|
||||
};
|
||||
|
||||
//Reroll Functionality
|
||||
rerollEvent = async(event,message)=> {
|
||||
let DieTerm=foundry.dice.terms.Die;
|
||||
rerollEvent = async (event, message) => {
|
||||
let DieTerm = foundry.dice.terms.Die;
|
||||
let dicetype = event.target.value;
|
||||
let originalRoll_parsed=message.rolls.map(roll => JSON.parse(roll))[0];
|
||||
console.log("Parsed Map:",originalRoll_parsed);
|
||||
let originalRoll=Roll.fromData(originalRoll_parsed);
|
||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
||||
console.log('Parsed Map:', originalRoll_parsed);
|
||||
let originalRoll = Roll.fromData(originalRoll_parsed);
|
||||
let diceIndex;
|
||||
console.log("Dice to reroll is:",dicetype,", and the message id is:",message._id,originalRoll_parsed);
|
||||
console.log("Original Roll Terms:",originalRoll.terms);
|
||||
switch(dicetype){
|
||||
case "hope": {
|
||||
diceIndex=0; //Hope Die
|
||||
console.log('Dice to reroll is:', dicetype, ', and the message id is:', message._id, originalRoll_parsed);
|
||||
console.log('Original Roll Terms:', originalRoll.terms);
|
||||
switch (dicetype) {
|
||||
case 'hope': {
|
||||
diceIndex = 0; //Hope Die
|
||||
break;
|
||||
};
|
||||
case "fear" :{
|
||||
diceIndex=2; //Fear Die
|
||||
}
|
||||
case 'fear': {
|
||||
diceIndex = 2; //Fear Die
|
||||
break;
|
||||
};
|
||||
default:
|
||||
ui.notifications.warn("Invalid Dice type selected.");
|
||||
}
|
||||
default:
|
||||
ui.notifications.warn('Invalid Dice type selected.');
|
||||
break;
|
||||
}
|
||||
let rollClone=originalRoll.clone();
|
||||
let rerolledTerm=originalRoll.terms[diceIndex];
|
||||
console.log("originalRoll:",originalRoll,"rerolledTerm",rerolledTerm);
|
||||
let rollClone = originalRoll.clone();
|
||||
let rerolledTerm = originalRoll.terms[diceIndex];
|
||||
console.log('originalRoll:', originalRoll, 'rerolledTerm', rerolledTerm);
|
||||
if (!(rerolledTerm instanceof DieTerm)) {
|
||||
ui.notifications.error("Selected term is not a die.");
|
||||
ui.notifications.error('Selected term is not a die.');
|
||||
return;
|
||||
}
|
||||
await rollClone.reroll({allowStrings:true})[diceIndex];
|
||||
await rollClone.reroll({ allowStrings: true })[diceIndex];
|
||||
console.log(rollClone);
|
||||
await rollClone.evaluate({allowStrings:true});
|
||||
await rollClone.evaluate({ allowStrings: true });
|
||||
console.log(rollClone.result);
|
||||
/*
|
||||
/*
|
||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||
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>`
|
||||
|
|
@ -343,5 +355,5 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
if (!confirm) return;
|
||||
rollClone.toMessage({flavor: 'Selective reroll applied for ${dicetype}.'});
|
||||
console.log("Updated Roll",rollClone);*/
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,11 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
}
|
||||
|
||||
async setCombatantSpotlight(combatantId) {
|
||||
const update = {
|
||||
system: {
|
||||
'spotlight.requesting': false
|
||||
}
|
||||
};
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
|
||||
const toggleTurn = this.viewed.combatants.contents
|
||||
|
|
@ -73,10 +78,18 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
.map(x => x.id)
|
||||
.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 combatant.update({ 'system.spotlight.requesting': false });
|
||||
await combatant.update(update);
|
||||
}
|
||||
|
||||
static async requestSpotlight(_, target) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { countdownTypes } from '../../config/generalConfig.mjs';
|
||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import constructHTMLButton from '../../helpers/utils.mjs';
|
||||
import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
|
||||
|
|
@ -328,43 +327,29 @@ export class EncounterCountdowns extends Countdowns {
|
|||
};
|
||||
}
|
||||
|
||||
export const registerCountdownApplicationHooks = () => {
|
||||
const updateCountdowns = async shouldProgress => {
|
||||
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).countdowns) {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
for (let countdownCategoryKey in countdownSetting) {
|
||||
const countdownCategory = countdownSetting[countdownCategoryKey];
|
||||
for (let countdownKey in countdownCategory.countdowns) {
|
||||
const countdown = countdownCategory.countdowns[countdownKey];
|
||||
|
||||
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();
|
||||
}
|
||||
export async function updateCountdowns(progressType) {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const update = Object.keys(countdownSetting).reduce((update, typeKey) => {
|
||||
return foundry.utils.mergeObject(
|
||||
update,
|
||||
Object.keys(countdownSetting[typeKey].countdowns).reduce((acc, countdownKey) => {
|
||||
const countdown = countdownSetting[typeKey].countdowns[countdownKey];
|
||||
if (countdown.progress.current > 0 && countdown.progress.type.value === progressType) {
|
||||
acc[`${typeKey}.countdowns.${countdownKey}.progress.current`] = countdown.progress.current - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.characterAttack, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return (
|
||||
countdown.progress.type.value === countdownTypes.characterAttack.id && countdown.progress.current > 0
|
||||
);
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
}, {});
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.spotlight, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return countdown.progress.type.value === countdownTypes.spotlight.id && countdown.progress.current > 0;
|
||||
});
|
||||
await countdownSetting.updateSource(update);
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSetting);
|
||||
|
||||
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.stopPropagation();
|
||||
const { clientX, clientY } = event;
|
||||
const selector = '[data-item-id]';
|
||||
const selector = '[data-item-uuid]';
|
||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||
target?.dispatchEvent(
|
||||
new PointerEvent('contextmenu', {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export { default as DhMeasuredTemplate } from './measuredTemplate.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';
|
||||
|
|
|
|||
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 flagsConfig from './flagsConfig.mjs';
|
||||
export * as generalConfig from './generalConfig.mjs';
|
||||
export * as hooksConfig from './hooksConfig.mjs';
|
||||
export * as itemConfig from './itemConfig.mjs';
|
||||
export * as settingsConfig from './settingsConfig.mjs';
|
||||
export * as systemConfig from './system.mjs';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export const abilities = {
|
||||
agility: {
|
||||
id: 'agility',
|
||||
label: 'DAGGERHEART.CONFIG.Traits.agility.name',
|
||||
verbs: [
|
||||
'DAGGERHEART.CONFIG.Traits.agility.verb.sprint',
|
||||
|
|
@ -8,6 +9,7 @@ export const abilities = {
|
|||
]
|
||||
},
|
||||
strength: {
|
||||
id: 'strength',
|
||||
label: 'DAGGERHEART.CONFIG.Traits.strength.name',
|
||||
verbs: [
|
||||
'DAGGERHEART.CONFIG.Traits.strength.verb.lift',
|
||||
|
|
@ -16,6 +18,7 @@ export const abilities = {
|
|||
]
|
||||
},
|
||||
finesse: {
|
||||
id: 'finesse',
|
||||
label: 'DAGGERHEART.CONFIG.Traits.finesse.name',
|
||||
verbs: [
|
||||
'DAGGERHEART.CONFIG.Traits.finesse.verb.control',
|
||||
|
|
@ -24,6 +27,7 @@ export const abilities = {
|
|||
]
|
||||
},
|
||||
instinct: {
|
||||
id: 'instinct',
|
||||
label: 'DAGGERHEART.CONFIG.Traits.instinct.name',
|
||||
verbs: [
|
||||
'DAGGERHEART.CONFIG.Traits.instinct.verb.perceive',
|
||||
|
|
@ -32,6 +36,7 @@ export const abilities = {
|
|||
]
|
||||
},
|
||||
presence: {
|
||||
id: 'presence',
|
||||
label: 'DAGGERHEART.CONFIG.Traits.presence.name',
|
||||
verbs: [
|
||||
'DAGGERHEART.CONFIG.Traits.presence.verb.charm',
|
||||
|
|
@ -40,6 +45,7 @@ export const abilities = {
|
|||
]
|
||||
},
|
||||
knowledge: {
|
||||
id: 'knowledge',
|
||||
label: 'DAGGERHEART.CONFIG.Traits.knowledge.name',
|
||||
verbs: [
|
||||
'DAGGERHEART.CONFIG.Traits.knowledge.verb.recall',
|
||||
|
|
@ -113,7 +119,7 @@ export const adversaryTypes = {
|
|||
},
|
||||
social: {
|
||||
id: 'social',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryTypee.social.label',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
|
||||
},
|
||||
solo: {
|
||||
|
|
@ -411,7 +417,7 @@ export const levelupData = {
|
|||
};
|
||||
|
||||
export const subclassFeatureLabels = {
|
||||
1: 'DAGGERHEART.ITEMS.DomainCard.foundation',
|
||||
1: 'DAGGERHEART.ITEMS.DomainCard.foundationTitle',
|
||||
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
|
||||
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,55 +3,55 @@ export const domains = {
|
|||
id: 'arcana',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.arcana.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/arcana.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Arcana'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.arcana.description'
|
||||
},
|
||||
blade: {
|
||||
id: 'blade',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.blade.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/blade.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Blade'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.blade.description'
|
||||
},
|
||||
bone: {
|
||||
id: 'bone',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.bone.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/bone.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Bone'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.bone.description'
|
||||
},
|
||||
codex: {
|
||||
id: 'codex',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.codex.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/codex.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Codex'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.codex.description'
|
||||
},
|
||||
grace: {
|
||||
id: 'grace',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.grace.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/grace.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Grace'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.grace.description'
|
||||
},
|
||||
midnight: {
|
||||
id: 'midnight',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.midnight.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/midnight.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Midnight'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.midnight.description'
|
||||
},
|
||||
sage: {
|
||||
id: 'sage',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.sage.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/sage.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Sage'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.sage.description'
|
||||
},
|
||||
splendor: {
|
||||
id: 'splendor',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.splendor.label',
|
||||
src: 'systems/daggerheart/assets/icons/domains/splendor.svg',
|
||||
description: 'DAGGERHEART.GENERAL.Domain.Splendor'
|
||||
description: 'DAGGERHEART.GENERAL.Domain.splendor.description'
|
||||
},
|
||||
valor: {
|
||||
id: 'valor',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.valor.label',
|
||||
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',
|
||||
position: 'countdown-encounter-position'
|
||||
};
|
||||
|
||||
export const itemAttachmentSource = 'attachmentSource';
|
||||
|
|
|
|||
|
|
@ -59,13 +59,13 @@ export const damageTypes = {
|
|||
id: 'physical',
|
||||
label: 'DAGGERHEART.CONFIG.DamageType.physical.name',
|
||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation',
|
||||
icon: ["fa-hand-fist"]
|
||||
icon: 'fa-hand-fist'
|
||||
},
|
||||
magical: {
|
||||
id: 'magical',
|
||||
label: 'DAGGERHEART.CONFIG.DamageType.magical.name',
|
||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation',
|
||||
icon: ["fa-wand-sparkles"]
|
||||
icon: 'fa-wand-sparkles'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -89,6 +89,11 @@ export const healingTypes = {
|
|||
id: 'armorStack',
|
||||
label: 'DAGGERHEART.CONFIG.HealingType.armorStack.name',
|
||||
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',
|
||||
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
||||
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: {
|
||||
id: 'tendToWounds',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.name'),
|
||||
icon: 'fa-solid fa-bandage',
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.description'),
|
||||
actions: [
|
||||
|
|
@ -127,11 +145,11 @@ export const defaultRestOptions = {
|
|||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
actionType: 'action',
|
||||
healing: {
|
||||
type: 'health',
|
||||
applyTo: healingTypes.hitPoints.id,
|
||||
value: {
|
||||
custom: {
|
||||
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: {
|
||||
id: 'clearStress',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.name'),
|
||||
icon: 'fa-regular fa-face-surprise',
|
||||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.description'),
|
||||
actions: [
|
||||
|
|
@ -150,11 +169,11 @@ export const defaultRestOptions = {
|
|||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||
actionType: 'action',
|
||||
healing: {
|
||||
type: 'stress',
|
||||
applyTo: healingTypes.stress.id,
|
||||
value: {
|
||||
custom: {
|
||||
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: {
|
||||
id: 'repairArmor',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'),
|
||||
icon: 'fa-solid fa-hammer',
|
||||
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
||||
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: {
|
||||
id: 'prepare',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'),
|
||||
icon: 'fa-solid fa-dumbbell',
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
|
||||
actions: []
|
||||
|
|
@ -180,6 +217,7 @@ export const defaultRestOptions = {
|
|||
tendToWounds: {
|
||||
id: 'tendToWounds',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.name'),
|
||||
icon: 'fa-solid fa-bandage',
|
||||
img: 'icons/magic/life/cross-worn-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.description'),
|
||||
actions: []
|
||||
|
|
@ -187,6 +225,7 @@ export const defaultRestOptions = {
|
|||
clearStress: {
|
||||
id: 'clearStress',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.name'),
|
||||
icon: 'fa-regular fa-face-surprise',
|
||||
img: 'icons/magic/perception/eye-ringed-green.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.description'),
|
||||
actions: []
|
||||
|
|
@ -194,6 +233,7 @@ export const defaultRestOptions = {
|
|||
repairArmor: {
|
||||
id: 'repairArmor',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.name'),
|
||||
icon: 'fa-solid fa-hammer',
|
||||
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.description'),
|
||||
actions: []
|
||||
|
|
@ -201,6 +241,7 @@ export const defaultRestOptions = {
|
|||
prepare: {
|
||||
id: 'prepare',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'),
|
||||
icon: 'fa-solid fa-dumbbell',
|
||||
img: 'icons/skills/trades/academics-merchant-scribe.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
|
||||
actions: []
|
||||
|
|
@ -208,19 +249,12 @@ export const defaultRestOptions = {
|
|||
workOnAProject: {
|
||||
id: 'workOnAProject',
|
||||
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.name'),
|
||||
icon: 'fa-solid fa-diagram-project',
|
||||
img: 'icons/skills/social/thumbsup-approval-like.webp',
|
||||
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
|
||||
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 = {
|
||||
|
|
@ -245,25 +279,21 @@ export const deathMoves = {
|
|||
};
|
||||
|
||||
export const tiers = {
|
||||
tier1: {
|
||||
id: 'tier1',
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.tier1',
|
||||
value: 1
|
||||
1: {
|
||||
id: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.1'
|
||||
},
|
||||
tier2: {
|
||||
id: 'tier2',
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.tier2',
|
||||
value: 2
|
||||
2: {
|
||||
id: 2,
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.2'
|
||||
},
|
||||
tier3: {
|
||||
id: 'tier3',
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.tier3',
|
||||
value: 3
|
||||
3: {
|
||||
id: 3,
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.3'
|
||||
},
|
||||
tier4: {
|
||||
id: 'tier4',
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.tier4',
|
||||
value: 4
|
||||
4: {
|
||||
id: 4,
|
||||
label: 'DAGGERHEART.GENERAL.Tiers.4'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -366,31 +396,6 @@ export const abilityCosts = {
|
|||
label: 'Armor Stack',
|
||||
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: {
|
||||
id: 'fear',
|
||||
label: 'Fear',
|
||||
|
|
@ -401,29 +406,29 @@ export const abilityCosts = {
|
|||
export const countdownTypes = {
|
||||
spotlight: {
|
||||
id: 'spotlight',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.Spotlight'
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
||||
},
|
||||
characterAttack: {
|
||||
id: 'characterAttack',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.CharacterAttack'
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
||||
},
|
||||
custom: {
|
||||
id: 'custom',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownTypes.Custom'
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.custom'
|
||||
}
|
||||
};
|
||||
export const rollTypes = {
|
||||
weapon: {
|
||||
id: 'weapon',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.weapon.name'
|
||||
attack: {
|
||||
id: 'attack',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.attack.name'
|
||||
},
|
||||
spellcast: {
|
||||
id: 'spellcast',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.spellcast.name'
|
||||
},
|
||||
ability: {
|
||||
id: 'ability',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.ability.name'
|
||||
trait: {
|
||||
id: 'trait',
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.trait.name'
|
||||
},
|
||||
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 ITEM from './itemConfig.mjs';
|
||||
import * as SETTINGS from './settingsConfig.mjs';
|
||||
import { hooks as HOOKS } from './hooksConfig.mjs';
|
||||
import * as EFFECTS from './effectConfig.mjs';
|
||||
import * as ACTIONS from './actionConfig.mjs';
|
||||
import * as FLAGS from './flagsConfig.mjs';
|
||||
|
|
@ -17,7 +16,6 @@ export const SYSTEM = {
|
|||
ACTOR,
|
||||
ITEM,
|
||||
SETTINGS,
|
||||
HOOKS,
|
||||
EFFECTS,
|
||||
ACTIONS,
|
||||
FLAGS
|
||||
|
|
|
|||
|
|
@ -76,11 +76,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
|||
};
|
||||
}
|
||||
|
||||
getFormula(actor) {
|
||||
/* 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}`) : ''}`; */
|
||||
getFormula() {
|
||||
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
||||
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.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 = {}) {
|
||||
const damageFields = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export class DHDamageData extends foundry.abstract.DataModel {
|
||||
export class DHResourceData extends foundry.abstract.DataModel {
|
||||
/** @override */
|
||||
static defineSchema() {
|
||||
return {
|
||||
// ...super.defineSchema(),
|
||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||
type: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||
initial: 'physical',
|
||||
label: 'Type',
|
||||
nullable: false,
|
||||
required: true
|
||||
applyTo: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.healingTypes,
|
||||
required: true,
|
||||
blank: false,
|
||||
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.applyTo.label'
|
||||
}),
|
||||
resultBased: new fields.BooleanField({
|
||||
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 getRollType(parent) {
|
||||
return parent.type === 'weapon' ? 'weapon' : 'spellcast';
|
||||
return parent.type === 'weapon' ? 'attack' : 'spellcast';
|
||||
}
|
||||
|
||||
get chatTemplate() {
|
||||
|
|
@ -14,14 +14,14 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
|
||||
prepareData() {
|
||||
super.prepareData();
|
||||
if(!!this.item?.system?.attack) {
|
||||
if (!!this.item?.system?.attack) {
|
||||
if (this.damage.includeBase) {
|
||||
const baseDamage = this.getParentDamage();
|
||||
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.type = 'weapon';
|
||||
this.roll.type = 'attack';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,4 +37,17 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
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 D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||
|
||||
|
|
@ -35,12 +35,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}),
|
||||
cost: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
type: new fields.StringField({
|
||||
choices: CONFIG.DH.GENERAL.abilityCosts,
|
||||
key: new fields.StringField({
|
||||
nullable: false,
|
||||
required: true,
|
||||
initial: 'hope'
|
||||
}),
|
||||
keyIsID: new fields.BooleanField(),
|
||||
value: new fields.NumberField({ nullable: true, initial: 1 }),
|
||||
scalable: new fields.BooleanField({ initial: false }),
|
||||
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 })
|
||||
})
|
||||
),
|
||||
healing: new fields.SchemaField({
|
||||
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)
|
||||
}),
|
||||
healing: new fields.EmbeddedDataField(DHResourceData),
|
||||
beastform: new fields.SchemaField({
|
||||
tierAccess: new fields.SchemaField({
|
||||
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) {
|
||||
return 'ability';
|
||||
return 'trait';
|
||||
}
|
||||
|
||||
static getSourceConfig(parent) {
|
||||
const updateSource = {};
|
||||
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['range'] = parent?.system?.attack?.range;
|
||||
updateSource['roll'] = {
|
||||
useDefault: true
|
||||
}
|
||||
};
|
||||
} else {
|
||||
if (parent?.system?.trait) {
|
||||
updateSource['roll'] = {
|
||||
|
|
@ -177,18 +163,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
getRollData(data = {}) {
|
||||
if (!this.actor) return null;
|
||||
const actorData = this.actor.getRollData(false);
|
||||
|
||||
// Remove when included directly in Actor getRollData
|
||||
actorData.prof = actorData.proficiency?.value ?? 1;
|
||||
actorData.cast = actorData.spellcast?.value ?? 1;
|
||||
// Add Roll results to RollDatas
|
||||
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.
|
||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||
: 1;
|
||||
|
|
@ -198,6 +178,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
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);
|
||||
// Prepare base Config
|
||||
const initConfig = this.initActionConfig(event);
|
||||
|
|
@ -211,7 +193,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
// Prepare Costs
|
||||
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.");
|
||||
|
||||
// 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;
|
||||
|
||||
// Display configuration window if necessary
|
||||
// if (config.dialog?.configure && this.requireConfigurationDialog(config)) {
|
||||
if (this.requireConfigurationDialog(config)) {
|
||||
config = await D20RollDialog.configure(null, config);
|
||||
if (!config) return;
|
||||
|
|
@ -275,7 +256,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
hasDamage: !!this.damage?.parts?.length,
|
||||
hasHealing: !!this.healing,
|
||||
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() {
|
||||
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
|
||||
return costs;
|
||||
return this.calcCosts(costs);
|
||||
}
|
||||
|
||||
prepareUse() {
|
||||
|
|
@ -295,7 +277,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
prepareTarget() {
|
||||
if(!this.target?.type) return [];
|
||||
if (!this.target?.type) return [];
|
||||
let targets;
|
||||
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
|
||||
targets = this.constructor.formatTarget(this.actor.token ?? this.actor.prototypeToken);
|
||||
|
|
@ -315,7 +297,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
prepareRoll() {
|
||||
const roll = {
|
||||
modifiers: [],
|
||||
modifiers: this.modifiers,
|
||||
trait: this.roll?.trait,
|
||||
label: 'Attack',
|
||||
type: this.actionType,
|
||||
|
|
@ -334,10 +316,26 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
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
|
||||
.filter(c => c.enabled !== false)
|
||||
.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);
|
||||
|
|
@ -353,6 +351,13 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
get hasRoll() {
|
||||
return !!this.roll?.type || !!this.roll?.bonus;
|
||||
}
|
||||
|
||||
get modifiers() {
|
||||
if (!this.actor) return [];
|
||||
const modifiers = [];
|
||||
/** Placeholder for specific bonuses **/
|
||||
return modifiers;
|
||||
}
|
||||
/* ROLL */
|
||||
|
||||
/* 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),
|
||||
hasFearCost = realCosts.findIndex(c => c.type === 'fear');
|
||||
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
||||
if (hasFearCost > -1) {
|
||||
const fearCost = realCosts.splice(hasFearCost, 1);
|
||||
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
||||
if (
|
||||
!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;
|
||||
}
|
||||
|
||||
/* 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(
|
||||
(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
|
||||
);
|
||||
}
|
||||
/* COST */
|
||||
|
||||
/* USES */
|
||||
calcUses(uses) {
|
||||
|
|
@ -409,7 +437,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
if (!uses) return true;
|
||||
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
|
||||
}
|
||||
/* USES */
|
||||
|
||||
/* TARGET */
|
||||
isTargetFriendly(target) {
|
||||
|
|
@ -432,7 +459,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
name: actor.actor.name,
|
||||
img: actor.actor.img,
|
||||
difficulty: actor.actor.system.difficulty,
|
||||
evasion: actor.actor.system.evasion?.total
|
||||
evasion: actor.actor.system.evasion
|
||||
};
|
||||
}
|
||||
/* TARGET */
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ export default class DhBeastformAction extends DHBaseAction {
|
|||
const abort = await this.handleActiveTransformations();
|
||||
if (abort) return;
|
||||
|
||||
const beastformUuid = await BeastformDialog.configure(beastformConfig);
|
||||
if (!beastformUuid) return;
|
||||
const item = args[0];
|
||||
|
||||
await this.transform(beastformUuid);
|
||||
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, item);
|
||||
if (!selected) return;
|
||||
|
||||
await this.transform(selected, evolved, hybrid);
|
||||
}
|
||||
|
||||
prepareBeastformConfig(config) {
|
||||
|
|
@ -29,21 +31,48 @@ export default class DhBeastformAction extends DHBaseAction {
|
|||
};
|
||||
}
|
||||
|
||||
async transform(beastformUuid) {
|
||||
const beastform = await foundry.utils.fromUuid(beastformUuid);
|
||||
this.actor.createEmbeddedDocuments('Item', [beastform.toObject()]);
|
||||
async transform(selectedForm, evolvedData, hybridData) {
|
||||
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.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() {
|
||||
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
|
||||
if (beastformEffects.length > 0) {
|
||||
for (let effect of beastformEffects) {
|
||||
await effect.delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
const existingEffects = beastformEffects.length > 0;
|
||||
await this.actor.deleteEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
beastformEffects.map(x => x.id)
|
||||
);
|
||||
return existingEffects;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { setsEqual } from '../../helpers/utils.mjs';
|
||||
import DHBaseAction from './baseAction.mjs';
|
||||
|
||||
export default class DHDamageAction extends DHBaseAction {
|
||||
|
|
@ -6,33 +7,63 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
getFormulaValue(part, data) {
|
||||
let formulaValue = part.value;
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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 roll = { formula: formula, total: formula },
|
||||
bonusDamage = [];
|
||||
let formulas = this.damage.parts.map(p => ({
|
||||
formula: this.getFormulaValue(p, data).getFormula(this.actor),
|
||||
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 = {
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
|
||||
roll: { formula },
|
||||
targets: data.system?.targets.filter(t => t.hit) ?? data.targets,
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: game.i18n.localize(this.name) }),
|
||||
roll: formulas,
|
||||
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
||||
hasSave: this.hasSave,
|
||||
isCritical: data.system?.roll?.isCritical ?? false,
|
||||
source: data.system?.source,
|
||||
isCritical: systemData.roll?.isCritical ?? false,
|
||||
source: systemData.source,
|
||||
data: this.getRollData(),
|
||||
event
|
||||
};
|
||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||
if (data.system) {
|
||||
config.source.message = data._id;
|
||||
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) {
|
||||
let formulaValue = this.getFormulaValue(data),
|
||||
formula = formulaValue.getFormula(this.actor);
|
||||
|
||||
if (!formula || formula == '') return;
|
||||
let roll = { formula: formula, total: formula },
|
||||
bonusDamage = [];
|
||||
const systemData = data.system ?? data;
|
||||
let formulas = [
|
||||
{
|
||||
formula: this.getFormulaValue(data).getFormula(this.actor),
|
||||
applyTo: this.healing.applyTo
|
||||
}
|
||||
];
|
||||
|
||||
const config = {
|
||||
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),
|
||||
messageType: 'healing',
|
||||
type: this.healing.type,
|
||||
source: systemData.source,
|
||||
data: this.getRollData(),
|
||||
event
|
||||
};
|
||||
|
||||
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||
return CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||
}
|
||||
|
||||
get chatTemplate() {
|
||||
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,
|
||||
nullable: true
|
||||
}),
|
||||
tokenRingImg: new fields.FilePathField({
|
||||
initial: 'icons/svg/mystery-man.svg',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
tokenSize: new fields.SchemaField({
|
||||
height: 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() {
|
||||
if (this.parent.parent.type === 'character') {
|
||||
const update = {
|
||||
|
|
@ -28,6 +40,11 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
|||
width: this.characterTokenData.tokenSize.width,
|
||||
texture: {
|
||||
src: this.characterTokenData.tokenImg
|
||||
},
|
||||
ring: {
|
||||
subject: {
|
||||
texture: this.characterTokenData.tokenRingImg
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import BaseDataActor from './base.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 })
|
||||
});
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhpAdversary extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
||||
|
|
@ -22,28 +17,44 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
tier: new fields.StringField({
|
||||
...super.defineSchema(),
|
||||
tier: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers,
|
||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
||||
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||
}),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ACTOR.adversaryTypes,
|
||||
initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id
|
||||
}),
|
||||
description: new fields.StringField(),
|
||||
motivesAndTactics: new fields.StringField(),
|
||||
notes: new fields.HTMLField(),
|
||||
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({
|
||||
major: new fields.NumberField({ required: true, initial: 0, integer: true }),
|
||||
severe: new fields.NumberField({ required: true, initial: 0, integer: true })
|
||||
major: new fields.NumberField({
|
||||
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({
|
||||
hitPoints: resourceField(),
|
||||
stress: resourceField()
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
|
||||
}),
|
||||
attack: new ActionField({
|
||||
initial: {
|
||||
|
|
@ -58,11 +69,12 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
amount: 1
|
||||
},
|
||||
roll: {
|
||||
type: 'weapon'
|
||||
type: 'attack'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
type: ['physical'],
|
||||
value: {
|
||||
multiplier: 'flat'
|
||||
}
|
||||
|
|
@ -74,13 +86,18 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
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({
|
||||
difficulty: new fields.SchemaField({
|
||||
all: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
reaction: new fields.NumberField({ integer: true, initial: 0 })
|
||||
roll: new fields.SchemaField({
|
||||
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
|
||||
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() {
|
||||
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
|
||||
|
|
@ -16,6 +23,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
type: 'base',
|
||||
isNPC: true,
|
||||
settingSheet: null,
|
||||
hasResistances: true
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -27,10 +35,15 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const schema = {};
|
||||
|
||||
return {
|
||||
description: new fields.HTMLField({ required: true, nullable: true })
|
||||
};
|
||||
if (this.metadata.isNPC) schema.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 ActionField from '../fields/actionField.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
const attributeField = () =>
|
||||
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 })
|
||||
});
|
||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
|
||||
export default class DhCharacter extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.character',
|
||||
type: 'character',
|
||||
isNPC: false,
|
||||
isNPC: false
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -37,36 +20,43 @@ export default class DhCharacter extends BaseDataActor {
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: new fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
stress: resourceField(6),
|
||||
hope: resourceField(6),
|
||||
tokens: new fields.ObjectField(),
|
||||
dice: new fields.ObjectField()
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||
stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField(),
|
||||
strength: attributeField(),
|
||||
finesse: attributeField(),
|
||||
instinct: attributeField(),
|
||||
presence: attributeField(),
|
||||
knowledge: attributeField()
|
||||
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
|
||||
strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'),
|
||||
finesse: attributeField('DAGGERHEART.CONFIG.Traits.finesse.name'),
|
||||
instinct: attributeField('DAGGERHEART.CONFIG.Traits.instinct.name'),
|
||||
presence: attributeField('DAGGERHEART.CONFIG.Traits.presence.name'),
|
||||
knowledge: attributeField('DAGGERHEART.CONFIG.Traits.knowledge.name')
|
||||
}),
|
||||
proficiency: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 1, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
proficiency: new fields.NumberField({
|
||||
initial: 1,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.proficiency'
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||
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(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField(),
|
||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
||||
value: new fields.NumberField({ integer: true, initial: 0 })
|
||||
})
|
||||
),
|
||||
gold: new fields.SchemaField({
|
||||
|
|
@ -78,7 +68,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
scars: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.HTMLField()
|
||||
description: new fields.StringField()
|
||||
})
|
||||
),
|
||||
biography: new fields.SchemaField({
|
||||
|
|
@ -98,43 +88,174 @@ export default class DhCharacter extends BaseDataActor {
|
|||
value: 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),
|
||||
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({
|
||||
attack: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
spellcast: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
action: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
hopeOrFear: new fields.NumberField({ integer: true, initial: 0 })
|
||||
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
|
||||
spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'),
|
||||
trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'),
|
||||
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({
|
||||
all: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
physical: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
magic: new fields.NumberField({ integer: true, initial: 0 })
|
||||
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
|
||||
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
|
||||
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 }),
|
||||
rules: new fields.SchemaField({
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }),
|
||||
stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 })
|
||||
damageReduction: new fields.SchemaField({
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({
|
||||
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({
|
||||
severe: stressDamageReductionRule(),
|
||||
major: stressDamageReductionRule(),
|
||||
minor: stressDamageReductionRule()
|
||||
attack: new fields.SchemaField({
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
initial: '@profd4',
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.value.label'
|
||||
})
|
||||
})
|
||||
}),
|
||||
strangePatterns: new fields.NumberField({
|
||||
integer: true,
|
||||
min: 1,
|
||||
max: 12,
|
||||
nullable: true,
|
||||
initial: null
|
||||
weapon: new fields.SchemaField({
|
||||
/* Unimplemented
|
||||
-> Should remove the lowest damage dice from weapon damage
|
||||
-> Reflect this in the chat message somehow so players get feedback that their choice is helping them.
|
||||
*/
|
||||
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 })
|
||||
})
|
||||
|
|
@ -169,6 +290,11 @@ export default class DhCharacter extends BaseDataActor {
|
|||
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() {
|
||||
return {
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
const ancestryFeatures = [],
|
||||
communityFeatures = [],
|
||||
|
|
@ -207,23 +351,23 @@ export default class DhCharacter extends BaseDataActor {
|
|||
features = [];
|
||||
|
||||
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);
|
||||
} 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);
|
||||
} 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);
|
||||
} 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 identifier = item.system.identifier;
|
||||
const subType = item.system.subType;
|
||||
if (
|
||||
identifier === 'foundationFeature' ||
|
||||
(identifier === 'specializationFeature' && subclassState >= 2) ||
|
||||
(identifier === 'masterFeature' && subclassState >= 3)
|
||||
subType === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
||||
(subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
|
||||
(subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
|
||||
) {
|
||||
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);
|
||||
} else if (item.type === 'feature' && !item.system.type) {
|
||||
features.push(item);
|
||||
|
|
@ -278,9 +422,14 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
get deathMoveViable() {
|
||||
return (
|
||||
this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal
|
||||
);
|
||||
return this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
|
||||
}
|
||||
|
||||
get armorApplicableDamageTypes() {
|
||||
return {
|
||||
physical: !this.rules.damageReduction.magical,
|
||||
magical: !this.rules.damageReduction.physical
|
||||
};
|
||||
}
|
||||
|
||||
static async unequipBeforeEquip(itemToEquip) {
|
||||
|
|
@ -306,6 +455,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
prepareBaseData() {
|
||||
this.evasion = this.class.value?.system?.evasion ?? 0;
|
||||
|
||||
const currentLevel = this.levelData.level.current;
|
||||
const currentTier =
|
||||
currentLevel === 1
|
||||
|
|
@ -316,32 +467,32 @@ export default class DhCharacter extends BaseDataActor {
|
|||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
|
||||
this.proficiency.bonus += level.achievements.proficiency;
|
||||
this.proficiency += level.achievements.proficiency;
|
||||
|
||||
for (let selection of level.selections) {
|
||||
switch (selection.type) {
|
||||
case 'trait':
|
||||
selection.data.forEach(data => {
|
||||
this.traits[data].bonus += 1;
|
||||
this.traits[data].value += 1;
|
||||
this.traits[data].tierMarked = selection.tier === currentTier;
|
||||
});
|
||||
break;
|
||||
case 'hitPoint':
|
||||
this.resources.hitPoints.bonus += selection.value;
|
||||
this.resources.hitPoints.max += selection.value;
|
||||
break;
|
||||
case 'stress':
|
||||
this.resources.stress.bonus += selection.value;
|
||||
this.resources.stress.max += selection.value;
|
||||
break;
|
||||
case 'evasion':
|
||||
this.evasion.bonus += selection.value;
|
||||
this.evasion += selection.value;
|
||||
break;
|
||||
case 'proficiency':
|
||||
this.proficiency.bonus = selection.value;
|
||||
this.proficiency = selection.value;
|
||||
break;
|
||||
case 'experience':
|
||||
Object.keys(this.experiences).forEach(key => {
|
||||
const experience = this.experiences[key];
|
||||
experience.bonus += selection.value;
|
||||
experience.value += selection.value;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
@ -349,6 +500,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
const armor = this.armor;
|
||||
this.armorScore = armor ? armor.system.baseScore : 0;
|
||||
this.damageThresholds = {
|
||||
major: armor
|
||||
? 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
|
||||
: 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() {
|
||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||
this.resources.hope.value = Math.min(this.resources.hope.value, 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;
|
||||
const baseHope = this.resources.hope.value + (this.companion?.system?.resources?.hope ?? 0);
|
||||
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||
}
|
||||
|
||||
getRollData() {
|
||||
const data = super.getRollData();
|
||||
return {
|
||||
...data,
|
||||
...this.resources.tokens,
|
||||
...this.resources.dice,
|
||||
...this.bonuses,
|
||||
tier: this.tier,
|
||||
level: this.levelData.level.current
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
|||
import ActionField from '../fields/actionField.mjs';
|
||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||
|
||||
export default class DhCompanion extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
|
||||
|
|
@ -12,6 +13,7 @@ export default class DhCompanion extends BaseDataActor {
|
|||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.companion',
|
||||
type: 'companion',
|
||||
isNPC: false,
|
||||
settingSheet: DHCompanionSettings
|
||||
});
|
||||
}
|
||||
|
|
@ -20,24 +22,23 @@ export default class DhCompanion extends BaseDataActor {
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
|
||||
resources: new fields.SchemaField({
|
||||
stress: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 3, integer: true })
|
||||
}),
|
||||
hope: new fields.NumberField({ initial: 0, integer: true })
|
||||
stress: resourceField(3, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
evasion: new fields.NumberField({
|
||||
required: true,
|
||||
min: 1,
|
||||
initial: 10,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.evasion'
|
||||
}),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
||||
value: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
{
|
||||
initial: {
|
||||
|
|
@ -59,17 +60,16 @@ export default class DhCompanion extends BaseDataActor {
|
|||
amount: 1
|
||||
},
|
||||
roll: {
|
||||
type: 'weapon',
|
||||
bonus: 0,
|
||||
trait: 'instinct'
|
||||
type: 'attack',
|
||||
bonus: 0
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
multiplier: 'flat',
|
||||
type: ['physical'],
|
||||
value: {
|
||||
dice: 'd6',
|
||||
multiplier: 'flat'
|
||||
multiplier: 'prof'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -77,20 +77,22 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
}),
|
||||
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() {
|
||||
return {
|
||||
instinct: { total: this.attack.roll.bonus }
|
||||
};
|
||||
get proficiency() {
|
||||
return this.partner?.system?.proficiency ?? 1;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main;
|
||||
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;
|
||||
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
|
|
@ -107,15 +109,15 @@ export default class DhCompanion extends BaseDataActor {
|
|||
}
|
||||
break;
|
||||
case 'stress':
|
||||
this.resources.stress.bonus += selection.value;
|
||||
this.resources.stress.max += selection.value;
|
||||
break;
|
||||
case 'evasion':
|
||||
this.evasion.bonus += selection.value;
|
||||
this.evasion += selection.value;
|
||||
break;
|
||||
case 'experience':
|
||||
Object.keys(this.experiences).forEach(key => {
|
||||
const experience = this.experiences[key];
|
||||
experience.bonus += selection.value;
|
||||
experience.value += selection.value;
|
||||
});
|
||||
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() {
|
||||
if (this.partner) {
|
||||
await this.partner.update({ 'system.companion': null });
|
||||
|
|
|
|||
|
|
@ -9,20 +9,22 @@ export default class DhEnvironment extends BaseDataActor {
|
|||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.environment',
|
||||
type: 'environment',
|
||||
settingSheet: DHEnvironmentSettings
|
||||
settingSheet: DHEnvironmentSettings,
|
||||
hasResistances: false
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
tier: new fields.StringField({
|
||||
...super.defineSchema(),
|
||||
tier: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
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 }),
|
||||
description: new fields.StringField(),
|
||||
impulses: new fields.StringField(),
|
||||
difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }),
|
||||
potentialAdversaries: new fields.TypedObjectField(
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class DhCountdown extends foundry.abstract.DataModel {
|
|||
value: new fields.StringField({
|
||||
required: true,
|
||||
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: new fields.StringField({
|
||||
|
|
@ -132,7 +132,13 @@ class DhCountdown extends foundry.abstract.DataModel {
|
|||
export const registerCountdownHooks = () => {
|
||||
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
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 DHArmor from './armor.mjs';
|
||||
import DHAttachableItem from './attachableItem.mjs';
|
||||
import DHClass from './class.mjs';
|
||||
import DHCommunity from './community.mjs';
|
||||
import DHConsumable from './consumable.mjs';
|
||||
|
|
@ -13,6 +14,7 @@ import DHBeastform from './beastform.mjs';
|
|||
export {
|
||||
DHAncestry,
|
||||
DHArmor,
|
||||
DHAttachableItem,
|
||||
DHClass,
|
||||
DHCommunity,
|
||||
DHConsumable,
|
||||
|
|
@ -27,6 +29,7 @@ export {
|
|||
export const config = {
|
||||
ancestry: DHAncestry,
|
||||
armor: DHArmor,
|
||||
attachableItem: DHAttachableItem,
|
||||
class: DHClass,
|
||||
community: DHCommunity,
|
||||
consumable: DHConsumable,
|
||||
|
|
|
|||
|
|
@ -18,4 +18,20 @@ export default class DHAncestry extends BaseDataItem {
|
|||
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 { armorFeatures } from '../../config/itemConfig.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
|
||||
export default class DHArmor extends BaseDataItem {
|
||||
export default class DHArmor extends AttachableItem {
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Item.armor',
|
||||
type: 'armor',
|
||||
hasDescription: true,
|
||||
isQuantifiable: 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 }),
|
||||
equipped: new fields.BooleanField({ initial: false }),
|
||||
baseScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
features: new fields.ArrayField(
|
||||
armorFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
|
|
@ -44,25 +44,28 @@ export default class DHArmor extends BaseDataItem {
|
|||
};
|
||||
}
|
||||
|
||||
get featureInfo() {
|
||||
return this.feature ? CONFIG.DH.ITEM.armorFeatures[this.feature] : null;
|
||||
get customActions() {
|
||||
return this.actions.filter(
|
||||
action => !this.armorFeatures.some(feature => feature.actionIds.includes(action.id))
|
||||
);
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (changes.system.features) {
|
||||
const removed = this.features.filter(x => !changes.system.features.includes(x));
|
||||
const added = changes.system.features.filter(x => !this.features.includes(x));
|
||||
if (changes.system.armorFeatures) {
|
||||
const removed = this.armorFeatures.filter(x => !changes.system.armorFeatures.includes(x));
|
||||
const added = changes.system.armorFeatures.filter(x => !this.armorFeatures.includes(x));
|
||||
|
||||
const effectIds = [];
|
||||
const actionIds = [];
|
||||
for (var feature of removed) {
|
||||
for (var effectId of feature.effectIds) {
|
||||
await this.parent.effects.get(effectId).delete();
|
||||
}
|
||||
|
||||
changes.system.actions = this.actions.filter(x => !feature.actionIds.includes(x._id));
|
||||
effectIds.push(...feature.effectIds);
|
||||
actionIds.push(...feature.actionIds);
|
||||
}
|
||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
||||
changes.system.actions = this.actions.filter(x => !actionIds.includes(x._id));
|
||||
|
||||
for (var feature of added) {
|
||||
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;
|
||||
|
||||
export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ITEMS'];
|
||||
|
||||
/** @returns {ItemDataModelMetadata}*/
|
||||
static get metadata() {
|
||||
return {
|
||||
label: 'Base Item',
|
||||
type: 'base',
|
||||
hasDescription: false,
|
||||
hasResource: false,
|
||||
isQuantifiable: 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.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)
|
||||
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;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _preCreate(data, options, user) {
|
||||
// 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 actionType = { weapon: 'attack' }[metadataType];
|
||||
const ActionClass = game.system.api.models.actions.actionsTypes[actionType];
|
||||
const action = new ActionClass(
|
||||
{
|
||||
_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(
|
||||
{
|
||||
_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] });
|
||||
this.updateSource({ actions: [action] });
|
||||
}
|
||||
}
|
||||
|
||||
_onCreate(data) {
|
||||
|
|
@ -95,7 +127,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
...feature,
|
||||
system: {
|
||||
...feature.system,
|
||||
type: this.parent.type,
|
||||
originItemType: this.parent.type,
|
||||
originId: data._id,
|
||||
identifier: feature.identifier
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayFie
|
|||
import BaseDataItem from './base.mjs';
|
||||
|
||||
export default class DHBeastform extends BaseDataItem {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Beastform'];
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ITEMS.Beastform'];
|
||||
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
|
|
@ -19,23 +19,65 @@ export default class DHBeastform extends BaseDataItem {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
tier: new fields.StringField({
|
||||
beastformType: new fields.StringField({
|
||||
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,
|
||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
||||
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||
}),
|
||||
tokenImg: new fields.FilePathField({
|
||||
initial: 'icons/svg/mystery-man.svg',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
tokenRingImg: new fields.FilePathField({
|
||||
initial: 'icons/svg/mystery-man.svg',
|
||||
categories: ['IMAGE'],
|
||||
base64: false
|
||||
}),
|
||||
tokenSize: new fields.SchemaField({
|
||||
height: 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(),
|
||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
|
||||
advantageOn: new fields.TypedObjectField(
|
||||
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',
|
||||
this.features.map(x => x.toObject())
|
||||
);
|
||||
const effects = await this.parent.parent.createEmbeddedDocuments(
|
||||
|
||||
const extraEffects = await this.parent.parent.createEmbeddedDocuments(
|
||||
'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', [
|
||||
{
|
||||
type: 'beastform',
|
||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp',
|
||||
system: {
|
||||
isBeastform: true,
|
||||
characterTokenData: {
|
||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||
tokenSize: {
|
||||
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)
|
||||
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
||||
await beastformEffect.updateSource({
|
||||
changes: [
|
||||
...beastformEffect.changes,
|
||||
{
|
||||
key: 'system.advantageSources',
|
||||
mode: 2,
|
||||
value: Object.values(this.advantageOn)
|
||||
.map(x => x.value)
|
||||
.join(', ')
|
||||
}
|
||||
],
|
||||
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, {
|
||||
height: this.tokenSize.height,
|
||||
width: this.tokenSize.width,
|
||||
texture: {
|
||||
src: this.tokenImg
|
||||
},
|
||||
ring: {
|
||||
subject: {
|
||||
texture: this.tokenRingImg
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
min: 1,
|
||||
initial: 5,
|
||||
label: 'DAGGERHEART.GENERAL.hitPoints'
|
||||
label: 'DAGGERHEART.GENERAL.HitPoints.plural'
|
||||
}),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||
hopeFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
classFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
inventory: new fields.SchemaField({
|
||||
take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
|
|
@ -52,12 +51,18 @@ export default class DHClass extends BaseDataItem {
|
|||
};
|
||||
}
|
||||
|
||||
get hopeFeature() {
|
||||
return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null;
|
||||
get hopeFeatures() {
|
||||
return (
|
||||
this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.hope) ??
|
||||
(this.features.filter(x => !x).length > 0 ? {} : null)
|
||||
);
|
||||
}
|
||||
|
||||
get features() {
|
||||
return [...this.hopeFeatures.filter(x => x), ...this.classFeatures.filter(x => x)];
|
||||
get classFeatures() {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Item.domainCard',
|
||||
type: 'domainCard',
|
||||
hasDescription: true
|
||||
hasDescription: true,
|
||||
hasResource: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +29,6 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
required: true,
|
||||
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
||||
}),
|
||||
foundation: new fields.BooleanField({ initial: false }),
|
||||
inVault: new fields.BooleanField({ initial: false }),
|
||||
actions: new fields.ArrayField(new ActionField())
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ export default class DHFeature extends BaseDataItem {
|
|||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Item.feature',
|
||||
type: 'feature',
|
||||
hasDescription: true
|
||||
hasDescription: true,
|
||||
hasResource: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -16,10 +17,33 @@ export default class DHFeature extends BaseDataItem {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...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 }),
|
||||
identifier: new fields.StringField(),
|
||||
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';
|
||||
|
||||
export default class DHSubclass extends BaseDataItem {
|
||||
|
|
@ -22,20 +22,22 @@ export default class DHSubclass extends BaseDataItem {
|
|||
nullable: true,
|
||||
initial: null
|
||||
}),
|
||||
foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
||||
isMulticlass: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
|
||||
get features() {
|
||||
return [
|
||||
{ ...this.foundationFeature.toObject(), identifier: 'foundationFeature' },
|
||||
{ ...this.specializationFeature.toObject(), identifier: 'specializationFeature' },
|
||||
{ ...this.masteryFeature.toObject(), identifier: 'masteryFeature' }
|
||||
];
|
||||
get foundationFeatures() {
|
||||
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.foundation);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import AttachableItem from './attachableItem.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
|
||||
export default class DHWeapon extends BaseDataItem {
|
||||
export default class DHWeapon extends AttachableItem {
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Item.weapon',
|
||||
type: 'weapon',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
// hasInitialAction: true
|
||||
});
|
||||
}
|
||||
|
|
@ -26,8 +25,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
//SETTINGS
|
||||
secondary: new fields.BooleanField({ initial: false }),
|
||||
burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded' }),
|
||||
|
||||
features: new fields.ArrayField(
|
||||
weaponFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
|
|
@ -52,14 +50,15 @@ export default class DHWeapon extends BaseDataItem {
|
|||
},
|
||||
roll: {
|
||||
trait: 'agility',
|
||||
type: 'weapon'
|
||||
type: 'attack'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
type: ['physical'],
|
||||
value: {
|
||||
multiplier: 'prof',
|
||||
dice: "d8"
|
||||
dice: 'd8'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -74,22 +73,30 @@ export default class DHWeapon extends BaseDataItem {
|
|||
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) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (changes.system?.features) {
|
||||
const removed = this.features.filter(x => !changes.system.features.includes(x));
|
||||
const added = changes.system.features.filter(x => !this.features.includes(x));
|
||||
if (changes.system?.weaponFeatures) {
|
||||
const removed = this.weaponFeatures.filter(x => !changes.system.weaponFeatures.includes(x));
|
||||
const added = changes.system.weaponFeatures.filter(x => !this.weaponFeatures.includes(x));
|
||||
|
||||
const removedEffectsUpdate = [];
|
||||
const removedActionsUpdate = [];
|
||||
for (let weaponFeature of removed) {
|
||||
for (var effectId of weaponFeature.effectIds) {
|
||||
await this.parent.effects.get(effectId).delete();
|
||||
}
|
||||
|
||||
changes.system.actions = this.actions.filter(x => !weaponFeature.actionIds.includes(x._id));
|
||||
removedEffectsUpdate.push(...weaponFeature.effectIds);
|
||||
removedActionsUpdate.push(...weaponFeature.actionIds);
|
||||
}
|
||||
|
||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate);
|
||||
changes.system.actions = this.actions.filter(x => !removedActionsUpdate.includes(x._id));
|
||||
|
||||
for (let weaponFeature of added) {
|
||||
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
|
||||
if (featureData.effects?.length > 0) {
|
||||
|
|
@ -102,17 +109,37 @@ export default class DHWeapon extends BaseDataItem {
|
|||
]);
|
||||
weaponFeature.effectIds = embeddedItems.map(x => x.id);
|
||||
}
|
||||
|
||||
const newActions = [];
|
||||
if (featureData.actions?.length > 0) {
|
||||
const newActions = featureData.actions.map(action => {
|
||||
const cls = actionsTypes[action.type];
|
||||
return new cls(
|
||||
{ ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) },
|
||||
{ parent: this }
|
||||
for (let action of featureData.actions) {
|
||||
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
(action.effects ?? []).map(effect => ({
|
||||
...effect,
|
||||
transfer: false,
|
||||
name: game.i18n.localize(effect.name),
|
||||
description: game.i18n.localize(effect.description)
|
||||
}))
|
||||
);
|
||||
});
|
||||
changes.system.actions = [...this.actions, ...newActions];
|
||||
weaponFeature.actionIds = newActions.map(x => x._id);
|
||||
const cls = actionsTypes[action.type];
|
||||
newActions.push(
|
||||
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 })),
|
||||
secondaryData: new fields.TypedObjectField(new fields.StringField({ 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() {
|
||||
return this.level.current < this.level.changed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ export const CompanionLevelOptionType = {
|
|||
{
|
||||
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.name',
|
||||
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',
|
||||
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',
|
||||
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' }),
|
||||
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 {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Automation']; // Doesn't work for some reason
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
hope: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hope.label'
|
||||
}), // Label need to be updated into something like "Duality Roll Auto Gain" + a hint
|
||||
hopeFear: new fields.SchemaField({
|
||||
gm: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
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({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label'
|
||||
}),
|
||||
countdowns: new fields.BooleanField({
|
||||
requireD: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdowns.label'
|
||||
hordeDamage: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
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