175 - Character Sheet Levelup Functionality (#181)

* Attached CharacterCreate and Levelup functionality to the sheet

* Changed to a warning icon
This commit is contained in:
WBHarry 2025-06-25 16:33:38 +02:00 committed by GitHub
parent 7f898bb983
commit d9c003b64b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 218 additions and 61 deletions

View file

@ -1078,8 +1078,9 @@
"faith": "Faith", "faith": "Faith",
"ShortRest": "Take a Short Rest", "ShortRest": "Take a Short Rest",
"LongRest": "Take a Long Rest", "LongRest": "Take a Long Rest",
"CharacterSetup": "Character setup isn't done yet",
"Level": "Level", "Level": "Level",
"LevelUp": "Level Up!", "LevelUp": "You can level up",
"Tabs": { "Tabs": {
"Features": "Features", "Features": "Features",
"Inventory": "Inventory", "Inventory": "Inventory",
@ -1177,7 +1178,9 @@
"NewScar": "New Scar", "NewScar": "New Scar",
"DeleteConfirmation": "Are you sure you want to delete the item - {item}?", "DeleteConfirmation": "Are you sure you want to delete the item - {item}?",
"Errors": { "Errors": {
"missingClassOrSubclass": "The character doesn't have a class and subclass" "missingClassOrSubclass": "The character doesn't have a class and subclass",
"tooHighLevel": "You cannot raise the character level past the maximum",
"tooLowLevel": "You cannot lower the character level below starting level"
} }
}, },
"Adversary": { "Adversary": {

View file

@ -5,7 +5,7 @@ import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs';
import DaggerheartSheet from './daggerheart-sheet.mjs'; import DaggerheartSheet from './daggerheart-sheet.mjs';
import { abilities } from '../../config/actorConfig.mjs'; import { abilities } from '../../config/actorConfig.mjs';
import DhlevelUp from '../levelup.mjs'; import DhlevelUp from '../levelup.mjs';
import DHDualityRoll from '../../data/chat-message/dualityRoll.mjs'; import DhCharacterCreation from '../characterCreation.mjs';
const { ActorSheetV2 } = foundry.applications.sheets; const { ActorSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux; const { TextEditor } = foundry.applications.ux;
@ -47,7 +47,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
useAdvancementCard: this.useAdvancementCard, useAdvancementCard: this.useAdvancementCard,
useAdvancementAbility: this.useAdvancementAbility, useAdvancementAbility: this.useAdvancementAbility,
toggleEquipItem: this.toggleEquipItem, toggleEquipItem: this.toggleEquipItem,
levelup: this.openLevelUp, levelManagement: this.levelManagement,
editImage: this._onEditImage editImage: this._onEditImage
}, },
window: { window: {
@ -217,7 +217,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
// htmlElement.querySelector('.level-value').addEventListener('change', this.onLevelChange.bind(this)); htmlElement.querySelector('.level-value')?.addEventListener('change', this.onLevelChange.bind(this));
// To Remove when ContextMenu Handler is made // To Remove when ContextMenu Handler is made
htmlElement htmlElement
.querySelectorAll('[data-item-id]') .querySelectorAll('[data-item-id]')
@ -457,7 +457,19 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
} }
} }
static openLevelUp() { static levelManagement() {
if (this.document.system.needsCharacterSetup) {
this.characterSetup();
} else {
this.openLevelUp();
}
}
characterSetup() {
new DhCharacterCreation(this.document).render(true);
}
openLevelUp() {
if (!this.document.system.class.value || !this.document.system.class.subclass) { if (!this.document.system.class.value || !this.document.system.class.subclass) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.missingClassOrSubclass')); ui.notifications.error(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.missingClassOrSubclass'));
return; return;

View file

@ -110,6 +110,10 @@ export default class DhCharacter extends BaseDataActor {
return this.parent.items.find(x => x.type === 'community') ?? null; return this.parent.items.find(x => x.type === 'community') ?? null;
} }
get needsCharacterSetup() {
return !this.class.value || !this.class.subclass;
}
get domains() { get domains() {
const classDomains = this.class.value ? this.class.value.system.domains : []; const classDomains = this.class.value ? this.class.value.system.domains : [];
const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : []; const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : [];

View file

@ -32,10 +32,22 @@ export default class DhpActor extends Actor {
if (this.type !== 'character' || newLevel === this.system.levelData.level.changed) return; if (this.type !== 'character' || newLevel === this.system.levelData.level.changed) return;
if (newLevel > this.system.levelData.level.current) { if (newLevel > this.system.levelData.level.current) {
await this.update({ 'system.levelData.level.changed': newLevel }); const maxLevel = Object.values(
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers).tiers
).reduce((acc, tier) => Math.max(acc, tier.levels.end), 0);
if (newLevel > maxLevel) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.tooHighLevel'));
}
await this.update({ 'system.levelData.level.changed': Math.min(newLevel, maxLevel) });
} else { } else {
const usedLevel = Math.max(newLevel, 1);
if (newLevel < 1) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.tooLowLevel'));
}
const updatedLevelups = Object.keys(this.system.levelData.levelups).reduce((acc, level) => { const updatedLevelups = Object.keys(this.system.levelData.levelups).reduce((acc, level) => {
if (Number(level) > newLevel) acc[`-=${level}`] = null; if (Number(level) > usedLevel) acc[`-=${level}`] = null;
return acc; return acc;
}, {}); }, {});
@ -45,7 +57,7 @@ export default class DhpActor extends Actor {
const subclassFeatureState = { class: null, multiclass: null }; const subclassFeatureState = { class: null, multiclass: null };
let multiclass = null; let multiclass = null;
Object.keys(this.system.levelData.levelups) Object.keys(this.system.levelData.levelups)
.filter(x => x > newLevel) .filter(x => x > usedLevel)
.forEach(levelKey => { .forEach(levelKey => {
const level = this.system.levelData.levelups[levelKey]; const level = this.system.levelData.levelups[levelKey];
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid); const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
@ -106,8 +118,8 @@ export default class DhpActor extends Actor {
system: { system: {
levelData: { levelData: {
level: { level: {
current: newLevel, current: usedLevel,
changed: newLevel changed: usedLevel
}, },
levelups: updatedLevelups levelups: updatedLevelups
} }

View file

@ -189,20 +189,20 @@ export const tagifyElement = (element, options, onChange, tagifyOptions = {}) =>
} }
}); });
const onSelect = async event => { tagifyElement.on('add', event => {
const inputElement = event.detail.tagify.DOM.originalInput; const input = event.detail.tagify.DOM.originalInput;
const selectedOptions = event.detail?.value ? JSON.parse(event.detail.value) : []; const currentList = input.value ? JSON.parse(input.value) : [];
onChange([...currentList, event.detail.data], { option: event.detail.data.value, removed: false }, input);
const unusedDropDownItems = event.detail.tagify.suggestedListItems; });
const missingOptions = Object.keys(options).filter(x => !unusedDropDownItems.find(item => item.value === x)); tagifyElement.on('remove', event => {
const removedItem = missingOptions.find(x => !selectedOptions.find(item => item.value === x)); const input = event.detail.tagify.DOM.originalInput;
const addedItem = removedItem ? null : selectedOptions.find(x => missingOptions.find(item => item === x.value)); const currentList = input.value ? JSON.parse(input.value) : [];
onChange(
const changedItem = { option: removedItem ?? addedItem.value, removed: Boolean(removedItem) }; currentList.filter(x => x.value !== event.detail.data.value),
{ option: event.detail.data.value, removed: true },
onChange(selectedOptions, changedItem, inputElement); event.detail.tagify.DOM.originalInput
}; );
tagifyElement.on('change', onSelect); });
}; };
export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue) => { export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue) => {

View file

@ -3452,16 +3452,16 @@ div.daggerheart.views.multiclass {
} }
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row { .application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row {
display: flex; display: flex;
gap: 20px; gap: 5px;
align-items: baseline; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0; padding: 0;
padding-top: 5px; padding-top: 5px;
flex: 1;
} }
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row input[type='text'] { .application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row input[type='text'] {
font-size: 32px; font-size: 32px;
height: 42px; height: 42px;
width: 380px;
text-align: start; text-align: start;
border: 1px solid transparent; border: 1px solid transparent;
outline: 2px solid transparent; outline: 2px solid transparent;
@ -3472,6 +3472,36 @@ div.daggerheart.views.multiclass {
} }
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row .level-div { .application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row .level-div {
white-space: nowrap; white-space: nowrap;
display: flex;
justify-content: end;
}
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row .level-div .label {
display: flex;
align-items: center;
gap: 4px;
}
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row .level-div input {
width: 40px;
padding: 0;
text-align: center;
}
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row .level-div .level-button {
color: light-dark(#222, #efe6d8);
font-size: 18px;
line-height: 1;
min-height: unset;
height: min-content;
padding: 4px;
font-family: 'Cinzel', serif;
margin: 0;
font-weight: normal;
border-color: light-dark(#18162e, #f3c267);
background-color: light-dark(transparent, #0e0d15);
}
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .name-row .level-div .level-button:hover {
background-image: none;
background-color: var(--color-warm-2);
filter: drop-shadow(0 0 3px lightgray);
} }
.application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .character-details { .application.sheet.daggerheart.actor.dh-style.character .character-header-sheet .character-details {
display: flex; display: flex;
@ -4071,6 +4101,22 @@ div.daggerheart.views.multiclass {
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
} }
@keyframes glow {
0% {
box-shadow: 0 0 1px 1px #f3c267;
}
100% {
box-shadow: 0 0 2px 2px #f3c267;
}
}
@keyframes glow-dark {
0% {
box-shadow: 0 0 1px 1px #18162e;
}
100% {
box-shadow: 0 0 2px 2px #18162e;
}
}
@font-face { @font-face {
font-family: 'Cinzel'; font-family: 'Cinzel';
font-style: normal; font-style: normal;
@ -4254,6 +4300,9 @@ div.daggerheart.views.multiclass {
background: light-dark(rgba(0, 0, 0, 0.3), #18162e); background: light-dark(rgba(0, 0, 0, 0.3), #18162e);
color: light-dark(#18162e, #f3c267); color: light-dark(#18162e, #f3c267);
} }
.application.sheet.dh-style button.glow {
animation: glow 0.75s infinite alternate;
}
.application.sheet.dh-style select { .application.sheet.dh-style select {
background: light-dark(transparent, transparent); background: light-dark(transparent, transparent);
color: light-dark(#222, #efe6d8); color: light-dark(#222, #efe6d8);
@ -4477,6 +4526,9 @@ div.daggerheart.views.multiclass {
.system-daggerheart.theme-light .tagify__dropdown .tagify__dropdown__item--active { .system-daggerheart.theme-light .tagify__dropdown .tagify__dropdown__item--active {
color: #efe6d8; color: #efe6d8;
} }
.theme-light .application.sheet.dh-style button.glow {
animation: glow-dark 0.75s infinite alternate;
}
.theme-light .application .component.dh-style.card-preview-container { .theme-light .application .component.dh-style.card-preview-container {
background-image: url('../assets/parchments/dh-parchment-light.png'); background-image: url('../assets/parchments/dh-parchment-light.png');
} }

View file

@ -9,16 +9,16 @@
.name-row { .name-row {
display: flex; display: flex;
gap: 20px; gap: 5px;
align-items: baseline; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0; padding: 0;
padding-top: 5px; padding-top: 5px;
flex: 1;
input[type='text'] { input[type='text'] {
font-size: 32px; font-size: 32px;
height: 42px; height: 42px;
width: 380px;
text-align: start; text-align: start;
border: 1px solid transparent; border: 1px solid transparent;
outline: 2px solid transparent; outline: 2px solid transparent;
@ -31,6 +31,40 @@
.level-div { .level-div {
white-space: nowrap; white-space: nowrap;
display: flex;
justify-content: end;
.label {
display: flex;
align-items: center;
gap: 4px;
}
input {
width: 40px;
padding: 0;
text-align: center;
}
.level-button {
color: light-dark(@dark, @beige);
font-size: 18px;
line-height: 1;
min-height: unset;
height: min-content;
padding: 4px;
font-family: 'Cinzel', serif;
margin: 0;
font-weight: normal;
border-color: light-dark(@dark-blue, @golden);
background-color: light-dark(transparent, @deep-black);
&:hover {
background-image: none;
background-color: var(--color-warm-2);
filter: drop-shadow(0 0 3px lightgray);
}
}
} }
} }

View file

@ -48,6 +48,10 @@
background: light-dark(@light-black, @dark-blue); background: light-dark(@light-black, @dark-blue);
color: light-dark(@dark-blue, @golden); color: light-dark(@dark-blue, @golden);
} }
&.glow {
animation: glow 0.75s infinite alternate;
}
} }
select { select {
@ -336,16 +340,24 @@
} }
} }
.theme-light .application .component.dh-style.card-preview-container { .theme-light .application {
background-image: url('../assets/parchments/dh-parchment-light.png'); &.sheet.dh-style {
button.glow {
.preview-text-container { animation: glow-dark 0.75s infinite alternate;
background-image: url(../assets/parchments/dh-parchment-dark.png); }
} }
.preview-selected-icon-container { .component.dh-style.card-preview-container {
background-image: url(../assets/parchments/dh-parchment-dark.png); background-image: url('../assets/parchments/dh-parchment-light.png');
color: var(--color-light-5);
.preview-text-container {
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
.preview-selected-icon-container {
background-image: url(../assets/parchments/dh-parchment-dark.png);
color: var(--color-light-5);
}
} }
} }

View file

@ -1,21 +1,41 @@
@primary-blue: #1488cc; @primary-blue: #1488cc;
@secondary-blue: #2b32b2; @secondary-blue: #2b32b2;
@golden: #f3c267; @golden: #f3c267;
@golden-40: #f3c26740; @golden-40: #f3c26740;
@dark-blue-40: #18162e40; @dark-blue-40: #18162e40;
@golden-10: #f3c26710; @golden-10: #f3c26710;
@dark-blue-10: #18162e10; @dark-blue-10: #18162e10;
@dark-blue-50: #18162e50; @dark-blue-50: #18162e50;
@dark-blue: #18162e; @dark-blue: #18162e;
@deep-black: #0e0d15; @deep-black: #0e0d15;
@beige: #efe6d8; @beige: #efe6d8;
@beige-15: #efe6d815; @beige-15: #efe6d815;
@beige-50: #efe6d850; @beige-50: #efe6d850;
@dark-blue: rgb(24, 22, 46); @dark-blue: rgb(24, 22, 46);
@semi-transparent-dark-blue: rgba(24, 22, 46, 0.33); @semi-transparent-dark-blue: rgba(24, 22, 46, 0.33);
@dark: #222; @dark: #222;
@dark-15: #22222215; @dark-15: #22222215;
@light-black: rgba(0, 0, 0, 0.3); @light-black: rgba(0, 0, 0, 0.3);
@soft-shadow: rgba(0, 0, 0, 0.05); @soft-shadow: rgba(0, 0, 0, 0.05);
@gradient-hp: linear-gradient(15deg, rgb(70, 20, 10) 0%, rgb(190, 0, 0) 42%, rgb(252, 176, 69) 100%); @gradient-hp: linear-gradient(15deg, rgb(70, 20, 10) 0%, rgb(190, 0, 0) 42%, rgb(252, 176, 69) 100%);
@gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%); @gradient-stress: linear-gradient(15deg, rgb(130, 59, 1) 0%, rgb(252, 142, 69) 65%, rgb(190, 0, 0) 100%);
@keyframes glow {
0% {
box-shadow: 0 0 1px 1px @golden;
}
100% {
box-shadow: 0 0 2px 2px @golden;
}
}
@keyframes glow-dark {
0% {
box-shadow: 0 0 1px 1px @dark-blue;
}
100% {
box-shadow: 0 0 2px 2px @dark-blue;
}
}

View file

@ -12,8 +12,16 @@
<div class='level-div'> <div class='level-div'>
<h3 class='label'> <h3 class='label'>
{{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}}
<button
class="level-button glow" data-tooltip="{{#if document.system.needsCharacterSetup}}{{localize "DAGGERHEART.Sheets.PC.CharacterSetup"}}{{else}}{{localize "DAGGERHEART.Sheets.PC.LevelUp"}}{{/if}}"
data-action="levelManagement"
>
<i class="fa-solid fa-triangle-exclamation"></i>
</button>
{{/if}}
{{localize 'DAGGERHEART.Sheets.PC.Level'}} {{localize 'DAGGERHEART.Sheets.PC.Level'}}
{{document.system.levelData.level.current}} <input type="text" data-dtype="Number" class="level-value" value={{#if document.system.needsCharacterSetup}}0{{else}}{{document.system.levelData.level.changed}}{{/if}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} />
</h3> </h3>
</div> </div>
</div> </div>