Fixed class/subclass drag-drop

This commit is contained in:
WBHarry 2025-07-31 16:56:56 +02:00
parent 39eb3dce69
commit 0cf9e1d17c
10 changed files with 247 additions and 28 deletions

View file

@ -173,6 +173,14 @@
"experienceDataRemoveConfirmation": { "experienceDataRemoveConfirmation": {
"title": "Remove Experience Data", "title": "Remove Experience Data",
"text": "The experience you are about to remove has levelup data linked to it (assumably because you did levelups with the 'levelupAuto' automation setting on). Removing it will remove this automation data aswell. Do you want to proceed?" "text": "The experience you are about to remove has levelup data linked to it (assumably because you did levelups with the 'levelupAuto' automation setting on). Removing it will remove this automation data aswell. Do you want to proceed?"
},
"manualMulticlass": {
"title": "Multiclass",
"text": "Do you want to add this class as your multiclass?"
},
"manualMulticlassSubclass": {
"title": "Multiclass Subclass",
"text": "Do you want to add this subclass as your multiclass subclass?"
} }
}, },
"Companion": { "Companion": {
@ -466,6 +474,11 @@
}, },
"title": "{actor} Level Up" "title": "{actor} Level Up"
}, },
"MulticlassChoice": {
"title": "Multiclassing - {actor}",
"explanation": "You are adding {class} as your multiclass",
"selectDomainPrompt": "Select your new domain"
},
"OwnershipSelection": { "OwnershipSelection": {
"title": "Ownership Selection - {name}", "title": "Ownership Selection - {name}",
"default": "Default Ownership" "default": "Default Ownership"
@ -2209,6 +2222,7 @@
"tooLowLevel": "You cannot lower the character level below starting level", "tooLowLevel": "You cannot lower the character level below starting level",
"subclassNotInClass": "This subclass does not belong to your selected class.", "subclassNotInClass": "This subclass does not belong to your selected class.",
"missingClass": "You don't have a class selected yet.", "missingClass": "You don't have a class selected yet.",
"missingMulticlass": "Missing multiclass",
"wrongDomain": "The card isn't from one of your class domains.", "wrongDomain": "The card isn't from one of your class domains.",
"cardTooHighLevel": "The card is too high level!", "cardTooHighLevel": "The card is too high level!",
"duplicateCard": "You cannot select the same card more than once.", "duplicateCard": "You cannot select the same card more than once.",
@ -2238,7 +2252,9 @@
"beastformToManyFeatures": "You cannot select any more features.", "beastformToManyFeatures": "You cannot select any more features.",
"beastformEquipWeapon": "You cannot use weapons while in a Beastform.", "beastformEquipWeapon": "You cannot use weapons while in a Beastform.",
"loadoutMaxReached": "You already have {max} cards in your loadout. Move atleast one to your vault before adding a new one.", "loadoutMaxReached": "You already have {max} cards in your loadout. Move atleast one to your vault before adding a new one.",
"insufficientResources": "You have insufficient resources" "insufficientResources": "You have insufficient resources",
"multiclassAlreadyPresent": "You already have a class and multiclass",
"subclassesAlreadyPresent": "You already have a class and multiclass subclass"
}, },
"Tooltip": { "Tooltip": {
"disableEffect": "Disable Effect", "disableEffect": "Disable Effect",

View file

@ -5,6 +5,7 @@ export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs'; export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs';
export { default as DeathMove } from './deathMove.mjs'; export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs'; export { default as Downtime } from './downtime.mjs';
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
export { default as OwnershipSelection } from './ownershipSelection.mjs'; export { default as OwnershipSelection } from './ownershipSelection.mjs';
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs'; export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs'; export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';

View file

@ -0,0 +1,73 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class MulticlassChoiceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(actor, multiclass, options) {
super(options);
this.actor = actor;
this.multiclass = multiclass;
this.selectedDomain = null;
}
get title() {
return game.i18n.format('DAGGERHEART.APPLICATIONS.MulticlassChoice.title', { actor: this.actor.name });
}
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'dh-style', 'dialog', 'views', 'multiclass-choice'],
position: { width: 'auto', height: 'auto' },
window: { icon: 'fa-solid fa-person-rays' },
actions: {
save: MulticlassChoiceDialog.#save,
selectDomain: MulticlassChoiceDialog.#selectDomain
}
};
static PARTS = {
application: {
id: 'multiclass-choice',
template: 'systems/daggerheart/templates/dialogs/multiclassChoice.hbs'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.multiclass = this.multiclass;
context.domainChoices = this.multiclass.domains.map(value => {
const domain = CONFIG.DH.DOMAIN.domains[value];
return {
value: value,
label: game.i18n.localize(domain.label),
description: game.i18n.localize(domain.description),
src: domain.src,
selected: value === this.selectedDomain,
disabled: this.actor.system.domains.includes(domain)
};
});
context.multiclassDisabled = !this.selectedDomain;
return context;
}
/** @override */
_onClose(options = {}) {
if (!options.submitted) this.move = null;
}
static async configure(actor, multiclass, options = {}) {
return new Promise(resolve => {
const app = new this(actor, multiclass, options);
app.addEventListener('close', () => resolve(app.selectedDomain), { once: true });
app.render({ force: true });
});
}
static #save() {
this.close({ submitted: true });
}
static #selectDomain(_event, button) {
this.selectedDomain = this.selectedDomain === button.dataset.domain ? null : button.dataset.domain;
this.render();
}
}

View file

@ -62,16 +62,37 @@ export default class DHClass extends BaseDataItem {
} }
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
const allowed = await super._preCreate(data, options, user);
if (allowed === false) return;
if (this.actor?.type === 'character') { if (this.actor?.type === 'character') {
const path = data.system.isMulticlass ? 'system.multiclass.value' : 'system.class.value'; const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
if (foundry.utils.getProperty(this.actor, path)) { if (levelupAuto) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.classAlreadySelected')); const path = data.system.isMulticlass ? 'system.multiclass.value' : 'system.class.value';
return false; if (foundry.utils.getProperty(this.actor, path)) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.classAlreadySelected'));
return false;
}
} else {
if (this.actor.system.class.value) {
if (this.actor.system.multiclass.value) {
ui.notifications.warn(
game.i18n.localize('DAGGERHEART.UI.Notifications.multiclassAlreadyPresent')
);
return false;
} else {
const selectedDomain =
await game.system.api.applications.dialogs.MulticlassChoiceDialog.configure(
this.actor,
this
);
if (!selectedDomain) return false;
await this.updateSource({ isMulticlass: true, domains: [selectedDomain] });
}
}
} }
} }
const allowed = await super._preCreate(data, options, user);
if (allowed === false) return;
} }
_onCreate(data, options, userId) { _onCreate(data, options, userId) {

View file

@ -45,21 +45,23 @@ export default class DHSubclass extends BaseDataItem {
if (allowed === false) return; if (allowed === false) return;
if (this.actor?.type === 'character') { if (this.actor?.type === 'character') {
const classData = this.actor.items.find( if (this.actor.system.class.subclass) {
x => x.type === 'class' && x.system.isMulticlass === data.system.isMulticlass if (this.actor.system.multiclass.subclass) {
); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));
const subclassData = this.actor.items.find( return false;
x => x.type === 'subclass' && x.system.isMulticlass === data.system.isMulticlass } else {
); if (!this.actor.system.multiclass.value) {
if (!classData) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingMulticlass'));
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass')); return false;
return false; }
} else if (subclassData) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassAlreadySelected')); await this.updateSource({ isMulticlass: true });
return false; }
} else if (classData.system.subclasses.every(x => x.uuid !== (data.uuid ?? `Item.${data._id}`))) { } else {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass')); if (!this.actor.system.class.value) {
return false; ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass'));
return false;
}
} }
} }
} }

View file

@ -24,3 +24,5 @@
@import './dice-roll/roll-selection.less'; @import './dice-roll/roll-selection.less';
@import './damage-reduction/damage-reduction-container.less'; @import './damage-reduction/damage-reduction-container.less';
@import './damage-reduction/sheets.less'; @import './damage-reduction/sheets.less';
@import './multiclass-choice/sheet.less';

View file

@ -0,0 +1,76 @@
.theme-light .daggerheart.dh-style.dialog.multiclass-choice {
.multiclass-container .domain-choice-container button label {
background-image: url(../assets/parchments/dh-parchment-light.png);
}
}
.daggerheart.dh-style.dialog.multiclass-choice {
.multiclass-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
.multiclass-explanation {
margin-top: 0;
font-style: italic;
}
.multiclass-domains-container {
display: flex;
gap: 16px;
.domain-choice-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
button {
position: relative;
display: flex;
justify-content: center;
width: 120px;
height: 120px;
background: light-dark(@dark-blue-10, @golden-10);
color: light-dark(@dark-blue, @golden);
&.selected {
background: light-dark(@dark-blue-40, @golden-40);
}
label {
position: absolute;
top: 4px;
font-family: @font-body;
border-radius: 6px;
border: 2px solid;
padding: 0 2px;
background-image: url(../assets/parchments/dh-parchment-dark.png);
color: light-dark(@dark, @beige);
}
}
.domain-description {
width: 240px;
display: flex;
flex-wrap: wrap;
font-style: italic;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
padding: 4px 4px;
}
}
}
footer {
width: 100%;
display: flex;
gap: 8px;
button {
flex: 1;
}
}
}
}

View file

@ -88,6 +88,11 @@
font-size: 12px; font-size: 12px;
color: light-dark(@dark-blue, @golden); color: light-dark(@dark-blue, @golden);
.missing-header-feature {
opacity: 0.5;
text-decoration: line-through;
}
span { span {
padding: 3px; padding: 3px;
border-radius: 3px; border-radius: 3px;

View file

@ -0,0 +1,23 @@
<div>
<div class="multiclass-container">
<h5 class="multiclass-explanation">{{localize "DAGGERHEART.APPLICATIONS.MulticlassChoice.explanation" class=multiclass.parent.name }}</h5>
<h5 class="multiclass-explanation">{{localize "DAGGERHEART.APPLICATIONS.MulticlassChoice.selectDomainPrompt" }}</h5>
<div class="multiclass-domains-container">
{{#each domainChoices as | choice |}}
<div class="domain-choice-container">
<button data-action="selectDomain" data-domain="{{choice.value}}" {{#if choice.selected}}class="selected"{{/if}}>
<label>{{choice.label}}</label>
<img src="{{choice.src}}" />
</button>
<div class="domain-description">{{choice.description}}</div>
</div>
{{/each}}
</div>
<footer>
<button data-action="close">{{localize "Cancel"}}</button>
<button data-action="save" {{disabled multiclassDisabled}}>{{localize "DAGGERHEART.GENERAL.multiclass"}}</button>
</footer>
</div>
</div>

View file

@ -31,25 +31,25 @@
{{#if document.system.class.value}} {{#if document.system.class.value}}
<span data-action="editDoc" data-item-uuid="{{document.system.class.value.uuid}}">{{document.system.class.value.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.class.value.uuid}}">{{document.system.class.value.name}}</span>
{{else}} {{else}}
<span data-action="openPack" data-key="daggerheart.classes">{{localize 'TYPES.Item.class'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="daggerheart.classes">{{localize 'TYPES.Item.class'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.class.subclass}} {{#if document.system.class.subclass}}
<span data-action="editDoc" data-item-uuid="{{document.system.class.subclass.uuid}}">{{document.system.class.subclass.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.class.subclass.uuid}}">{{document.system.class.subclass.name}}</span>
{{else}} {{else}}
<span data-action="openPack" data-key="daggerheart.subclass">{{localize 'TYPES.Item.subclass'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="daggerheart.subclasses">{{localize 'TYPES.Item.subclass'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.community}} {{#if document.system.community}}
<span data-action="editDoc" data-item-uuid="{{document.system.community.uuid}}">{{document.system.community.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.community.uuid}}">{{document.system.community.name}}</span>
{{else}} {{else}}
<span data-action="openPack" data-key="daggerheart.community">{{localize 'TYPES.Item.community'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="daggerheart.communities">{{localize 'TYPES.Item.community'}}</span>
{{/if}} {{/if}}
<span class="dot">•</span> <span class="dot">•</span>
{{#if document.system.ancestry}} {{#if document.system.ancestry}}
<span data-action="editDoc" data-item-uuid="{{document.system.ancestry.uuid}}">{{document.system.ancestry.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.ancestry.uuid}}">{{document.system.ancestry.name}}</span>
{{else}} {{else}}
<span data-action="openPack" data-key="daggerheart.ancestry">{{localize 'TYPES.Item.ancestry'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="daggerheart.ancestries">{{localize 'TYPES.Item.ancestry'}}</span>
{{/if}} {{/if}}
</div> </div>
@ -64,7 +64,7 @@
{{#if document.system.multiclass.subclass}} {{#if document.system.multiclass.subclass}}
<span data-action="editDoc" data-item-uuid="{{document.system.multiclass.subclass.uuid}}">{{document.system.multiclass.subclass.name}}</span> <span data-action="editDoc" data-item-uuid="{{document.system.multiclass.subclass.uuid}}">{{document.system.multiclass.subclass.name}}</span>
{{else}} {{else}}
<span data-action="openPack" data-key="daggerheart.subclass">{{localize 'TYPES.Item.subclass'}}</span> <span class="missing-header-feature" data-action="openPack" data-key="daggerheart.subclasses">{{localize 'TYPES.Item.subclass'}}</span>
{{/if}} {{/if}}
</div> </div>
{{/if}} {{/if}}