mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
[Feature] Beastform Types (#372)
* Temp * Finished Evolved * Fixed hybrid * Changed generalConfig.tiers to be number based * Weaponhandling while in beastform * Added unarmed strike in sidebar * Added DamageEnricher * Added effect enricher * Corrected downtime buttons and actions * Added BeastformTooltip * Split the BeastformDialog into parts with tabs * Added temp beastform features * rollData change * Improvement * character.getRollData cleanup
This commit is contained in:
parent
867947c2c5
commit
42a705a870
93 changed files with 2795 additions and 538 deletions
|
|
@ -113,7 +113,7 @@ export class DHResourceData extends foundry.abstract.DataModel {
|
|||
}),
|
||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +134,6 @@ export class DHDamageData extends DHResourceData {
|
|||
label: 'Type'
|
||||
}
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
getRollData(data = {}) {
|
||||
if(!this.actor) return null;
|
||||
if (!this.actor) return null;
|
||||
const actorData = this.actor.getRollData(false);
|
||||
|
||||
// Add Roll results to RollDatas
|
||||
|
|
@ -178,8 +178,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
async use(event, ...args) {
|
||||
if(!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||
|
||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||
|
||||
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
|
||||
// Prepare base Config
|
||||
const initConfig = this.initActionConfig(event);
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ export default class DhBeastformAction extends DHBaseAction {
|
|||
const abort = await this.handleActiveTransformations();
|
||||
if (abort) return;
|
||||
|
||||
const beastformUuid = await BeastformDialog.configure(beastformConfig);
|
||||
if (!beastformUuid) return;
|
||||
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig);
|
||||
if (!selected) return;
|
||||
|
||||
await this.transform(beastformUuid);
|
||||
await this.transform(selected, evolved, hybrid);
|
||||
}
|
||||
|
||||
prepareBeastformConfig(config) {
|
||||
|
|
@ -29,21 +29,48 @@ export default class DhBeastformAction extends DHBaseAction {
|
|||
};
|
||||
}
|
||||
|
||||
async transform(beastformUuid) {
|
||||
const beastform = await foundry.utils.fromUuid(beastformUuid);
|
||||
this.actor.createEmbeddedDocuments('Item', [beastform.toObject()]);
|
||||
async transform(selectedForm, evolvedData, hybridData) {
|
||||
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject();
|
||||
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
||||
if (!beastformEffect) {
|
||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||
return;
|
||||
}
|
||||
|
||||
if (evolvedData?.form) {
|
||||
const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform');
|
||||
if (!evolvedForm) {
|
||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||
return;
|
||||
}
|
||||
|
||||
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,31 +22,32 @@ export default class DHDamageAction extends DHBaseAction {
|
|||
formatFormulas(formulas, systemData) {
|
||||
const formattedFormulas = [];
|
||||
formulas.forEach(formula => {
|
||||
if (isNaN(formula.formula)) formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
|
||||
const same = formattedFormulas.find(f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo);
|
||||
if(same)
|
||||
same.formula += ` + ${formula.formula}`;
|
||||
else
|
||||
formattedFormulas.push(formula);
|
||||
})
|
||||
if (isNaN(formula.formula))
|
||||
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
|
||||
const same = formattedFormulas.find(
|
||||
f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
|
||||
);
|
||||
if (same) same.formula += ` + ${formula.formula}`;
|
||||
else formattedFormulas.push(formula);
|
||||
});
|
||||
return formattedFormulas;
|
||||
}
|
||||
|
||||
async rollDamage(event, data) {
|
||||
const systemData = data.system ?? data;
|
||||
|
||||
|
||||
let formulas = this.damage.parts.map(p => ({
|
||||
formula: this.getFormulaValue(p, data).getFormula(this.actor),
|
||||
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
||||
applyTo: p.applyTo
|
||||
}));
|
||||
|
||||
if(!formulas.length) return;
|
||||
if (!formulas.length) return;
|
||||
|
||||
formulas = this.formatFormulas(formulas, 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: formulas,
|
||||
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
||||
hasSave: this.hasSave,
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ export default class DHHealingAction extends DHBaseAction {
|
|||
|
||||
async rollHealing(event, data) {
|
||||
const systemData = data.system ?? data;
|
||||
let formulas = [{
|
||||
formula: this.getFormulaValue(data).getFormula(this.actor),
|
||||
applyTo: this.healing.applyTo
|
||||
}];
|
||||
|
||||
let formulas = [
|
||||
{
|
||||
formula: this.getFormulaValue(data).getFormula(this.actor),
|
||||
applyTo: this.healing.applyTo
|
||||
}
|
||||
];
|
||||
|
||||
const config = {
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.healingRoll.title', {
|
||||
healing: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[this.healing.applyTo].label)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
|||
};
|
||||
}
|
||||
|
||||
async _onCreate() {
|
||||
if (this.parent.parent?.type === 'character') {
|
||||
this.parent.parent.system.primaryWeapon?.update?.({ 'system.equipped': false });
|
||||
this.parent.parent.system.secondayWeapon?.update?.({ 'system.equipped': false });
|
||||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
if (this.parent.parent.type === 'character') {
|
||||
const update = {
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
tier: new fields.StringField({
|
||||
tier: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers,
|
||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
||||
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||
}),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
|
|
@ -52,7 +53,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
})
|
||||
}),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints.plural', true),
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
|
||||
}),
|
||||
attack: new ActionField({
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
|
|
@ -21,7 +22,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
return {
|
||||
...super.defineSchema(),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints.plural', true),
|
||||
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||
stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
|
||||
hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
|
||||
}),
|
||||
|
|
@ -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: 'Attack',
|
||||
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({
|
||||
|
|
@ -198,6 +236,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
|
||||
|
|
@ -277,6 +324,24 @@ export default class DhCharacter extends BaseDataActor {
|
|||
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
|
||||
}
|
||||
|
||||
get activeBeastform() {
|
||||
return this.parent.effects.find(x => x.type === 'beastform');
|
||||
}
|
||||
|
||||
get usedUnarmed() {
|
||||
const primaryWeaponEquipped = this.primaryWeapon?.system?.equipped;
|
||||
const secondaryWeaponEquipped = this.secondaryWeapon?.system?.equipped;
|
||||
return !primaryWeaponEquipped && !secondaryWeaponEquipped
|
||||
? {
|
||||
...this.attack,
|
||||
id: this.attack.id,
|
||||
name: this.activeBeastform ? 'DAGGERHEART.ITEMS.Beastform.attackName' : this.attack.name,
|
||||
img: this.activeBeastform ? 'icons/creatures/claws/claw-straight-brown.webp' : this.attack.img,
|
||||
actor: this.parent
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
get sheetLists() {
|
||||
const ancestryFeatures = [],
|
||||
communityFeatures = [],
|
||||
|
|
@ -457,9 +522,6 @@ 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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ export default class DhEnvironment extends BaseDataActor {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
tier: new fields.StringField({
|
||||
tier: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers,
|
||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
||||
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||
}),
|
||||
type: new fields.StringField({ choices: CONFIG.DH.ACTOR.environmentTypes }),
|
||||
impulses: new fields.StringField(),
|
||||
|
|
|
|||
|
|
@ -19,10 +19,16 @@ export default class DHBeastform extends BaseDataItem {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
tier: new fields.StringField({
|
||||
beastformType: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ITEM.beastformTypes,
|
||||
initial: CONFIG.DH.ITEM.beastformTypes.normal.id
|
||||
}),
|
||||
tier: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers,
|
||||
initial: CONFIG.DH.GENERAL.tiers.tier1.id
|
||||
initial: CONFIG.DH.GENERAL.tiers[1].id
|
||||
}),
|
||||
tokenImg: new fields.FilePathField({
|
||||
initial: 'icons/svg/mystery-man.svg',
|
||||
|
|
@ -38,9 +44,40 @@ export default class DHBeastform extends BaseDataItem {
|
|||
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||
}),
|
||||
mainTrait: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.ACTOR.abilities,
|
||||
initial: CONFIG.DH.ACTOR.abilities.agility.id
|
||||
}),
|
||||
examples: new fields.StringField(),
|
||||
advantageOn: new fields.StringField(),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
|
||||
advantageOn: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField()
|
||||
})
|
||||
),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
evolved: new fields.SchemaField({
|
||||
maximumTier: new fields.NumberField({
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers
|
||||
}),
|
||||
mainTraitBonus: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
min: 0,
|
||||
initial: 0
|
||||
})
|
||||
}),
|
||||
hybrid: new fields.SchemaField({
|
||||
maximumTier: new fields.NumberField({
|
||||
integer: true,
|
||||
choices: CONFIG.DH.GENERAL.tiers,
|
||||
label: 'DAGGERHEART.ITEMS.Beastform.FIELDS.evolved.maximumTier.label'
|
||||
}),
|
||||
beastformOptions: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 }),
|
||||
advantages: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 }),
|
||||
features: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 })
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +106,16 @@ export default class DHBeastform extends BaseDataItem {
|
|||
|
||||
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
||||
await beastformEffect.updateSource({
|
||||
changes: [...beastformEffect.changes, { key: 'system.advantageSources', mode: 2, value: this.advantageOn }],
|
||||
changes: [
|
||||
...beastformEffect.changes,
|
||||
{
|
||||
key: 'system.advantageSources',
|
||||
mode: 2,
|
||||
value: Object.values(this.advantageOn)
|
||||
.map(x => x.value)
|
||||
.join(', ')
|
||||
}
|
||||
],
|
||||
system: {
|
||||
characterTokenData: {
|
||||
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default class DHClass extends BaseDataItem {
|
|||
integer: true,
|
||||
min: 1,
|
||||
initial: 5,
|
||||
label: 'DAGGERHEART.GENERAL.hitPoints.plural'
|
||||
label: 'DAGGERHEART.GENERAL.HitPoints.plural'
|
||||
}),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue