mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Levelup Remake (#100)
* Set up DhLevelTier datamodel * Added Levelup data model and started at the render * Fixed data handling in the LevelUp view * Added back the save function * Finalised levelup selections and propagating to PC * Added level advancement selection data * Added DomainCard selection * Css merge commit * Added PC level/delevel benefits of leveling up * Fixed sticky previous selections on continous leveling * Fixed up Summary. Fixed multiclass/subclass blocking on selection * Removed unused level.hbs * Fixed attribute base for PC * Improved naming of attribute properties * Renamed/structured resources/evasion/proficiency * Improved trait marking * Rework to level up once at a time * Added markers * Removed tabs when in Summary * Fixed multilevel buttons * Improved multiclass/subclass recognition * Fixed tagify error on selection * Review fixes
This commit is contained in:
parent
47a6abddfb
commit
a92221778e
41 changed files with 3279 additions and 1283 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,5 @@
|
|||
import { DualityRollColor } from '../config/settingsConfig.mjs';
|
||||
import { defaultLevelTiers, DhLevelTiers } from '../data/levelTier.mjs';
|
||||
import DhAppearance from '../data/settings/Appearance.mjs';
|
||||
import DHAppearanceSettings from './settings/appearanceSettings.mjs';
|
||||
import DhVariantRules from '../data/settings/VariantRules.mjs';
|
||||
|
|
@ -268,6 +270,23 @@ export const registerDHSettings = () => {
|
|||
default: DhAppearance.defaultSchema
|
||||
});
|
||||
|
||||
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.DualityRollColor, {
|
||||
name: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Name'),
|
||||
hint: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Hint'),
|
||||
scope: 'world',
|
||||
config: true,
|
||||
type: Number,
|
||||
choices: Object.values(DualityRollColor),
|
||||
default: DualityRollColor.colorful.value
|
||||
});
|
||||
|
||||
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers, {
|
||||
scope: 'world',
|
||||
config: false,
|
||||
type: DhLevelTiers,
|
||||
default: defaultLevelTiers
|
||||
});
|
||||
|
||||
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.Automation.Name, {
|
||||
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Name'),
|
||||
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Label'),
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
name: x.actor.name,
|
||||
img: x.actor.img,
|
||||
difficulty: x.actor.system.difficulty,
|
||||
evasion: x.actor.system.evasion
|
||||
evasion: x.actor.system.evasion.value
|
||||
}));
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { capitalize } from '../../helpers/utils.mjs';
|
||||
import DhpDeathMove from '../deathMove.mjs';
|
||||
import DhpDowntime from '../downtime.mjs';
|
||||
import DhpLevelup from '../levelup.mjs';
|
||||
import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs';
|
||||
import DaggerheartSheet from './daggerheart-sheet.mjs';
|
||||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
import DhlevelUp from '../levelup.mjs';
|
||||
|
||||
const { ActorSheetV2 } = foundry.applications.sheets;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
|
@ -167,13 +167,23 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
$(htmlElement).find('.attribute-value').on('change', this.attributeChange.bind(this));
|
||||
$(htmlElement).find('.tab-selector').on('click', this.tabSwitch.bind(this));
|
||||
$(htmlElement).find('.level-title.levelup').on('click', this.openLevelUp.bind(this));
|
||||
$(htmlElement).find('.feature-input').on('change', this.onFeatureInputBlur.bind(this));
|
||||
$(htmlElement).find('.experience-description').on('change', this.experienceDescriptionChange.bind(this));
|
||||
$(htmlElement).find('.experience-value').on('change', this.experienceValueChange.bind(this));
|
||||
$(htmlElement).find('[data-item]').on('change', this.itemUpdate.bind(this));
|
||||
htmlElement
|
||||
.querySelectorAll('.attribute-value')
|
||||
.forEach(element => element.addEventListener('change', this.attributeChange.bind(this)));
|
||||
htmlElement
|
||||
.querySelectorAll('.tab-selector')
|
||||
.forEach(element => element.addEventListener('click', this.tabSwitch.bind(this)));
|
||||
htmlElement.querySelector('.level-title.levelup')?.addEventListener('click', this.openLevelUp.bind(this));
|
||||
htmlElement
|
||||
.querySelectorAll('.feature-input')
|
||||
.forEach(element => element.addEventListener('change', this.onFeatureInputBlur.bind(this)));
|
||||
htmlElement
|
||||
.querySelectorAll('.experience-description')
|
||||
.forEach(element => element.addEventListener('change', this.experienceDescriptionChange.bind(this)));
|
||||
htmlElement
|
||||
.querySelectorAll('.experience-value')
|
||||
.forEach(element => element.addEventListener('change', this.experienceValueChange.bind(this)));
|
||||
htmlElement.querySelector('.level-value').addEventListener('change', this.onLevelChange.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
|
|
@ -188,7 +198,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
context.storyEditor = this.storyEditor;
|
||||
context.multiclassFeatureSetSelected = this.multiclassFeatureSetSelected;
|
||||
|
||||
const selectedAttributes = Object.values(this.document.system.attributes).map(x => x.data.base);
|
||||
const selectedAttributes = Object.values(this.document.system.traits).map(x => x.base);
|
||||
context.abilityScoreArray = JSON.parse(
|
||||
await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray)
|
||||
).reduce((acc, x) => {
|
||||
|
|
@ -215,9 +225,9 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
: {};
|
||||
|
||||
context.attributes = Object.keys(this.document.system.attributes).reduce((acc, key) => {
|
||||
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
...this.document.system.attributes[key],
|
||||
...this.document.system.traits[key],
|
||||
name: game.i18n.localize(SYSTEM.ACTOR.abilities[key].name),
|
||||
verbs: SYSTEM.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x))
|
||||
};
|
||||
|
|
@ -479,7 +489,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
|
||||
async attributeChange(event) {
|
||||
const path = `system.attributes.${event.currentTarget.dataset.attribute}.data.base`;
|
||||
const path = `system.traits.${event.currentTarget.dataset.attribute}.base`;
|
||||
await this.document.update({ [path]: event.currentTarget.value });
|
||||
}
|
||||
|
||||
|
|
@ -531,21 +541,21 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
|
||||
static async toggleAttributeMark(_, button) {
|
||||
const attribute = this.document.system.attributes[button.dataset.attribute];
|
||||
const attribute = this.document.system.traits[button.dataset.attribute];
|
||||
const newMark = this.document.system.availableAttributeMarks
|
||||
.filter(x => x > Math.max.apply(null, this.document.system.attributes[button.dataset.attribute].levelMarks))
|
||||
.filter(x => x > Math.max.apply(null, this.document.system.traits[button.dataset.attribute].levelMarks))
|
||||
.sort((a, b) => (a > b ? 1 : -1))[0];
|
||||
|
||||
if (attribute.levelMark || !newMark) return;
|
||||
|
||||
const path = `system.attributes.${button.dataset.attribute}.levelMarks`;
|
||||
const path = `system.traits.${button.dataset.attribute}.levelMarks`;
|
||||
await this.document.update({ [path]: [...attribute.levelMarks, newMark] });
|
||||
}
|
||||
|
||||
static async toggleHP(_, button) {
|
||||
const healthValue = Number.parseInt(button.dataset.value);
|
||||
const newValue = this.document.system.resources.health.value >= healthValue ? healthValue - 1 : healthValue;
|
||||
await this.document.update({ 'system.resources.health.value': newValue });
|
||||
const newValue = this.document.system.resources.hitPoints.value >= healthValue ? healthValue - 1 : healthValue;
|
||||
await this.document.update({ 'system.resources.hitPoints.value': newValue });
|
||||
}
|
||||
|
||||
static async toggleStress(_, button) {
|
||||
|
|
@ -576,7 +586,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
type: weapon.system.damage.type,
|
||||
bonusDamage: this.document.system.bonuses.damage
|
||||
};
|
||||
const modifier = this.document.system.attributes[weapon.system.trait].data.value;
|
||||
const modifier = this.document.system.traits[weapon.system.trait].value;
|
||||
|
||||
const { roll, hope, fear, advantage, disadvantage, modifiers, bonusDamageString } =
|
||||
await this.document.dualityRoll(
|
||||
|
|
@ -592,7 +602,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
name: x.actor.name,
|
||||
img: x.actor.img,
|
||||
difficulty: x.actor.system.difficulty,
|
||||
evasion: x.actor.system.evasion
|
||||
evasion: x.actor.system.evasion.value
|
||||
}));
|
||||
|
||||
const systemData = {
|
||||
|
|
@ -633,7 +643,12 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
|
||||
openLevelUp() {
|
||||
new DhpLevelup(this.document).render(true);
|
||||
if (!this.document.system.class || !this.document.system.subclass) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.missingClassOrSubclass'));
|
||||
return;
|
||||
}
|
||||
|
||||
new DhlevelUp(this.document).render(true);
|
||||
}
|
||||
|
||||
static domainCardsTab(toVault) {
|
||||
|
|
@ -781,7 +796,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
}
|
||||
|
||||
static async makeDeathMove() {
|
||||
if (this.document.system.resources.health.value === this.document.system.resources.health.max) {
|
||||
if (this.document.system.resources.hitPoints.value === this.document.system.resources.hitPoints.max) {
|
||||
await new DhpDeathMove(this.document).render(true);
|
||||
await this.minimize();
|
||||
}
|
||||
|
|
@ -866,6 +881,11 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
|
|||
await item.update({ [name]: event.currentTarget.value });
|
||||
}
|
||||
|
||||
async onLevelChange(event) {
|
||||
await this.document.updateLevel(Number(event.currentTarget.value));
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async deleteItem(_, button) {
|
||||
const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId);
|
||||
await item.delete();
|
||||
|
|
|
|||
|
|
@ -52,31 +52,31 @@ export const abilities = {
|
|||
export const featureProperties = {
|
||||
agility: {
|
||||
name: 'DAGGERHEART.Abilities.agility.name',
|
||||
path: actor => actor.system.attributes.agility.data.value
|
||||
path: actor => actor.system.traits.agility.data.value
|
||||
},
|
||||
strength: {
|
||||
name: 'DAGGERHEART.Abilities.strength.name',
|
||||
path: actor => actor.system.attributes.strength.data.value
|
||||
path: actor => actor.system.traits.strength.data.value
|
||||
},
|
||||
finesse: {
|
||||
name: 'DAGGERHEART.Abilities.finesse.name',
|
||||
path: actor => actor.system.attributes.finesse.data.value
|
||||
path: actor => actor.system.traits.finesse.data.value
|
||||
},
|
||||
instinct: {
|
||||
name: 'DAGGERHEART.Abilities.instinct.name',
|
||||
path: actor => actor.system.attributes.instinct.data.value
|
||||
path: actor => actor.system.traits.instinct.data.value
|
||||
},
|
||||
presence: {
|
||||
name: 'DAGGERHEART.Abilities.presence.name',
|
||||
path: actor => actor.system.attributes.presence.data.value
|
||||
path: actor => actor.system.traits.presence.data.value
|
||||
},
|
||||
knowledge: {
|
||||
name: 'DAGGERHEART.Abilities.knowledge.name',
|
||||
path: actor => actor.system.attributes.knowledge.data.value
|
||||
path: actor => actor.system.traits.knowledge.data.value
|
||||
},
|
||||
spellcastingTrait: {
|
||||
name: 'DAGGERHEART.FeatureProperty.SpellcastingTrait',
|
||||
path: actor => actor.system.attributes[actor.system.subclass.system.spellcastingTrait].data.value
|
||||
path: actor => actor.system.traits[actor.system.subclass.system.spellcastingTrait].data.value
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,19 @@ export const gameSettings = {
|
|||
AbilityArray: 'AbilityArray',
|
||||
RangeMeasurement: 'RangeMeasurement'
|
||||
},
|
||||
DualityRollColor: 'DualityRollColor',
|
||||
LevelTiers: 'LevelTiers',
|
||||
appearance: 'Appearance',
|
||||
variantRules: 'VariantRules'
|
||||
};
|
||||
|
||||
export const DualityRollColor = {
|
||||
colorful: {
|
||||
value: 0,
|
||||
label: 'DAGGERHEART.Settings.DualityRollColor.Options.Colorful'
|
||||
},
|
||||
normal: {
|
||||
value: 1,
|
||||
label: 'DAGGERHEART.Settings.DualityRollColor.Options.Normal'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
340
module/data/levelTier.mjs
Normal file
340
module/data/levelTier.mjs
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
export class DhLevelTiers extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
tiers: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelTier))
|
||||
};
|
||||
}
|
||||
|
||||
get availableChoicesPerLevel() {
|
||||
return Object.values(this.tiers).reduce((acc, tier) => {
|
||||
for (var level = tier.levels.start; level < tier.levels.end + 1; level++) {
|
||||
acc[level] = tier.availableOptions;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
class DhLevelTier extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
name: new fields.StringField({ required: true }),
|
||||
levels: new fields.SchemaField({
|
||||
start: new fields.NumberField({ required: true, integer: true }),
|
||||
end: new fields.NumberField({ required: true, integer: true })
|
||||
}),
|
||||
initialAchievements: new fields.SchemaField({
|
||||
experience: new fields.SchemaField({
|
||||
nr: new fields.NumberField({ required: true, initial: 1 }),
|
||||
modifier: new fields.NumberField({ required: true, initial: 2 })
|
||||
}),
|
||||
proficiency: new fields.NumberField({ integer: true, initial: 1 })
|
||||
}),
|
||||
availableOptions: new fields.NumberField({ required: true, initial: 2 }),
|
||||
domainCardByLevel: new fields.NumberField({ initial: 1 }),
|
||||
options: new fields.TypedObjectField(new fields.EmbeddedDataField(DhLevelOption))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class DhLevelOption extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
label: new fields.StringField({ required: true }),
|
||||
checkboxSelections: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
minCost: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true })
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const LevelOptionType = {
|
||||
trait: {
|
||||
id: 'trait',
|
||||
label: 'Character Trait',
|
||||
dataPath: ''
|
||||
},
|
||||
hitPoint: {
|
||||
id: 'hitPoint',
|
||||
label: 'Hit Points',
|
||||
dataPath: 'resources.hitPoints',
|
||||
dataPathData: {
|
||||
property: 'max',
|
||||
dependencies: ['value']
|
||||
}
|
||||
},
|
||||
stress: {
|
||||
id: 'stress',
|
||||
label: 'Stress',
|
||||
dataPath: 'resources.stress',
|
||||
dataPathData: {
|
||||
property: 'max',
|
||||
dependencies: ['value']
|
||||
}
|
||||
},
|
||||
evasion: {
|
||||
id: 'evasion',
|
||||
label: 'Evasion',
|
||||
dataPath: 'evasion'
|
||||
},
|
||||
proficiency: {
|
||||
id: 'proficiency',
|
||||
label: 'Proficiency'
|
||||
},
|
||||
experience: {
|
||||
id: 'experience',
|
||||
label: 'Experience'
|
||||
},
|
||||
domainCard: {
|
||||
id: 'domainCard',
|
||||
label: 'Domain Card'
|
||||
},
|
||||
subclass: {
|
||||
id: 'subclass',
|
||||
label: 'Subclass'
|
||||
},
|
||||
multiclass: {
|
||||
id: 'multiclass',
|
||||
label: 'Multiclass'
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultLevelTiers = {
|
||||
tiers: {
|
||||
2: {
|
||||
tier: 2,
|
||||
name: 'Tier 2',
|
||||
levels: {
|
||||
start: 2,
|
||||
end: 4
|
||||
},
|
||||
initialAchievements: {
|
||||
experience: {
|
||||
nr: 1,
|
||||
modifier: 2
|
||||
},
|
||||
proficiency: 1
|
||||
},
|
||||
availableOptions: 2,
|
||||
domainCardByLevel: 1,
|
||||
options: {
|
||||
trait: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.trait',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.trait.id,
|
||||
amount: 2
|
||||
},
|
||||
hitPoint: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.hitPoint',
|
||||
checkboxSelections: 2,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.hitPoint.id,
|
||||
value: 1,
|
||||
value: 1
|
||||
},
|
||||
stress: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.stress',
|
||||
checkboxSelections: 2,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.stress.id,
|
||||
value: 1
|
||||
},
|
||||
experience: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.experience',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.experience.id,
|
||||
value: 1,
|
||||
amount: 2
|
||||
},
|
||||
domainCard: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.domainCard',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.domainCard.id,
|
||||
amount: 1
|
||||
},
|
||||
evasion: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.evasion',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.evasion.id,
|
||||
value: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
3: {
|
||||
tier: 3,
|
||||
name: 'Tier 3',
|
||||
levels: {
|
||||
start: 5,
|
||||
end: 7
|
||||
},
|
||||
initialAchievements: {
|
||||
experience: {
|
||||
nr: 1,
|
||||
modifier: 2
|
||||
},
|
||||
proficiency: 1
|
||||
},
|
||||
availableOptions: 2,
|
||||
domainCardByLevel: 1,
|
||||
options: {
|
||||
trait: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.trait',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.trait.id,
|
||||
amount: 2
|
||||
},
|
||||
hitPoint: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.hitPoint',
|
||||
checkboxSelections: 2,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.hitPoint.id,
|
||||
value: 1
|
||||
},
|
||||
stress: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.stress',
|
||||
checkboxSelections: 2,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.stress.id,
|
||||
value: 1
|
||||
},
|
||||
experience: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.experience',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.experience.id,
|
||||
value: 1,
|
||||
amount: 2
|
||||
},
|
||||
domainCard: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.domainCard',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.domainCard.id,
|
||||
amount: 1
|
||||
},
|
||||
evasion: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.evasion',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.evasion.id,
|
||||
value: 1
|
||||
},
|
||||
subclass: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.subclass',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.subclass.id
|
||||
},
|
||||
proficiency: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.proficiency',
|
||||
checkboxSelections: 2,
|
||||
minCost: 2,
|
||||
type: LevelOptionType.proficiency.id,
|
||||
value: 1
|
||||
},
|
||||
multiclass: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.multiclass',
|
||||
checkboxSelections: 2,
|
||||
minCost: 2,
|
||||
type: LevelOptionType.multiclass.id
|
||||
}
|
||||
}
|
||||
},
|
||||
4: {
|
||||
tier: 4,
|
||||
name: 'Tier 4',
|
||||
levels: {
|
||||
start: 8,
|
||||
end: 10
|
||||
},
|
||||
initialAchievements: {
|
||||
experience: {
|
||||
nr: 1,
|
||||
modifier: 2
|
||||
},
|
||||
proficiency: 1
|
||||
},
|
||||
availableOptions: 2,
|
||||
domainCardByLevel: 1,
|
||||
options: {
|
||||
trait: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.trait',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.trait.id,
|
||||
amount: 2
|
||||
},
|
||||
hitPoint: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.hitPoint',
|
||||
checkboxSelections: 2,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.hitPoint.id,
|
||||
value: 1
|
||||
},
|
||||
stress: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.stress',
|
||||
checkboxSelections: 2,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.stress.id,
|
||||
value: 1
|
||||
},
|
||||
experience: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.experience',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.experience.id,
|
||||
value: 1,
|
||||
amount: 2
|
||||
},
|
||||
domainCard: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.domainCard',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.domainCard.id,
|
||||
amount: 1
|
||||
},
|
||||
evasion: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.evasion',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.evasion.id,
|
||||
value: 1
|
||||
},
|
||||
subclass: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.subclass',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.subclass.id
|
||||
},
|
||||
proficiency: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.proficiency',
|
||||
checkboxSelections: 2,
|
||||
minCost: 2,
|
||||
type: LevelOptionType.proficiency.id,
|
||||
value: 1
|
||||
},
|
||||
multiclass: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.multiclass',
|
||||
checkboxSelections: 2,
|
||||
minCost: 2,
|
||||
type: LevelOptionType.multiclass.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
311
module/data/levelup.mjs
Normal file
311
module/data/levelup.mjs
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
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.amount ? checkbox.data.length === checkbox.amount : checkbox.data.length === 1;
|
||||
case 'multiclass':
|
||||
const classSelected = checkbox.data.length === 1;
|
||||
const domainSelected = checkbox.secondaryData;
|
||||
return classSelected && domainSelected;
|
||||
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 classUpgradeChoices() {
|
||||
let subclass = null;
|
||||
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 ?? null,
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
};
|
||||
}
|
||||
if (checkbox.type === 'subclass') {
|
||||
subclass = {
|
||||
tier: checkbox.tier,
|
||||
level: levelKey
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return { subclass, 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, subclass } = this.classUpgradeChoices;
|
||||
return tierKeys.map(tierKey => {
|
||||
const tier = this.tiers[tierKey];
|
||||
const multiclassInTier = multiclass?.tier === Number(tierKey);
|
||||
const subclassInTier = subclass?.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;
|
||||
});
|
||||
return {
|
||||
label: game.i18n.localize(option.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.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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +1,29 @@
|
|||
import { getPathValue, getTier } from '../helpers/utils.mjs';
|
||||
import { getPathValue } from '../helpers/utils.mjs';
|
||||
import { LevelOptionType } from './levelTier.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
const attributeField = () =>
|
||||
new fields.SchemaField({
|
||||
data: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
base: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
actualValue: new fields.NumberField({ initial: 0, integer: true }),
|
||||
overrideValue: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
levelMarks: new fields.ArrayField(new fields.NumberField({ nullable: true, initial: null, integer: true })),
|
||||
levelMark: new fields.NumberField({ nullable: true, initial: null, integer: true })
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
base: new fields.NumberField({ initial: 0, integer: true }),
|
||||
tierMarked: new fields.BooleanField({ required: true, initial: false })
|
||||
});
|
||||
|
||||
const levelUpTier = () => ({
|
||||
attributes: new fields.TypedObjectField(new fields.BooleanField()),
|
||||
hitPointSlots: new fields.TypedObjectField(new fields.BooleanField()),
|
||||
stressSlots: new fields.TypedObjectField(new fields.BooleanField()),
|
||||
experiences: new fields.TypedObjectField(new fields.ArrayField(new fields.StringField({}))),
|
||||
proficiency: new fields.TypedObjectField(new fields.BooleanField()),
|
||||
armorOrEvasionSlot: new fields.TypedObjectField(new fields.StringField({})),
|
||||
subclass: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
multiclass: new fields.BooleanField(),
|
||||
feature: new fields.StringField({})
|
||||
})
|
||||
),
|
||||
multiclass: new fields.TypedObjectField(new fields.BooleanField())
|
||||
});
|
||||
const resourceField = max =>
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
min: new fields.NumberField({ initial: 0, integer: true }),
|
||||
baseMax: new fields.NumberField({ initial: max, integer: true })
|
||||
});
|
||||
|
||||
export default class DhpPC extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
return {
|
||||
resources: new fields.SchemaField({
|
||||
health: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
min: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 6, integer: true })
|
||||
}),
|
||||
stress: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
min: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 6, integer: true })
|
||||
}),
|
||||
hitPoints: resourceField(6),
|
||||
stress: resourceField(6),
|
||||
hope: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: -1, integer: true }), // FIXME. Logic is gte and needs -1 in PC/Hope. Change to 0
|
||||
min: new fields.NumberField({ initial: 0, integer: true })
|
||||
|
|
@ -61,7 +40,7 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
})
|
||||
)
|
||||
}),
|
||||
attributes: new fields.SchemaField({
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField(),
|
||||
strength: attributeField(),
|
||||
finesse: attributeField(),
|
||||
|
|
@ -70,22 +49,22 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
knowledge: attributeField()
|
||||
}),
|
||||
proficiency: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 1, integer: true }),
|
||||
min: new fields.NumberField({ initial: 1, integer: true }),
|
||||
max: new fields.NumberField({ initial: 6, integer: true })
|
||||
base: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
||||
bonus: new fields.NumberField({ required: true, initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
||||
experiences: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
id: new fields.StringField({ required: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
description: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||
}),
|
||||
{
|
||||
initial: [
|
||||
{ id: foundry.utils.randomID(), level: 1, description: '', value: 2 },
|
||||
{ id: foundry.utils.randomID(), level: 1, description: '', value: 2 }
|
||||
{ id: foundry.utils.randomID(), description: '', value: 2 },
|
||||
{ id: foundry.utils.randomID(), description: '', value: 2 }
|
||||
]
|
||||
}
|
||||
),
|
||||
|
|
@ -100,30 +79,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
maxLoadout: new fields.NumberField({ initial: 2, integer: true }),
|
||||
maxCards: new fields.NumberField({ initial: 2, integer: true })
|
||||
}),
|
||||
levelData: new fields.SchemaField({
|
||||
currentLevel: new fields.NumberField({ initial: 1, integer: true }),
|
||||
changedLevel: new fields.NumberField({ initial: 1, integer: true }),
|
||||
levelups: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
tier1: new fields.SchemaField({
|
||||
...levelUpTier()
|
||||
}),
|
||||
tier2: new fields.SchemaField(
|
||||
{
|
||||
...levelUpTier()
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
tier3: new fields.SchemaField(
|
||||
{
|
||||
...levelUpTier()
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
)
|
||||
})
|
||||
)
|
||||
}),
|
||||
story: new fields.SchemaField({
|
||||
background: new fields.HTMLField(),
|
||||
appearance: new fields.HTMLField(),
|
||||
|
|
@ -140,15 +95,11 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
armorMarks: new fields.SchemaField({
|
||||
max: new fields.NumberField({ initial: 6, integer: true }),
|
||||
value: new fields.NumberField({ initial: 0, integer: true })
|
||||
})
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhPCLevelData)
|
||||
};
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
// return Object.values(this.levels.data).some(x => !x.completed);
|
||||
return this.levelData.currentLevel !== this.levelData.changedLevel;
|
||||
}
|
||||
|
||||
get tier() {
|
||||
return this.#getTier(this.levelData.currentLevel);
|
||||
}
|
||||
|
|
@ -281,42 +232,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
}
|
||||
|
||||
get inventoryWeapons() {
|
||||
const inventoryWeaponFirst = this.parent.items.find(x => x.type === 'weapon' && x.system.inventoryWeapon === 1);
|
||||
const inventoryWeaponSecond = this.parent.items.find(
|
||||
x => x.type === 'weapon' && x.system.inventoryWeapon === 2
|
||||
);
|
||||
return {
|
||||
first: this.#weaponData(inventoryWeaponFirst),
|
||||
second: this.#weaponData(inventoryWeaponSecond)
|
||||
};
|
||||
}
|
||||
|
||||
get totalAttributeMarks() {
|
||||
return Object.keys(this.levelData.levelups).reduce((nr, level) => {
|
||||
const nrAttributeMarks = Object.keys(this.levelData.levelups[level]).reduce((nr, tier) => {
|
||||
nr += Object.keys(this.levelData.levelups[level][tier]?.attributes ?? {}).length * 2;
|
||||
|
||||
return nr;
|
||||
}, 0);
|
||||
|
||||
nr.push(...Array(nrAttributeMarks).fill(Number.parseInt(level)));
|
||||
|
||||
return nr;
|
||||
}, []);
|
||||
}
|
||||
|
||||
get availableAttributeMarks() {
|
||||
const attributeMarks = Object.keys(this.attributes).flatMap(y => this.attributes[y].levelMarks);
|
||||
return this.totalAttributeMarks.reduce((acc, attribute) => {
|
||||
if (!attributeMarks.findSplice(x => x === attribute)) {
|
||||
acc.push(attribute);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
get effects() {
|
||||
return this.parent.items.reduce((acc, item) => {
|
||||
const effects = item.system.effectData;
|
||||
|
|
@ -367,141 +282,37 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
: null;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
this.resources.hitPoints.max = this.resources.hitPoints.baseMax + this.resources.hitPoints.bonus;
|
||||
this.resources.stress.max = this.resources.stress.baseMax + this.resources.stress.bonus;
|
||||
this.evasion.value = (this.class?.system?.evasion ?? 0) + this.evasion.bonus;
|
||||
this.proficiency.value = this.proficiency.base + this.proficiency.bonus;
|
||||
|
||||
for (var attributeKey in this.traits) {
|
||||
const attribute = this.traits[attributeKey];
|
||||
attribute.value = attribute.base + attribute.bonus;
|
||||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.resources.hope.max = 6 - this.story.scars.length;
|
||||
if (this.resources.hope.value >= this.resources.hope.max) {
|
||||
this.resources.hope.value = Math.max(this.resources.hope.max - 1, 0);
|
||||
}
|
||||
|
||||
for (var attributeKey in this.attributes) {
|
||||
const attribute = this.attributes[attributeKey];
|
||||
const armor = this.armor;
|
||||
this.damageThresholds = {
|
||||
major: armor
|
||||
? armor.system.baseThresholds.major + this.levelData.level.current
|
||||
: this.levelData.level.current,
|
||||
severe: armor
|
||||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
||||
: this.levelData.level.current * 2
|
||||
};
|
||||
|
||||
attribute.levelMark = attribute.levelMarks.find(x => this.isSameTier(x)) ?? null;
|
||||
|
||||
const actualValue = attribute.data.base + attribute.levelMarks.length + attribute.data.bonus;
|
||||
attribute.data.actualValue = actualValue;
|
||||
attribute.data.value = attribute.data.overrideValue
|
||||
? attribute.data.overrideValue
|
||||
: attribute.data.actualValue;
|
||||
}
|
||||
|
||||
this.evasion = this.class?.system?.evasion ?? 0;
|
||||
// this.armor.value = this.activeArmor?.baseScore ?? 0;
|
||||
this.damageThresholds = this.computeDamageThresholds();
|
||||
|
||||
this.applyLevels();
|
||||
this.applyEffects();
|
||||
}
|
||||
|
||||
computeDamageThresholds() {
|
||||
// TODO: missing weapon features and domain cards calculation
|
||||
if (!this.armor) {
|
||||
return {
|
||||
major: this.levelData.currentLevel,
|
||||
severe: this.levelData.currentLevel * 2
|
||||
};
|
||||
}
|
||||
const {
|
||||
baseThresholds: { major = 0, severe = 0 }
|
||||
} = this.armor.system;
|
||||
return {
|
||||
major: major + this.levelData.currentLevel,
|
||||
severe: severe + this.levelData.currentLevel
|
||||
};
|
||||
}
|
||||
|
||||
applyLevels() {
|
||||
let healthBonus = 0,
|
||||
stressBonus = 0,
|
||||
proficiencyBonus = 0,
|
||||
evasionBonus = 0,
|
||||
armorBonus = 0;
|
||||
let experienceBonuses = {};
|
||||
let advancementFirst = null,
|
||||
advancementSecond = null;
|
||||
for (var level in this.levelData.levelups) {
|
||||
var levelData = this.levelData.levelups[level];
|
||||
for (var tier in levelData) {
|
||||
var tierData = levelData[tier];
|
||||
if (tierData) {
|
||||
healthBonus += Object.keys(tierData.hitPointSlots).length;
|
||||
stressBonus += Object.keys(tierData.stressSlots).length;
|
||||
proficiencyBonus += Object.keys(tierData.proficiency).length;
|
||||
advancementFirst =
|
||||
Object.keys(tierData.subclass).length > 0 && level >= 5 && level <= 7
|
||||
? { ...tierData.subclass[0], tier: getTier(Number.parseInt(level), true) }
|
||||
: advancementFirst;
|
||||
advancementSecond =
|
||||
Object.keys(tierData.subclass).length > 0 && level >= 8 && level <= 10
|
||||
? { ...tierData.subclass[0], tier: getTier(Number.parseInt(level), true) }
|
||||
: advancementSecond;
|
||||
|
||||
for (var index in Object.keys(tierData.experiences)) {
|
||||
for (var experienceKey in tierData.experiences[index]) {
|
||||
var experience = tierData.experiences[index][experienceKey];
|
||||
experienceBonuses[experience] = experienceBonuses[experience]
|
||||
? experienceBonuses[experience] + 1
|
||||
: 1;
|
||||
}
|
||||
}
|
||||
|
||||
evasionBonus += Object.keys(tierData.armorOrEvasionSlot).filter(
|
||||
x => tierData.armorOrEvasionSlot[x] === 'evasion'
|
||||
).length;
|
||||
armorBonus += Object.keys(tierData.armorOrEvasionSlot).filter(
|
||||
x => tierData.armorOrEvasionSlot[x] === 'armor'
|
||||
).length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.resources.health.max += healthBonus;
|
||||
this.resources.stress.max += stressBonus;
|
||||
this.proficiency.value += proficiencyBonus;
|
||||
this.evasion += evasionBonus;
|
||||
this.armorMarks = {
|
||||
max: this.armor ? this.armor.system.marks.max + armorBonus : 0,
|
||||
value: this.armor ? this.armor.system.marks.value : 0
|
||||
};
|
||||
|
||||
this.experiences = this.experiences.map(x => ({ ...x, value: x.value + (experienceBonuses[x.id] ?? 0) }));
|
||||
|
||||
const subclassFeatures = this.subclassFeatures;
|
||||
if (advancementFirst) {
|
||||
if (advancementFirst.multiclass) {
|
||||
this.multiclassSubclass.system[`${advancementFirst.feature}Feature`].unlocked = true;
|
||||
this.multiclassSubclass.system[`${advancementFirst.feature}Feature`].tier = advancementFirst.tier;
|
||||
subclassFeatures.multiclassSubclass[advancementFirst.feature].forEach(x => (x.system.disabled = false));
|
||||
} else {
|
||||
this.subclass.system[`${advancementFirst.feature}Feature`].unlocked = true;
|
||||
this.subclass.system[`${advancementFirst.feature}Feature`].tier = advancementFirst.tier;
|
||||
subclassFeatures.subclass[advancementFirst.feature].forEach(x => (x.system.disabled = false));
|
||||
}
|
||||
}
|
||||
if (advancementSecond) {
|
||||
if (advancementSecond.multiclass) {
|
||||
this.multiclassSubclass.system[`${advancementSecond.feature}Feature`].unlocked = true;
|
||||
this.multiclassSubclass.system[`${advancementSecond.feature}Feature`].tier = advancementSecond.tier;
|
||||
subclassFeatures.multiclassSubclass[advancementSecond.feature].forEach(
|
||||
x => (x.system.disabled = false)
|
||||
);
|
||||
} else {
|
||||
this.subclass.system[`${advancementSecond.feature}Feature`].unlocked = true;
|
||||
this.subclass.system[`${advancementSecond.feature}Feature`].tier = advancementSecond.tier;
|
||||
subclassFeatures.subclass[advancementSecond.feature].forEach(x => (x.system.disabled = false));
|
||||
}
|
||||
}
|
||||
|
||||
//General progression
|
||||
for (var i = 0; i < this.levelData.currentLevel; i++) {
|
||||
const tier = getTier(i + 1);
|
||||
if (tier !== 'tier0') {
|
||||
this.domainData.maxLoadout = Math.min(this.domainData.maxLoadout + 1, 5);
|
||||
this.domainData.maxCards += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyEffects() {
|
||||
const effects = this.effects;
|
||||
for (var key in effects) {
|
||||
|
|
@ -509,10 +320,10 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
for (var effect of effectType) {
|
||||
switch (key) {
|
||||
case SYSTEM.EFFECTS.effectTypes.health.id:
|
||||
this.resources.health.max += effect.value.valueData.value;
|
||||
this.resources.hitPoints.bonus += effect.value.valueData.value;
|
||||
break;
|
||||
case SYSTEM.EFFECTS.effectTypes.stress.id:
|
||||
this.resources.stress.max += effect.value.valueData.value;
|
||||
this.resources.stress.bonus += effect.value.valueData.value;
|
||||
break;
|
||||
case SYSTEM.EFFECTS.effectTypes.damage.id:
|
||||
this.bonuses.damage.push({
|
||||
|
|
@ -539,10 +350,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
return twoHanded ? 'twoHanded' : oneHanded ? 'oneHanded' : null;
|
||||
}
|
||||
|
||||
isSameTier(level) {
|
||||
return this.#getTier(this.levelData.currentLevel) === this.#getTier(level);
|
||||
}
|
||||
|
||||
#getTier(level) {
|
||||
if (level >= 8) return 3;
|
||||
else if (level >= 5) return 2;
|
||||
|
|
@ -550,3 +357,55 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
|
|||
else return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class DhPCLevelData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
return {
|
||||
level: new fields.SchemaField({
|
||||
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
}),
|
||||
levelups: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
achievements: new fields.SchemaField(
|
||||
{
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
modifier: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
domainCards: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
uuid: new fields.StringField({ required: true }),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
),
|
||||
proficiency: new fields.NumberField({ integer: true })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
selections: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
optionKey: new fields.StringField({ required: true }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
checkboxNr: new fields.NumberField({ required: true, integer: true }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
minCost: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
data: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
secondaryData: new fields.StringField(),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.level.current < this.level.changed;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
|||
|
||||
export default class DhpActor extends Actor {
|
||||
async _preCreate(data, options, user) {
|
||||
if ( (await super._preCreate(data, options, user)) === false ) return false;
|
||||
|
||||
if ((await super._preCreate(data, options, user)) === false) return false;
|
||||
|
||||
// Configure prototype token settings
|
||||
const prototypeToken = {};
|
||||
if ( this.type === "pc" ) Object.assign(prototypeToken, {
|
||||
sight: { enabled: true }, actorLink: true, disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
||||
});
|
||||
if (this.type === 'pc')
|
||||
Object.assign(prototypeToken, {
|
||||
sight: { enabled: true },
|
||||
actorLink: true,
|
||||
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
||||
});
|
||||
this.updateSource({ prototypeToken });
|
||||
}
|
||||
|
||||
|
|
@ -21,46 +24,103 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
|
||||
async _preUpdate(changed, options, user) {
|
||||
//Level Down
|
||||
if (
|
||||
changed.system?.levelData?.changedLevel &&
|
||||
this.system.levelData.currentLevel > changed.system.levelData.changedLevel
|
||||
) {
|
||||
changed.system.levelData.currentLevel = changed.system.levelData.changedLevel;
|
||||
changed.system.levelData.levelups = Object.keys(this.system.levelData.levelups).reduce((acc, x) => {
|
||||
if (x > changed.system.levelData.currentLevel) {
|
||||
acc[`-=${x}`] = null;
|
||||
super._preUpdate(changed, options, user);
|
||||
}
|
||||
|
||||
async updateLevel(newLevel) {
|
||||
if (this.type !== 'pc' || newLevel === this.system.levelData.level.changed) return;
|
||||
|
||||
if (newLevel > this.system.levelData.level.current) {
|
||||
await this.update({ 'system.levelData.level.changed': newLevel });
|
||||
} else {
|
||||
const updatedLevelups = Object.keys(this.system.levelData.levelups).reduce((acc, level) => {
|
||||
if (Number(level) > newLevel) acc[`-=${level}`] = null;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const domainCards = Object.keys(this.system.levelData.levelups)
|
||||
.filter(x => x > newLevel)
|
||||
.flatMap(levelKey => {
|
||||
const level = this.system.levelData.levelups[levelKey];
|
||||
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
|
||||
const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid);
|
||||
return [...achievementCards, ...advancementCards];
|
||||
});
|
||||
|
||||
for (var domainCard of domainCards) {
|
||||
const itemCard = await this.items.find(x => x.uuid === domainCard);
|
||||
itemCard.delete();
|
||||
}
|
||||
|
||||
await this.update({
|
||||
system: {
|
||||
levelData: {
|
||||
level: {
|
||||
current: newLevel,
|
||||
changed: newLevel
|
||||
},
|
||||
levelups: updatedLevelups
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
async levelUp(levelupData) {
|
||||
const levelups = {};
|
||||
for (var levelKey of Object.keys(levelupData)) {
|
||||
const level = levelupData[levelKey];
|
||||
const achievementDomainCards = [];
|
||||
for (var card of Object.values(level.achievements.domainCards)) {
|
||||
const item = await foundry.utils.fromUuid(card.uuid);
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
card.itemUuid = embeddedItem[0].uuid;
|
||||
achievementDomainCards.push(card);
|
||||
}
|
||||
|
||||
changed.system.attributes = Object.keys(this.system.attributes).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
levelMarks: this.system.attributes[key].levelMarks.filter(
|
||||
x => x <= changed.system.levelData.currentLevel
|
||||
)
|
||||
};
|
||||
const selections = [];
|
||||
for (var optionKey of Object.keys(level.choices)) {
|
||||
const selection = level.choices[optionKey];
|
||||
for (var checkboxNr of Object.keys(selection)) {
|
||||
const checkbox = selection[checkboxNr];
|
||||
let itemUuid = null;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
if (checkbox.type === 'domainCard') {
|
||||
const item = await foundry.utils.fromUuid(checkbox.data[0]);
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
itemUuid = embeddedItem[0].uuid;
|
||||
}
|
||||
|
||||
changed.system.experiences = this.system.experiences.filter(
|
||||
x => x.level <= changed.system.levelData.currentLevel
|
||||
);
|
||||
|
||||
if (
|
||||
this.system.multiclass &&
|
||||
this.system.multiclass.system.multiclass > changed.system.levelData.changedLevel
|
||||
) {
|
||||
const multiclassFeatures = this.items.filter(x => x.system.multiclass);
|
||||
for (var feature of multiclassFeatures) {
|
||||
await feature.delete();
|
||||
selections.push({
|
||||
...checkbox,
|
||||
level: Number(levelKey),
|
||||
optionKey: optionKey,
|
||||
checkboxNr: Number(checkboxNr),
|
||||
itemUuid
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
levelups[levelKey] = {
|
||||
achievements: {
|
||||
...level.achievements,
|
||||
domainCards: achievementDomainCards
|
||||
},
|
||||
selections: selections
|
||||
};
|
||||
}
|
||||
|
||||
super._preUpdate(changed, options, user);
|
||||
await this.update({
|
||||
system: {
|
||||
levelData: {
|
||||
level: {
|
||||
current: this.system.levelData.level.changed
|
||||
},
|
||||
levelups: levelups
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async diceRoll(modifier, shiftKey) {
|
||||
|
|
@ -286,9 +346,9 @@ export default class DhpActor extends Actor {
|
|||
: 0;
|
||||
|
||||
const update = {
|
||||
'system.resources.health.value': Math.min(
|
||||
this.system.resources.health.value + hpDamage,
|
||||
this.system.resources.health.max
|
||||
'system.resources.hitPoints.value': Math.min(
|
||||
this.system.resources.hitPoints.value + hpDamage,
|
||||
this.system.resources.hitPoints.max
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -311,9 +371,9 @@ export default class DhpActor extends Actor {
|
|||
switch (type) {
|
||||
case SYSTEM.GENERAL.healingTypes.health.id:
|
||||
update = {
|
||||
'system.resources.health.value': Math.min(
|
||||
this.system.resources.health.value + healing,
|
||||
this.system.resources.health.max
|
||||
'system.resources.hitPoints.value': Math.min(
|
||||
this.system.resources.hitPoints.value + healing,
|
||||
this.system.resources.hitPoints.max
|
||||
)
|
||||
};
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -3,22 +3,19 @@ import { getWidthOfText } from './utils.mjs';
|
|||
export default class RegisterHandlebarsHelpers {
|
||||
static registerHelpers() {
|
||||
Handlebars.registerHelper({
|
||||
looseEq: this.looseEq,
|
||||
times: this.times,
|
||||
join: this.join,
|
||||
add: this.add,
|
||||
subtract: this.subtract,
|
||||
objectSelector: this.objectSelector,
|
||||
includes: this.includes,
|
||||
simpleEditor: this.simpleEditor,
|
||||
debug: this.debug
|
||||
debug: this.debug,
|
||||
signedNumber: this.signedNumber,
|
||||
switch: this.switch,
|
||||
case: this.case
|
||||
});
|
||||
}
|
||||
|
||||
static looseEq(a, b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
static times(nr, block) {
|
||||
var accum = '';
|
||||
for (var i = 0; i < nr; ++i) accum += block.fn(i);
|
||||
|
|
@ -77,33 +74,25 @@ export default class RegisterHandlebarsHelpers {
|
|||
return new Handlebars.SafeString(html);
|
||||
}
|
||||
|
||||
static rangePicker(options) {
|
||||
let { name, value, min, max, step } = options.hash;
|
||||
name = name || 'range';
|
||||
value = value ?? '';
|
||||
if (Number.isNaN(value)) value = '';
|
||||
const html = `<input type="range" name="${name}" value="${value}" min="${min}" max="${max}" step="${step}"/>
|
||||
<span class="range-value">${value}</span>`;
|
||||
return new Handlebars.SafeString(html);
|
||||
}
|
||||
|
||||
static includes(list, item) {
|
||||
return list.includes(item);
|
||||
}
|
||||
|
||||
static simpleEditor(content, options) {
|
||||
const {
|
||||
target,
|
||||
editable = true,
|
||||
button,
|
||||
engine = 'tinymce',
|
||||
collaborate = false,
|
||||
class: cssClass
|
||||
} = options.hash;
|
||||
const config = { name: target, value: content, button, collaborate, editable, engine };
|
||||
const element = foundry.applications.fields.createEditorInput(config);
|
||||
if (cssClass) element.querySelector('.editor-content').classList.add(cssClass);
|
||||
return new Handlebars.SafeString(element.outerHTML);
|
||||
static signedNumber(number) {
|
||||
return number >= 0 ? `+${number}` : number;
|
||||
}
|
||||
|
||||
static switch(value, options) {
|
||||
this.switch_value = value;
|
||||
this.switch_break = false;
|
||||
return options.fn(this);
|
||||
}
|
||||
|
||||
static case(value, options) {
|
||||
if (value == this.switch_value) {
|
||||
this.switch_break = true;
|
||||
return options.fn(this);
|
||||
}
|
||||
}
|
||||
|
||||
static debug(a) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import Tagify from '@yaireo/tagify';
|
||||
|
||||
export const loadCompendiumOptions = async compendiums => {
|
||||
const compendiumValues = [];
|
||||
|
|
@ -131,3 +132,93 @@ export const setDiceSoNiceForDualityRoll = (rollResult, advantage, disadvantage)
|
|||
rollResult.dice[2].options.appearance = diceSoNicePresets.disadvantage;
|
||||
}
|
||||
};
|
||||
|
||||
export const chunkify = (array, chunkSize, mappingFunc) => {
|
||||
var chunkifiedArray = [];
|
||||
for (let i = 0; i < array.length; i += chunkSize) {
|
||||
const chunk = array.slice(i, i + chunkSize);
|
||||
if (mappingFunc) {
|
||||
chunkifiedArray.push(mappingFunc(chunk));
|
||||
} else {
|
||||
chunkifiedArray.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
return chunkifiedArray;
|
||||
};
|
||||
|
||||
export const tagifyElement = (element, options, onChange, tagifyOptions = {}) => {
|
||||
const { maxTags } = tagifyOptions;
|
||||
const tagifyElement = new Tagify(element, {
|
||||
tagTextProp: 'name',
|
||||
enforceWhitelist: true,
|
||||
whitelist: Object.keys(options).map(key => {
|
||||
const option = options[key];
|
||||
return {
|
||||
value: key,
|
||||
name: game.i18n.localize(option.label),
|
||||
src: option.src
|
||||
};
|
||||
}),
|
||||
maxTags: maxTags,
|
||||
dropdown: {
|
||||
mapValueTo: 'name',
|
||||
searchKeys: ['name'],
|
||||
enabled: 0,
|
||||
maxItems: 20,
|
||||
closeOnSelect: true,
|
||||
highlightFirst: false
|
||||
},
|
||||
templates: {
|
||||
tag(tagData) {
|
||||
return `<tag title="${tagData.title || tagData.value}"
|
||||
contenteditable='false'
|
||||
spellcheck='false'
|
||||
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
||||
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
||||
${this.getAttributes(tagData)}>
|
||||
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
||||
<div>
|
||||
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
|
||||
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
|
||||
</div>
|
||||
</tag>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const onSelect = async event => {
|
||||
const inputElement = event.detail.tagify.DOM.originalInput;
|
||||
const selectedOptions = event.detail?.value ? JSON.parse(event.detail.value) : [];
|
||||
|
||||
const unusedDropDownItems = event.detail.tagify.suggestedListItems;
|
||||
const missingOptions = Object.keys(options).filter(x => !unusedDropDownItems.find(item => item.value === x));
|
||||
const removedItem = missingOptions.find(x => !selectedOptions.find(item => item.value === x));
|
||||
const addedItem = removedItem
|
||||
? null
|
||||
: selectedOptions.find(x => !missingOptions.find(item => item === x.value));
|
||||
|
||||
const changedItem = { option: removedItem ?? addedItem.value, removed: Boolean(removedItem) };
|
||||
|
||||
onChange(selectedOptions, changedItem, inputElement);
|
||||
};
|
||||
tagifyElement.on('change', onSelect);
|
||||
};
|
||||
|
||||
export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue) => {
|
||||
return Object.keys(property).reduce((acc, key) => {
|
||||
if (innerProperty) {
|
||||
if (innerPropertyDefaultValue !== undefined) {
|
||||
acc[`${key}`] = {
|
||||
[innerProperty]: innerPropertyDefaultValue
|
||||
};
|
||||
} else {
|
||||
acc[`${key}.-=${innerProperty}`] = null;
|
||||
}
|
||||
} else {
|
||||
acc[`-=${key}`] = null;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue