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,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 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue