Initial datamodel

This commit is contained in:
WBHarry 2025-06-28 18:50:19 +02:00
parent 1f5b5f9915
commit c9e123e389
15 changed files with 279 additions and 101 deletions

View file

@ -75,6 +75,7 @@ Hooks.once('init', () => {
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
Actors.registerSheet(SYSTEM.id, applications.DhCharacterSheet, { types: ['character'], makeDefault: true });
Actors.registerSheet(SYSTEM.id, applications.DhCompanionSheet, { types: ['companion'], makeDefault: true });
Actors.registerSheet(SYSTEM.id, applications.DhpAdversarySheet, { types: ['adversary'], makeDefault: true });
Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true });

View file

@ -14,6 +14,7 @@
},
"Actor": {
"character": "Character",
"companion": "Companion",
"adversary": "Adversary",
"environment": "Environment"
}
@ -1201,6 +1202,23 @@
"tooLowLevel": "You cannot lower the character level below starting level"
}
},
"Companion": {
"FIELDS": {
"partner": { "label": "Partner" },
"evasion": {
"value": { "label": "Evasion" }
},
"resources": {
"stress": {
"value": { "label": "Stress" }
}
},
"attack": {
"name": { "label": "Attack Name" }
}
},
"Experiences": "Experiences"
},
"Adversary": {
"FIELDS": {
"tier": { "label": "Tier" },

View file

@ -1,4 +1,5 @@
export { default as DhCharacterSheet } from './sheets/character.mjs';
export { default as DhCompanionSheet } from './sheets/companion.mjs';
export { default as DhpAdversarySheet } from './sheets/adversary.mjs';
export { default as DhpClassSheet } from './sheets/items/class.mjs';
export { default as DhpSubclass } from './sheets/items/subclass.mjs';

View file

@ -0,0 +1,35 @@
import DaggerheartSheet from './daggerheart-sheet.mjs';
const { ActorSheetV2 } = foundry.applications.sheets;
export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'],
position: { width: 700, height: 1000 },
actions: {},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
};
static PARTS = {
sidebar: { template: 'systems/daggerheart/templates/sheets/actors/companion/tempMain.hbs' }
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.playerCharacters = game.users
.filter(x => !x.isGM && x.character)
.map(x => ({ key: x.character.uuid, name: x.character.name }));
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
}

View file

@ -244,48 +244,6 @@ export const tiers = {
}
};
export const objectTypes = {
character: {
name: 'TYPES.Actor.character'
},
npc: {
name: 'TYPES.Actor.npc'
},
adversary: {
name: 'TYPES.Actor.adversary'
},
ancestry: {
name: 'TYPES.Item.ancestry'
},
community: {
name: 'TYPES.Item.community'
},
class: {
name: 'TYPES.Item.class'
},
subclass: {
name: 'TYPES.Item.subclass'
},
feature: {
name: 'TYPES.Item.feature'
},
domainCard: {
name: 'TYPES.Item.domainCard'
},
consumable: {
name: 'TYPES.Item.consumable'
},
miscellaneous: {
name: 'TYPES.Item.miscellaneous'
},
weapon: {
name: 'TYPES.Item.weapon'
},
armor: {
name: 'TYPES.Item.armor'
}
};
export const diceTypes = {
d4: 'd4',
d6: 'd6',

View file

@ -1,11 +1,13 @@
import DhCharacter from './character.mjs';
import DhCompanion from './companion.mjs';
import DhAdversary from './adversary.mjs';
import DhEnvironment from './environment.mjs';
export { DhCharacter, DhAdversary, DhEnvironment };
export { DhCharacter, DhCompanion, DhAdversary, DhEnvironment };
export const config = {
character: DhCharacter,
companion: DhCompanion,
adversary: DhAdversary,
environment: DhEnvironment
};

View file

@ -1,6 +1,6 @@
import { burden } from '../../config/generalConfig.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import { LevelOptionType } from '../levelTier.mjs';
import DhLevelData from '../levelData.mjs';
import BaseDataActor from './base.mjs';
const attributeField = () =>
@ -88,7 +88,7 @@ export default class DhCharacter extends BaseDataActor {
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
}),
levelData: new fields.EmbeddedDataField(DhPCLevelData),
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
attack: new fields.NumberField({ integer: true, initial: 0 }),
spellcast: new fields.NumberField({ integer: true, initial: 0 }),
@ -117,6 +117,13 @@ export default class DhCharacter extends BaseDataActor {
return !this.class.value || !this.class.subclass;
}
get spellcastingModifiers() {
return {
main: this.class.subclass?.system?.spellcastingTrait,
multiclass: this.multiclass.subclass?.system?.spellcastingTrait
};
}
get domains() {
const classDomains = this.class.value ? this.class.value.system.domains : [];
const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : [];
@ -260,57 +267,3 @@ export default class DhCharacter extends BaseDataActor {
};
}
}
class DhPCLevelData extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
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.TypedObjectField(new fields.StringField({ required: true })),
itemUuid: new fields.StringField({ required: true })
})
)
})
)
};
}
get canLevelUp() {
return this.level.current < this.level.changed;
}
}

View file

