mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
* - Move all DataModel item files to a new 'items' subfolder for better organization - Add _module.mjs file to simplify imports - Update all import paths - Rename class for use the new acronym DH * FIX: remove unnecessary import * FEAT: BaseDataItem class add TODO comments for future improvements FIX: Remove effect field on template FIX: remove unused DhpEffects file * FEAT: new FormulaField class FEAT: add getRollData on BaseDataItem Class FEAT: weapon FIX: remove inventoryWeapon field on Weapon Data Model * FEAT: add class prepareBaseData for domains * FEAT: new ForeignDocumentUUIDField FIX: Remove unnecessary fields FEAT: use ForeignDocumentUUIDField in the Item Class DataModel * FIX: remove wrong option in String Field * FIX: remove unused import * FIX: ADD htmlFields description in manifest * FIX: minor fixes * REFACTOR: rename folder `data/items` -> `data/item` REFACTOR: rename folder `data/messages` -> `data/chat-message`. * FIX: imports FIX: items sheet new paths FIX: ItemDataModelMetadata type jsdoc * FEAT: formatting code FIX: fix fields used FEAT: add jsdoc * 110 - Class Data Model (#111) * Added PreCreate/Create/Delete logic for Class/Subclass and set it as foreignUUID fields in PC * Moved methods into TypedModelData * Simplified Subclass * Fixed up data model and a basic placeholder template (#117) * 118 - adversary data model (#119) * Fixed datamodel and set up basic template in new style * Added in a temp attack button, because why not * Restored HitPoints counting up * 113 - Character Data Model (#114) * Improved Character datamodel * Removed additional unneccessary getters * Preliminary cleanup in the class sheet * Cleanup of 'pc' references * Corrected Duality rolling from Character * Fix to damage roll * Added a basic BaseDataActor data model * Gathered exports * getRollData recursion fix * Feature/112 items use action datamodel (#127) * Create new actions classes * actions types - attack roll * fixes before merge * First PR * Add daggerheart.css to gitignore * Update ToDo * Remove console log * Fixed chat /dr roll * Remove jQuery * Fixed so the different chat themes work again * Fixed duality roll buttons * Fix to advantage/disadvantage shortcut * Extand action to other item types * Roll fixes * Fixes to adversary rolls * resources * Fixed adversary dice --------- Co-authored-by: WBHarry <williambjrklund@gmail.com> * Feature/116-implementation-of-pseudo-documents (#125) * FEAT: add baseDataModel logic * FEAT: new PseudoDocumentsField FIX: BasePseudoDocument 's getEmbeddedDocument * FEAT: PseudoDocument class * FEAT: add TypedPseudoDocument REFACTOR: PreudoDocument FIX: Typos Bug * FIX: CONFIG types * FEAT: basic PseudoDocumentSheet * FIX: remove schema ADD: input of example --------- Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> Co-authored-by: WBHarry <williambjrklund@gmail.com> * Levelup Followup (#126) * Levelup applies bonuses to character * Added visualisation of domain card levels * Fixed domaincard level max for selections in a tier * A trait can now only be level up once within the same tier --------- Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com> Co-authored-by: Dapoulp <74197441+Dapoulp@users.noreply.github.com>
350 lines
15 KiB
JavaScript
350 lines
15 KiB
JavaScript
import { abilities } from '../config/actorConfig.mjs';
|
|
import { chunkify } from '../helpers/utils.mjs';
|
|
import { LevelOptionType } from './levelTier.mjs';
|
|
|
|
export class DhLevelup extends foundry.abstract.DataModel {
|
|
static initializeData(levelTierData, pcLevelData) {
|
|
const startLevel = pcLevelData.level.current + 1;
|
|
const currentLevel = pcLevelData.level.current + 1;
|
|
const endLevel = pcLevelData.level.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 domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => {
|
|
const id = foundry.utils.randomID();
|
|
acc[id] = { uuid: null, itemUuid: null, level: i };
|
|
return acc;
|
|
}, {});
|
|
|
|
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.availableOptions, {
|
|
...initialAchievements,
|
|
experiences,
|
|
domainCards
|
|
});
|
|
}
|
|
|
|
belongingLevels.push(i);
|
|
}
|
|
|
|
tiers[key] = {
|
|
name: tier.name,
|
|
belongingLevels: belongingLevels,
|
|
options: Object.keys(tier.options).reduce((acc, key) => {
|
|
acc[key] = tier.options[key].toObject();
|
|
return acc;
|
|
}, {})
|
|
};
|
|
});
|
|
|
|
return {
|
|
tiers,
|
|
levels,
|
|
startLevel,
|
|
currentLevel,
|
|
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':
|
|
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: 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
|
|
};
|
|
}
|
|
}
|