diff --git a/daggerheart.mjs b/daggerheart.mjs index 239b307d..3b1ea3f4 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -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 }); diff --git a/lang/en.json b/lang/en.json index 9a614b68..bd962928 100755 --- a/lang/en.json +++ b/lang/en.json @@ -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" }, diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index c9f5ddc6..25d48846 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -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'; diff --git a/module/applications/sheets/companion.mjs b/module/applications/sheets/companion.mjs new file mode 100644 index 00000000..fd5dd3d6 --- /dev/null +++ b/module/applications/sheets/companion.mjs @@ -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(); + } +} diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 18c8aba4..b1d72435 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -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', diff --git a/module/data/actor/_module.mjs b/module/data/actor/_module.mjs index cdbf7178..c19036eb 100644 --- a/module/data/actor/_module.mjs +++ b/module/data/actor/_module.mjs @@ -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 }; diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 8d6e2e41..027914a0 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -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; - } -} diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs new file mode 100644 index 00000000..f0561629 --- /dev/null +++ b/module/data/actor/companion.mjs @@ -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 + }; + } +} diff --git a/module/data/levelData.mjs b/module/data/levelData.mjs new file mode 100644 index 00000000..0bc7a506 --- /dev/null +++ b/module/data/levelData.mjs @@ -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; + } +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 6af98f31..a0bfefd3 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -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, diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 994293bf..fcf4dd04 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -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; } diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 8bd60fd4..67cbdc9b 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -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'; diff --git a/styles/less/actors/companion/sheet.less b/styles/less/actors/companion/sheet.less new file mode 100644 index 00000000..1beb28a7 --- /dev/null +++ b/styles/less/actors/companion/sheet.less @@ -0,0 +1,11 @@ +.application.sheet.daggerheart.actor.dh-style.companion { + .profile { + height: 80px; + width: 80px; + } + + .temp-container { + position: relative; + top: 32px; + } +} diff --git a/system.json b/system.json index c0af75d1..dc25f130 100644 --- a/system.json +++ b/system.json @@ -206,6 +206,7 @@ "character": { "htmlFields": ["story", "description", "scars.*.description"] }, + "companion": {}, "adversary": { "htmlFields": ["description", "motivesAndTactics"] }, diff --git a/templates/sheets/actors/companion/tempMain.hbs b/templates/sheets/actors/companion/tempMain.hbs new file mode 100644 index 00000000..bfdcbe0e --- /dev/null +++ b/templates/sheets/actors/companion/tempMain.hbs @@ -0,0 +1,27 @@ +
+ {{document.name}} +
+
+ + +
+
+ + {{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 }} + +
+ {{#each source.system.experiences as |experience key|}} +
+ + +
+ {{/each}} +
+ +
+ {{formGroup systemFields.attack.fields.name value=source.system.attack.name localize=true }} +
+
\ No newline at end of file