@ -0,0 +1,84 @@
import BaseDataActor from './base.mjs';
import DhLevelData from '../levelData.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
export default class DhCompanion extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Companion'];
static get metadata() {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.companion',
type: 'character'
});
}
static defineSchema() {
const fields = foundry.data.fields;
return {
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
resources: new fields.SchemaField({
stress: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 3, integer: true })
})
}),
evasion: new fields.SchemaField({
value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true })
}),
experiences: new fields.TypedObjectField(
new fields.SchemaField({
description: new fields.StringField({}),
value: new fields.NumberField({ integer: true, initial: 0 }),
bonus: new fields.NumberField({ integer: true, initial: 0 })
}),
{
initial: {
experience1: { value: 2 },
experience2: { value: 2 }
}
}
),
attack: new fields.SchemaField({
name: new fields.StringField({}),
range: new fields.StringField({
required: true,
choices: SYSTEM.GENERAL.range,
initial: SYSTEM.GENERAL.range.melee.id
}),
damage: new fields.SchemaField({
value: new fields.StringField({ initial: 'd6' }),
type: new fields.StringField({
required: true,
choices: SYSTEM.GENERAL.damageTypes,
initial: SYSTEM.GENERAL.damageTypes.physical.id
})
})
}),
levelData: new fields.EmbeddedDataField(DhLevelData)
};
}
prepareBaseData() {
this.attack.modifier = this.partner?.system?.spellcastingModifiers?.main ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing;
}
prepareDerivedData() {
for (var experienceKey in this.experiences) {
var experience = this.experiences[experienceKey];
experience.total = experience.value + experience.bonus;
}
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
this.evasion.total = this.evasion.value + this.evasion.bonus;
}
getRollData() {
const data = super.getRollData();
return {
...data
};
}
}

55
module/data/levelData.mjs Normal file
View file

@ -0,0 +1,55 @@
import { LevelOptionType } from './levelTier.mjs';
export default class DhLevelData extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
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.TypedObjectField(new fields.StringField({ required: true })),
itemUuid: new fields.StringField({ required: true })
})
)
})
)
};
}
get canLevelUp() {
return this.level.current < this.level.changed;
}
}

View file

@ -11,7 +11,7 @@ export default class DhpActor extends Actor {
// Configure prototype token settings
const prototypeToken = {};
if (this.type === 'character')
if (['character', 'companion'].includes(this.type))
Object.assign(prototypeToken, {
sight: { enabled: true },
actorLink: true,

View file

@ -3619,13 +3619,35 @@ div.daggerheart.views.multiclass {
.theme-light .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet {
background: transparent;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet img {
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait {
position: relative;
height: 235px;
width: 275px;
border-bottom: 1px solid light-dark(#18162e, #f3c267);
cursor: pointer;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait img {
height: 235px;
width: 275px;
object-fit: cover;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait .death-roll-btn {
display: none;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait.death-roll {
filter: grayscale(1);
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait.death-roll .death-roll-btn {
display: flex;
position: absolute;
top: 30%;
right: 30%;
font-size: 6rem;
color: #efe6d8;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait.death-roll .death-roll-btn:hover {
text-shadow: 0 0 8px #efe6d8;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section {
position: relative;
display: flex;
@ -4023,6 +4045,14 @@ div.daggerheart.views.multiclass {
scrollbar-width: thin;
scrollbar-color: light-dark(#18162e, #f3c267) transparent;
}
.application.sheet.daggerheart.actor.dh-style.companion .profile {
height: 80px;
width: 80px;
}
.application.sheet.daggerheart.actor.dh-style.companion .temp-container {
position: relative;
top: 32px;
}
.application.sheet.daggerheart.actor.dh-style.adversary .window-content {
overflow: auto;
}

View file

@ -24,6 +24,8 @@
@import './less/actors/character/biography.less';
@import './less/actors/character/features.less';
@import './less/actors/companion/sheet.less';
@import './less/actors/adversary.less';
@import './less/actors/environment.less';

View file

@ -0,0 +1,11 @@
.application.sheet.daggerheart.actor.dh-style.companion {
.profile {
height: 80px;
width: 80px;
}
.temp-container {
position: relative;
top: 32px;
}
}

View file

@ -206,6 +206,7 @@
"character": {
"htmlFields": ["story", "description", "scars.*.description"]
},
"companion": {},
"adversary": {
"htmlFields": ["description", "motivesAndTactics"]
},

View file

@ -0,0 +1,27 @@
<div class="temp-container standard-form">
<img class="profile" src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img">
<div class="form-group">
<div class="form-fields">
<label>{{localize "DAGGERHEART.Sheets.Companion.FIELDS.partner.label"}}</label>
<select name="system.partner">
{{selectOptions playerCharacters selected=source.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}}
</select>
</div>
</div>
{{formGroup systemFields.resources.fields.stress.fields.value value=source.system.resources.stress.value localize=true }}
{{formGroup systemFields.evasion.fields.value value=source.system.evasion.value localize=true }}
<div class="flexcol">
{{#each source.system.experiences as |experience key|}}
<div class="flexrow">
<input type="text" name="{{concat "system.experiences." key ".description"}}" value="{{experience.description}}" />
<input type="text" data-dtype="Number" name="{{concat "system.experiences." key ".value"}}" value="{{experience.value}}" />
</div>
{{/each}}
</div>
<div class="flexcol">
{{formGroup systemFields.attack.fields.name value=source.system.attack.name localize=true }}
</div>
</div>