mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-17 23:49:02 +01:00
Fixed hybrid
This commit is contained in:
parent
011f5d2b14
commit
796cec01ee
13 changed files with 464 additions and 110 deletions
|
|
@ -7,7 +7,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
this.configData = configData;
|
||||
this.selected = null;
|
||||
this.evolved = { form: null };
|
||||
this.hybrid = null;
|
||||
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
||||
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
}
|
||||
|
|
@ -21,6 +21,8 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
},
|
||||
actions: {
|
||||
selectBeastform: this.selectBeastform,
|
||||
toggleHybridFeature: this.toggleHybridFeature,
|
||||
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
||||
submitBeastform: this.submitBeastform
|
||||
},
|
||||
form: {
|
||||
|
|
@ -38,7 +40,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
/** @override */
|
||||
static PARTS = {
|
||||
beastform: {
|
||||
template: 'systems/daggerheart/templates/dialogs/beastformDialog.hbs'
|
||||
template: 'systems/daggerheart/templates/dialogs/beastform/beastformDialog.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -65,7 +67,33 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
context.selectedBeastformEffect = this.selected?.effects?.find?.(x => x.type === 'beastform');
|
||||
|
||||
context.evolved = this.evolved;
|
||||
context.hybrid = this.hybrid;
|
||||
|
||||
context.hybridForms = Object.keys(this.hybrid.forms).reduce((acc, formKey) => {
|
||||
if (!this.hybrid.forms[formKey]) {
|
||||
acc[formKey] = null;
|
||||
} else {
|
||||
const data = this.hybrid.forms[formKey].toObject();
|
||||
acc[formKey] = {
|
||||
...data,
|
||||
system: {
|
||||
...data.system,
|
||||
features: this.hybrid.forms[formKey].system.features.map(feature => ({
|
||||
...feature.toObject(),
|
||||
uuid: feature.uuid,
|
||||
selected: Boolean(this.hybrid.features?.[formKey]?.[feature.uuid])
|
||||
})),
|
||||
advantageOn: Object.keys(data.system.advantageOn).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
...data.system.advantageOn[key],
|
||||
selected: Boolean(this.hybrid.advantages?.[formKey]?.[key])
|
||||
};
|
||||
return acc;
|
||||
}, {})
|
||||
}
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const maximumDragTier = Math.max(
|
||||
this.selected?.system?.evolved?.maximumTier ?? 0,
|
||||
|
|
@ -83,13 +111,16 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
acc[tier.id].values[x.uuid] = {
|
||||
selected: this.selected?.uuid == x.uuid,
|
||||
value: x,
|
||||
draggable: maximumDragTier ? x.system.tier <= maximumDragTier : false
|
||||
draggable:
|
||||
!['evolved', 'hybrid'].includes(x.system.beastformType) && maximumDragTier
|
||||
? x.system.tier <= maximumDragTier
|
||||
: false
|
||||
};
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
); // Also get from compendium when added
|
||||
);
|
||||
|
||||
context.canSubmit = this.canSubmit();
|
||||
|
||||
|
|
@ -103,6 +134,19 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
return true;
|
||||
case 'evolved':
|
||||
return this.evolved.form;
|
||||
case 'hybrid':
|
||||
const selectedAdvantages = Object.values(this.hybrid.advantages).reduce(
|
||||
(acc, form) => acc + Object.values(form).length,
|
||||
0
|
||||
);
|
||||
const selectedFeatures = Object.values(this.hybrid.features).reduce(
|
||||
(acc, form) => acc + Object.values(form).length,
|
||||
0
|
||||
);
|
||||
|
||||
const advantagesSelected = selectedAdvantages === this.selected.system.hybrid.advantages;
|
||||
const featuresSelected = selectedFeatures === this.selected.system.hybrid.features;
|
||||
return advantagesSelected && featuresSelected;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +179,62 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
|
||||
if (this.selected) {
|
||||
if (this.selected.system.beastformType !== 'evolved') this.evolved.form = null;
|
||||
if (this.selected.system.beastformType !== 'hybrid') this.hybrid = null;
|
||||
if (this.selected.system.beastformType !== 'hybrid') {
|
||||
this.hybrid.forms = {};
|
||||
this.hybrid.advantages = {};
|
||||
this.hybrid.features = {};
|
||||
} else {
|
||||
this.hybrid.forms = [...Array(this.selected.system.hybrid.beastformOptions).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = null;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static toggleHybridFeature(_, button) {
|
||||
const current = this.hybrid.features[button.dataset.form];
|
||||
if (!current) this.hybrid.features[button.dataset.form] = {};
|
||||
|
||||
if (this.hybrid.features[button.dataset.form][button.id])
|
||||
delete this.hybrid.features[button.dataset.form][button.id];
|
||||
else {
|
||||
const currentFeatures = Object.values(this.hybrid.features).reduce(
|
||||
(acc, form) => acc + Object.values(form).length,
|
||||
0
|
||||
);
|
||||
if (currentFeatures === this.selected.system.hybrid.features) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyFeatures'));
|
||||
return;
|
||||
}
|
||||
|
||||
const feature = this.hybrid.forms[button.dataset.form].system.features.find(x => x.uuid === button.id);
|
||||
this.hybrid.features[button.dataset.form][button.id] = feature;
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static toggleHybridAdvantage(_, button) {
|
||||
const current = this.hybrid.advantages[button.dataset.form];
|
||||
if (!current) this.hybrid.advantages[button.dataset.form] = {};
|
||||
|
||||
if (this.hybrid.advantages[button.dataset.form][button.id])
|
||||
delete this.hybrid.advantages[button.dataset.form][button.id];
|
||||
else {
|
||||
const currentAdvantages = Object.values(this.hybrid.advantages).reduce(
|
||||
(acc, form) => acc + Object.values(form).length,
|
||||
0
|
||||
);
|
||||
if (currentAdvantages === this.selected.system.hybrid.advantages) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyAdvantages'));
|
||||
return;
|
||||
}
|
||||
|
||||
const advantage = this.hybrid.forms[button.dataset.form].system.advantageOn[button.id];
|
||||
this.hybrid.advantages[button.dataset.form][button.id] = advantage;
|
||||
}
|
||||
|
||||
this.render();
|
||||
|
|
@ -164,23 +263,17 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
|
||||
async _onDragStart(event) {
|
||||
const target = event.currentTarget;
|
||||
if (!this.selected) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
const abort = () => event.preventDefault();
|
||||
if (!this.selected) abort();
|
||||
|
||||
const draggedForm = await foundry.utils.fromUuid(target.dataset.uuid);
|
||||
if (['evolved', 'hybrid'].includes(draggedForm.system.beastformType)) abort();
|
||||
|
||||
if (this.selected.system.beastformType === 'evolved') {
|
||||
if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) abort();
|
||||
}
|
||||
if (this.selected.system.beastformType === 'hybrid') {
|
||||
if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) abort();
|
||||
}
|
||||
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(target.dataset));
|
||||
|
|
@ -191,9 +284,20 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (!item) return;
|
||||
|
||||
if (event.target.closest('.advanced-form-container.evolved')) {
|
||||
this.evolved.form = item;
|
||||
} else {
|
||||
const hybridContainer = event.target.closest('.advanced-form-container.hybridized');
|
||||
if (hybridContainer) {
|
||||
const existingId = Object.keys(this.hybrid.forms).find(
|
||||
key => this.hybrid.forms[key]?.uuid === item.uuid
|
||||
);
|
||||
if (existingId) this.hybrid.forms[existingId] = null;
|
||||
|
||||
this.hybrid.forms[hybridContainer.id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
this.render();
|
||||
|
|
|
|||
|
|
@ -36,7 +36,25 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
|||
|
||||
const advantageOnInput = htmlElement.querySelector('.advantageon-input');
|
||||
if (advantageOnInput) {
|
||||
const tagifyElement = new Tagify(advantageOnInput);
|
||||
const tagifyElement = new Tagify(advantageOnInput, {
|
||||
tagTextProp: 'name',
|
||||
templates: {
|
||||
tag(tagData) {
|
||||
return `<tag
|
||||
contenteditable='false'
|
||||
spellcheck='false'
|
||||
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
||||
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
||||
${this.getAttributes(tagData)}>
|
||||
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
||||
<div>
|
||||
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
|
||||
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
|
||||
</div>
|
||||
</tag>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
tagifyElement.on('add', this.advantageOnAdd.bind(this));
|
||||
tagifyElement.on('remove', this.advantageOnRemove.bind(this));
|
||||
}
|
||||
|
|
@ -61,19 +79,25 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
|||
|
||||
return data;
|
||||
});
|
||||
context.advantageOn = JSON.stringify(
|
||||
Object.keys(context.document.system.advantageOn).map(key => ({
|
||||
value: key,
|
||||
name: context.document.system.advantageOn[key].value
|
||||
}))
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async advantageOnAdd(event) {
|
||||
await this.document.update({
|
||||
'system.advantageOn': [...this.document.system.advantageOn, event.detail.data.value]
|
||||
[`system.advantageOn.${foundry.utils.randomID()}`]: { value: event.detail.data.value }
|
||||
});
|
||||
}
|
||||
|
||||
async advantageOnRemove(event) {
|
||||
await this.document.update({
|
||||
'system.advantageOn': this.document.system.advantageOn.filter(x => x !== event.detail.data.value)
|
||||
[`system.advantageOn.-=${event.detail.data.value}`]: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,19 +48,29 @@ export default class DhBeastformAction extends DHBaseAction {
|
|||
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
|
||||
}
|
||||
|
||||
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
|
||||
formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
|
||||
Object.keys(formCategory).forEach(advantageKey => {
|
||||
advantages[advantageKey] = formCategory[advantageKey];
|
||||
});
|
||||
return advantages;
|
||||
}, {});
|
||||
formData.system.features = [
|
||||
...formData.system.features,
|
||||
...Object.values(hybridData.features).flatMap(x => Object.keys(x))
|
||||
];
|
||||
}
|
||||
|
||||
this.actor.createEmbeddedDocuments('Item', [formData]);
|
||||
}
|
||||
|
||||
async handleActiveTransformations() {
|
||||
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
|
||||
if (beastformEffects.length > 0) {
|
||||
for (let effect of beastformEffects) {
|
||||
await effect.delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
const existingEffects = beastformEffects.length > 0;
|
||||
await this.actor.deleteEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
beastformEffects.map(x => x.id)
|
||||
);
|
||||
return existingEffects;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData));
|
||||
|
||||
const config = {
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: game.i18n.localize(this.name) }),
|
||||
roll: { formula },
|
||||
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
||||
hasSave: this.hasSave,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
|||
import DhLevelData from '../levelData.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
|
||||
export default class DhCharacter extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||
|
|
@ -87,8 +88,45 @@ export default class DhCharacter extends BaseDataActor {
|
|||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField()),
|
||||
disadvantageSources: new fields.ArrayField(new fields.StringField()),
|
||||
attack: new ActionField({
|
||||
initial: {
|
||||
name: 'DAGGERHEART.GENERAL.unarmedStrike',
|
||||
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
|
||||
_id: foundry.utils.randomID(),
|
||||
systemPath: 'attack',
|
||||
type: 'attack',
|
||||
range: 'melee',
|
||||
target: {
|
||||
type: 'any',
|
||||
amount: 1
|
||||
},
|
||||
roll: {
|
||||
type: 'attack',
|
||||
trait: 'strength'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
type: ['physical'],
|
||||
value: {
|
||||
custom: {
|
||||
enabled: true,
|
||||
formula: '@system.rules.attack.damage.value'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
advantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
|
||||
}),
|
||||
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
|
||||
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
|
||||
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
roll: new fields.SchemaField({
|
||||
|
|
@ -161,6 +199,15 @@ export default class DhCharacter extends BaseDataActor {
|
|||
magical: new fields.BooleanField({ initial: false }),
|
||||
physical: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
attack: new fields.SchemaField({
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
initial: '@profd4',
|
||||
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.value.label'
|
||||
})
|
||||
})
|
||||
}),
|
||||
weapon: new fields.SchemaField({
|
||||
/* Unimplemented
|
||||
-> Should remove the lowest damage dice from weapon damage
|
||||
|
|
@ -420,11 +467,14 @@ export default class DhCharacter extends BaseDataActor {
|
|||
const data = super.getRollData();
|
||||
return {
|
||||
...data,
|
||||
...this.resources.tokens,
|
||||
...this.resources.dice,
|
||||
...this.bonuses,
|
||||
tier: this.tier,
|
||||
level: this.levelData.level.current
|
||||
system: {
|
||||
...this.resources.tokens,
|
||||
...this.resources.dice,
|
||||
...this.bonuses,
|
||||
...this.rules,
|
||||
tier: this.tier,
|
||||
level: this.levelData.level.current
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,11 @@ export default class DHBeastform extends BaseDataItem {
|
|||
initial: CONFIG.DH.ACTOR.abilities.agility.id
|
||||
}),
|
||||
examples: new fields.StringField(),
|
||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
||||
advantageOn: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField()
|
||||
})
|
||||
),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
evolved: new fields.SchemaField({
|
||||
maximumTier: new fields.NumberField({
|
||||
|
|
@ -104,7 +108,13 @@ export default class DHBeastform extends BaseDataItem {
|
|||
await beastformEffect.updateSource({
|
||||
changes: [
|
||||
...beastformEffect.changes,
|
||||
{ key: 'system.advantageSources', mode: 2, value: this.advantageOn.join(', ') }
|
||||
{
|
||||
key: 'system.advantageSources',
|
||||
mode: 2,
|
||||
value: Object.values(this.advantageOn)
|
||||
.map(x => x.value)
|
||||
.join(', ')
|
||||
}
|
||||
],
|
||||
system: {
|
||||
characterTokenData: {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export default class DHToken extends TokenDocument {
|
|||
});
|
||||
bars.sort((a, b) => a.label.compare(b.label));
|
||||
|
||||
const invalidAttributes = ['gold', 'levelData', 'rules.damageReduction.maxArmorMarked.value'];
|
||||
const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value'];
|
||||
const values = attributes.value.reduce((acc, v) => {
|
||||
const a = v.join('.');
|
||||
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
|
||||
|
|
@ -32,19 +32,21 @@ export default class DHToken extends TokenDocument {
|
|||
|
||||
return bars.concat(values);
|
||||
}
|
||||
|
||||
static _getTrackedAttributesFromSchema(schema, _path=[]) {
|
||||
const attributes = {bar: [], value: []};
|
||||
for ( const [name, field] of Object.entries(schema.fields) ) {
|
||||
|
||||
static _getTrackedAttributesFromSchema(schema, _path = []) {
|
||||
const attributes = { bar: [], value: [] };
|
||||
for (const [name, field] of Object.entries(schema.fields)) {
|
||||
const p = _path.concat([name]);
|
||||
if ( field instanceof foundry.data.fields.NumberField ) attributes.value.push(p);
|
||||
if ( field instanceof foundry.data.fields.ArrayField ) attributes.value.push(p);
|
||||
if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p);
|
||||
if (field instanceof foundry.data.fields.StringField) attributes.value.push(p);
|
||||
if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p);
|
||||
const isSchema = field instanceof foundry.data.fields.SchemaField;
|
||||
const isModel = field instanceof foundry.data.fields.EmbeddedDataField;
|
||||
if ( isSchema || isModel ) {
|
||||
|
||||
if (isSchema || isModel) {
|
||||
const schema = isModel ? field.model.schema : field;
|
||||
const isBar = schema.has && schema.has("value") && schema.has("max");
|
||||
if ( isBar ) attributes.bar.push(p);
|
||||
const isBar = schema.has && schema.has('value') && schema.has('max');
|
||||
if (isBar) attributes.bar.push(p);
|
||||
else {
|
||||
const inner = this.getTrackedAttributes(schema, p);
|
||||
attributes.bar.push(...inner.bar);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue