mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
159 - Companion (#224)
* Initial datamodel * Fixed attack * Temp * Fixed normal levelup * Fixed showing summary of new experiences * Touchups * level sync fixes * Reworked Action storage * Companions now take stress when damaged * Fixed Feature flow * Removed retroactive companion levelup * Restored delevel on partner removal * PR fixes * Added a check for card duplicates on character
This commit is contained in:
parent
6f1529fefe
commit
b7e4169079
57 changed files with 1682 additions and 1012 deletions
|
|
@ -1,19 +1,24 @@
|
|||
# Daggerheart
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [User Install Guide](#user-install)
|
||||
- [Developer Setup](#developer-setup)
|
||||
- [Contribution Info](#contributing)
|
||||
|
||||
## Overview
|
||||
|
||||
This is a community repo for a Foundry VTT implementation of Daggerheart. It is not associated with Critical Role or Darrington Press.
|
||||
|
||||
## User Install
|
||||
|
||||
1. **(Not Yet Supported - No Releases Yet)** Pasting `https://raw.githubusercontent.com/Foundryborne/daggerheart/refs/heads/main/system.json` into the Install System dialog on the Setup menu of the application.
|
||||
2. **(Not Yet Supported - No Releases Yet)** Browsing the repository's Releases page, where you can copy any system.json link for use in the Install System dialog.
|
||||
3. **(Not Yet Supported - No Releases Yet)** Downloading one of the .zip archives from the Releases page and extracting it into your foundry Data folder, under Data/systems/daggerheart.
|
||||
|
||||
## Development Setup
|
||||
|
||||
- Open a terminal in the directory with the repo `cd <path>/<to>/<repo>`
|
||||
- NOTE: The repo should be placed in the system files are or somewhere else and a link (if on linux) is placed in the system directory
|
||||
- NOTE: Linux link can be made using `ln -snf <path to development folder> daggerheart` inside the system folder
|
||||
|
|
@ -33,8 +38,8 @@ This is a community repo for a Foundry VTT implementation of Daggerheart. It is
|
|||
Now you should be able to build the app using `npm start`
|
||||
[Foundry VTT Website][1]
|
||||
|
||||
[1]: https://foundryvtt.com/
|
||||
[1]: https://foundryvtt.com/
|
||||
|
||||
## Contributing
|
||||
## Contributing
|
||||
|
||||
Looking to contribute to the project? Look no further, check out our [contributing guide](contributing.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ Hooks.once('init', () => {
|
|||
|
||||
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
|
||||
Actors.registerSheet(SYSTEM.id, applications.DhCharacterSheet, { types: ['character'], makeDefault: true });
|
||||
Actors.registerSheet(SYSTEM.id, applications.DhCompanionSheet, { types: ['companion'], makeDefault: true });
|
||||
Actors.registerSheet(SYSTEM.id, applications.DhpAdversarySheet, { types: ['adversary'], makeDefault: true });
|
||||
Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true });
|
||||
|
||||
|
|
@ -285,6 +286,7 @@ const preloadHandlebarsTemplates = async function () {
|
|||
return foundry.applications.handlebars.loadTemplates([
|
||||
'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs',
|
||||
'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs',
|
||||
'systems/daggerheart/templates/sheets/global/partials/action-item.hbs',
|
||||
'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs',
|
||||
'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs',
|
||||
|
||||
|
|
@ -322,7 +324,7 @@ const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/views/actionTypes/range-target.hbs',
|
||||
'systems/daggerheart/templates/views/actionTypes/effect.hbs',
|
||||
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
|
||||
|
||||
|
||||
'systems/daggerheart/templates/chat/parts/target-chat.hbs'
|
||||
]);
|
||||
};
|
||||
|
|
|
|||
58
lang/en.json
58
lang/en.json
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
"Actor": {
|
||||
"character": "Character",
|
||||
"companion": "Companion",
|
||||
"adversary": "Adversary",
|
||||
"environment": "Environment"
|
||||
}
|
||||
|
|
@ -490,7 +491,29 @@
|
|||
"evasion": "Permanently gain a +1 bonus to your Evasion.",
|
||||
"subclass": "Take an upgraded subclass card. Then cross out the multiclass option for this tier.",
|
||||
"proficiency": "Increase your Proficiency by +1.",
|
||||
"multiclass": "Multiclass: Choose an additional class for your character, then cross out an unused “Take an upgraded subclass card” and the other multiclass option on this sheet."
|
||||
"multiclass": "Multiclass: Choose an additional class for your character, then cross out an unused “Take an upgraded subclass card” and the other multiclass option on this sheet.",
|
||||
"intelligent": "Your companion gains a permanent +1 bonus to a Companion Experience of your choice.",
|
||||
"lightInTheDark": "Gain an additional Hope slot for your character.",
|
||||
"creatureComfort": "Once per rest, when you take time during a quiet moment to give your companion love and attention, you can gain a Hope or you can both clear a Stress.",
|
||||
"armored": "When your companion takes damage, you can mark one of your Armor Slots instead of marking one of their Stress.",
|
||||
"vicious": "Increase your companion's damage dice or range by one step (d6 to d8, Close to Far, etc.)",
|
||||
"resilient": "Your companion gains an additional Stress slot.",
|
||||
"bonded": "When you mark your last Hit Point, your companion rushes to your side to comfort you. Roll a number of d6s equal to the unmarked Stress slots they have and mark them. If any roll a 6, your companion helps you up. Clear your last Hit Point and return to the scene.",
|
||||
"aware": "Your companion gains a permanent +2 bonus to their Evasion."
|
||||
},
|
||||
"Actions": {
|
||||
"CreatureComfort": {
|
||||
"Name": "Creature Comfort",
|
||||
"Description": "Once per rest, when you take time during a quiet moment to give your companion love and attention, you can gain a Hope or you can both clear a Stress."
|
||||
},
|
||||
"Armored": {
|
||||
"Name": "Armored",
|
||||
"Description": "When your companion takes damage, you can mark one of your Armor Slots instead of marking one of their Stress."
|
||||
},
|
||||
"Bonded": {
|
||||
"Name": "Bonded",
|
||||
"Description": "When you mark your last Hit Point, your companion rushes to your side to comfort you. Roll a number of d6s equal to the unmarked Stress slots they have and mark them. If any roll a 6, your companion helps you up. Clear your last Hit Point and return to the scene."
|
||||
}
|
||||
},
|
||||
"Tier2": {
|
||||
"Label": "Levels 2-4",
|
||||
|
|
@ -961,7 +984,9 @@
|
|||
"content": "Returning to the previous level selection will remove all selections made for this level. Do you want to proceed?"
|
||||
},
|
||||
"Selections": {
|
||||
"emptyDomainCardHint": "{domain} level {level} or below"
|
||||
"emptyDomainCardHint": "{domain} level {level} or below",
|
||||
"viciousDamage": "Damage",
|
||||
"viciousRange": "Range"
|
||||
},
|
||||
"summary": {
|
||||
"levelAchievements": "Level Achievements",
|
||||
|
|
@ -979,7 +1004,11 @@
|
|||
"multiclass": "Multiclass",
|
||||
"traits": "Increased Traits",
|
||||
"experienceIncreases": "Experience Increases",
|
||||
"damageThresholds": "Damage Thresholds"
|
||||
"damageThresholds": "Damage Thresholds",
|
||||
"vicious": "Vicious",
|
||||
"damageIncreased": "Damage Increased: {damage}",
|
||||
"rangeIncreased": "Range Increased: {range}",
|
||||
"simpleFeature": "Feature: {feature}"
|
||||
},
|
||||
"notifications": {
|
||||
"info": {
|
||||
|
|
@ -1132,6 +1161,8 @@
|
|||
"CharacterSetup": "Character setup isn't done yet",
|
||||
"Level": "Level",
|
||||
"LevelUp": "You can level up",
|
||||
"Features": "Features",
|
||||
"CompanionFeatures": "Companion Features",
|
||||
"Tabs": {
|
||||
"Features": "Features",
|
||||
"Inventory": "Inventory",
|
||||
|
|
@ -1176,9 +1207,6 @@
|
|||
"Experience": {
|
||||
"Title": "Experience"
|
||||
},
|
||||
"Features": {
|
||||
"Title": "Class Features"
|
||||
},
|
||||
"Gold": {
|
||||
"Title": "Gold",
|
||||
"Coins": "Coins",
|
||||
|
|
@ -1245,6 +1273,24 @@
|
|||
"tooLowLevel": "You cannot lower the character level below starting level"
|
||||
}
|
||||
},
|
||||
"Companion": {
|
||||
"FIELDS": {
|
||||
"partner": { "label": "Partner" },
|
||||
"evasion": {
|
||||
"value": { "label": "Evasion" }
|
||||
},
|
||||
"resources": {
|
||||
"stress": {
|
||||
"value": { "label": "Stress" }
|
||||
}
|
||||
},
|
||||
"attack": {
|
||||
"name": { "label": "Attack Name" }
|
||||
}
|
||||
},
|
||||
"Experiences": "Experiences",
|
||||
"Level": "Level"
|
||||
},
|
||||
"Adversary": {
|
||||
"FIELDS": {
|
||||
"tier": { "label": "Tier" },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export { default as DhCharacterSheet } from './sheets/character.mjs';
|
||||
export { default as DhCompanionSheet } from './sheets/companion.mjs';
|
||||
export { default as DhpAdversarySheet } from './sheets/adversary.mjs';
|
||||
export { default as DhpClassSheet } from './sheets/items/class.mjs';
|
||||
export { default as DhpSubclass } from './sheets/items/subclass.mjs';
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export default class AncestrySelectionDialog extends HandlebarsApplicationMixin(
|
|||
}
|
||||
|
||||
static _onEditImage() {
|
||||
const fp = new FilePicker({
|
||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||
current: this.data.ancestryInfo.img,
|
||||
type: 'image',
|
||||
redirectToRoot: ['icons/svg/mystery-man.svg'],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default class DhContextMenu extends ContextMenu {
|
||||
export default class DhContextMenu extends foundry.applications.ux.ContextMenu.implementation {
|
||||
constructor(container, selector, menuItems, options) {
|
||||
super(container, selector, menuItems, options);
|
||||
|
||||
|
|
@ -26,10 +26,16 @@ export default class DhContextMenu extends ContextMenu {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const { clientX, clientY } = event;
|
||||
const selector = "[data-item-id]";
|
||||
const selector = '[data-item-id]';
|
||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||
target?.dispatchEvent(new PointerEvent("contextmenu", {
|
||||
view: window, bubbles: true, cancelable: true, clientX, clientY
|
||||
}));
|
||||
target?.dispatchEvent(
|
||||
new PointerEvent('contextmenu', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX,
|
||||
clientY
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
static onEditImage(_, target) {
|
||||
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
const current = setting.countdowns[target.dataset.countdown].img;
|
||||
const fp = new FilePicker({
|
||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||
current,
|
||||
type: 'image',
|
||||
callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown),
|
||||
|
|
|
|||
383
module/applications/levelup/characterLevelup.mjs
Normal file
383
module/applications/levelup/characterLevelup.mjs
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
import LevelUpBase from './levelup.mjs';
|
||||
import { DhLevelup } from '../../data/levelup.mjs';
|
||||
import { domains } from '../../config/domainConfig.mjs';
|
||||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
|
||||
export default class DhCharacterLevelUp extends LevelUpBase {
|
||||
constructor(actor) {
|
||||
super(actor);
|
||||
|
||||
this.levelTiers = this.addBonusChoices(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers));
|
||||
const playerLevelupData = actor.system.levelData;
|
||||
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData));
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
|
||||
const currentLevel = this.levelup.levels[this.levelup.currentLevel];
|
||||
switch (partId) {
|
||||
case 'selections':
|
||||
const advancementChoices = Object.keys(currentLevel.choices).reduce((acc, choiceKey) => {
|
||||
Object.keys(currentLevel.choices[choiceKey]).forEach(checkboxNr => {
|
||||
const checkbox = currentLevel.choices[choiceKey][checkboxNr];
|
||||
const data = {
|
||||
...checkbox,
|
||||
path: `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}`,
|
||||
level: this.levelup.currentLevel
|
||||
};
|
||||
|
||||
if (!acc[choiceKey]) acc[choiceKey] = [];
|
||||
acc[choiceKey].push(data);
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const traits = Object.values(advancementChoices.trait ?? {});
|
||||
const traitValues = traits.filter(trait => trait.data.length > 0).flatMap(trait => trait.data);
|
||||
context.traits = {
|
||||
values: traitValues,
|
||||
active: traits.length > 0,
|
||||
progress: {
|
||||
selected: traitValues.length,
|
||||
max: traits.reduce((acc, exp) => acc + exp.amount, 0)
|
||||
}
|
||||
};
|
||||
|
||||
const experienceIncreases = Object.values(advancementChoices.experience ?? {});
|
||||
const experienceIncreaseValues = experienceIncreases
|
||||
.filter(exp => exp.data.length > 0)
|
||||
.flatMap(exp =>
|
||||
exp.data.map(data => {
|
||||
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
|
||||
return this.actor.system.experiences[experience].name;
|
||||
})
|
||||
);
|
||||
context.experienceIncreases = {
|
||||
values: experienceIncreaseValues,
|
||||
active: experienceIncreases.length > 0,
|
||||
progress: {
|
||||
selected: experienceIncreaseValues.length,
|
||||
max: experienceIncreases.reduce((acc, exp) => acc + exp.amount, 0)
|
||||
}
|
||||
};
|
||||
|
||||
context.newExperiences = Object.keys(currentLevel.achievements.experiences).map(key => {
|
||||
const experience = currentLevel.achievements.experiences[key];
|
||||
return {
|
||||
...experience,
|
||||
level: this.levelup.currentLevel,
|
||||
key: key
|
||||
};
|
||||
});
|
||||
|
||||
const allDomainCards = {
|
||||
...advancementChoices.domainCard,
|
||||
...currentLevel.achievements.domainCards
|
||||
};
|
||||
const allDomainCardKeys = Object.keys(allDomainCards);
|
||||
|
||||
const classDomainsData = this.actor.system.class.value.system.domains.map(domain => ({
|
||||
domain,
|
||||
multiclass: false
|
||||
}));
|
||||
const multiclassDomainsData = (this.actor.system.multiclass?.value?.system?.domains ?? []).map(
|
||||
domain => ({ domain, multiclass: true })
|
||||
);
|
||||
const domainsData = [...classDomainsData, ...multiclassDomainsData];
|
||||
const multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain;
|
||||
if (multiclassDomain) {
|
||||
if (!domainsData.some(x => x.domain === multiclassDomain))
|
||||
domainsData.push({ domain: multiclassDomain, multiclass: true });
|
||||
}
|
||||
|
||||
context.domainCards = [];
|
||||
for (var key of allDomainCardKeys) {
|
||||
const domainCard = allDomainCards[key];
|
||||
if (domainCard.level > this.levelup.endLevel) continue;
|
||||
|
||||
const uuid = domainCard.data?.length > 0 ? domainCard.data[0] : domainCard.uuid;
|
||||
const card = uuid ? await foundry.utils.fromUuid(uuid) : {};
|
||||
|
||||
context.domainCards.push({
|
||||
...(card.toObject?.() ?? card),
|
||||
emptySubtexts: domainsData.map(domain => {
|
||||
const levelBase = domain.multiclass
|
||||
? Math.ceil(this.levelup.currentLevel / 2)
|
||||
: this.levelup.currentLevel;
|
||||
const levelMax = domainCard.secondaryData?.limit
|
||||
? Math.min(domainCard.secondaryData.limit, levelBase)
|
||||
: levelBase;
|
||||
|
||||
return game.i18n.format('DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', {
|
||||
domain: game.i18n.localize(domains[domain.domain].label),
|
||||
level: levelMax
|
||||
});
|
||||
}),
|
||||
path: domainCard.data
|
||||
? `${domainCard.path}.data`
|
||||
: `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`,
|
||||
limit: domainCard.secondaryData?.limit ?? null,
|
||||
compendium: 'domains'
|
||||
});
|
||||
}
|
||||
|
||||
const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? [];
|
||||
const possibleSubclasses = [this.actor.system.class.subclass];
|
||||
if (this.actor.system.multiclass?.subclass) {
|
||||
possibleSubclasses.push(this.actor.system.multiclass.subclass);
|
||||
}
|
||||
|
||||
context.subclassCards = [];
|
||||
if (advancementChoices.subclass?.length > 0) {
|
||||
const featureStateIncrease = Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||
acc += Object.values(level.choices).filter(choice => {
|
||||
return Object.values(choice).every(checkbox => checkbox.type === 'subclass');
|
||||
}).length;
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
for (var subclass of possibleSubclasses) {
|
||||
const choice =
|
||||
advancementChoices.subclass.find(x => x.data[0] === subclass.uuid) ??
|
||||
advancementChoices.subclass.find(x => x.data.length === 0);
|
||||
const featureState = subclass.system.featureState + featureStateIncrease;
|
||||
const data = await foundry.utils.fromUuid(subclass.uuid);
|
||||
context.subclassCards.push({
|
||||
...data.toObject(),
|
||||
path: choice?.path,
|
||||
uuid: data.uuid,
|
||||
selected: subclassSelections.includes(subclass.uuid),
|
||||
featureState: featureState,
|
||||
featureLabel: game.i18n.localize(subclassFeatureLabels[featureState]),
|
||||
isMulticlass: subclass.system.isMulticlass ? 'true' : 'false'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const multiclasses = Object.values(advancementChoices.multiclass ?? {});
|
||||
if (multiclasses?.[0]) {
|
||||
const data = multiclasses[0];
|
||||
const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {};
|
||||
|
||||
context.multiclass = {
|
||||
...data,
|
||||
...(multiclass.toObject?.() ?? multiclass),
|
||||
uuid: multiclass.uuid,
|
||||
domains:
|
||||
multiclass?.system?.domains.map(key => {
|
||||
const domain = domains[key];
|
||||
const alreadySelected = this.actor.system.class.value.system.domains.includes(key);
|
||||
|
||||
return {
|
||||
...domain,
|
||||
selected: key === data.secondaryData.domain,
|
||||
disabled:
|
||||
(data.secondaryData.domain && key !== data.secondaryData.domain) ||
|
||||
alreadySelected
|
||||
};
|
||||
}) ?? [],
|
||||
subclasses:
|
||||
multiclass?.system?.subclasses.map(subclass => ({
|
||||
...subclass,
|
||||
uuid: subclass.uuid,
|
||||
selected: data.secondaryData.subclass === subclass.uuid,
|
||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||
})) ?? [],
|
||||
compendium: 'classes',
|
||||
limit: 1
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case 'summary':
|
||||
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;
|
||||
const achievementCards = [];
|
||||
let achievementExperiences = [];
|
||||
for (var levelKey of levelKeys) {
|
||||
const level = this.levelup.levels[levelKey];
|
||||
if (Number(levelKey) < this.levelup.startLevel) continue;
|
||||
|
||||
achivementProficiency += level.achievements.proficiency ?? 0;
|
||||
const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null;
|
||||
if (cards) {
|
||||
for (var card of cards) {
|
||||
const itemCard = await foundry.utils.fromUuid(card.uuid);
|
||||
achievementCards.push(itemCard);
|
||||
}
|
||||
}
|
||||
|
||||
achievementExperiences = level.achievements.experiences
|
||||
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
|
||||
if (experience.name) acc.push(experience);
|
||||
return acc;
|
||||
}, [])
|
||||
: [];
|
||||
}
|
||||
|
||||
context.achievements = {
|
||||
proficiency: {
|
||||
old: this.actor.system.proficiency.total,
|
||||
new: this.actor.system.proficiency.total + achivementProficiency,
|
||||
shown: achivementProficiency > 0
|
||||
},
|
||||
damageThresholds: {
|
||||
major: {
|
||||
old: this.actor.system.damageThresholds.major,
|
||||
new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel
|
||||
},
|
||||
severe: {
|
||||
old: this.actor.system.damageThresholds.severe,
|
||||
new:
|
||||
this.actor.system.damageThresholds.severe +
|
||||
(actorArmor
|
||||
? changedActorLevel - currentActorLevel
|
||||
: (changedActorLevel - currentActorLevel) * 2)
|
||||
},
|
||||
unarmored: !actorArmor
|
||||
},
|
||||
domainCards: {
|
||||
values: achievementCards,
|
||||
shown: achievementCards.length > 0
|
||||
},
|
||||
experiences: {
|
||||
values: achievementExperiences,
|
||||
shown: achievementExperiences.length > 0
|
||||
}
|
||||
};
|
||||
|
||||
const advancement = {};
|
||||
for (var levelKey of levelKeys) {
|
||||
const level = this.levelup.levels[levelKey];
|
||||
if (Number(levelKey) < this.levelup.startLevel) continue;
|
||||
|
||||
for (var choiceKey of Object.keys(level.choices)) {
|
||||
const choice = level.choices[choiceKey];
|
||||
for (var checkbox of Object.values(choice)) {
|
||||
switch (choiceKey) {
|
||||
case 'proficiency':
|
||||
case 'hitPoint':
|
||||
case 'stress':
|
||||
case 'evasion':
|
||||
advancement[choiceKey] = advancement[choiceKey]
|
||||
? advancement[choiceKey] + Number(checkbox.value)
|
||||
: Number(checkbox.value);
|
||||
break;
|
||||
case 'trait':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = {};
|
||||
for (var traitKey of checkbox.data) {
|
||||
if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0;
|
||||
advancement[choiceKey][traitKey] += 1;
|
||||
}
|
||||
break;
|
||||
case 'domainCard':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
if (checkbox.data.length === 1) {
|
||||
const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
advancement[choiceKey].push(choiceItem.toObject());
|
||||
}
|
||||
break;
|
||||
case 'experience':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
const data = checkbox.data.map(data => {
|
||||
const experience = Object.keys(this.actor.system.experiences).find(
|
||||
x => x === data
|
||||
);
|
||||
return this.actor.system.experiences[experience]?.description ?? '';
|
||||
});
|
||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||
break;
|
||||
case 'subclass':
|
||||
if (checkbox.data[0]) {
|
||||
const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
advancement[choiceKey].push({
|
||||
...subclassItem.toObject(),
|
||||
featureLabel: game.i18n.localize(
|
||||
subclassFeatureLabels[Number(checkbox.secondaryData.featureState)]
|
||||
)
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'multiclass':
|
||||
const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
const subclass = multiclassItem
|
||||
? await foundry.utils.fromUuid(checkbox.secondaryData.subclass)
|
||||
: null;
|
||||
advancement[choiceKey] = multiclassItem
|
||||
? {
|
||||
...multiclassItem.toObject(),
|
||||
domain: checkbox.secondaryData.domain
|
||||
? game.i18n.localize(domains[checkbox.secondaryData.domain].label)
|
||||
: null,
|
||||
subclass: subclass ? subclass.name : null
|
||||
}
|
||||
: {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.advancements = {
|
||||
statistics: {
|
||||
proficiency: {
|
||||
old: context.achievements.proficiency.new,
|
||||
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)
|
||||
},
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
|
||||
if (advancement.trait?.[traitKey]) {
|
||||
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]
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, null),
|
||||
domainCards: advancement.domainCard ?? [],
|
||||
experiences:
|
||||
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
|
||||
[],
|
||||
multiclass: advancement.multiclass,
|
||||
subclass: advancement.subclass
|
||||
};
|
||||
|
||||
context.advancements.statistics.proficiency.shown =
|
||||
context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old;
|
||||
context.advancements.statistics.hitPoints.shown =
|
||||
context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old;
|
||||
context.advancements.statistics.stress.shown =
|
||||
context.advancements.statistics.stress.new > context.advancements.statistics.stress.old;
|
||||
context.advancements.statistics.evasion.shown =
|
||||
context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old;
|
||||
context.advancements.statistics.shown =
|
||||
context.advancements.statistics.proficiency.shown ||
|
||||
context.advancements.statistics.hitPoints.shown ||
|
||||
context.advancements.statistics.stress.shown ||
|
||||
context.advancements.statistics.evasion.shown;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
163
module/applications/levelup/companionLevelup.mjs
Normal file
163
module/applications/levelup/companionLevelup.mjs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import BaseLevelUp from './levelup.mjs';
|
||||
import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs';
|
||||
import { DhLevelup } from '../../data/levelup.mjs';
|
||||
import { diceTypes, range } from '../../config/generalConfig.mjs';
|
||||
|
||||
export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||
constructor(actor) {
|
||||
super(actor);
|
||||
|
||||
this.levelTiers = this.addBonusChoices(defaultCompanionTier);
|
||||
const playerLevelupData = actor.system.levelData;
|
||||
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData));
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context) {
|
||||
await super._preparePartContext(partId, context);
|
||||
|
||||
const currentLevel = this.levelup.levels[this.levelup.currentLevel];
|
||||
switch (partId) {
|
||||
case 'selections':
|
||||
const advancementChoices = Object.keys(currentLevel.choices).reduce((acc, choiceKey) => {
|
||||
Object.keys(currentLevel.choices[choiceKey]).forEach(checkboxNr => {
|
||||
const checkbox = currentLevel.choices[choiceKey][checkboxNr];
|
||||
const data = {
|
||||
...checkbox,
|
||||
path: `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}`,
|
||||
level: this.levelup.currentLevel
|
||||
};
|
||||
|
||||
if (!acc[choiceKey]) acc[choiceKey] = [];
|
||||
acc[choiceKey].push(data);
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const experienceIncreases = Object.values(advancementChoices.experience ?? {});
|
||||
const experienceIncreaseValues = experienceIncreases
|
||||
.filter(exp => exp.data.length > 0)
|
||||
.flatMap(exp =>
|
||||
exp.data.map(data => {
|
||||
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
|
||||
return this.actor.system.experiences[experience].name;
|
||||
})
|
||||
);
|
||||
context.experienceIncreases = {
|
||||
values: experienceIncreaseValues,
|
||||
active: experienceIncreases.length > 0,
|
||||
progress: {
|
||||
selected: experienceIncreaseValues.length,
|
||||
max: experienceIncreases.reduce((acc, exp) => acc + exp.amount, 0)
|
||||
}
|
||||
};
|
||||
|
||||
context.newExperiences = Object.keys(currentLevel.achievements.experiences).map(key => {
|
||||
const experience = currentLevel.achievements.experiences[key];
|
||||
return {
|
||||
...experience,
|
||||
level: this.levelup.currentLevel,
|
||||
key: key
|
||||
};
|
||||
});
|
||||
|
||||
context.vicious = advancementChoices.vicious ? Object.values(advancementChoices.vicious) : null;
|
||||
context.viciousChoices = {
|
||||
damage: game.i18n.localize('DAGGERHEART.Application.LevelUp.Selections.viciousDamage'),
|
||||
range: game.i18n.localize('DAGGERHEART.Application.LevelUp.Selections.viciousRange')
|
||||
};
|
||||
|
||||
break;
|
||||
case 'summary':
|
||||
const levelKeys = Object.keys(this.levelup.levels);
|
||||
const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice;
|
||||
const actorRange = this.actor.system.attack.range;
|
||||
const advancement = {};
|
||||
for (var levelKey of levelKeys) {
|
||||
const level = this.levelup.levels[levelKey];
|
||||
if (Number(levelKey) < this.levelup.startLevel) continue;
|
||||
|
||||
for (var choiceKey of Object.keys(level.choices)) {
|
||||
const choice = level.choices[choiceKey];
|
||||
for (var checkbox of Object.values(choice)) {
|
||||
switch (choiceKey) {
|
||||
case 'stress':
|
||||
case 'evasion':
|
||||
advancement[choiceKey] = advancement[choiceKey]
|
||||
? advancement[choiceKey] + Number(checkbox.value)
|
||||
: Number(checkbox.value);
|
||||
break;
|
||||
case 'experience':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||
const data = checkbox.data.map(data => {
|
||||
const experience = Object.keys(this.actor.system.experiences).find(
|
||||
x => x === data
|
||||
);
|
||||
return this.actor.system.experiences[experience]?.name ?? '';
|
||||
});
|
||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||
break;
|
||||
case 'vicious':
|
||||
if (!advancement[choiceKey]) advancement[choiceKey] = { damage: null, range: null };
|
||||
const isDamage = checkbox.data[0] === 'damage';
|
||||
const options = isDamage ? diceTypes : range;
|
||||
const keys = Object.keys(options);
|
||||
const actorKey = keys.indexOf(isDamage ? actorDamageDice : actorRange);
|
||||
const currentIndex = advancement[choiceKey][checkbox.data[0]]
|
||||
? keys.indexOf(advancement[choiceKey][checkbox.data[0]])
|
||||
: actorKey;
|
||||
advancement[choiceKey][checkbox.data[0]] =
|
||||
options[keys[Math.min(currentIndex + 1, keys.length - 1)]];
|
||||
default:
|
||||
if (!advancement.simple) advancement.simple = {};
|
||||
advancement.simple[choiceKey] = game.i18n.localize(
|
||||
LevelOptionType[checkbox.type].label
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.advancements = {
|
||||
statistics: {
|
||||
stress: {
|
||||
old: this.actor.system.resources.stress.maxTotal,
|
||||
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
|
||||
},
|
||||
evasion: {
|
||||
old: this.actor.system.evasion.total,
|
||||
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
|
||||
}
|
||||
},
|
||||
experiences:
|
||||
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
|
||||
[],
|
||||
vicious: {
|
||||
damage: advancement.vicious?.damage
|
||||
? {
|
||||
old: actorDamageDice,
|
||||
new: advancement.vicious.damage
|
||||
}
|
||||
: null,
|
||||
range: advancement.vicious?.range
|
||||
? {
|
||||
old: game.i18n.localize(`DAGGERHEART.Range.${actorRange}.name`),
|
||||
new: game.i18n.localize(advancement.vicious.range.label)
|
||||
}
|
||||
: null
|
||||
},
|
||||
simple: advancement.simple ?? {}
|
||||
};
|
||||
|
||||
context.advancements.statistics.stress.shown =
|
||||
context.advancements.statistics.stress.new > context.advancements.statistics.stress.old;
|
||||
context.advancements.statistics.evasion.shown =
|
||||
context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old;
|
||||
context.advancements.statistics.shown =
|
||||
context.advancements.statistics.stress.shown || context.advancements.statistics.evasion.shown;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { abilities, subclassFeatureLabels } from '../config/actorConfig.mjs';
|
||||
import { domains } from '../config/domainConfig.mjs';
|
||||
import { DhLevelup } from '../data/levelup.mjs';
|
||||
import { getDeleteKeys, tagifyElement } from '../helpers/utils.mjs';
|
||||
import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs';
|
||||
import { domains } from '../../config/domainConfig.mjs';
|
||||
import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -10,10 +9,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
super({});
|
||||
|
||||
this.actor = actor;
|
||||
this.levelTiers = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers);
|
||||
|
||||
const playerLevelupData = actor.system.levelData;
|
||||
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData, actor.system.level));
|
||||
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
this.tabGroups.primary = 'advancements';
|
||||
|
|
@ -81,6 +76,21 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
}
|
||||
};
|
||||
|
||||
addBonusChoices(levelTiers) {
|
||||
for (var tierKey in levelTiers.tiers) {
|
||||
const tier = levelTiers.tiers[tierKey];
|
||||
tier.maxSelections = [...Array(tier.levels.end - tier.levels.start + 1).keys()].reduce((acc, index) => {
|
||||
const level = tier.levels.start + index;
|
||||
const bonus = this.actor.system.levelData.level.bonuses[level];
|
||||
acc[level] = tier.availableOptions + (bonus ?? 0);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return levelTiers;
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.levelup = this.levelup;
|
||||
|
|
@ -118,181 +128,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
|
||||
context.showTabs = this.tabGroups.primary !== 'summary';
|
||||
break;
|
||||
case 'selections':
|
||||
const advancementChoices = Object.keys(currentLevel.choices).reduce((acc, choiceKey) => {
|
||||
Object.keys(currentLevel.choices[choiceKey]).forEach(checkboxNr => {
|
||||
const checkbox = currentLevel.choices[choiceKey][checkboxNr];
|
||||
const data = {
|
||||
...checkbox,
|
||||
path: `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}`,
|
||||
level: this.levelup.currentLevel
|
||||
};
|
||||
|
||||
if (!acc[choiceKey]) acc[choiceKey] = [];
|
||||
acc[choiceKey].push(data);
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const traits = Object.values(advancementChoices.trait ?? {});
|
||||
const traitValues = traits.filter(trait => trait.data.length > 0).flatMap(trait => trait.data);
|
||||
context.traits = {
|
||||
values: traitValues,
|
||||
active: traits.length > 0,
|
||||
progress: {
|
||||
selected: traitValues.length,
|
||||
max: traits.reduce((acc, exp) => acc + exp.amount, 0)
|
||||
}
|
||||
};
|
||||
|
||||
const experienceIncreases = Object.values(advancementChoices.experience ?? {});
|
||||
const experienceIncreaseValues = experienceIncreases
|
||||
.filter(exp => exp.data.length > 0)
|
||||
.flatMap(exp =>
|
||||
exp.data.map(data => {
|
||||
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
|
||||
return this.actor.system.experiences[experience].description;
|
||||
})
|
||||
);
|
||||
context.experienceIncreases = {
|
||||
values: experienceIncreaseValues,
|
||||
active: experienceIncreases.length > 0,
|
||||
progress: {
|
||||
selected: experienceIncreaseValues.length,
|
||||
max: experienceIncreases.reduce((acc, exp) => acc + exp.amount, 0)
|
||||
}
|
||||
};
|
||||
|
||||
context.newExperiences = Object.keys(currentLevel.achievements.experiences).map(key => {
|
||||
const experience = currentLevel.achievements.experiences[key];
|
||||
return {
|
||||
...experience,
|
||||
level: this.levelup.currentLevel,
|
||||
key: key
|
||||
};
|
||||
});
|
||||
|
||||
const allDomainCards = {
|
||||
...advancementChoices.domainCard,
|
||||
...currentLevel.achievements.domainCards
|
||||
};
|
||||
const allDomainCardKeys = Object.keys(allDomainCards);
|
||||
|
||||
const classDomainsData = this.actor.system.class.value.system.domains.map(domain => ({
|
||||
domain,
|
||||
multiclass: false
|
||||
}));
|
||||
const multiclassDomainsData = (this.actor.system.multiclass?.value?.system?.domains ?? []).map(
|
||||
domain => ({ domain, multiclass: true })
|
||||
);
|
||||
const domainsData = [...classDomainsData, ...multiclassDomainsData];
|
||||
const multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain;
|
||||
if (multiclassDomain) {
|
||||
if (!domainsData.some(x => x.domain === multiclassDomain))
|
||||
domainsData.push({ domain: multiclassDomain, multiclass: true });
|
||||
}
|
||||
|
||||
context.domainCards = [];
|
||||
for (var key of allDomainCardKeys) {
|
||||
const domainCard = allDomainCards[key];
|
||||
if (domainCard.level > this.levelup.endLevel) continue;
|
||||
|
||||
const uuid = domainCard.data?.length > 0 ? domainCard.data[0] : domainCard.uuid;
|
||||
const card = uuid ? await foundry.utils.fromUuid(uuid) : {};
|
||||
|
||||
context.domainCards.push({
|
||||
...(card.toObject?.() ?? card),
|
||||
emptySubtexts: domainsData.map(domain => {
|
||||
const levelBase = domain.multiclass
|
||||
? Math.ceil(this.levelup.currentLevel / 2)
|
||||
: this.levelup.currentLevel;
|
||||
const levelMax = domainCard.secondaryData?.limit
|
||||
? Math.min(domainCard.secondaryData.limit, levelBase)
|
||||
: levelBase;
|
||||
|
||||
return game.i18n.format('DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', {
|
||||
domain: game.i18n.localize(domains[domain.domain].label),
|
||||
level: levelMax
|
||||
});
|
||||
}),
|
||||
path: domainCard.data
|
||||
? `${domainCard.path}.data`
|
||||
: `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`,
|
||||
limit: domainCard.secondaryData?.limit ?? null,
|
||||
compendium: 'domains'
|
||||
});
|
||||
}
|
||||
|
||||
const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? [];
|
||||
const possibleSubclasses = [this.actor.system.class.subclass];
|
||||
if (this.actor.system.multiclass?.subclass) {
|
||||
possibleSubclasses.push(this.actor.system.multiclass.subclass);
|
||||
}
|
||||
|
||||
context.subclassCards = [];
|
||||
if (advancementChoices.subclass?.length > 0) {
|
||||
const featureStateIncrease = Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||
acc += Object.values(level.choices).filter(choice => {
|
||||
return Object.values(choice).every(checkbox => checkbox.type === 'subclass');
|
||||
}).length;
|
||||
return acc;
|
||||
}, 0);
|
||||
|
||||
for (var subclass of possibleSubclasses) {
|
||||
const choice =
|
||||
advancementChoices.subclass.find(x => x.data[0] === subclass.uuid) ??
|
||||
advancementChoices.subclass.find(x => x.data.length === 0);
|
||||
const featureState = subclass.system.featureState + featureStateIncrease;
|
||||
const data = await foundry.utils.fromUuid(subclass.uuid);
|
||||
context.subclassCards.push({
|
||||
...data.toObject(),
|
||||
path: choice?.path,
|
||||
uuid: data.uuid,
|
||||
selected: subclassSelections.includes(subclass.uuid),
|
||||
featureState: featureState,
|
||||
featureLabel: game.i18n.localize(subclassFeatureLabels[featureState]),
|
||||
isMulticlass: subclass.system.isMulticlass ? 'true' : 'false'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const multiclasses = Object.values(advancementChoices.multiclass ?? {});
|
||||
if (multiclasses?.[0]) {
|
||||
const data = multiclasses[0];
|
||||
const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {};
|
||||
|
||||
context.multiclass = {
|
||||
...data,
|
||||
...(multiclass.toObject?.() ?? multiclass),
|
||||
uuid: multiclass.uuid,
|
||||
domains:
|
||||
multiclass?.system?.domains.map(key => {
|
||||
const domain = domains[key];
|
||||
const alreadySelected = this.actor.system.class.value.system.domains.includes(key);
|
||||
|
||||
return {
|
||||
...domain,
|
||||
selected: key === data.secondaryData.domain,
|
||||
disabled:
|
||||
(data.secondaryData.domain && key !== data.secondaryData.domain) ||
|
||||
alreadySelected
|
||||
};
|
||||
}) ?? [],
|
||||
subclasses:
|
||||
multiclass?.system?.subclasses.map(subclass => ({
|
||||
...subclass,
|
||||
uuid: subclass.uuid,
|
||||
selected: data.secondaryData.subclass === subclass.uuid,
|
||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||
})) ?? [],
|
||||
compendium: 'classes',
|
||||
limit: 1
|
||||
};
|
||||
}
|
||||
|
||||
break;
|
||||
case 'summary':
|
||||
const { current: currentActorLevel, changed: changedActorLevel } = this.actor.system.levelData.level;
|
||||
const actorArmor = this.actor.system.armor;
|
||||
const levelKeys = Object.keys(this.levelup.levels);
|
||||
|
|
@ -516,7 +351,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
experienceIncreaseTagify,
|
||||
Object.keys(this.actor.system.experiences).reduce((acc, id) => {
|
||||
const experience = this.actor.system.experiences[id];
|
||||
acc[id] = { label: experience.description };
|
||||
acc[id] = { label: experience.name };
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
|
|
@ -594,20 +429,20 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
Object.values(this.levelup.levels).some(level => {
|
||||
const achievementExists = Object.values(level.achievements.domainCards).some(
|
||||
card => card.uuid === item.uuid
|
||||
);
|
||||
const advancementExists = Object.keys(level.choices).some(choiceKey => {
|
||||
if (choiceKey !== 'domainCard') return false;
|
||||
const choice = level.choices[choiceKey];
|
||||
return Object.values(choice).some(checkbox => checkbox.data.includes(item.uuid));
|
||||
});
|
||||
const cardExistsInCharacter = this.actor.items.find(x => x.name === item.name); // Any other way to check? The item is a copy so different ids
|
||||
const cardExistsInLevelup = Object.values(this.levelup.levels).some(level => {
|
||||
const achievementExists = Object.values(level.achievements.domainCards).some(
|
||||
card => card.uuid === item.uuid
|
||||
);
|
||||
const advancementExists = Object.keys(level.choices).some(choiceKey => {
|
||||
if (choiceKey !== 'domainCard') return false;
|
||||
const choice = level.choices[choiceKey];
|
||||
return Object.values(choice).some(checkbox => checkbox.data.includes(item.uuid));
|
||||
});
|
||||
|
||||
return achievementExists || advancementExists;
|
||||
})
|
||||
) {
|
||||
return achievementExists || advancementExists;
|
||||
});
|
||||
if (cardExistsInCharacter || cardExistsInLevelup) {
|
||||
ui.notifications.error(
|
||||
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardDuplicate')
|
||||
);
|
||||
|
|
@ -29,7 +29,7 @@ export class DHRoll extends Roll {
|
|||
for (const hook of config.hooks) {
|
||||
if (Hooks.call(`${SYSTEM.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||
}
|
||||
|
||||
|
||||
this.applyKeybindings(config);
|
||||
|
||||
let roll = new this(config.roll.formula, config.data, config);
|
||||
|
|
@ -39,7 +39,7 @@ export class DHRoll extends Roll {
|
|||
const configDialog = await DialogClass.configure(roll, config, message);
|
||||
if (!configDialog) return;
|
||||
}
|
||||
|
||||
|
||||
for (const hook of config.hooks) {
|
||||
if (Hooks.call(`${SYSTEM.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false)
|
||||
return [];
|
||||
|
|
@ -66,7 +66,7 @@ export class DHRoll extends Roll {
|
|||
}
|
||||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
if(!config.roll) config.roll = {};
|
||||
if (!config.roll) config.roll = {};
|
||||
config.roll.total = roll.total;
|
||||
config.roll.formula = roll.formula;
|
||||
config.roll.dice = [];
|
||||
|
|
@ -98,7 +98,7 @@ export class DHRoll extends Roll {
|
|||
|
||||
constructFormula(config) {
|
||||
// const formula = Roll.replaceFormulaData(this.options.roll.formula, config.data);
|
||||
this.terms = Roll.parse(this.options.roll.formula, config.data)
|
||||
this.terms = Roll.parse(this.options.roll.formula, config.data);
|
||||
return (this._formula = this.constructor.getFormula(this.terms));
|
||||
}
|
||||
}
|
||||
|
|
@ -195,7 +195,7 @@ export class D20Roll extends DHRoll {
|
|||
this.applyAdvantage();
|
||||
// this.options.roll.modifiers = [];
|
||||
this.applyBaseBonus();
|
||||
|
||||
|
||||
this.options.experiences?.forEach(m => {
|
||||
if (this.options.data.experiences?.[m])
|
||||
this.options.roll.modifiers.push({
|
||||
|
|
@ -225,10 +225,12 @@ export class D20Roll extends DHRoll {
|
|||
}
|
||||
|
||||
applyBaseBonus() {
|
||||
this.options.roll.modifiers = [{
|
||||
label : 'Bonus to Hit',
|
||||
value: Roll.replaceFormulaData('@attackBonus', this.data)
|
||||
}];
|
||||
this.options.roll.modifiers = [
|
||||
{
|
||||
label: 'Bonus to Hit',
|
||||
value: Roll.replaceFormulaData('@attackBonus', this.data)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
|
|
@ -238,7 +240,8 @@ export class D20Roll extends DHRoll {
|
|||
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
||||
target.hit = this.isCritical || roll.total >= difficulty;
|
||||
});
|
||||
} else if (config.roll.difficulty) config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||
} else if (config.roll.difficulty)
|
||||
config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||
config.roll.advantage = {
|
||||
type: config.advantage,
|
||||
dice: roll.dAdvantage?.denomination,
|
||||
|
|
@ -350,7 +353,7 @@ export class DualityRoll extends D20Roll {
|
|||
bardRallyFaces = this.hasBarRally,
|
||||
advDie = new foundry.dice.terms.Die({ faces: dieFaces });
|
||||
if (this.hasAdvantage || this.hasDisadvantage || bardRallyFaces)
|
||||
this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: (this.hasDisadvantage ? '-' : '+') }));
|
||||
this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }));
|
||||
if (bardRallyFaces) {
|
||||
const rallyDie = new foundry.dice.terms.Die({ faces: bardRallyFaces });
|
||||
if (this.hasAdvantage) {
|
||||
|
|
@ -367,10 +370,12 @@ export class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
applyBaseBonus() {
|
||||
this.options.roll.modifiers = [{
|
||||
label : `DAGGERHEART.Abilities.${this.options.roll.trait}.name`,
|
||||
value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.total`, this.data)
|
||||
}];
|
||||
this.options.roll.modifiers = [
|
||||
{
|
||||
label: `DAGGERHEART.Abilities.${this.options.roll.trait}.name`,
|
||||
value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.total`, this.data)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
|
|
@ -388,7 +393,7 @@ export class DualityRoll extends D20Roll {
|
|||
total: roll.dHope.total + roll.dFear.total,
|
||||
label: roll.totalLabel
|
||||
};
|
||||
console.log(roll, config)
|
||||
console.log(roll, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
|
||||
static onEditImage() {
|
||||
const fp = new FilePicker({
|
||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||
current: this.img,
|
||||
type: 'image',
|
||||
callback: async path => {
|
||||
|
|
|
|||
|
|
@ -46,19 +46,18 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
|
||||
switch (partId) {
|
||||
case 'description':
|
||||
const value = foundry.utils.getProperty(this.document, "system.description") ?? "";
|
||||
const value = foundry.utils.getProperty(this.document, 'system.description') ?? '';
|
||||
context.enrichedDescription = await TextEditor.enrichHTML(value, {
|
||||
relativeTo: this.item,
|
||||
rollData: this.item.getRollData(),
|
||||
secrets: this.item.isOwner
|
||||
})
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -70,11 +69,11 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
*/
|
||||
static async selectActionType() {
|
||||
const content = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/views/actionType.hbs',
|
||||
{ types: SYSTEM.ACTIONS.actionTypes }
|
||||
),
|
||||
title = 'Select Action Type'
|
||||
|
||||
'systems/daggerheart/templates/views/actionType.hbs',
|
||||
{ types: SYSTEM.ACTIONS.actionTypes }
|
||||
),
|
||||
title = 'Select Action Type';
|
||||
|
||||
return foundry.applications.api.DialogV2.prompt({
|
||||
window: { title },
|
||||
content,
|
||||
|
|
@ -92,7 +91,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
*/
|
||||
static async #addAction(_event, _button) {
|
||||
const actionType = await DHBaseItemSheet.selectActionType();
|
||||
if(!actionType) return;
|
||||
if (!actionType) return;
|
||||
try {
|
||||
const cls = actionsTypes[actionType] ?? actionsTypes.attack,
|
||||
action = new cls(
|
||||
|
|
@ -134,9 +133,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
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)
|
||||
)
|
||||
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import DhpDowntime from '../downtime.mjs';
|
|||
import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs';
|
||||
import DaggerheartSheet from './daggerheart-sheet.mjs';
|
||||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
import DhlevelUp from '../levelup.mjs';
|
||||
import DhCharacterlevelUp from '../levelup/characterLevelup.mjs';
|
||||
import DhCharacterCreation from '../characterCreation.mjs';
|
||||
|
||||
const { ActorSheetV2 } = foundry.applications.sheets;
|
||||
|
|
@ -303,8 +303,11 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
|
||||
getItem(element) {
|
||||
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
|
||||
item = this.document.items.get(itemId);
|
||||
const listElement = (element.target ?? element).closest('[data-item-id]');
|
||||
const document = listElement.dataset.companion ? this.document.system.companion : this.document;
|
||||
|
||||
const itemId = listElement.dataset.itemId,
|
||||
item = document.items.get(itemId);
|
||||
return item;
|
||||
}
|
||||
|
||||
|
|
@ -313,7 +316,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
|
||||
static _onEditImage() {
|
||||
const fp = new FilePicker({
|
||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||
current: this.document.img,
|
||||
type: 'image',
|
||||
redirectToRoot: ['icons/svg/mystery-man.svg'],
|
||||
|
|
@ -328,25 +331,8 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
const context = await super._prepareContext(_options);
|
||||
context.document = this.document;
|
||||
context.tabs = super._getTabs(this.constructor.TABS);
|
||||
|
||||
context.config = SYSTEM;
|
||||
|
||||
const selectedAttributes = Object.values(this.document.system.traits).map(x => x.base);
|
||||
context.abilityScoreArray = await game.settings
|
||||
.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew)
|
||||
.traitArray.reduce((acc, x) => {
|
||||
const selectedIndex = selectedAttributes.indexOf(x);
|
||||
if (selectedIndex !== -1) {
|
||||
selectedAttributes.splice(selectedIndex, 1);
|
||||
} else {
|
||||
acc.push({ name: x, value: x });
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
if (!context.abilityScoreArray.includes(0)) context.abilityScoreArray.push({ name: 0, value: 0 });
|
||||
context.abilityScoresFinished = context.abilityScoreArray.every(x => x.value === 0);
|
||||
|
||||
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
...this.document.system.traits[key],
|
||||
|
|
@ -357,67 +343,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const ancestry = await this.mapFeatureType(
|
||||
this.document.system.ancestry ? [this.document.system.ancestry] : [],
|
||||
SYSTEM.GENERAL.objectTypes
|
||||
);
|
||||
const community = await this.mapFeatureType(
|
||||
this.document.system.community ? [this.document.system.community] : [],
|
||||
SYSTEM.GENERAL.objectTypes
|
||||
);
|
||||
const foundation = {
|
||||
ancestry: ancestry[0],
|
||||
community: community[0],
|
||||
advancement: {}
|
||||
};
|
||||
|
||||
const nrLoadoutCards = this.document.system.domainCards.loadout.length;
|
||||
const loadout = await this.mapFeatureType(this.document.system.domainCards.loadout, SYSTEM.DOMAIN.cardTypes);
|
||||
const vault = await this.mapFeatureType(this.document.system.domainCards.vault, SYSTEM.DOMAIN.cardTypes);
|
||||
context.abilities = {
|
||||
foundation: foundation,
|
||||
loadout: {
|
||||
top: loadout.slice(0, Math.min(2, nrLoadoutCards)),
|
||||
bottom: nrLoadoutCards > 2 ? loadout.slice(2, Math.min(5, nrLoadoutCards)) : [],
|
||||
nrTotal: nrLoadoutCards,
|
||||
listView: game.user.getFlag(SYSTEM.id, SYSTEM.FLAGS.displayDomainCardsAsList)
|
||||
},
|
||||
vault: vault.map(x => ({
|
||||
...x,
|
||||
uuid: x.uuid,
|
||||
sendToLoadoutDisabled: this.document.system.domainCards.loadout.length >= 5
|
||||
}))
|
||||
};
|
||||
|
||||
context.inventory = {
|
||||
consumable: {
|
||||
titles: {
|
||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ConsumableTitle'),
|
||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
||||
},
|
||||
items: this.document.items.filter(x => x.type === 'consumable')
|
||||
},
|
||||
miscellaneous: {
|
||||
titles: {
|
||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.MiscellaneousTitle'),
|
||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
||||
},
|
||||
items: this.document.items.filter(x => x.type === 'miscellaneous')
|
||||
},
|
||||
weapons: {
|
||||
titles: {
|
||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.WeaponsTitle'),
|
||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
||||
},
|
||||
items: this.document.items.filter(x => x.type === 'weapon')
|
||||
},
|
||||
armor: {
|
||||
titles: {
|
||||
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ArmorsTitle'),
|
||||
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
|
||||
},
|
||||
items: this.document.items.filter(x => x.type === 'armor')
|
||||
},
|
||||
currency: {
|
||||
title: game.i18n.localize('DAGGERHEART.Sheets.PC.Gold.Title'),
|
||||
coins: game.i18n.localize('DAGGERHEART.Sheets.PC.Gold.Coins'),
|
||||
|
|
@ -536,28 +462,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async mapFeatureType(data, configType) {
|
||||
return await Promise.all(
|
||||
data.map(async x => {
|
||||
const abilities = x.system.abilities
|
||||
? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid)))
|
||||
: [];
|
||||
|
||||
return {
|
||||
...x,
|
||||
uuid: x.uuid,
|
||||
system: {
|
||||
...x.system,
|
||||
abilities: abilities,
|
||||
type: game.i18n.localize(configType[x.system.type ?? x.type].label)
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
static async rollAttribute(event, button) {
|
||||
const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
|
||||
const config = {
|
||||
|
|
@ -643,7 +547,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
return;
|
||||
}
|
||||
|
||||
new DhlevelUp(this.document).render(true);
|
||||
new DhCharacterlevelUp(this.document).render(true);
|
||||
}
|
||||
|
||||
static async useDomainCard(event, button) {
|
||||
|
|
@ -709,15 +613,22 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
static async useItem(event, button) {
|
||||
const item = this.getItem(button);
|
||||
if (!item) return;
|
||||
const wasUsed = await item.use(event);
|
||||
if (wasUsed && item.type === 'weapon') {
|
||||
Hooks.callAll(SYSTEM.HOOKS.characterAttack, {});
|
||||
|
||||
// Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board
|
||||
if (item.type === 'feature') {
|
||||
item.toChat();
|
||||
} else {
|
||||
const wasUsed = await item.use(event);
|
||||
if (wasUsed && item.type === 'weapon') {
|
||||
Hooks.callAll(SYSTEM.HOOKS.characterAttack, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async viewObject(event, button) {
|
||||
static async viewObject(event) {
|
||||
const item = this.getItem(event);
|
||||
if (!item) return;
|
||||
|
||||
item.sheet.render(true);
|
||||
}
|
||||
|
||||
|
|
@ -771,9 +682,10 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
this.render();
|
||||
}
|
||||
|
||||
static async deleteItem(event, button) {
|
||||
static async deleteItem(event) {
|
||||
const item = this.getItem(event);
|
||||
if (!item) return;
|
||||
|
||||
await item.delete();
|
||||
}
|
||||
|
||||
|
|
|
|||
86
module/applications/sheets/companion.mjs
Normal file
86
module/applications/sheets/companion.mjs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { GMUpdateEvent, socketEvent } from '../../helpers/socket.mjs';
|
||||
import DhCompanionlevelUp from '../levelup/companionLevelup.mjs';
|
||||
import DaggerheartSheet from './daggerheart-sheet.mjs';
|
||||
|
||||
const { ActorSheetV2 } = foundry.applications.sheets;
|
||||
export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'],
|
||||
position: { width: 700, height: 1000 },
|
||||
actions: {
|
||||
attackRoll: this.attackRoll,
|
||||
levelUp: this.levelUp
|
||||
},
|
||||
form: {
|
||||
handler: this.updateForm,
|
||||
submitOnChange: true,
|
||||
closeOnSubmit: false
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
sidebar: { template: 'systems/daggerheart/templates/sheets/actors/companion/tempMain.hbs' }
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.document = this.document;
|
||||
context.playerCharacters = game.actors
|
||||
.filter(
|
||||
x =>
|
||||
x.type === 'character' &&
|
||||
(x.ownership.default === 3 ||
|
||||
x.ownership[game.user.id] === 3 ||
|
||||
this.document.system.partner?.uuid === x.uuid)
|
||||
)
|
||||
.map(x => ({ key: x.uuid, name: x.name }));
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateForm(event, _, formData) {
|
||||
await this.document.update(formData.object);
|
||||
this.render();
|
||||
}
|
||||
|
||||
async onPartnerChange(event) {
|
||||
const partnerDocument = event.target.value
|
||||
? await foundry.utils.fromUuid(event.target.value)
|
||||
: this.document.system.partner;
|
||||
const partnerUpdate = { 'system.companion': event.target.value ? this.document.uuid : null };
|
||||
|
||||
if (!partnerDocument.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) {
|
||||
await game.socket.emit(`system.${SYSTEM.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateDocument,
|
||||
uuid: partnerDocument.uuid,
|
||||
update: update
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await partnerDocument.update(partnerUpdate);
|
||||
}
|
||||
|
||||
await this.document.update({ 'system.partner': event.target.value });
|
||||
|
||||
if (!event.target.value) {
|
||||
await this.document.updateLevel(1);
|
||||
}
|
||||
}
|
||||
|
||||
static async attackRoll(event) {
|
||||
this.actor.system.attack.use(event);
|
||||
}
|
||||
|
||||
static async levelUp() {
|
||||
new DhCompanionlevelUp(this.document).render(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ export default function DhpApplicationMixin(Base) {
|
|||
const attr = target.dataset.edit;
|
||||
const current = foundry.utils.getProperty(this.document, attr);
|
||||
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
|
||||
const fp = new FilePicker({
|
||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||
current,
|
||||
type: 'image',
|
||||
redirectToRoot: img ? [img] : [],
|
||||
|
|
|
|||
|
|
@ -6,11 +6,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
id: 'daggerheart-feature',
|
||||
classes: ['feature'],
|
||||
position: { height: 600 },
|
||||
window: { resizable: true },
|
||||
actions: {
|
||||
addEffect: this.addEffect,
|
||||
removeEffect: this.removeEffect
|
||||
}
|
||||
window: { resizable: true }
|
||||
};
|
||||
|
||||
/**@override */
|
||||
|
|
@ -22,27 +18,16 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
|
||||
scrollable: ['.actions']
|
||||
},
|
||||
settings: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/feature/settings.hbs',
|
||||
scrollable: ['.settings']
|
||||
},
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/feature/effects.hbs',
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Internally tracks the selected effect type from the select.
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
_selectedEffectType;
|
||||
|
||||
/**@override */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }, { id: 'effects' }],
|
||||
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }],
|
||||
initial: 'description',
|
||||
labelPrefix: 'DAGGERHEART.Sheets.TABS'
|
||||
}
|
||||
|
|
@ -50,68 +35,10 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**@inheritdoc*/
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
if (partId === 'effects')
|
||||
htmlElement.querySelector('.effect-select')?.addEventListener('change', this._effectSelect.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles selection of a new effect type.
|
||||
* @param {Event} event - Change Event
|
||||
*/
|
||||
_effectSelect(event) {
|
||||
const value = event.currentTarget.value;
|
||||
this._selectedEffectType = value;
|
||||
this.render({ parts: ['effects'] });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**@inheritdoc */
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.properties = CONFIG.daggerheart.ACTOR.featureProperties;
|
||||
context.dice = CONFIG.daggerheart.GENERAL.diceTypes;
|
||||
context.effectConfig = CONFIG.daggerheart.EFFECTS;
|
||||
|
||||
context.selectedEffectType = this._selectedEffectType;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Adds a new effect to the item, based on the selected effect type.
|
||||
* @param {PointerEvent} _event - The originating click event
|
||||
* @param {HTMLElement} _target - The capturing HTML element which defines the [data-action]
|
||||
* @returns
|
||||
*/
|
||||
static async addEffect(_event, _target) {
|
||||
const type = this._selectedEffectType;
|
||||
if (!type) return;
|
||||
const { id, name, ...rest } = CONFIG.daggerheart.EFFECTS.effectTypes[type];
|
||||
await this.item.update({
|
||||
[`system.effects.${foundry.utils.randomID()}`]: {
|
||||
type,
|
||||
value: '',
|
||||
...rest
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an effect from the item.
|
||||
* @param {PointerEvent} _event - The originating click event
|
||||
* @param {HTMLElement} target - The capturing HTML element which defines the [data-action]
|
||||
* @returns
|
||||
*/
|
||||
static async removeEffect(_event, target) {
|
||||
const path = `system.effects.-=${target.dataset.effect}`;
|
||||
await this.item.update({ [path]: null });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export const damageOnSave = {
|
|||
label: 'Full damage',
|
||||
mod: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const diceCompare = {
|
||||
below: {
|
||||
|
|
@ -104,4 +104,4 @@ export const diceCompare = {
|
|||
label: 'Above',
|
||||
operator: '>'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -261,48 +261,6 @@ export const tiers = {
|
|||
}
|
||||
};
|
||||
|
||||
export const objectTypes = {
|
||||
character: {
|
||||
name: 'TYPES.Actor.character'
|
||||
},
|
||||
npc: {
|
||||
name: 'TYPES.Actor.npc'
|
||||
},
|
||||
adversary: {
|
||||
name: 'TYPES.Actor.adversary'
|
||||
},
|
||||
ancestry: {
|
||||
name: 'TYPES.Item.ancestry'
|
||||
},
|
||||
community: {
|
||||
name: 'TYPES.Item.community'
|
||||
},
|
||||
class: {
|
||||
name: 'TYPES.Item.class'
|
||||
},
|
||||
subclass: {
|
||||
name: 'TYPES.Item.subclass'
|
||||
},
|
||||
feature: {
|
||||
name: 'TYPES.Item.feature'
|
||||
},
|
||||
domainCard: {
|
||||
name: 'TYPES.Item.domainCard'
|
||||
},
|
||||
consumable: {
|
||||
name: 'TYPES.Item.consumable'
|
||||
},
|
||||
miscellaneous: {
|
||||
name: 'TYPES.Item.miscellaneous'
|
||||
},
|
||||
weapon: {
|
||||
name: 'TYPES.Item.weapon'
|
||||
},
|
||||
armor: {
|
||||
name: 'TYPES.Item.armor'
|
||||
}
|
||||
};
|
||||
|
||||
export const diceTypes = {
|
||||
d4: 'd4',
|
||||
d6: 'd6',
|
||||
|
|
@ -325,7 +283,7 @@ export const diceSetNumbers = {
|
|||
cast: 'Spellcast',
|
||||
scale: 'Cost Scaling',
|
||||
flat: 'Flat'
|
||||
}
|
||||
};
|
||||
|
||||
export const getDiceSoNicePresets = () => {
|
||||
const { diceSoNice } = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,10 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
save: new fields.SchemaField({
|
||||
trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }),
|
||||
difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }),
|
||||
damageMod: new fields.StringField({ initial: SYSTEM.ACTIONS.damageOnSave.none.id, choices: SYSTEM.ACTIONS.damageOnSave })
|
||||
damageMod: new fields.StringField({
|
||||
initial: SYSTEM.ACTIONS.damageOnSave.none.id,
|
||||
choices: SYSTEM.ACTIONS.damageOnSave
|
||||
})
|
||||
}),
|
||||
target: new fields.SchemaField({
|
||||
type: new fields.StringField({
|
||||
|
|
@ -98,9 +101,12 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
initial: SYSTEM.GENERAL.healingTypes.hitPoints.id,
|
||||
label: 'Healing'
|
||||
}),
|
||||
resultBased: new fields.BooleanField({ initial: false, label: "DAGGERHEART.Actions.Settings.ResultBased.label" }),
|
||||
resultBased: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.Actions.Settings.ResultBased.label'
|
||||
}),
|
||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
||||
})
|
||||
},
|
||||
extraSchemas = {};
|
||||
|
|
@ -124,7 +130,11 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
get actor() {
|
||||
return this.item instanceof DhpActor ? this.item : this.item?.actor;
|
||||
return this.item instanceof DhpActor
|
||||
? this.item
|
||||
: this.item?.parent instanceof DhpActor
|
||||
? this.item.parent
|
||||
: this.item?.actor;
|
||||
}
|
||||
|
||||
get chatTemplate() {
|
||||
|
|
@ -153,7 +163,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
return updateSource;
|
||||
}
|
||||
|
||||
getRollData(data={}) {
|
||||
getRollData(data = {}) {
|
||||
const actorData = this.actor.getRollData(false);
|
||||
|
||||
// Remove when included directly in Actor getRollData
|
||||
|
|
@ -166,11 +176,11 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
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;
|
||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||
: 1;
|
||||
actorData.roll = {};
|
||||
|
||||
|
||||
return actorData;
|
||||
}
|
||||
|
||||
|
|
@ -191,12 +201,14 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
// Prepare Costs
|
||||
const costsConfig = this.prepareCost();
|
||||
if(isFastForward && !this.hasCost(costsConfig)) return ui.notifications.warn("You don't have the resources to use that action.");
|
||||
if (isFastForward && !this.hasCost(costsConfig))
|
||||
return ui.notifications.warn("You don't have the resources to use that action.");
|
||||
// config = this.prepareUseCost(config)
|
||||
|
||||
// Prepare Uses
|
||||
const usesConfig = this.prepareUse();
|
||||
if(isFastForward && !this.hasUses(usesConfig)) return ui.notifications.warn("That action doesn't have remaining uses.");
|
||||
if (isFastForward && !this.hasUses(usesConfig))
|
||||
return ui.notifications.warn("That action doesn't have remaining uses.");
|
||||
// config = this.prepareUseCost(config)
|
||||
|
||||
// Prepare Roll Data
|
||||
|
|
@ -209,24 +221,24 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
costs: costsConfig,
|
||||
uses: usesConfig,
|
||||
data: actorData
|
||||
}
|
||||
|
||||
if ( Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false ) return;
|
||||
};
|
||||
|
||||
if (Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false) return;
|
||||
|
||||
// Display configuration window if necessary
|
||||
if ( config.dialog?.configure && this.requireConfigurationDialog(config) ) {
|
||||
if (config.dialog?.configure && this.requireConfigurationDialog(config)) {
|
||||
config = await D20RollDialog.configure(config);
|
||||
if (!config) return;
|
||||
}
|
||||
|
||||
if ( this.hasRoll ) {
|
||||
if (this.hasRoll) {
|
||||
const rollConfig = this.prepareRoll(config);
|
||||
config.roll = rollConfig;
|
||||
config = await this.actor.diceRoll(config);
|
||||
if (!config) return;
|
||||
}
|
||||
|
||||
if( this.hasSave ) {
|
||||
if (this.hasSave) {
|
||||
/* config.targets.forEach((t) => {
|
||||
if(t.hit) {
|
||||
const target = game.canvas.tokens.get(t.id),
|
||||
|
|
@ -258,16 +270,16 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}) */
|
||||
}
|
||||
|
||||
if ( this.doFollowUp() ) {
|
||||
if(this.rollDamage) await this.rollDamage(event, config);
|
||||
if(this.rollHealing) await this.rollHealing(event, config);
|
||||
if(this.trigger) await this.trigger(event, config);
|
||||
if (this.doFollowUp()) {
|
||||
if (this.rollDamage) await this.rollDamage(event, config);
|
||||
if (this.rollHealing) await this.rollHealing(event, config);
|
||||
if (this.trigger) await this.trigger(event, config);
|
||||
}
|
||||
|
||||
// Consume resources
|
||||
await this.consume(config);
|
||||
|
||||
if ( Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false ) return;
|
||||
|
||||
if (Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false) return;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -287,7 +299,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
hasHealing: !!this.healing,
|
||||
hasEffect: !!this.effects?.length,
|
||||
hasSave: this.hasSave
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
requireConfigurationDialog(config) {
|
||||
|
|
@ -317,7 +329,6 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
targets = targets.map(t => this.formatTarget(t));
|
||||
return targets;
|
||||
|
||||
}
|
||||
|
||||
prepareRange() {
|
||||
|
|
@ -326,7 +337,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
prepareRoll() {
|
||||
const roll = {
|
||||
const roll = {
|
||||
modifiers: [],
|
||||
trait: this.roll?.trait,
|
||||
label: 'Attack',
|
||||
|
|
@ -334,8 +345,8 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
difficulty: this.roll?.difficulty,
|
||||
formula: this.roll.getFormula()
|
||||
};
|
||||
if(this.roll?.type === 'diceSet') roll.lite = true;
|
||||
|
||||
if (this.roll?.type === 'diceSet') roll.lite = true;
|
||||
|
||||
return roll;
|
||||
}
|
||||
|
||||
|
|
@ -344,12 +355,14 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
async consume(config) {
|
||||
const resources = config.costs.filter(c => c.enabled !== false).map(c => {
|
||||
return { type: c.type, value: (c.total ?? c.value) * -1 };
|
||||
});
|
||||
|
||||
const resources = config.costs
|
||||
.filter(c => c.enabled !== false)
|
||||
.map(c => {
|
||||
return { type: c.type, value: (c.total ?? c.value) * -1 };
|
||||
});
|
||||
|
||||
await this.actor.modifyResource(resources);
|
||||
if(config.uses?.enabled) {
|
||||
if (config.uses?.enabled) {
|
||||
const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject());
|
||||
newActions[this.index].uses.value++;
|
||||
await this.item.update({ [`system.${this.systemPath}`]: newActions });
|
||||
|
|
@ -388,13 +401,16 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
hasCost(costs) {
|
||||
const realCosts = this.getRealCosts(costs);
|
||||
return realCosts.reduce((a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), true);
|
||||
return realCosts.reduce(
|
||||
(a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value),
|
||||
true
|
||||
);
|
||||
}
|
||||
/* COST */
|
||||
|
||||
/* USES */
|
||||
calcUses(uses) {
|
||||
if(!uses) return null;
|
||||
if (!uses) return null;
|
||||
return {
|
||||
...uses,
|
||||
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
|
||||
|
|
@ -402,7 +418,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
hasUses(uses) {
|
||||
if(!uses) return true;
|
||||
if (!uses) return true;
|
||||
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
|
||||
}
|
||||
/* USES */
|
||||
|
|
@ -432,7 +448,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
/* TARGET */
|
||||
|
||||
/* RANGE */
|
||||
|
||||
|
||||
/* RANGE */
|
||||
|
||||
/* EFFECTS */
|
||||
|
|
@ -441,10 +457,10 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
let effects = this.effects;
|
||||
data.system.targets.forEach(async token => {
|
||||
if (!token.hit && !force) return;
|
||||
if(this.hasSave && token.saved.success === true) {
|
||||
effects = this.effects.filter(e => e.onSave === true)
|
||||
if (this.hasSave && token.saved.success === true) {
|
||||
effects = this.effects.filter(e => e.onSave === true);
|
||||
}
|
||||
if(!effects.length) return;
|
||||
if (!effects.length) return;
|
||||
effects.forEach(async e => {
|
||||
const actor = canvas.tokens.get(token.id)?.actor,
|
||||
effect = this.item.effects.get(e._id);
|
||||
|
|
@ -479,35 +495,42 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
/* SAVE */
|
||||
async rollSave(target, event, message) {
|
||||
if(!target?.actor) return;
|
||||
target.actor.diceRoll({
|
||||
event,
|
||||
title: 'Roll Save',
|
||||
roll: {
|
||||
trait: this.save.trait,
|
||||
difficulty: this.save.difficulty,
|
||||
type: "reaction"
|
||||
},
|
||||
data: target.actor.getRollData()
|
||||
}).then(async (result) => {
|
||||
if(result) this.updateChatMessage(message, target.id, {result: result.roll.total, success: result.roll.success});
|
||||
})
|
||||
if (!target?.actor) return;
|
||||
target.actor
|
||||
.diceRoll({
|
||||
event,
|
||||
title: 'Roll Save',
|
||||
roll: {
|
||||
trait: this.save.trait,
|
||||
difficulty: this.save.difficulty,
|
||||
type: 'reaction'
|
||||
},
|
||||
data: target.actor.getRollData()
|
||||
})
|
||||
.then(async result => {
|
||||
if (result)
|
||||
this.updateChatMessage(message, target.id, {
|
||||
result: result.roll.total,
|
||||
success: result.roll.success
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async updateChatMessage(message, targetId, changes, chain=true) {
|
||||
async updateChatMessage(message, targetId, changes, chain = true) {
|
||||
setTimeout(async () => {
|
||||
const chatMessage = ui.chat.collection.get(message._id),
|
||||
msgTargets = chatMessage.system.targets,
|
||||
msgTarget = msgTargets.find(mt => mt.id === targetId);
|
||||
msgTarget.saved = changes;
|
||||
await chatMessage.update({'system.targets': msgTargets});
|
||||
},100);
|
||||
if(chain) {
|
||||
if(message.system.source.message) this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
|
||||
await chatMessage.update({ 'system.targets': msgTargets });
|
||||
}, 100);
|
||||
if (chain) {
|
||||
if (message.system.source.message)
|
||||
this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
|
||||
const relatedChatMessages = ui.chat.collection.filter(c => c.system.source.message === message._id);
|
||||
relatedChatMessages.forEach(c => {
|
||||
this.updateChatMessage(c, targetId, changes, false);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
/* SAVE */
|
||||
|
|
@ -525,7 +548,7 @@ export 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;
|
||||
if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
||||
return formulaValue;
|
||||
}
|
||||
|
||||
|
|
@ -535,19 +558,19 @@ export class DHDamageAction extends DHBaseAction {
|
|||
if (!formula || formula == '') return;
|
||||
let roll = { formula: formula, total: formula },
|
||||
bonusDamage = [];
|
||||
|
||||
if(isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
|
||||
|
||||
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
|
||||
|
||||
const config = {
|
||||
title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: this.name }),
|
||||
roll: {formula},
|
||||
targets: (data.system?.targets.filter(t => t.hit) ?? data.targets),
|
||||
roll: { formula },
|
||||
targets: data.system?.targets.filter(t => t.hit) ?? data.targets,
|
||||
hasSave: this.hasSave,
|
||||
source: data.system?.source,
|
||||
event
|
||||
};
|
||||
if(this.hasSave) config.onSave = this.save.damageMod;
|
||||
if(data.system) {
|
||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||
if (data.system) {
|
||||
config.source.message = data._id;
|
||||
config.directDamage = false;
|
||||
}
|
||||
|
|
@ -597,7 +620,8 @@ export class DHHealingAction extends DHBaseAction {
|
|||
|
||||
getFormulaValue(data) {
|
||||
let formulaValue = this.healing.value;
|
||||
if(this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1) return this.healing.valueAlt;
|
||||
if (this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1)
|
||||
return this.healing.valueAlt;
|
||||
return formulaValue;
|
||||
}
|
||||
|
||||
|
|
@ -608,12 +632,12 @@ export class DHHealingAction extends DHBaseAction {
|
|||
if (!formula || formula == '') return;
|
||||
let roll = { formula: formula, total: formula },
|
||||
bonusDamage = [];
|
||||
|
||||
|
||||
const config = {
|
||||
title: game.i18n.format('DAGGERHEART.Chat.HealingRoll.Title', {
|
||||
healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label)
|
||||
}),
|
||||
roll: {formula},
|
||||
roll: { formula },
|
||||
targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
|
||||
messageType: 'healing',
|
||||
type: this.healing.type,
|
||||
|
|
|
|||
|
|
@ -25,17 +25,20 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
|||
initial: 'above',
|
||||
label: 'Should be'
|
||||
}),
|
||||
treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' }),
|
||||
treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' })
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
getFormula() {
|
||||
if(!this.type) return;
|
||||
if (!this.type) return;
|
||||
let formula = '';
|
||||
switch (this.type) {
|
||||
case 'diceSet':
|
||||
const multiplier = this.diceRolling.multiplier === 'flat' ? this.diceRolling.flatMultiplier : `@${this.diceRolling.multiplier}`;
|
||||
const multiplier =
|
||||
this.diceRolling.multiplier === 'flat'
|
||||
? this.diceRolling.flatMultiplier
|
||||
: `@${this.diceRolling.multiplier}`;
|
||||
formula = `${multiplier}${this.diceRolling.dice}cs${SYSTEM.ACTIONS.diceCompare[this.diceRolling.compare].operator}${this.diceRolling.treshold}`;
|
||||
break;
|
||||
default:
|
||||
|
|
@ -75,9 +78,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
|||
: `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; */
|
||||
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}`;
|
||||
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,9 +106,12 @@ export class DHDamageData extends foundry.abstract.DataModel {
|
|||
nullable: false,
|
||||
required: true
|
||||
}),
|
||||
resultBased: new fields.BooleanField({ initial: false, label: "DAGGERHEART.Actions.Settings.ResultBased.label" }),
|
||||
resultBased: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.Actions.Settings.ResultBased.label'
|
||||
}),
|
||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import DhCharacter from './character.mjs';
|
||||
import DhCompanion from './companion.mjs';
|
||||
import DhAdversary from './adversary.mjs';
|
||||
import DhEnvironment from './environment.mjs';
|
||||
|
||||
export { DhCharacter, DhAdversary, DhEnvironment };
|
||||
export { DhCharacter, DhCompanion, DhAdversary, DhEnvironment };
|
||||
|
||||
export const config = {
|
||||
character: DhCharacter,
|
||||
companion: DhCompanion,
|
||||
adversary: DhAdversary,
|
||||
environment: DhEnvironment
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { burden } from '../../config/generalConfig.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import { LevelOptionType } from '../levelTier.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
const attributeField = () =>
|
||||
|
|
@ -96,7 +97,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhPCLevelData),
|
||||
actions: new fields.ArrayField(new ActionField()),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
|
|
@ -115,6 +117,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
magic: new fields.NumberField({ integer: true, initial: 0 })
|
||||
})
|
||||
}),
|
||||
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 }),
|
||||
|
|
@ -154,10 +157,25 @@ export default class DhCharacter extends BaseDataActor {
|
|||
return this.parent.items.find(x => x.type === 'community') ?? null;
|
||||
}
|
||||
|
||||
get features() {
|
||||
return this.parent.items.filter(x => x.type === 'feature') ?? [];
|
||||
}
|
||||
|
||||
get companionFeatures() {
|
||||
return this.companion ? this.companion.items.filter(x => x.type === 'feature') : [];
|
||||
}
|
||||
|
||||
get needsCharacterSetup() {
|
||||
return !this.class.value || !this.class.subclass;
|
||||
}
|
||||
|
||||
get spellcastingModifiers() {
|
||||
return {
|
||||
main: this.class.subclass?.system?.spellcastingTrait,
|
||||
multiclass: this.multiclass.subclass?.system?.spellcastingTrait
|
||||
};
|
||||
}
|
||||
|
||||
get domains() {
|
||||
const classDomains = this.class.value ? this.class.value.system.domains : [];
|
||||
const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : [];
|
||||
|
|
@ -197,6 +215,12 @@ export default class DhCharacter extends BaseDataActor {
|
|||
: null;
|
||||
}
|
||||
|
||||
get deathMoveViable() {
|
||||
return (
|
||||
this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal
|
||||
);
|
||||
}
|
||||
|
||||
static async unequipBeforeEquip(itemToEquip) {
|
||||
const primary = this.primaryWeapon,
|
||||
secondary = this.secondaryWeapon;
|
||||
|
|
@ -307,58 +331,10 @@ export default class DhCharacter extends BaseDataActor {
|
|||
level: this.levelData.level.current
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class DhPCLevelData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
level: new fields.SchemaField({
|
||||
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
}),
|
||||
levelups: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
achievements: new fields.SchemaField(
|
||||
{
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
modifier: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
domainCards: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
uuid: new fields.StringField({ required: true }),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
),
|
||||
proficiency: new fields.NumberField({ integer: true })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
selections: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
optionKey: new fields.StringField({ required: true }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
checkboxNr: new fields.NumberField({ required: true, integer: true }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
minCost: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
data: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.level.current < this.level.changed;
|
||||
async _preDelete() {
|
||||
if (this.companion) {
|
||||
this.companion.updateLevel(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
139
module/data/actor/companion.mjs
Normal file
139
module/data/actor/companion.mjs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DhCompanion extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Companion'];
|
||||
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.companion',
|
||||
type: 'companion'
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
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 })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
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 })
|
||||
}),
|
||||
{
|
||||
initial: {
|
||||
experience1: { value: 2 },
|
||||
experience2: { value: 2 }
|
||||
}
|
||||
}
|
||||
),
|
||||
attack: new ActionField({
|
||||
initial: {
|
||||
name: 'Attack',
|
||||
_id: foundry.utils.randomID(),
|
||||
systemPath: 'attack',
|
||||
type: 'attack',
|
||||
range: 'melee',
|
||||
target: {
|
||||
type: 'any',
|
||||
amount: 1
|
||||
},
|
||||
roll: {
|
||||
type: 'weapon',
|
||||
bonus: 0
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
multiplier: 'flat',
|
||||
value: {
|
||||
dice: 'd6',
|
||||
multiplier: 'flat'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
actions: new fields.ArrayField(new ActionField()),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData)
|
||||
};
|
||||
}
|
||||
|
||||
get attackBonus() {
|
||||
return this.attack.roll.bonus ?? 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
for (let selection of level.selections) {
|
||||
switch (selection.type) {
|
||||
case 'hope':
|
||||
this.resources.hope += selection.value;
|
||||
break;
|
||||
case 'vicious':
|
||||
if (selection.data[0] === 'damage') {
|
||||
this.attack.damage.parts[0].value.dice = adjustDice(this.attack.damage.parts[0].value.dice);
|
||||
} else {
|
||||
this.attack.range = adjustRange(this.attack.range);
|
||||
}
|
||||
break;
|
||||
case 'stress':
|
||||
this.resources.stress.bonus += selection.value;
|
||||
break;
|
||||
case 'evasion':
|
||||
this.evasion.bonus += selection.value;
|
||||
break;
|
||||
case 'experience':
|
||||
Object.keys(this.experiences).forEach(key => {
|
||||
const experience = this.experiences[key];
|
||||
experience.bonus += selection.value;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ export default class DHArmor extends BaseDataItem {
|
|||
type: 'armor',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
type: 'base',
|
||||
hasDescription: false,
|
||||
isQuantifiable: false,
|
||||
isInventoryItem: false,
|
||||
isInventoryItem: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -54,9 +54,9 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
const data = { ...actorRollData, item: { ...this } };
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
if(!this.constructor.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return;
|
||||
if (!this.constructor.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return;
|
||||
const actionType = {
|
||||
weapon: 'attack'
|
||||
}[this.constructor.metadata.type],
|
||||
|
|
@ -72,6 +72,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
parent: this.parent
|
||||
}
|
||||
);
|
||||
this.updateSource({actions: [action]});
|
||||
this.updateSource({ actions: [action] });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class DHConsumable extends BaseDataItem {
|
|||
type: 'consumable',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getTier } from '../../helpers/utils.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
|
||||
|
|
@ -17,135 +16,7 @@ export default class DHFeature extends BaseDataItem {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
|
||||
//A type of feature seems unnecessary
|
||||
type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }),
|
||||
|
||||
//TODO: remove actionType field
|
||||
actionType: new fields.StringField({
|
||||
choices: SYSTEM.ITEM.actionTypes,
|
||||
initial: SYSTEM.ITEM.actionTypes.passive.id
|
||||
}),
|
||||
//TODO: remove featureType field
|
||||
featureType: new fields.SchemaField({
|
||||
type: new fields.StringField({
|
||||
choices: SYSTEM.ITEM.valueTypes,
|
||||
initial: Object.keys(SYSTEM.ITEM.valueTypes).find(x => x === 'normal')
|
||||
}),
|
||||
data: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
property: new fields.StringField({
|
||||
choices: SYSTEM.ACTOR.featureProperties,
|
||||
initial: Object.keys(SYSTEM.ACTOR.featureProperties).find(x => x === 'spellcastingTrait')
|
||||
}),
|
||||
max: new fields.NumberField({ initial: 1, integer: true }),
|
||||
numbers: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
used: new fields.BooleanField({ initial: false })
|
||||
})
|
||||
)
|
||||
})
|
||||
}),
|
||||
refreshData: new fields.SchemaField(
|
||||
{
|
||||
type: new fields.StringField({ choices: SYSTEM.GENERAL.refreshTypes }),
|
||||
uses: new fields.NumberField({ initial: 1, integer: true }),
|
||||
//TODO: remove refreshed field
|
||||
refreshed: new fields.BooleanField({ initial: true })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
//TODO: remove refreshed field
|
||||
multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
disabled: new fields.BooleanField({ initial: false }),
|
||||
|
||||
//TODO: re do it completely or just remove it
|
||||
effects: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.EFFECTS.effectTypes }),
|
||||
valueType: new fields.StringField({ choices: SYSTEM.EFFECTS.valueTypes }),
|
||||
parseType: new fields.StringField({ choices: SYSTEM.EFFECTS.parseTypes }),
|
||||
initiallySelected: new fields.BooleanField({ initial: true }),
|
||||
options: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
value: new fields.StringField({})
|
||||
}),
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
dataField: new fields.StringField({}),
|
||||
appliesOn: new fields.StringField(
|
||||
{
|
||||
choices: SYSTEM.EFFECTS.applyLocations
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), {
|
||||
nullable: true,
|
||||
initial: null
|
||||
}),
|
||||
valueData: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
fromValue: new fields.StringField({ initial: null, nullable: true }),
|
||||
type: new fields.StringField({ initial: null, nullable: true }),
|
||||
hopeIncrease: new fields.StringField({ initial: null, nullable: true })
|
||||
})
|
||||
})
|
||||
),
|
||||
actions: new fields.ArrayField(new ActionField())
|
||||
};
|
||||
}
|
||||
|
||||
get multiclassTier() {
|
||||
return getTier(this.multiclass);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
if (this.refreshData) {
|
||||
if (this.featureType.type === SYSTEM.ITEM.valueTypes.dice.id) {
|
||||
const update = { 'system.refreshData.refreshed': true };
|
||||
Object.keys(this.featureType.data.numbers).forEach(
|
||||
x => (update[`system.featureType.data.numbers.-=${x}`] = null)
|
||||
);
|
||||
await this.parent.update(update);
|
||||
} else {
|
||||
await this.parent.update({ 'system.refreshData.refreshed': true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get effectData() {
|
||||
const effectValues = Object.values(this.effects);
|
||||
const effectCategories = Object.keys(SYSTEM.EFFECTS.effectTypes).reduce((acc, effectType) => {
|
||||
acc[effectType] = effectValues.reduce((acc, effect) => {
|
||||
if (effect.type === effectType) {
|
||||
acc.push({ ...effect, valueData: this.#parseValues(effect.parseType, effect.valueData) });
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return effectCategories;
|
||||
}
|
||||
|
||||
#parseValues(parseType, values) {
|
||||
return Object.keys(values).reduce((acc, prop) => {
|
||||
acc[prop] = this.#parseValue(parseType, values[prop]);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
#parseValue(parseType, value) {
|
||||
switch (parseType) {
|
||||
case SYSTEM.EFFECTS.parseTypes.number.id:
|
||||
return Number.parseInt(value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class DHMiscellaneous extends BaseDataItem {
|
|||
type: 'miscellaneous',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs';
|
|||
import FormulaField from '../fields/formulaField.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import { weaponFeatures } from '../../config/itemConfig.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
|
||||
export default class DHWeapon extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -13,7 +13,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
hasInitialAction: true,
|
||||
hasInitialAction: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
61
module/data/levelData.mjs
Normal file
61
module/data/levelData.mjs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { LevelOptionType } from './levelTier.mjs';
|
||||
|
||||
export default class DhLevelData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
level: new fields.SchemaField({
|
||||
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false }))
|
||||
}),
|
||||
levelups: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
achievements: new fields.SchemaField(
|
||||
{
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
modifier: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
domainCards: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
uuid: new fields.StringField({ required: true }),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
),
|
||||
proficiency: new fields.NumberField({ integer: true })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
selections: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
optionKey: new fields.StringField({ required: true }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
checkboxNr: new fields.NumberField({ required: true, integer: true }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
minCost: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
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())
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get actions() {
|
||||
return Object.values(this.levelups).flatMap(level => level.selections.flatMap(s => s.actions));
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.level.current < this.level.changed;
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,58 @@ class DhLevelOption extends foundry.abstract.DataModel {
|
|||
}
|
||||
}
|
||||
|
||||
export const CompanionLevelOptionType = {
|
||||
hope: {
|
||||
id: 'hope',
|
||||
label: 'Light In The Dark'
|
||||
},
|
||||
creatureComfort: {
|
||||
id: 'creatureComfort',
|
||||
label: 'Creature Comfort',
|
||||
features: [
|
||||
{
|
||||
name: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Name',
|
||||
img: 'icons/magic/life/heart-cross-purple-orange.webp',
|
||||
description: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Description'
|
||||
}
|
||||
]
|
||||
},
|
||||
armored: {
|
||||
id: 'armored',
|
||||
label: 'Armored',
|
||||
features: [
|
||||
{
|
||||
name: 'DAGGERHEART.LevelUp.Actions.Armored.Name',
|
||||
img: 'icons/equipment/shield/kite-wooden-oak-glow.webp',
|
||||
description: 'DAGGERHEART.LevelUp.Actions.Armored.Description'
|
||||
}
|
||||
]
|
||||
},
|
||||
vicious: {
|
||||
id: 'vicious',
|
||||
label: 'Viscious'
|
||||
},
|
||||
resilient: {
|
||||
id: 'resilient',
|
||||
label: 'Resilient'
|
||||
},
|
||||
bonded: {
|
||||
id: 'bonded',
|
||||
label: 'Bonded',
|
||||
features: [
|
||||
{
|
||||
name: 'DAGGERHEART.LevelUp.Actions.Bonded.Name',
|
||||
img: 'icons/magic/life/heart-red-blue.webp',
|
||||
description: 'DAGGERHEART.LevelUp.Actions.Bonded.Description'
|
||||
}
|
||||
]
|
||||
},
|
||||
aware: {
|
||||
id: 'aware',
|
||||
label: 'Aware'
|
||||
}
|
||||
};
|
||||
|
||||
export const LevelOptionType = {
|
||||
trait: {
|
||||
id: 'trait',
|
||||
|
|
@ -106,7 +158,8 @@ export const LevelOptionType = {
|
|||
multiclass: {
|
||||
id: 'multiclass',
|
||||
label: 'Multiclass'
|
||||
}
|
||||
},
|
||||
...CompanionLevelOptionType
|
||||
};
|
||||
|
||||
export const defaultLevelTiers = {
|
||||
|
|
@ -338,3 +391,80 @@ export const defaultLevelTiers = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultCompanionTier = {
|
||||
tiers: {
|
||||
2: {
|
||||
tier: 2,
|
||||
name: 'Companion Choices',
|
||||
levels: {
|
||||
start: 2,
|
||||
end: 10
|
||||
},
|
||||
initialAchievements: {},
|
||||
availableOptions: 1,
|
||||
domainCardByLevel: 0,
|
||||
options: {
|
||||
experience: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.intelligent',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.experience.id,
|
||||
value: 1,
|
||||
amount: 1
|
||||
},
|
||||
hope: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.lightInTheDark',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.hope.id,
|
||||
value: 1
|
||||
},
|
||||
creatureComfort: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.creatureComfort',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.creatureComfort.id,
|
||||
value: 1
|
||||
},
|
||||
armored: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.armored',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.armored.id,
|
||||
value: 1
|
||||
},
|
||||
vicious: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.vicious',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.vicious.id,
|
||||
value: 1,
|
||||
amount: 1
|
||||
},
|
||||
stress: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.resilient',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.stress.id,
|
||||
value: 1
|
||||
},
|
||||
bonded: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.bonded',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.bonded.id,
|
||||
value: 1
|
||||
},
|
||||
evasion: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.aware',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.evasion.id,
|
||||
value: 2,
|
||||
amount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.availableOptions, {
|
||||
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.maxSelections[i], {
|
||||
...initialAchievements,
|
||||
experiences,
|
||||
domainCards
|
||||
|
|
@ -46,7 +46,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
name: tier.name,
|
||||
belongingLevels: belongingLevels,
|
||||
options: Object.keys(tier.options).reduce((acc, key) => {
|
||||
acc[key] = tier.options[key].toObject();
|
||||
acc[key] = tier.options[key].toObject?.() ?? tier.options[key];
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
|
|
@ -98,6 +98,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
case 'experience':
|
||||
case 'domainCard':
|
||||
case 'subclass':
|
||||
case 'vicious':
|
||||
return checkbox.data.length === (checkbox.amount ?? 1);
|
||||
case 'multiclass':
|
||||
const classSelected = checkbox.data.length === 1;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
} 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) : [...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)
|
||||
: [...this.config.experiences, button.dataset.key];
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +112,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
if (!options.submitted) this.config = false;
|
||||
}
|
||||
|
||||
static async configure(roll, config = {}, options={}) {
|
||||
static async configure(roll, config = {}, options = {}) {
|
||||
return new Promise(resolve => {
|
||||
const app = new this(roll, config, options);
|
||||
app.addEventListener('close', () => resolve(app.config), { once: true });
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class DamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(roll, config={}, options={}) {
|
||||
constructor(roll, config = {}, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.roll = roll;
|
||||
|
|
@ -42,19 +42,19 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
}
|
||||
|
||||
static async submitRoll() {
|
||||
await this.close({ submitted: true });
|
||||
await this.close({ submitted: true });
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onClose(options={}) {
|
||||
if ( !options.submitted ) this.config = false;
|
||||
_onClose(options = {}) {
|
||||
if (!options.submitted) this.config = false;
|
||||
}
|
||||
|
||||
static async configure(roll, config={}) {
|
||||
static async configure(roll, config = {}) {
|
||||
return new Promise(resolve => {
|
||||
const app = new this(roll, config);
|
||||
app.addEventListener("close", () => resolve(app.config), { once: true });
|
||||
app.addEventListener('close', () => resolve(app.config), { once: true });
|
||||
app.render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import DamageSelectionDialog from '../applications/damageSelectionDialog.mjs';
|
||||
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
|
||||
import DamageReductionDialog from '../applications/damageReductionDialog.mjs';
|
||||
import { LevelOptionType } from '../data/levelTier.mjs';
|
||||
import DHFeature from '../data/item/feature.mjs';
|
||||
|
||||
export default class DhpActor extends Actor {
|
||||
async _preCreate(data, options, user) {
|
||||
|
|
@ -8,7 +10,7 @@ export default class DhpActor extends Actor {
|
|||
|
||||
// Configure prototype token settings
|
||||
const prototypeToken = {};
|
||||
if (this.type === 'character')
|
||||
if (['character', 'companion'].includes(this.type))
|
||||
Object.assign(prototypeToken, {
|
||||
sight: { enabled: true },
|
||||
actorLink: true,
|
||||
|
|
@ -18,7 +20,7 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
|
||||
async updateLevel(newLevel) {
|
||||
if (this.type !== 'character' || newLevel === this.system.levelData.level.changed) return;
|
||||
if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return;
|
||||
|
||||
if (newLevel > this.system.levelData.level.current) {
|
||||
const maxLevel = Object.values(
|
||||
|
|
@ -41,6 +43,7 @@ export default class DhpActor extends Actor {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
const featureIds = [];
|
||||
const domainCards = [];
|
||||
const experiences = [];
|
||||
const subclassFeatureState = { class: null, multiclass: null };
|
||||
|
|
@ -53,6 +56,7 @@ export default class DhpActor extends Actor {
|
|||
const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid);
|
||||
domainCards.push(...achievementCards, ...advancementCards);
|
||||
experiences.push(...Object.keys(level.achievements.experiences));
|
||||
featureIds.push(...level.selections.flatMap(x => x.featureIds));
|
||||
|
||||
const subclass = level.selections.find(x => x.type === 'subclass');
|
||||
if (subclass) {
|
||||
|
|
@ -66,13 +70,21 @@ export default class DhpActor extends Actor {
|
|||
multiclass = level.selections.find(x => x.type === 'multiclass');
|
||||
});
|
||||
|
||||
for (let featureId of featureIds) {
|
||||
this.items.get(featureId).delete();
|
||||
}
|
||||
|
||||
if (experiences.length > 0) {
|
||||
this.update({
|
||||
const getUpdate = () => ({
|
||||
'system.experiences': experiences.reduce((acc, key) => {
|
||||
acc[`-=${key}`] = null;
|
||||
return acc;
|
||||
}, {})
|
||||
});
|
||||
this.update(getUpdate());
|
||||
if (this.system.companion) {
|
||||
this.system.companion.update(getUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
if (subclassFeatureState.class) {
|
||||
|
|
@ -114,10 +126,15 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.system.companion) {
|
||||
this.system.companion.updateLevel(newLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async levelUp(levelupData) {
|
||||
const actions = [];
|
||||
const levelups = {};
|
||||
for (var levelKey of Object.keys(levelupData)) {
|
||||
const level = levelupData[levelKey];
|
||||
|
|
@ -126,13 +143,23 @@ export default class DhpActor extends Actor {
|
|||
const experience = level.achievements.experiences[experienceKey];
|
||||
await this.update({
|
||||
[`system.experiences.${experienceKey}`]: {
|
||||
description: experience.name,
|
||||
name: experience.name,
|
||||
value: experience.modifier
|
||||
}
|
||||
});
|
||||
|
||||
if (this.system.companion) {
|
||||
await this.system.companion.update({
|
||||
[`system.experiences.${experienceKey}`]: {
|
||||
name: '',
|
||||
value: experience.modifier
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let multiclass = null;
|
||||
const featureAdditions = [];
|
||||
const domainCards = [];
|
||||
const subclassFeatureState = { class: null, multiclass: null };
|
||||
const selections = [];
|
||||
|
|
@ -141,7 +168,18 @@ export default class DhpActor extends Actor {
|
|||
for (var checkboxNr of Object.keys(selection)) {
|
||||
const checkbox = selection[checkboxNr];
|
||||
|
||||
if (checkbox.type === 'multiclass') {
|
||||
const tierOption = LevelOptionType[checkbox.type];
|
||||
if (tierOption.features?.length > 0) {
|
||||
featureAdditions.push({
|
||||
checkbox: {
|
||||
...checkbox,
|
||||
level: Number(levelKey),
|
||||
optionKey: optionKey,
|
||||
checkboxNr: Number(checkboxNr)
|
||||
},
|
||||
features: tierOption.features
|
||||
});
|
||||
} else if (checkbox.type === 'multiclass') {
|
||||
multiclass = {
|
||||
...checkbox,
|
||||
level: Number(levelKey),
|
||||
|
|
@ -174,6 +212,28 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
for (var addition of featureAdditions) {
|
||||
for (var featureData of addition.features) {
|
||||
const feature = new DHFeature({
|
||||
...featureData,
|
||||
description: game.i18n.localize(featureData.description)
|
||||
});
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...featureData,
|
||||
name: game.i18n.localize(featureData.name),
|
||||
type: 'feature',
|
||||
system: feature
|
||||
}
|
||||
]);
|
||||
addition.checkbox.featureIds = !addition.checkbox.featureIds
|
||||
? [embeddedItem[0].id]
|
||||
: [...addition.checkbox.featureIds, embeddedItem[0].id];
|
||||
}
|
||||
|
||||
selections.push(addition.checkbox);
|
||||
}
|
||||
|
||||
if (multiclass) {
|
||||
const subclassItem = await foundry.utils.fromUuid(multiclass.secondaryData.subclass);
|
||||
const subclassData = subclassItem.toObject();
|
||||
|
|
@ -238,6 +298,7 @@ export default class DhpActor extends Actor {
|
|||
|
||||
await this.update({
|
||||
system: {
|
||||
actions: [...this.system.actions, ...actions],
|
||||
levelData: {
|
||||
level: {
|
||||
current: this.system.levelData.level.changed
|
||||
|
|
@ -246,6 +307,10 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.system.companion) {
|
||||
this.system.companion.updateLevel(this.system.levelData.level.changed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -266,7 +331,7 @@ export default class DhpActor extends Actor {
|
|||
* @param {object} [config.costs]
|
||||
*/
|
||||
async diceRoll(config) {
|
||||
config.source = {...(config.source ?? {}), actor: this.uuid};
|
||||
config.source = { ...(config.source ?? {}), actor: this.uuid };
|
||||
config.data = this.getRollData();
|
||||
const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass;
|
||||
return await rollClass.build(config);
|
||||
|
|
@ -364,6 +429,11 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
|
||||
async takeDamage(damage, type) {
|
||||
if (this.type === 'companion') {
|
||||
await this.modifyResource([{ value: 1, type: 'stress' }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const hpDamage =
|
||||
damage >= this.system.damageThresholds.severe
|
||||
? 3
|
||||
|
|
@ -422,14 +492,17 @@ export default class DhpActor extends Actor {
|
|||
break;
|
||||
default:
|
||||
updates.actor.resources[`system.resources.${r.type}.value`] = Math.max(
|
||||
Math.min(this.system.resources[r.type].value + r.value, (this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max)),
|
||||
Math.min(
|
||||
this.system.resources[r.type].value + r.value,
|
||||
this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max
|
||||
),
|
||||
0
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
Object.values(updates).forEach(async u => {
|
||||
console.log(updates, u)
|
||||
console.log(updates, u);
|
||||
if (Object.keys(u.resources).length > 0) {
|
||||
if (game.user.isGM) {
|
||||
await u.target.update(u.resources);
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ export default class DHItem extends foundry.documents.Item {
|
|||
isInventoryItem === true
|
||||
? 'Inventory Items'
|
||||
: isInventoryItem === false
|
||||
? 'Character Items'
|
||||
: 'Other';
|
||||
? 'Character Items'
|
||||
: 'Other';
|
||||
|
||||
return { value: type, label, group };
|
||||
}
|
||||
|
|
@ -83,13 +83,14 @@ export default class DHItem extends foundry.documents.Item {
|
|||
{ actions: this.system.actions }
|
||||
),
|
||||
title = 'Select Action';
|
||||
|
||||
|
||||
return foundry.applications.api.DialogV2.prompt({
|
||||
window: { title },
|
||||
content,
|
||||
ok: {
|
||||
label: title,
|
||||
callback: (event, button, dialog) => this.system.actions.find(a => a._id === button.form.elements.actionId.value)
|
||||
callback: (event, button, dialog) =>
|
||||
this.system.actions.find(a => a._id === button.form.elements.actionId.value)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -115,7 +116,9 @@ export default class DHItem extends foundry.documents.Item {
|
|||
this.type === 'ancestry'
|
||||
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle')
|
||||
: this.type === 'community'
|
||||
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle')
|
||||
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle')
|
||||
: this.type === 'feature'
|
||||
? game.i18n.localize('TYPES.Item.feature')
|
||||
: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
|
||||
origin: origin,
|
||||
img: this.img,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import { diceTypes, getDiceSoNicePresets, range } from '../config/generalConfig.mjs';
|
||||
import Tagify from '@yaireo/tagify';
|
||||
|
||||
export const loadCompendiumOptions = async compendiums => {
|
||||
|
|
@ -225,10 +225,10 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue
|
|||
|
||||
// Fix on Foundry native formula replacement for DH
|
||||
const nativeReplaceFormulaData = Roll.replaceFormulaData;
|
||||
Roll.replaceFormulaData = function (formula, data={}, { missing, warn = false } = {}) {
|
||||
Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false } = {}) {
|
||||
const terms = Object.keys(SYSTEM.GENERAL.multiplierTypes).map(type => {
|
||||
return { term: type, default: 1}
|
||||
})
|
||||
return { term: type, default: 1 };
|
||||
});
|
||||
formula = terms.reduce((a, c) => a.replaceAll(`@${c.term}`, data[c.term] ?? c.default), formula);
|
||||
return nativeReplaceFormulaData(formula, data, { missing, warn });
|
||||
};
|
||||
|
|
@ -258,3 +258,17 @@ export const damageKeyToNumber = key => {
|
|||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const adjustDice = (dice, decrease) => {
|
||||
const diceKeys = Object.keys(diceTypes);
|
||||
const index = diceKeys.indexOf(dice);
|
||||
const newIndex = decrease ? Math.max(index - 1, 0) : Math.min(index + 1, diceKeys.length - 1);
|
||||
return diceTypes[diceKeys[newIndex]];
|
||||
};
|
||||
|
||||
export const adjustRange = (rangeVal, decrease) => {
|
||||
const rangeKeys = Object.keys(range);
|
||||
const index = rangeKeys.indexOf(rangeVal);
|
||||
const newIndex = decrease ? Math.max(index - 1, 0) : Math.min(index + 1, rangeKeys.length - 1);
|
||||
return range[rangeKeys[newIndex]];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -154,14 +154,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
? message.system.targets.map(target => game.canvas.tokens.get(target.id))
|
||||
: Array.from(game.user.targets);
|
||||
|
||||
if(message.system.onSave && event.currentTarget.dataset.targetHit) {
|
||||
const pendingingSaves = message.system.targets.filter(target => target.hit && target.saved.success === null);
|
||||
if(pendingingSaves.length) {
|
||||
if (message.system.onSave && event.currentTarget.dataset.targetHit) {
|
||||
const pendingingSaves = message.system.targets.filter(
|
||||
target => target.hit && target.saved.success === null
|
||||
);
|
||||
if (pendingingSaves.length) {
|
||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {title: "Pending Reaction Rolls found"},
|
||||
window: { title: 'Pending Reaction Rolls found' },
|
||||
content: `<p>Some Tokens still need to roll their Reaction Roll.</p><p>Are you sure you want to continue ?</p><p><i>Undone reaction rolls will be considered as failed</i></p>`
|
||||
});
|
||||
if ( !confirm ) return;
|
||||
if (!confirm) return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,8 +171,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.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 * (SYSTEM.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
|
||||
|
||||
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
|
||||
damage = Math.ceil(damage * (SYSTEM.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
|
||||
|
||||
await target.actor.takeDamage(damage, message.system.roll.type);
|
||||
}
|
||||
};
|
||||
|
|
@ -181,7 +184,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
|
||||
if (targets.length === 0)
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
|
||||
|
||||
|
||||
for (var target of targets) {
|
||||
await target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2935,6 +2935,7 @@ div.daggerheart.views.multiclass {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.daggerheart.levelup .levelup-navigation-container {
|
||||
display: flex;
|
||||
|
|
@ -3096,6 +3097,13 @@ div.daggerheart.views.multiclass {
|
|||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.daggerheart.levelup .levelup-selections-container .levelup-radio-choices {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.daggerheart.levelup .levelup-selections-container .levelup-radio-choices label {
|
||||
flex: 0;
|
||||
}
|
||||
.daggerheart.levelup .levelup-summary-container .level-achievements-container,
|
||||
.daggerheart.levelup .levelup-summary-container .level-advancements-container {
|
||||
display: flex;
|
||||
|
|
@ -4211,6 +4219,14 @@ div.daggerheart.views.multiclass {
|
|||
scrollbar-width: thin;
|
||||
scrollbar-color: light-dark(#18162e, #f3c267) transparent;
|
||||
}
|
||||
.application.sheet.daggerheart.actor.dh-style.companion .profile {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
}
|
||||
.application.sheet.daggerheart.actor.dh-style.companion .temp-container {
|
||||
position: relative;
|
||||
top: 32px;
|
||||
}
|
||||
.application.sheet.daggerheart.actor.dh-style.adversary .window-content {
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
@import './less/actors/character/biography.less';
|
||||
@import './less/actors/character/features.less';
|
||||
|
||||
@import './less/actors/companion/sheet.less';
|
||||
|
||||
@import './less/actors/adversary.less';
|
||||
@import './less/actors/environment.less';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,70 +1,70 @@
|
|||
@import '../../utils/colors.less';
|
||||
@import '../../utils/fonts.less';
|
||||
|
||||
.application.sheet.daggerheart.actor.dh-style.character {
|
||||
.tab.inventory {
|
||||
.search-section {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
color: light-dark(@dark-blue-50, @beige-50);
|
||||
width: 100%;
|
||||
padding-top: 5px;
|
||||
|
||||
input {
|
||||
border-radius: 50px;
|
||||
font-family: @font-body;
|
||||
background: light-dark(@dark-blue-10, @golden-10);
|
||||
border: none;
|
||||
outline: 2px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0 20px;
|
||||
|
||||
&:hover {
|
||||
outline: 2px solid light-dark(@dark, @golden);
|
||||
}
|
||||
|
||||
&:placeholder {
|
||||
color: light-dark(@dark-blue-50, @beige-50);
|
||||
}
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-content: center;
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
font-size: 16px;
|
||||
z-index: 1;
|
||||
color: light-dark(@dark-blue-50, @beige-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
|
||||
padding: 20px 0;
|
||||
height: 80%;
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
|
||||
}
|
||||
|
||||
.currency-section {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@import '../../utils/colors.less';
|
||||
@import '../../utils/fonts.less';
|
||||
|
||||
.application.sheet.daggerheart.actor.dh-style.character {
|
||||
.tab.inventory {
|
||||
.search-section {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
color: light-dark(@dark-blue-50, @beige-50);
|
||||
width: 100%;
|
||||
padding-top: 5px;
|
||||
|
||||
input {
|
||||
border-radius: 50px;
|
||||
font-family: @font-body;
|
||||
background: light-dark(@dark-blue-10, @golden-10);
|
||||
border: none;
|
||||
outline: 2px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0 20px;
|
||||
|
||||
&:hover {
|
||||
outline: 2px solid light-dark(@dark, @golden);
|
||||
}
|
||||
|
||||
&:placeholder {
|
||||
color: light-dark(@dark-blue-50, @beige-50);
|
||||
}
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-content: center;
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
font-size: 16px;
|
||||
z-index: 1;
|
||||
color: light-dark(@dark-blue-50, @beige-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
|
||||
padding: 20px 0;
|
||||
height: 80%;
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
|
||||
}
|
||||
|
||||
.currency-section {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
styles/less/actors/companion/sheet.less
Normal file
11
styles/less/actors/companion/sheet.less
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.application.sheet.daggerheart.actor.dh-style.companion {
|
||||
.profile {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.temp-container {
|
||||
position: relative;
|
||||
top: 32px;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,6 +218,15 @@
|
|||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.levelup-radio-choices {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
label {
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.levelup-summary-container {
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@
|
|||
"character": {
|
||||
"htmlFields": ["story", "description", "scars.*.description"]
|
||||
},
|
||||
"companion": {},
|
||||
"adversary": {
|
||||
"htmlFields": ["description", "motivesAndTactics"]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@
|
|||
{{#if document.system.class.subclass}}
|
||||
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(concat (localize 'TYPES.Item.subclass') ' - ' document.system.class.subclass.name) type='subclass'}}
|
||||
{{/if}}
|
||||
{{#if document.system.features}}
|
||||
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize "DAGGERHEART.Sheets.PC.Features") type='features'}}
|
||||
{{/if}}
|
||||
{{#if document.system.companionFeatures}}
|
||||
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize "DAGGERHEART.Sheets.PC.CompanionFeatures") type='companionFeatures'}}
|
||||
{{/if}}
|
||||
{{#if document.system.community}}
|
||||
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(concat (localize 'TYPES.Item.community') ' - ' document.system.community.name) type='community'}}
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<aside class="character-sidebar-sheet">
|
||||
<div class="portrait {{#if (gte document.system.resources.hitPoints.value document.system.resources.hitPoints.maxTotal)}}death-roll{{/if}}">
|
||||
<div class="portrait {{#if document.system.deathMoveViable}}death-roll{{/if}}">
|
||||
<img src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img">
|
||||
<a class="death-roll-btn" data-tooltip="{{localize "DAGGERHEART.Sheets.PC.Health.DeathMoveTooltip"}}" data-action="makeDeathMove"><i class="fas fa-skull death-save" ></i></a>
|
||||
</div>
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
<div class="status-number">
|
||||
<div class='status-value armor-slots'>
|
||||
{{#if document.system.armor.system.marks}}
|
||||
<p>{{document.system.armor.system.marks.value}}/{{document.system.armor.system.marks.max}}</p>
|
||||
<p>{{document.system.armor.system.marks.value}}/{{document.system.armorScore}}</p>
|
||||
{{else}}
|
||||
<p>-</p>
|
||||
{{/if}}
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
<div class="experience-value">
|
||||
+{{experience.total}}
|
||||
</div>
|
||||
<input name="{{concat "system.experiences." id ".description"}}" data-experience={{id}} value="{{experience.description}}" type="text" />
|
||||
<input name="{{concat "system.experiences." id ".name"}}" data-experience={{id}} value="{{experience.name}}" type="text" />
|
||||
<div class="controls">
|
||||
<a data-action="toChat" data-type="experience" data-uuid="{{id}}"><i class="fa-regular fa-message"></i></a>
|
||||
</div>
|
||||
|
|
|
|||
44
templates/sheets/actors/companion/tempMain.hbs
Normal file
44
templates/sheets/actors/companion/tempMain.hbs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<div class="temp-container standard-form">
|
||||
<img class="profile" src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img">
|
||||
<div class="form-group">
|
||||
<div class="form-fields">
|
||||
<label>{{localize "DAGGERHEART.Sheets.Companion.FIELDS.partner.label"}}</label>
|
||||
<select class="partner-value">
|
||||
{{selectOptions playerCharacters selected=source.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{formGroup systemFields.resources.fields.stress.fields.value value=source.system.resources.stress.value localize=true }}
|
||||
{{formGroup systemFields.evasion.fields.value value=source.system.evasion.value localize=true }}
|
||||
|
||||
<div>{{localize "DAGGERHEART.Sheets.Companion.Experiences"}}</div>
|
||||
<div class="flexcol">
|
||||
{{#each source.system.experiences as |experience key|}}
|
||||
<div class="flexrow">
|
||||
<input type="text" name="{{concat "system.experiences." key ".name"}}" value="{{experience.name}}" />
|
||||
<div>{{signedNumber experience.value}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="flexrow">
|
||||
<div class="form-group">
|
||||
<div class="form-fields">
|
||||
<label>{{localize "DAGGERHEART.Sheets.Companion.FIELDS.attack.name.label"}}</label>
|
||||
<input type="text" name="system.attack.name" value="{{source.system.attack.name}}" />
|
||||
<button data-action="attackRoll">Attack</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flexrow">
|
||||
<div class="form-group">
|
||||
<div class="form-fields">
|
||||
<label>{{localize "DAGGERHEART.Sheets.Companion.Level"}}</label>
|
||||
<div>{{source.system.levelData.level.changed}}</div>
|
||||
<button data-action="levelUp" {{#if (not source.system.levelData.canLevelUp)}}disabled{{/if}}>Level Up</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
16
templates/sheets/global/partials/action-item.hbs
Normal file
16
templates/sheets/global/partials/action-item.hbs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<li class="inventory-item" data-item-id="{{item.id}}" data-is-action="true" data-partner="{{partner}}">
|
||||
<img src="{{item.img}}" class="item-img" data-action="useItem" />
|
||||
<div class="item-label">
|
||||
<div class="item-name">{{item.name}}</div>
|
||||
{{!-- <div class="item-tags">
|
||||
<div class="tag">
|
||||
</div>
|
||||
<div class="tag">
|
||||
</div>
|
||||
</div> --}}
|
||||
</div>
|
||||
<div class="controls">
|
||||
<a data-action="toChat" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToChat'}}"><i class="fa-regular fa-message"></i></a>
|
||||
<a data-action="triggerContextMenu" data-tooltip="{{localize 'DAGGERHEART.Tooltip.moreOptions'}}"><i class="fa-solid fa-ellipsis-vertical"></i></a>
|
||||
</div>
|
||||
</li>
|
||||
|
|
@ -34,6 +34,16 @@
|
|||
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=effect type=../type}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{#each document.system.features as |feature|}}
|
||||
{{#if (or (eq ../type 'features'))}}
|
||||
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=feature}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{#each document.system.companionFeatures as |feature|}}
|
||||
{{#if (or (eq ../type 'companionFeatures'))}}
|
||||
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=feature companion=true}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/unless}}
|
||||
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<li class="inventory-item" data-item-id="{{item.id}}">
|
||||
<li class="inventory-item" data-item-id="{{item.id}}" data-companion="{{companion}}">
|
||||
<img src="{{item.img}}" class="item-img" data-action="useItem"/>
|
||||
<div class="item-label">
|
||||
<div class="item-name">{{item.name}}</div>
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
<section
|
||||
class='tab {{tabs.effects.cssClass}} {{tabs.effects.id}}'
|
||||
data-tab='{{tabs.effects.id}}'
|
||||
data-group='{{tabs.effects.group}}'
|
||||
>
|
||||
<fieldset class="two-columns">
|
||||
<legend>{{localize "Effects"}}</legend>
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.effects.addEffect"}}</span>
|
||||
<div class="nest-inputs">
|
||||
<select class="effect-select">
|
||||
{{selectOptions effectConfig.effectTypes selected=selectedEffectType labelAttr="name" localize=true blank=""}}
|
||||
</select>
|
||||
<a data-action="addEffect" {{disabled (not selectedEffectType)}}>
|
||||
<i class="fa-solid fa-plus icon-button {{disabled (not selectedEffectType)}}"></i>
|
||||
</a>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{{#each document.system.effects as |effect key|}}
|
||||
<fieldset class="two-columns">
|
||||
<legend>
|
||||
{{localize (concat 'DAGGERHEART.Effects.Types.' effect.type '.Name')}}
|
||||
<a>
|
||||
<i class="fa-solid fa-trash icon-button flex0" data-action="removeEffect" data-effect="{{key}}"></i>
|
||||
</a>
|
||||
</legend>
|
||||
{{#if effect.applyLocationChoices}}
|
||||
<span>
|
||||
{{localize "DAGGERHEART.Sheets.Feature.effects.applyLocation"}}
|
||||
</span>
|
||||
<select name="system.effects.{{key}}.appliesOn">
|
||||
{{selectOptions effect.applyLocationChoices selected=effect.appliesOn localize=true}}
|
||||
</select>
|
||||
{{/if}}
|
||||
{{#if (eq effect.valueType @root.effectConfig.valueTypes.numberString.id)}}
|
||||
{{#if (eq effect.type @root.effectConfig.effectTypes.damage.id) }}
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.effects.value"}}</span>
|
||||
<input type="text" name="system.effects.{{key}}.valueData.value" value="{{effect.valueData.value}}" />
|
||||
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.effects.initiallySelected"}}</span>
|
||||
<input type="checkbox" name="system.effects.{{key}}.initiallySelected" {{checked effect.initiallySelected}} />
|
||||
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.effects.hopeIncrease"}}</span>
|
||||
<input type="text" name="system.effects.{{key}}.valueData.hopeIncrease" value="{{effect.valueData.hopeIncrease}}" />
|
||||
{{else}}
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.effects.value"}}</span>
|
||||
<input type="text" name="system.effects.{{key}}.valueData.value" value="{{effect.valueData.value}}" />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (eq effect.valueType @root.effectConfig.valueTypes.select.id)}}
|
||||
<span>
|
||||
{{localize effect.valueData.fromValue}}
|
||||
</span>
|
||||
<select name="system.effects.{{key}}.valueData.fromValue" value="{{effect.valueData.fromValue}}">
|
||||
{{selectOptions effect.options selected=effect.valueData.fromValue labelAttr="name" valueAttr="value" localize=true blank="" }}
|
||||
</select>
|
||||
|
||||
<span>
|
||||
{{localize effect.valueData.name}}
|
||||
</span>
|
||||
<select name="system.effects.{{key}}.valueData.value" value="{{effect.valueData.value}}">
|
||||
{{selectOptions effect.options selected=effect.valueData.value labelAttr="name" valueAttr="value" localize=true blank="" }}
|
||||
</select>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
{{/each}}
|
||||
</section>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<line-div></line-div>
|
||||
<h1 class='item-name'><input type='text' name='name' value='{{source.name}}' /></h1>
|
||||
<div class='item-description'>
|
||||
<h3>{{localize (concat 'DAGGERHEART.Feature.Type.' source.system.type)}}</h3>
|
||||
<h3>{{localize (concat 'TYPES.Item.feature' source.system.type)}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<section
|
||||
class='tab {{tabs.settings.cssClass}} {{tabs.settings.id}}'
|
||||
data-tab='{{tabs.settings.id}}'
|
||||
data-group='{{tabs.settings.group}}'
|
||||
>
|
||||
<fieldset class="two-columns">
|
||||
<legend>{{localize tabs.settings.label}}</legend>
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.FeatureType"}}</span>
|
||||
{{formField systemFields.type value=source.system.type rootId=partId localize=true }}
|
||||
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.ActionType"}}</span>
|
||||
{{formField systemFields.actionType value=source.system.actionType rootId=partId localize=true }}
|
||||
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.RefreshType"}}</span>
|
||||
<div class="nest-inputs">
|
||||
<span><input type="text" name="system.refreshData.uses" value="{{source.system.refreshData.uses}}" data-dtype="Number" /></span>
|
||||
{{formField systemFields.refreshData.fields.type value=source.system.refreshData.type rootId=partId localize=true }}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="two-columns">
|
||||
<legend>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}}</legend>
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}}</span>
|
||||
<select name="system.featureType.type">
|
||||
{{selectOptions itemConfig.valueTypes selected=document.system.featureType.type labelAttr="name" localize=true}}
|
||||
</select>
|
||||
|
||||
{{#if (eq document.system.featureType.type "dice")}}
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Dice"}}</span>
|
||||
<select name="system.featureType.data.value">
|
||||
{{selectOptions dice selected=document.system.featureType.data.value }}
|
||||
</select>
|
||||
|
||||
<span>{{localize "DAGGERHEART.Feature.Max"}}</span>
|
||||
<input type="text" name="system.featureType.data.max" value="{{document.system.featureType.data.max}}" />
|
||||
|
||||
<span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Property"}}</span>
|
||||
<select name="system.featureType.data.property">
|
||||
{{selectOptions properties selected=document.system.featureType.data.property labelAttr="name" localize=true}}
|
||||
</select>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
</section>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<i class="fa-solid fa-caret-left"></i>
|
||||
<div class="health-category">{{localize "DAGGERHEART.Sheets.PC.Health.Severe"}}</div>
|
||||
</div>
|
||||
<i data-action="makeDeathMove" class="fas fa-skull death-save {{#if (lt resources.hitPoints.value document.system.resources.hitPoints.maxTotal)}}disabled{{/if}}" title="{{localize "DAGGERHEART.Sheets.PC.Health.DeathMoveTooltip"}}"></i>
|
||||
<i data-action="makeDeathMove" class="fas fa-skull death-save {{#if document.system.deathMoveViable}}disabled{{/if}}" title="{{localize "DAGGERHEART.Sheets.PC.Health.DeathMoveTooltip"}}"></i>
|
||||
</div>
|
||||
<div class="flexrow" style="flex-wrap: nowrap; align-items: center;">
|
||||
<div class="flexcol flex0">
|
||||
|
|
|
|||
|
|
@ -111,5 +111,16 @@
|
|||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.vicious}}
|
||||
<div>
|
||||
<h3>{{localize "DAGGERHEART.Application.LevelUp.summary.vicious"}}</h3>
|
||||
{{#each this.vicious}}
|
||||
<div class="levelup-radio-choices">
|
||||
{{radioBoxes (concat "levelup." this.path ".data") @root.viciousChoices checked=(lookup this.data 0)}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -4,55 +4,58 @@
|
|||
data-group='{{tabs.summary.group}}'
|
||||
>
|
||||
<div class="section-container levelup-summary-container">
|
||||
<fieldset>
|
||||
<legend>{{localize "DAGGERHEART.Application.LevelUp.summary.levelAchievements"}}</legend>
|
||||
{{#if this.achievements}}
|
||||
<fieldset>
|
||||
<legend>{{localize "DAGGERHEART.Application.LevelUp.summary.levelAchievements"}}</legend>
|
||||
|
||||
<div class="level-achievements-container">
|
||||
{{#if this.achievements.proficiency.shown}}
|
||||
<div>
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.proficiencyIncrease" proficiency=this.achievements.proficiency.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.achievements.proficiency.new}}
|
||||
<div class="level-achievements-container">
|
||||
{{#if this.achievements.proficiency.shown}}
|
||||
<div>
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.proficiencyIncrease" proficiency=this.achievements.proficiency.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.achievements.proficiency.new}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div>
|
||||
<h5 class="summary-section">{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholds"}}{{#if this.levelAchievements.damageThresholds.unarmored}}({{localize "DAGGERHEART.General.unarmored"}}){{/if}}</h5>
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdMajorIncrease" threshold=this.achievements.damageThresholds.major.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.achievements.damageThresholds.major.new}}
|
||||
</div>
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdSevereIncrease" threshold=this.achievements.damageThresholds.severe.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.achievements.damageThresholds.severe.new}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.achievements.damageThresholds}}
|
||||
<div>
|
||||
<h5 class="summary-section">{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholds"}}{{#if this.levelAchievements.damageThresholds.unarmored}}({{localize "DAGGERHEART.General.unarmored"}}){{/if}}</h5>
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdMajorIncrease" threshold=this.achievements.damageThresholds.major.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.achievements.damageThresholds.major.new}}
|
||||
</div>
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdSevereIncrease" threshold=this.achievements.damageThresholds.severe.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.achievements.damageThresholds.severe.new}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.achievements.domainCards.shown}}
|
||||
<div>
|
||||
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.domainCards"}}</h5>
|
||||
<div class="summary-selection-container">
|
||||
{{#each this.achievements.domainCards.values}}
|
||||
<div class="summary-selection">{{this.name}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.achievements.experiences.shown}}
|
||||
<div>
|
||||
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.newExperiences"}}</h5>
|
||||
<div class="summary-selection-container">
|
||||
{{#each this.achievements.experiences.values}}
|
||||
<div class="summary-selection">{{this.name}} {{signedNumber this.modifier}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if this.achievements.domainCards.shown}}
|
||||
<div>
|
||||
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.domainCards"}}</h5>
|
||||
<div class="summary-selection-container">
|
||||
{{#each this.achievements.domainCards.values}}
|
||||
<div class="summary-selection">{{this.name}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.achievements.experiences.shown}}
|
||||
<div>
|
||||
<h5>{{localize "DAGGERHEART.Application.LevelUp.summary.newExperiences"}}</h5>
|
||||
<div class="summary-selection-container">
|
||||
{{#each this.achievements.experiences.values}}
|
||||
<div class="summary-selection">{{this.name}} {{signedNumber this.modifier}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
<fieldset>
|
||||
<legend>{{localize "DAGGERHEART.Application.LevelUp.summary.levelAdvancements"}}</legend>
|
||||
|
||||
|
|
@ -151,6 +154,26 @@
|
|||
</div>
|
||||
{{/with}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.advancements.vicious.damage}}
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.damageIncreased" damage=this.advancements.vicious.damage.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.advancements.vicious.damage.new}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.advancements.vicious.range}}
|
||||
<div class="increase-container">
|
||||
{{localize "DAGGERHEART.Application.LevelUp.summary.rangeIncreased" range=this.advancements.vicious.range.old }}
|
||||
<i class="fa-solid fa-arrow-right-long"></i>
|
||||
{{this.advancements.vicious.range.new}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#each this.advancements.simple}}
|
||||
<div class="summary-selection-container">
|
||||
<div class="summary-selection">{{localize "DAGGERHEART.Application.LevelUp.summary.simpleFeature" feature=this}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue