mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 14:36:13 +01:00
[Feature] 1383 - Companion Bonus Levelups (#1565)
* Fixed so that companions can get bonus levelupchoices from their partner * Fixed collection prep order * Added ActiveEffects to Beastbound features * Corrected styling * Added migration for overleveled companions * Raised version * Moved migration to 1.6.0. Sillyness
This commit is contained in:
parent
c42f876d4f
commit
ce96ffa0a3
15 changed files with 551 additions and 18 deletions
|
|
@ -3,6 +3,7 @@ import * as applications from './module/applications/_module.mjs';
|
||||||
import * as data from './module/data/_module.mjs';
|
import * as data from './module/data/_module.mjs';
|
||||||
import * as models from './module/data/_module.mjs';
|
import * as models from './module/data/_module.mjs';
|
||||||
import * as documents from './module/documents/_module.mjs';
|
import * as documents from './module/documents/_module.mjs';
|
||||||
|
import * as collections from './module/documents/collections/_module.mjs';
|
||||||
import * as dice from './module/dice/_module.mjs';
|
import * as dice from './module/dice/_module.mjs';
|
||||||
import * as fields from './module/data/fields/_module.mjs';
|
import * as fields from './module/data/fields/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
|
|
@ -36,6 +37,7 @@ CONFIG.Dice.daggerheart = {
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
CONFIG.Actor.dataModels = models.actors.config;
|
CONFIG.Actor.dataModels = models.actors.config;
|
||||||
|
CONFIG.Actor.collection = collections.DhActorCollection;
|
||||||
|
|
||||||
CONFIG.Item.documentClass = documents.DHItem;
|
CONFIG.Item.documentClass = documents.DHItem;
|
||||||
CONFIG.Item.dataModels = models.items.config;
|
CONFIG.Item.dataModels = models.items.config;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import BaseLevelUp from './levelup.mjs';
|
import BaseLevelUp from './levelup.mjs';
|
||||||
import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs';
|
import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs';
|
||||||
import { DhLevelup } from '../../data/levelup.mjs';
|
import { DhCompanionLevelup as DhLevelup } from '../../data/companionLevelup.mjs';
|
||||||
import { diceTypes, range } from '../../config/generalConfig.mjs';
|
import { diceTypes, range } from '../../config/generalConfig.mjs';
|
||||||
|
|
||||||
export default class DhCompanionLevelUp extends BaseLevelUp {
|
export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||||
|
|
@ -9,7 +9,9 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
||||||
|
|
||||||
this.levelTiers = this.addBonusChoices(defaultCompanionTier);
|
this.levelTiers = this.addBonusChoices(defaultCompanionTier);
|
||||||
const playerLevelupData = actor.system.levelData;
|
const playerLevelupData = actor.system.levelData;
|
||||||
this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData));
|
this.levelup = new DhLevelup(
|
||||||
|
DhLevelup.initializeData(this.levelTiers, playerLevelupData, actor.system.levelupChoicesLeft)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context) {
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,6 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
async _onRender(context, options) {
|
|
||||||
await super._onRender(context, options);
|
|
||||||
|
|
||||||
this.element
|
|
||||||
.querySelector('.level-value')
|
|
||||||
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -662,6 +662,11 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
|
||||||
this.resources.hope.max = globalHopeMax - this.scars;
|
this.resources.hope.max = globalHopeMax - this.scars;
|
||||||
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
this.resources.hitPoints.max += this.class.value?.system?.hitPoints ?? 0;
|
||||||
|
|
||||||
|
/* Companion Related Data */
|
||||||
|
this.companionData = {
|
||||||
|
levelupChoices: this.levelData.level.current - 1
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
|
|
@ -733,6 +738,16 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Force companion data prep */
|
||||||
|
if (this.companion) {
|
||||||
|
if (
|
||||||
|
changes.system?.levelData?.level?.current !== undefined &&
|
||||||
|
changes.system.levelData.level.current !== this._source.levelData.level.current
|
||||||
|
) {
|
||||||
|
this.companion.update(this.companion.toObject(), { diff: false, recursive: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete() {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,10 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
return this.partner?.system?.proficiency ?? 1;
|
return this.partner?.system?.proficiency ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canLevelUp() {
|
||||||
|
return this.levelupChoicesLeft > 0;
|
||||||
|
}
|
||||||
|
|
||||||
isItemValid() {
|
isItemValid() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +151,17 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareDerivedData() {
|
||||||
|
/* Partner Related Setup */
|
||||||
|
if (this.partner) {
|
||||||
|
this.levelData.level.changed = this.partner.system.levelData.level.current;
|
||||||
|
this.levelupChoicesLeft = Object.values(this.levelData.levelups).reduce((acc, curr) => {
|
||||||
|
acc = Math.max(acc - curr.selections.length, 0);
|
||||||
|
return acc;
|
||||||
|
}, this.partner.system.companionData.levelupChoices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _preUpdate(changes, options, userId) {
|
async _preUpdate(changes, options, userId) {
|
||||||
const allowed = await super._preUpdate(changes, options, userId);
|
const allowed = await super._preUpdate(changes, options, userId);
|
||||||
if (allowed === false) return;
|
if (allowed === false) return;
|
||||||
|
|
@ -162,6 +177,16 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
changes.system.experiences[experience].core = true;
|
changes.system.experiences[experience].core = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Force partner data prep */
|
||||||
|
if (this.partner) {
|
||||||
|
if (
|
||||||
|
changes.system?.levelData?.level?.current !== undefined &&
|
||||||
|
changes.system.levelData.level.current !== this._source.levelData.level.current
|
||||||
|
) {
|
||||||
|
this.partner.update(this.partner.toObject(), { diff: false, recursive: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete() {
|
||||||
|
|
|
||||||
370
module/data/companionLevelup.mjs
Normal file
370
module/data/companionLevelup.mjs
Normal file
|
|
@ -0,0 +1,370 @@
|
||||||
|
import { abilities } from '../config/actorConfig.mjs';
|
||||||
|
import { chunkify } from '../helpers/utils.mjs';
|
||||||
|
import { LevelOptionType } from './levelTier.mjs';
|
||||||
|
|
||||||
|
export class DhCompanionLevelup extends foundry.abstract.DataModel {
|
||||||
|
static initializeData(levelTierData, pcLevelData, origChoicesLeft) {
|
||||||
|
let choicesLeft = origChoicesLeft;
|
||||||
|
|
||||||
|
const { current, changed } = pcLevelData.level;
|
||||||
|
const bonusChoicesOnly = current === changed;
|
||||||
|
const startLevel = bonusChoicesOnly ? current : current + 1;
|
||||||
|
const endLevel = bonusChoicesOnly ? startLevel : changed;
|
||||||
|
|
||||||
|
const tiers = {};
|
||||||
|
const levels = {};
|
||||||
|
const tierKeys = Object.keys(levelTierData.tiers);
|
||||||
|
tierKeys.forEach(key => {
|
||||||
|
const tier = levelTierData.tiers[key];
|
||||||
|
const belongingLevels = [];
|
||||||
|
for (var i = tier.levels.start; i <= tier.levels.end; i++) {
|
||||||
|
if (i <= endLevel) {
|
||||||
|
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
|
||||||
|
const experiences = initialAchievements.experience
|
||||||
|
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
|
||||||
|
acc[foundry.utils.randomID()] = {
|
||||||
|
name: '',
|
||||||
|
modifier: initialAchievements.experience.modifier
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const currentChoices = pcLevelData.levelups[i]?.selections?.length;
|
||||||
|
const maxSelections =
|
||||||
|
i === endLevel
|
||||||
|
? choicesLeft + (currentChoices ?? 0)
|
||||||
|
: (currentChoices ?? tier.maxSelections[i]);
|
||||||
|
if (!pcLevelData.levelups[i]) choicesLeft -= maxSelections;
|
||||||
|
|
||||||
|
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], maxSelections, {
|
||||||
|
...initialAchievements,
|
||||||
|
experiences,
|
||||||
|
domainCards: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
belongingLevels.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improve. Temporary handling for Companion new experiences */
|
||||||
|
Object.keys(tier.extraAchievements ?? {}).forEach(key => {
|
||||||
|
const level = Number(key);
|
||||||
|
if (level >= startLevel && level <= endLevel) {
|
||||||
|
const levelExtras = tier.extraAchievements[level];
|
||||||
|
if (levelExtras.experience) {
|
||||||
|
levels[level].achievements.experiences[foundry.utils.randomID()] = {
|
||||||
|
name: '',
|
||||||
|
modifier: levelExtras.experience.modifier
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tiers[key] = {
|
||||||
|
name: tier.name,
|
||||||
|
belongingLevels: belongingLevels,
|
||||||
|
options: Object.keys(tier.options).reduce((acc, key) => {
|
||||||
|
acc[key] = tier.options[key].toObject?.() ?? tier.options[key];
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tiers,
|
||||||
|
levels,
|
||||||
|
startLevel,
|
||||||
|
currentLevel: startLevel,
|
||||||
|
endLevel
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tiers: new fields.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true }),
|
||||||
|
belongingLevels: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })),
|
||||||
|
options: new fields.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true }),
|
||||||
|
checkboxSelections: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
minCost: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||||
|
value: new fields.NumberField({ integer: true }),
|
||||||
|
amount: new fields.NumberField({ integer: true })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
levels: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelupLevel)),
|
||||||
|
startLevel: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
currentLevel: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
endLevel: new fields.NumberField({ required: true, integer: true })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#levelFinished(levelKey) {
|
||||||
|
const allSelectionsMade = this.levels[levelKey].nrSelections.available === 0;
|
||||||
|
const allChoicesMade = Object.keys(this.levels[levelKey].choices).every(choiceKey => {
|
||||||
|
const choice = this.levels[levelKey].choices[choiceKey];
|
||||||
|
return Object.values(choice).every(checkbox => {
|
||||||
|
switch (choiceKey) {
|
||||||
|
case 'trait':
|
||||||
|
case 'experience':
|
||||||
|
case 'domainCard':
|
||||||
|
case 'subclass':
|
||||||
|
case 'vicious':
|
||||||
|
return checkbox.data.length === (checkbox.amount ?? 1);
|
||||||
|
case 'multiclass':
|
||||||
|
const classSelected = checkbox.data.length === 1;
|
||||||
|
const domainSelected = checkbox.secondaryData.domain;
|
||||||
|
const subclassSelected = checkbox.secondaryData.subclass;
|
||||||
|
return classSelected && domainSelected && subclassSelected;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const experiencesSelected = !this.levels[levelKey].achievements.experiences
|
||||||
|
? true
|
||||||
|
: Object.values(this.levels[levelKey].achievements.experiences).every(exp => exp.name);
|
||||||
|
const domainCardsSelected = Object.values(this.levels[levelKey].achievements.domainCards)
|
||||||
|
.filter(x => x.level <= this.endLevel)
|
||||||
|
.every(card => card.uuid);
|
||||||
|
const allAchievementsSelected = experiencesSelected && domainCardsSelected;
|
||||||
|
|
||||||
|
return allSelectionsMade && allChoicesMade && allAchievementsSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentLevelFinished() {
|
||||||
|
return this.#levelFinished(this.currentLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
get allLevelsFinished() {
|
||||||
|
return Object.keys(this.levels)
|
||||||
|
.filter(level => Number(level) >= this.startLevel)
|
||||||
|
.every(this.#levelFinished.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
get unmarkedTraits() {
|
||||||
|
const possibleLevels = Object.values(this.tiers).reduce((acc, tier) => {
|
||||||
|
if (tier.belongingLevels.includes(this.currentLevel)) acc = tier.belongingLevels;
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return Object.keys(this.levels)
|
||||||
|
.filter(key => possibleLevels.some(x => x === Number(key)))
|
||||||
|
.reduce(
|
||||||
|
(acc, levelKey) => {
|
||||||
|
const level = this.levels[levelKey];
|
||||||
|
Object.values(level.choices).forEach(choice =>
|
||||||
|
Object.values(choice).forEach(checkbox => {
|
||||||
|
if (
|
||||||
|
checkbox.type === 'trait' &&
|
||||||
|
checkbox.data.length > 0 &&
|
||||||
|
Number(levelKey) !== this.currentLevel
|
||||||
|
) {
|
||||||
|
checkbox.data.forEach(data => delete acc[data]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ ...abilities }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get classUpgradeChoices() {
|
||||||
|
let subclasses = [];
|
||||||
|
let multiclass = null;
|
||||||
|
Object.keys(this.levels).forEach(levelKey => {
|
||||||
|
const level = this.levels[levelKey];
|
||||||
|
Object.values(level.choices).forEach(choice => {
|
||||||
|
Object.values(choice).forEach(checkbox => {
|
||||||
|
if (checkbox.type === 'multiclass') {
|
||||||
|
multiclass = {
|
||||||
|
class: checkbox.data.length > 0 ? checkbox.data[0] : null,
|
||||||
|
domain: checkbox.secondaryData.domain ?? null,
|
||||||
|
subclass: checkbox.secondaryData.subclass ?? null,
|
||||||
|
tier: checkbox.tier,
|
||||||
|
level: levelKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (checkbox.type === 'subclass') {
|
||||||
|
subclasses.push({
|
||||||
|
tier: checkbox.tier,
|
||||||
|
level: levelKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { subclasses, multiclass };
|
||||||
|
}
|
||||||
|
|
||||||
|
get tiersForRendering() {
|
||||||
|
const tierKeys = Object.keys(this.tiers);
|
||||||
|
const selections = Object.keys(this.levels).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
const level = this.levels[key];
|
||||||
|
Object.keys(level.choices).forEach(optionKey => {
|
||||||
|
const choice = level.choices[optionKey];
|
||||||
|
Object.keys(choice).forEach(checkboxNr => {
|
||||||
|
const checkbox = choice[checkboxNr];
|
||||||
|
if (!acc[checkbox.tier][optionKey]) acc[checkbox.tier][optionKey] = {};
|
||||||
|
Object.keys(choice).forEach(checkboxNr => {
|
||||||
|
acc[checkbox.tier][optionKey][checkboxNr] = { ...checkbox, level: Number(key) };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
tierKeys.reduce((acc, key) => {
|
||||||
|
acc[key] = {};
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { multiclass, subclasses } = this.classUpgradeChoices;
|
||||||
|
return tierKeys.map((tierKey, tierIndex) => {
|
||||||
|
const tier = this.tiers[tierKey];
|
||||||
|
const multiclassInTier = multiclass?.tier === Number(tierKey);
|
||||||
|
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: game.i18n.localize(tier.name),
|
||||||
|
active: this.currentLevel >= Math.min(...tier.belongingLevels),
|
||||||
|
groups: Object.keys(tier.options).map(optionKey => {
|
||||||
|
const option = tier.options[optionKey];
|
||||||
|
|
||||||
|
const checkboxes = [...Array(option.checkboxSelections).keys()].flatMap(index => {
|
||||||
|
const checkboxNr = index + 1;
|
||||||
|
const checkboxData = selections[tierKey]?.[optionKey]?.[checkboxNr];
|
||||||
|
const checkbox = { ...option, checkboxNr, tier: tierKey };
|
||||||
|
|
||||||
|
if (checkboxData) {
|
||||||
|
checkbox.level = checkboxData.level;
|
||||||
|
checkbox.selected = true;
|
||||||
|
checkbox.disabled = checkbox.level !== this.currentLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionKey === 'multiclass') {
|
||||||
|
if ((multiclass && !multiclassInTier) || subclassInTier) {
|
||||||
|
checkbox.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionKey === 'subclass' && multiclassInTier) {
|
||||||
|
checkbox.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkbox;
|
||||||
|
});
|
||||||
|
|
||||||
|
let label = game.i18n.localize(option.label);
|
||||||
|
if (optionKey === 'domainCard') {
|
||||||
|
const maxLevel = tier.belongingLevels[tier.belongingLevels.length - 1];
|
||||||
|
label = game.i18n.format(option.label, { maxLevel });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: label,
|
||||||
|
checkboxGroups: chunkify(checkboxes, option.minCost, chunkedBoxes => {
|
||||||
|
const anySelected = chunkedBoxes.some(x => x.selected);
|
||||||
|
const anyDisabled = chunkedBoxes.some(x => x.disabled);
|
||||||
|
return {
|
||||||
|
multi: option.minCost > 1,
|
||||||
|
checkboxes: chunkedBoxes.map(x => ({
|
||||||
|
...x,
|
||||||
|
selected: anySelected,
|
||||||
|
disabled: anyDisabled
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DhLevelupLevel extends foundry.abstract.DataModel {
|
||||||
|
static initializeData(levelData = { selections: [] }, maxSelections, achievements) {
|
||||||
|
return {
|
||||||
|
maxSelections: maxSelections,
|
||||||
|
achievements: {
|
||||||
|
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
|
||||||
|
domainCards: levelData.achievements?.domainCards
|
||||||
|
? levelData.achievements.domainCards.reduce((acc, card, index) => {
|
||||||
|
acc[index] = { ...card };
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
: (achievements.domainCards ?? {}),
|
||||||
|
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
|
||||||
|
},
|
||||||
|
choices: levelData.selections.reduce((acc, data) => {
|
||||||
|
if (!acc[data.optionKey]) acc[data.optionKey] = {};
|
||||||
|
acc[data.optionKey][data.checkboxNr] = { ...data };
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
maxSelections: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
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.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
uuid: new fields.StringField({ required: true, nullable: true, initial: null }),
|
||||||
|
itemUuid: new fields.StringField({ required: true }),
|
||||||
|
level: new fields.NumberField({ required: true, integer: true })
|
||||||
|
})
|
||||||
|
),
|
||||||
|
proficiency: new fields.NumberField({ integer: true })
|
||||||
|
}),
|
||||||
|
choices: new fields.TypedObjectField(
|
||||||
|
new fields.TypedObjectField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
tier: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
minCost: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
amount: new fields.NumberField({ integer: true }),
|
||||||
|
value: new fields.StringField(),
|
||||||
|
data: new fields.ArrayField(new fields.StringField()),
|
||||||
|
secondaryData: new fields.TypedObjectField(new fields.StringField()),
|
||||||
|
type: new fields.StringField({ required: true })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get nrSelections() {
|
||||||
|
const selections = Object.keys(this.choices).reduce((acc, choiceKey) => {
|
||||||
|
const choice = this.choices[choiceKey];
|
||||||
|
acc += Object.values(choice).reduce((acc, x) => acc + x.minCost, 0);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
selections: selections,
|
||||||
|
available: this.maxSelections - selections
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -241,6 +241,11 @@ export default class DhpActor extends Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.system.companion) {
|
||||||
|
this.system.companion.updateLevel(usedLevel);
|
||||||
|
}
|
||||||
|
|
||||||
this.sheet.render();
|
this.sheet.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
module/documents/collections/_module.mjs
Normal file
1
module/documents/collections/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as DhActorCollection } from './actorCollection.mjs';
|
||||||
14
module/documents/collections/actorCollection.mjs
Normal file
14
module/documents/collections/actorCollection.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
export default class DhActorCollection extends foundry.documents.collections.Actors {
|
||||||
|
/** Ensure companions are initialized after all other subtypes. */
|
||||||
|
_initialize() {
|
||||||
|
super._initialize();
|
||||||
|
const companions = [];
|
||||||
|
for (const actor of this.values()) {
|
||||||
|
if (actor.type === 'companion') companions.push(actor);
|
||||||
|
}
|
||||||
|
for (const actor of companions) {
|
||||||
|
this.delete(actor.id);
|
||||||
|
this.set(actor.id, actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -212,6 +212,7 @@ export async function runMigrations() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundry.utils.isNewerVersion('1.5.5', lastMigrationVersion)) {
|
if (foundry.utils.isNewerVersion('1.5.5', lastMigrationVersion)) {
|
||||||
|
/* Clear out Environments that were added directly from compendium */
|
||||||
for (const scene of game.scenes) {
|
for (const scene of game.scenes) {
|
||||||
if (!scene.flags.daggerheart) continue;
|
if (!scene.flags.daggerheart) continue;
|
||||||
const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
const systemData = new game.system.api.data.scenes.DHScene(scene.flags.daggerheart);
|
||||||
|
|
@ -226,6 +227,25 @@ export async function runMigrations() {
|
||||||
|
|
||||||
lastMigrationVersion = '1.5.5';
|
lastMigrationVersion = '1.5.5';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (foundry.utils.isNewerVersion('1.6.0', lastMigrationVersion)) {
|
||||||
|
/* Delevel any companions that are higher level than their partner character */
|
||||||
|
for (const companion of game.actors.filter(x => x.type === 'companion')) {
|
||||||
|
if (companion.system.levelData.level.current <= 1) continue;
|
||||||
|
|
||||||
|
if (!companion.system.partner) {
|
||||||
|
await companion.updateLevel(1);
|
||||||
|
} else {
|
||||||
|
const endLevel = companion.system.partner.system.levelData.level.current;
|
||||||
|
if (endLevel < companion.system.levelData.level.current) {
|
||||||
|
companion.system.levelData.level.changed = companion.system.levelData.level.current;
|
||||||
|
await companion.updateLevel(endLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMigrationVersion = '1.6.0';
|
||||||
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,51 @@
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"effects": [],
|
"effects": [
|
||||||
|
{
|
||||||
|
"name": "Advanced Training",
|
||||||
|
"type": "base",
|
||||||
|
"system": {
|
||||||
|
"rangeDependence": {
|
||||||
|
"enabled": false,
|
||||||
|
"type": "withinRange",
|
||||||
|
"target": "hostile",
|
||||||
|
"range": "melee"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_id": "bKOuMxhB2Jth3j2T",
|
||||||
|
"img": "icons/creatures/mammals/wolf-howl-moon-gray.webp",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"key": "system.companionData.levelupChoices",
|
||||||
|
"mode": 2,
|
||||||
|
"value": "2",
|
||||||
|
"priority": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disabled": false,
|
||||||
|
"duration": {
|
||||||
|
"startTime": null,
|
||||||
|
"combat": null,
|
||||||
|
"seconds": null,
|
||||||
|
"rounds": null,
|
||||||
|
"turns": null,
|
||||||
|
"startRound": null,
|
||||||
|
"startTurn": null
|
||||||
|
},
|
||||||
|
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Choose two additional level-up options for your companion.</span></p>",
|
||||||
|
"origin": null,
|
||||||
|
"tint": "#ffffff",
|
||||||
|
"transfer": true,
|
||||||
|
"statuses": [],
|
||||||
|
"sort": 0,
|
||||||
|
"flags": {},
|
||||||
|
"_stats": {
|
||||||
|
"compendiumSource": null
|
||||||
|
},
|
||||||
|
"_key": "!items.effects!uGcs785h94RMtueH.bKOuMxhB2Jth3j2T"
|
||||||
|
}
|
||||||
|
],
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,51 @@
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"effects": [],
|
"effects": [
|
||||||
|
{
|
||||||
|
"name": "Expert Training",
|
||||||
|
"type": "base",
|
||||||
|
"system": {
|
||||||
|
"rangeDependence": {
|
||||||
|
"enabled": false,
|
||||||
|
"type": "withinRange",
|
||||||
|
"target": "hostile",
|
||||||
|
"range": "melee"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_id": "rknTONvaUDZ2Yz1W",
|
||||||
|
"img": "icons/creatures/mammals/dog-husky-white-blue.webp",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"key": "system.companionData.levelupChoices",
|
||||||
|
"mode": 2,
|
||||||
|
"value": "1",
|
||||||
|
"priority": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disabled": false,
|
||||||
|
"duration": {
|
||||||
|
"startTime": null,
|
||||||
|
"combat": null,
|
||||||
|
"seconds": null,
|
||||||
|
"rounds": null,
|
||||||
|
"turns": null,
|
||||||
|
"startRound": null,
|
||||||
|
"startTurn": null
|
||||||
|
},
|
||||||
|
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Choose an additional level-up option for your companion.</span></p>",
|
||||||
|
"origin": null,
|
||||||
|
"tint": "#ffffff",
|
||||||
|
"transfer": true,
|
||||||
|
"statuses": [],
|
||||||
|
"sort": 0,
|
||||||
|
"flags": {},
|
||||||
|
"_stats": {
|
||||||
|
"compendiumSource": null
|
||||||
|
},
|
||||||
|
"_key": "!items.effects!iCXtOWBKv1FdKdWz.rknTONvaUDZ2Yz1W"
|
||||||
|
}
|
||||||
|
],
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@
|
||||||
.input-section {
|
.input-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "daggerheart",
|
"id": "daggerheart",
|
||||||
"title": "Daggerheart",
|
"title": "Daggerheart",
|
||||||
"description": "An unofficial implementation of the Daggerheart system",
|
"description": "An unofficial implementation of the Daggerheart system",
|
||||||
"version": "1.5.5",
|
"version": "1.6.0",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13.346",
|
"minimum": "13.346",
|
||||||
"verified": "13.351",
|
"verified": "13.351",
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
<h3 class='label'>
|
<h3 class='label'>
|
||||||
{{localize 'DAGGERHEART.GENERAL.level'}}
|
{{localize 'DAGGERHEART.GENERAL.level'}}
|
||||||
<div class="input-section">
|
<div class="input-section">
|
||||||
{{#if document.system.levelData.canLevelUp}}
|
{{#if document.system.canLevelUp}}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="level-button glow" data-tooltip="{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}"
|
class="level-button glow" data-tooltip="{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}"
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<span {{#unless document.system.partner}}data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.companionPartnerLevelBlock"}}"{{/unless}}>
|
<span {{#unless document.system.partner}}data-tooltip="{{localize "DAGGERHEART.UI.Tooltip.companionPartnerLevelBlock"}}"{{/unless}}>
|
||||||
<input type="text" data-dtype="Number" class="level-value" value={{document.system.levelData.level.changed}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} {{disabled (not document.system.partner)}} />
|
{{document.system.levelData.level.changed}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue