Compare commits

...

3 commits

Author SHA1 Message Date
Carlos Fernandez
b23095cb2f
[Fix] finishing levelup with a multiclass (#1906)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
* Fix finishing levelup with a multiclass

* Fix removal when de-leveling

* Also delete multiclass related stuff if reducing below the minimum multiclass level
2026-05-20 18:45:42 +02:00
Carlos Fernandez
2f589c1b8e
Minor visual tweaks to daggerheart setting dialogs (#1905) 2026-05-20 16:54:22 +02:00
Carlos Fernandez
10a608a1a5
Refocus armor slots when using number keys (#1908) 2026-05-20 16:49:45 +02:00
8 changed files with 128 additions and 112 deletions

View file

@ -718,7 +718,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
? {
'system.linkedClass.uuid': {
key: 'system.linkedClass.uuid',
value: this.document.system.class.value._stats.compendiumSource
value: this.document.system.class.value?._stats.compendiumSource
}
}
: undefined,

View file

@ -56,38 +56,30 @@ export default class DHSubclass extends BaseDataItem {
if (allowed === false) return;
if (this.actor?.type === 'character') {
const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`;
if (this.actor.system.class.subclass) {
if (this.actor.system.multiclass.subclass) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));
return false;
} else {
const multiclass = this.actor.items.find(x => x.type === 'class' && x.system.isMulticlass);
if (!multiclass) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingMulticlass'));
return false;
}
const { value: actorClass, subclass: existingSubclass } = this.actor.system.class;
const { value: multiclass, subclass: existingMultisubclass } = this.actor.system.multiclass;
if (!actorClass && !multiclass) {
ui.notifications.warn('DAGGERHEART.UI.Notifications.missingClass', { localize: true });
return false;
}
if (existingSubclass && existingMultisubclass) {
ui.notifications.warn('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent', { localize: true });
return false;
}
if (existingSubclass && !multiclass) {
ui.notifications.warn('DAGGERHEART.UI.Notifications.missingMulticlass', { localize: true });
return false;
}
if (multiclass.system.subclasses.every(x => x.uuid !== dataUuid)) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInMulticlass')
);
return false;
}
await this.updateSource({ isMulticlass: true });
}
} else {
const actorClass = this.actor.items.find(x => x.type === 'class' && !x.system.isMulticlass);
if (!actorClass) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass'));
return false;
}
if ((await actorClass.system.fetchSubclasses()).every(x => x.uuid !== dataUuid)) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
return false;
}
const match = [multiclass, actorClass].find(
c => c && (c._stats.compendiumSource ?? c.uuid) === this.linkedClass
);
if (!match) {
const key = multiclass ? 'subclassNotInMulticlass' : 'subclassNotInClass';
ui.notifications.warn(`DAGGERHEART.UI.Notifications.${key}`, { localize: true });
return false;
} else if (match.system.isMulticlass) {
await this.updateSource({ isMulticlass: true });
}
}
}

View file

@ -153,10 +153,13 @@ export default class DhpActor extends Actor {
async updateLevel(newLevel) {
if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return;
const tiers = Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers);
const maxLevel = tiers.reduce((acc, tier) => Math.max(acc, tier.levels.end), 0);
const multiclassMinLevel = Math.min(
maxLevel,
...tiers.filter(t => t.options.multiclass).map(t => t.levels.start)
);
if (newLevel > this.system.levelData.level.current) {
const maxLevel = Object.values(
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers
).reduce((acc, tier) => Math.max(acc, tier.levels.end), 0);
if (newLevel > maxLevel) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooHighLevel'));
}
@ -231,18 +234,19 @@ export default class DhpActor extends Actor {
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass });
}
if (multiclass) {
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
const multiclassFeatures = this.items.filter(
x => x.system.originItemType === 'class' && x.system.multiclassOrigin
);
const subclassFeatures = this.items.filter(
x => x.system.originItemType === 'subclass' && x.system.multiclassOrigin
// Remove multiclass if we're removing a multiclass feature or if we're below the multiclass minimum level
// Multclasses cannot be manually removed on the sheet, so this allows recovering in the case of errors
if (multiclass || newLevel < multiclassMinLevel) {
const multiclassItems = this.items.filter(
x =>
x.uuid === multiclass?.itemUuid ||
x.system.isMulticlass ||
(['class', 'subclass'].includes(x.system.originItemType) && x.system.multiclassOrigin)
);
this.deleteEmbeddedDocuments(
'Item',
[multiclassItem, ...multiclassFeatures, ...subclassFeatures].map(x => x.id)
multiclassItems.map(x => x.id)
);
this.update({
@ -281,6 +285,7 @@ export default class DhpActor extends Actor {
async levelUp(levelupData) {
const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
const getStatsWithSource = document => ({ ...(document._stats ?? {}), compendiumSource: document.uuid });
const levelups = {};
for (var levelKey of Object.keys(levelupData)) {
@ -393,8 +398,8 @@ export default class DhpActor extends Actor {
const embeddedItem = await this.createEmbeddedDocuments('Item', [
{
...multiclassData,
uuid: multiclassItem.uuid,
_stats: multiclassItem._stats,
uuid: multiclassItem.uuid, // todo: replace with setting an id and using keepId
_stats: getStatsWithSource(multiclassItem),
system: {
...multiclassData.system,
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
@ -407,8 +412,8 @@ export default class DhpActor extends Actor {
await this.createEmbeddedDocuments('Item', [
{
...subclassData,
uuid: subclassItem.uuid,
_stats: subclassItem._stats,
uuid: subclassItem.uuid, // todo: replace with setting an id and using keepId
_stats: getStatsWithSource(subclassItem),
system: {
...subclassData.system,
isMulticlass: true
@ -428,8 +433,8 @@ export default class DhpActor extends Actor {
const embeddedItem = await this.createEmbeddedDocuments('Item', [
{
...cardData,
uuid: cardItem.uuid,
_stats: cardItem._stats,
uuid: cardItem.uuid, // todo: replace with setting an id and using keepId
_stats: getStatsWithSource(cardItem),
system: {
...cardData.system,
inVault: true
@ -450,8 +455,7 @@ export default class DhpActor extends Actor {
const embeddedItem = await this.createEmbeddedDocuments('Item', [
{
...cardData,
uuid: cardItem.uuid,
_stats: cardItem._stats,
_stats: getStatsWithSource(cardItem),
system: {
...cardData.system,
inVault: true

View file

@ -7,12 +7,12 @@
input[type='text'],
input[type='number'],
textarea,
file-picker,
.input[contenteditable] {
background: light-dark(transparent, transparent);
border-radius: 6px;
box-shadow: 0 4px 30px @soft-shadow;
backdrop-filter: blur(9.5px);
-webkit-backdrop-filter: blur(9.5px);
outline: 2px solid transparent;
color: light-dark(@dark-blue, @golden);
border: 1px solid light-dark(@dark, @beige);
@ -98,7 +98,7 @@
color: light-dark(@dark, @beige);
}
button:where(:not(.plain)) {
button:where(:not(.plain, color-picker *, file-picker *)) {
background: light-dark(transparent, @golden);
border: 1px solid light-dark(@dark-blue, @dark-blue);
color: light-dark(@dark-blue, @dark-blue);
@ -252,6 +252,15 @@
text-shadow: 0 0 1px currentColor, 0 0 1px currentColor, 0 0 8px light-dark(@dark-blue, @golden);
}
file-picker, color-picker {
> input[type=text] {
background: transparent;
border: none;
outline: none;
backdrop-filter: unset;
}
}
fieldset {
align-items: center;
margin-top: 5px;
@ -597,59 +606,6 @@
}
}
.application.setting.dh-style {
h2,
h3,
h4 {
margin: 8px 0 4px;
text-align: center;
}
footer {
margin-top: 8px;
display: flex;
gap: 8px;
button {
flex: 1;
}
}
.form-group {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.25rem 0.5rem;
flex-wrap: wrap;
label {
font-size: var(--font-size-14);
font-weight: normal;
}
.form-fields {
display: flex;
gap: 4px;
align-items: center;
}
&.setting-two-values {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.25rem 0.5rem;
.form-group {
justify-content: end;
flex-wrap: nowrap;
}
.hint {
grid-column: 1 / -1;
}
}
}
}
.system-daggerheart {
.tagify {
background: light-dark(transparent, transparent);

View file

@ -24,7 +24,7 @@
.resource-icons-container {
display: flex;
justify-content: space-between;
gap: 8px;
gap: 10px;
width: 100%;
.resource-icon-container {

View file

@ -1,6 +1,67 @@
@import '../../utils/colors.less';
.daggerheart.dh-style.setting {
--color-form-label: var(--color-text-primary);
h2,
h3,
h4 {
margin: 8px 0 4px;
text-align: center;
}
footer {
margin-top: 8px;
display: flex;
gap: 8px;
button {
flex: 1;
}
}
.standard-form {
gap: var(--spacer-8);
.form-group .form-fields {
width: unset;
}
}
.form-group {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.25rem 0.5rem;
flex-wrap: wrap;
label {
font-size: var(--font-size-14);
font-weight: normal;
line-height: var(--input-height);
}
.form-fields {
display: flex;
gap: 4px;
align-items: center;
}
&.setting-two-values {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.25rem 0.5rem;
.form-group {
justify-content: end;
flex-wrap: nowrap;
}
.hint {
grid-column: 1 / -1;
}
}
}
fieldset {
display: flex;
flex-direction: column;
@ -19,7 +80,10 @@
&.three-columns {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 2px;
gap: 4px;
.form-group label {
line-height: unset;
}
}
&.six-columns {

View file

@ -1,4 +1,4 @@
<div>
<div class="standard-form">
{{formGroup settingFields.schema.fields.hideObserverPermissionInChat value=settingFields._source.hideObserverPermissionInChat localize=true}}
{{formGroup settingFields.schema.fields.hidePartyStats value=settingFields._source.hidePartyStats localize=true}}
</div>
{{formGroup settingFields.schema.fields.hidePartyStats value=settingFields._source.hidePartyStats localize=true}}
</div>

View file

@ -44,7 +44,7 @@
</div>
{{else}}
<div class='status-value'>
<input class="bar-input armor-marks-input" value="{{document.system.armorScore.value}}" type="number">
<input class="bar-input armor-marks-input" value="{{document.system.armorScore.value}}" type="number" id="{{document.uuid}}-armor-slots">
<span>/</span>
<span class="bar-label">{{document.system.armorScore.max}}</span>
</div>