refactor: resturcture files so that lincensing info can be bundled properly
This commit is contained in:
parent
699ba74840
commit
1aa284311f
484 changed files with 119 additions and 179 deletions
87
src/actor/actor-data-properties.ts
Normal file
87
src/actor/actor-data-properties.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseTotal, ResourceDataBaseTotalMax } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import {
|
||||
DS4CharacterDataSourceDataBaseInfo,
|
||||
DS4CharacterDataSourceDataCurrency,
|
||||
DS4CharacterDataSourceDataProfile,
|
||||
DS4CharacterDataSourceDataProgression,
|
||||
DS4CharacterDataSourceDataSlayerPoints,
|
||||
DS4CreatureDataSourceDataBaseInfo,
|
||||
} from "./actor-data-source";
|
||||
|
||||
declare global {
|
||||
interface DataConfig {
|
||||
Actor: DS4ActorDataProperties;
|
||||
}
|
||||
}
|
||||
|
||||
export type DS4ActorDataProperties = DS4CharacterDataProperties | DS4CreatureDataProperties;
|
||||
|
||||
interface DS4CharacterDataProperties {
|
||||
type: "character";
|
||||
data: DS4CharacterDataPropertiesData;
|
||||
}
|
||||
|
||||
interface DS4CreatureDataProperties {
|
||||
type: "creature";
|
||||
data: DS4CreatureDataPropertiesData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ActorDataPropertiesDataBase {
|
||||
attributes: DS4ActorDataPropertiesDataAttributes;
|
||||
traits: DS4ActorDataPropertiesDataTraits;
|
||||
combatValues: DS4ActorDataPropertiesDataCombatValues;
|
||||
rolling: DS4ActorDataPropertiesDataRolling;
|
||||
checks: DS4ActorDataPropertiesDataChecks;
|
||||
}
|
||||
|
||||
type DS4ActorDataPropertiesDataAttributes = {
|
||||
[Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
type DS4ActorDataPropertiesDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal<number> };
|
||||
|
||||
type DS4ActorDataPropertiesDataCombatValues = {
|
||||
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
|
||||
? ResourceDataBaseTotalMax<number>
|
||||
: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
interface DS4ActorDataPropertiesDataRolling {
|
||||
maximumCoupResult: number;
|
||||
minimumFumbleResult: number;
|
||||
}
|
||||
|
||||
type DS4ActorDataPropertiesDataChecks = {
|
||||
[key in Check]: number;
|
||||
};
|
||||
|
||||
export type Check = keyof typeof DS4.i18n.checks;
|
||||
|
||||
export function isCheck(value: string): value is Check {
|
||||
return Object.keys(DS4.i18n.checks).includes(value);
|
||||
}
|
||||
|
||||
// types
|
||||
|
||||
interface DS4CreatureDataPropertiesData extends DS4ActorDataPropertiesDataBase {
|
||||
baseInfo: DS4CreatureDataSourceDataBaseInfo;
|
||||
}
|
||||
|
||||
interface DS4CharacterDataPropertiesData extends DS4ActorDataPropertiesDataBase {
|
||||
baseInfo: DS4CharacterDataSourceDataBaseInfo;
|
||||
progression: DS4CharacterDataSourceDataProgression;
|
||||
profile: DS4CharacterDataSourceDataProfile;
|
||||
currency: DS4CharacterDataSourceDataCurrency;
|
||||
slayerPoints: DS4CharacterDataPropertiesDataSlayerPoints;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataPropertiesDataSlayerPoints extends DS4CharacterDataSourceDataSlayerPoints {
|
||||
max: number;
|
||||
}
|
125
src/actor/actor-data-source.ts
Normal file
125
src/actor/actor-data-source.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
// SPDX-FileCopyrightText: 2021 Siegfried Krug
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableData, ModifiableDataBase, ResourceData, UsableResource } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
|
||||
declare global {
|
||||
interface SourceConfig {
|
||||
Actor: DS4ActorDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
export type DS4ActorDataSource = DS4CharacterDataSource | DS4CreatureDataSource;
|
||||
|
||||
interface DS4CharacterDataSource {
|
||||
type: "character";
|
||||
data: DS4CharacterDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4CreatureDataSource {
|
||||
type: "creature";
|
||||
data: DS4CreatureDataSourceData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ActorDataSourceDataBase {
|
||||
attributes: DS4ActorDataSourceDataAttributes;
|
||||
traits: DS4ActorDataSourceDataTraits;
|
||||
combatValues: DS4ActorDataSourceDataCombatValues;
|
||||
}
|
||||
|
||||
type DS4ActorDataSourceDataAttributes = { [Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBase<number> };
|
||||
|
||||
type Attribute = keyof DS4ActorDataSourceDataAttributes;
|
||||
|
||||
export function isAttribute(value: unknown): value is Attribute {
|
||||
return (Object.keys(DS4.i18n.attributes) as Array<unknown>).includes(value);
|
||||
}
|
||||
|
||||
type DS4ActorDataSourceDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBase<number> };
|
||||
|
||||
type Trait = keyof DS4ActorDataSourceDataTraits;
|
||||
|
||||
export function isTrait(value: unknown): value is Trait {
|
||||
return (Object.keys(DS4.i18n.traits) as Array<unknown>).includes(value);
|
||||
}
|
||||
|
||||
type DS4ActorDataSourceDataCombatValues = {
|
||||
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
|
||||
? ResourceData<number>
|
||||
: ModifiableData<number>;
|
||||
};
|
||||
|
||||
type CombatValue = keyof DS4ActorDataSourceDataCombatValues;
|
||||
|
||||
export function isCombatValue(value: string): value is CombatValue {
|
||||
return (Object.keys(DS4.i18n.combatValues) as Array<unknown>).includes(value);
|
||||
}
|
||||
|
||||
// types
|
||||
|
||||
interface DS4CreatureDataSourceData extends DS4ActorDataSourceDataBase {
|
||||
baseInfo: DS4CreatureDataSourceDataBaseInfo;
|
||||
}
|
||||
|
||||
export interface DS4CreatureDataSourceDataBaseInfo {
|
||||
loot: string;
|
||||
foeFactor: number;
|
||||
creatureType: CreatureType;
|
||||
sizeCategory: SizeCategory;
|
||||
experiencePoints: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
type CreatureType = keyof typeof DS4.i18n.creatureTypes;
|
||||
|
||||
type SizeCategory = keyof typeof DS4.i18n.creatureSizeCategories;
|
||||
|
||||
interface DS4CharacterDataSourceData extends DS4ActorDataSourceDataBase {
|
||||
baseInfo: DS4CharacterDataSourceDataBaseInfo;
|
||||
progression: DS4CharacterDataSourceDataProgression;
|
||||
profile: DS4CharacterDataSourceDataProfile;
|
||||
currency: DS4CharacterDataSourceDataCurrency;
|
||||
slayerPoints: DS4CharacterDataSourceDataSlayerPoints;
|
||||
}
|
||||
export interface DS4CharacterDataSourceDataBaseInfo {
|
||||
race: string;
|
||||
class: string;
|
||||
heroClass: string;
|
||||
culture: string;
|
||||
}
|
||||
export interface DS4CharacterDataSourceDataProgression {
|
||||
level: number;
|
||||
experiencePoints: number;
|
||||
talentPoints: UsableResource<number>;
|
||||
progressPoints: UsableResource<number>;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataSourceDataProfile {
|
||||
biography: string;
|
||||
gender: string;
|
||||
birthday: string;
|
||||
birthplace: string;
|
||||
age: number;
|
||||
height: number;
|
||||
hairColor: string;
|
||||
weight: number;
|
||||
eyeColor: string;
|
||||
specialCharacteristics: string;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataSourceDataCurrency {
|
||||
gold: number;
|
||||
silver: number;
|
||||
copper: number;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataSourceDataSlayerPoints {
|
||||
value: number;
|
||||
}
|
443
src/actor/actor.ts
Normal file
443
src/actor/actor.ts
Normal file
|
@ -0,0 +1,443 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
// SPDX-FileCopyrightText: 2021 Oliver RÜmpelein
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseTotal } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { DS4ArmorDataProperties, DS4ShieldDataProperties } from "../item/item-data-properties";
|
||||
import { ItemType } from "../item/item-data-source";
|
||||
import { createCheckRoll } from "../rolls/check-factory";
|
||||
import { Check } from "./actor-data-properties";
|
||||
import { isAttribute, isTrait } from "./actor-data-source";
|
||||
|
||||
declare global {
|
||||
interface DocumentClassConfig {
|
||||
Actor: typeof DS4Actor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Actor class for DS4
|
||||
*/
|
||||
export class DS4Actor extends Actor {
|
||||
/** @override */
|
||||
prepareData(): void {
|
||||
this.data.reset();
|
||||
this.prepareBaseData();
|
||||
this.prepareEmbeddedDocuments();
|
||||
this.applyActiveEffectsToBaseData();
|
||||
this.prepareDerivedData();
|
||||
this.applyActiveEffectsToDerivedData();
|
||||
this.prepareFinalDerivedData();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
prepareBaseData(): void {
|
||||
const data = this.data;
|
||||
|
||||
data.data.rolling = {
|
||||
minimumFumbleResult: 20,
|
||||
maximumCoupResult: 1,
|
||||
};
|
||||
|
||||
const attributes = data.data.attributes;
|
||||
Object.values(attributes).forEach(
|
||||
(attribute: ModifiableDataBaseTotal<number>) => (attribute.total = attribute.base + attribute.mod),
|
||||
);
|
||||
|
||||
const traits = data.data.traits;
|
||||
Object.values(traits).forEach(
|
||||
(trait: ModifiableDataBaseTotal<number>) => (trait.total = trait.base + trait.mod),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* We override this with an empty implementation because we have our own custom way of applying
|
||||
* {@link ActiveEffect}s and {@link Actor#prepareEmbeddedDocuments} calls this.
|
||||
*/
|
||||
applyActiveEffects(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
applyActiveEffectsToBaseData(): void {
|
||||
// reset overrides because our variant of applying active effects does not set them, it only adds overrides
|
||||
this.overrides = {};
|
||||
this.applyActiveEffectsFiltered(
|
||||
(change) =>
|
||||
!this.derivedDataProperties.includes(change.key) &&
|
||||
!this.finalDerivedDataProperties.includes(change.key),
|
||||
);
|
||||
}
|
||||
|
||||
applyActiveEffectsToDerivedData(): void {
|
||||
this.applyActiveEffectsFiltered((change) => this.derivedDataProperties.includes(change.key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply ActiveEffectChanges to the Actor data which are caused by ActiveEffects and satisfy the given predicate.
|
||||
*
|
||||
* @param predicate - The predicate that ActiveEffectChanges need to satisfy in order to be applied
|
||||
*/
|
||||
applyActiveEffectsFiltered(predicate: (change: foundry.data.ActiveEffectData["changes"][number]) => boolean): void {
|
||||
const overrides: Record<string, unknown> = {};
|
||||
|
||||
// Organize non-disabled and -surpressed effects by their application priority
|
||||
const changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[] =
|
||||
this.effects.reduce(
|
||||
(changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[], e) => {
|
||||
if (e.data.disabled || e.isSurpressed) return changes;
|
||||
|
||||
const newChanges = e.data.changes.filter(predicate).flatMap((c) => {
|
||||
const changeSource = c.toObject();
|
||||
changeSource.priority = changeSource.priority ?? changeSource.mode * 10;
|
||||
return Array(e.factor).fill({ ...changeSource, effect: e });
|
||||
});
|
||||
|
||||
return changes.concat(newChanges);
|
||||
},
|
||||
[],
|
||||
);
|
||||
changes.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
||||
|
||||
// Apply all changes
|
||||
for (const change of changes) {
|
||||
const result = change.effect.apply(this, change);
|
||||
if (result !== null) overrides[change.key] = result;
|
||||
}
|
||||
|
||||
// Expand the set of final overrides
|
||||
this.overrides = foundry.utils.expandObject({ ...foundry.utils.flattenObject(this.overrides), ...overrides });
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply transformations to the Actor data after effects have been applied to the base data.
|
||||
* @override
|
||||
*/
|
||||
prepareDerivedData(): void {
|
||||
this.prepareCombatValues();
|
||||
this.prepareChecks();
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of properties that are derived from others, given in dot notation.
|
||||
*/
|
||||
get derivedDataProperties(): Array<string> {
|
||||
const combatValueProperties = Object.keys(DS4.i18n.combatValues).map(
|
||||
(combatValue) => `data.combatValues.${combatValue}.total`,
|
||||
);
|
||||
const checkProperties = Object.keys(DS4.i18n.checks)
|
||||
.filter((check) => check !== "defend")
|
||||
.map((check) => `data.checks.${check}`);
|
||||
return combatValueProperties.concat(checkProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply final transformations to the Actor data after all effects have been applied.
|
||||
*/
|
||||
prepareFinalDerivedData(): void {
|
||||
Object.values(this.data.data.attributes).forEach(
|
||||
(attribute: ModifiableDataBaseTotal<number>) => (attribute.total = Math.ceil(attribute.total)),
|
||||
);
|
||||
Object.values(this.data.data.traits).forEach(
|
||||
(trait: ModifiableDataBaseTotal<number>) => (trait.total = Math.ceil(trait.total)),
|
||||
);
|
||||
Object.entries(this.data.data.combatValues)
|
||||
.filter(([key]) => key !== "movement")
|
||||
.map(([, value]) => value)
|
||||
.forEach(
|
||||
(combatValue: ModifiableDataBaseTotal<number>) => (combatValue.total = Math.ceil(combatValue.total)),
|
||||
);
|
||||
(Object.keys(this.data.data.checks) as (keyof typeof this.data.data.checks)[]).forEach((key) => {
|
||||
this.data.data.checks[key] = Math.ceil(this.data.data.checks[key]);
|
||||
});
|
||||
|
||||
this.data.data.combatValues.hitPoints.max = this.data.data.combatValues.hitPoints.total;
|
||||
this.data.data.checks.defend = this.data.data.combatValues.defense.total;
|
||||
if (this.data.type === "character") {
|
||||
this.data.data.slayerPoints.max = 3;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of properties that are completely derived (i.e. {@link ActiveEffect}s cannot be applied to them),
|
||||
* given in dot notation.
|
||||
*/
|
||||
get finalDerivedDataProperties(): string[] {
|
||||
return ["data.combatValues.hitPoints.max", "data.checks.defend"].concat(
|
||||
this.data.type === "character" ? ["data.slayerPoints.max"] : [],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of item types that can be owned by this actor.
|
||||
*/
|
||||
get ownableItemTypes(): Array<ItemType> {
|
||||
switch (this.data.type) {
|
||||
case "character":
|
||||
return [
|
||||
"weapon",
|
||||
"armor",
|
||||
"shield",
|
||||
"equipment",
|
||||
"loot",
|
||||
"spell",
|
||||
"talent",
|
||||
"racialAbility",
|
||||
"language",
|
||||
"alphabet",
|
||||
];
|
||||
case "creature":
|
||||
return ["weapon", "armor", "shield", "equipment", "loot", "spell", "specialCreatureAbility"];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the given item type can be owned by the actor.
|
||||
* @param itemType - The item type to check
|
||||
*/
|
||||
canOwnItemType(itemType: ItemType): boolean {
|
||||
return this.ownableItemTypes.includes(itemType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the combat values of the actor.
|
||||
*/
|
||||
protected prepareCombatValues(): void {
|
||||
const data = this.data.data;
|
||||
const armorValueOfEquippedItems = this.calculateArmorValueOfEquippedItems();
|
||||
const spellMalusOfEquippedItems = this.calculateSpellMaluesOfEquippedItems();
|
||||
|
||||
data.combatValues.hitPoints.base = data.attributes.body.total + data.traits.constitution.total + 10;
|
||||
data.combatValues.defense.base =
|
||||
data.attributes.body.total + data.traits.constitution.total + armorValueOfEquippedItems;
|
||||
data.combatValues.initiative.base = data.attributes.mobility.total + data.traits.agility.total;
|
||||
data.combatValues.movement.base = data.attributes.mobility.total / 2 + 1;
|
||||
data.combatValues.meleeAttack.base = data.attributes.body.total + data.traits.strength.total;
|
||||
data.combatValues.rangedAttack.base = data.attributes.mobility.total + data.traits.dexterity.total;
|
||||
data.combatValues.spellcasting.base =
|
||||
data.attributes.mind.total + data.traits.aura.total - spellMalusOfEquippedItems;
|
||||
data.combatValues.targetedSpellcasting.base =
|
||||
data.attributes.mind.total + data.traits.dexterity.total - spellMalusOfEquippedItems;
|
||||
|
||||
Object.values(data.combatValues).forEach(
|
||||
(combatValue: ModifiableDataBaseTotal<number>) => (combatValue.total = combatValue.base + combatValue.mod),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total armor value of the equipped items.
|
||||
*/
|
||||
protected calculateArmorValueOfEquippedItems(): number {
|
||||
return this.getEquippedItemsWithArmor()
|
||||
.map((item) => item.data.data.armorValue)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the spell malus from equipped items.
|
||||
*/
|
||||
protected calculateSpellMaluesOfEquippedItems(): number {
|
||||
return this.getEquippedItemsWithArmor()
|
||||
.filter(
|
||||
(item) =>
|
||||
!(item.data.type === "armor" && ["cloth", "natural"].includes(item.data.data.armorMaterialType)),
|
||||
)
|
||||
.map((item) => item.data.data.armorValue)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
private getEquippedItemsWithArmor() {
|
||||
return this.items
|
||||
.filter(
|
||||
(item): item is DS4Item & { data: DS4ArmorDataProperties | DS4ShieldDataProperties } =>
|
||||
item.data.type === "armor" || item.data.type === "shield",
|
||||
)
|
||||
.filter((item) => item.data.data.equipped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the check target numbers of checks for the actor.
|
||||
*/
|
||||
protected prepareChecks(): void {
|
||||
const data = this.data.data;
|
||||
data.checks = {
|
||||
appraise: data.attributes.mind.total + data.traits.intellect.total,
|
||||
changeSpell: data.attributes.mind.total + data.traits.intellect.total,
|
||||
climb: data.attributes.mobility.total + data.traits.strength.total,
|
||||
communicate: data.attributes.mind.total + data.traits.dexterity.total,
|
||||
decipherScript: data.attributes.mind.total + data.traits.intellect.total,
|
||||
defend: 0, // assigned in prepareFinalDerivedData as it must always match data.combatValues.defense.total and is not changeable by effects
|
||||
defyPoison: data.attributes.body.total + data.traits.constitution.total,
|
||||
disableTraps: data.attributes.mind.total + data.traits.dexterity.total,
|
||||
featOfStrength: data.attributes.body.total + data.traits.strength.total,
|
||||
flirt: data.attributes.mind.total + data.traits.aura.total,
|
||||
haggle: data.attributes.mind.total + Math.max(data.traits.intellect.total, data.traits.intellect.total),
|
||||
hide: data.attributes.mobility.total + data.traits.agility.total,
|
||||
identifyMagic: data.attributes.mind.total + data.traits.intellect.total,
|
||||
jump: data.attributes.mobility.total + data.traits.agility.total,
|
||||
knowledge: data.attributes.mind.total + data.traits.intellect.total,
|
||||
openLock: data.attributes.mind.total + data.traits.dexterity.total,
|
||||
perception: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
|
||||
pickPocket: data.attributes.mobility.total + data.traits.dexterity.total,
|
||||
readTracks: data.attributes.mind.total + data.traits.intellect.total,
|
||||
resistDisease: data.attributes.body.total + data.traits.constitution.total,
|
||||
ride: data.attributes.mobility.total + Math.max(data.traits.agility.total, data.traits.aura.total),
|
||||
search: Math.max(data.attributes.mind.total + data.traits.intellect.total, 8),
|
||||
senseMagic: data.attributes.mind.total + data.traits.aura.total,
|
||||
sneak: data.attributes.mobility.total + data.traits.agility.total,
|
||||
startFire: data.attributes.mind.total + data.traits.dexterity.total,
|
||||
swim: data.attributes.mobility.total + data.traits.strength.total,
|
||||
wakeUp: data.attributes.mind.total + data.traits.intellect.total,
|
||||
workMechanism:
|
||||
data.attributes.mind.total + Math.max(data.traits.dexterity.total, data.traits.intellect.total),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle how changes to a Token attribute bar are applied to the Actor.
|
||||
* This only differs from the base implementation by also allowing negative values.
|
||||
* @override
|
||||
*/
|
||||
async modifyTokenAttribute(
|
||||
attribute: string,
|
||||
value: number,
|
||||
isDelta = false,
|
||||
isBar = true,
|
||||
): Promise<this | undefined> {
|
||||
const current = foundry.utils.getProperty(this.data.data, attribute);
|
||||
|
||||
// Determine the updates to make to the actor data
|
||||
let updates: Record<string, number>;
|
||||
if (isBar) {
|
||||
if (isDelta) value = Math.min(Number(current.value) + value, current.max);
|
||||
updates = { [`data.${attribute}.value`]: value };
|
||||
} else {
|
||||
if (isDelta) value = Number(current) + value;
|
||||
updates = { [`data.${attribute}`]: value };
|
||||
}
|
||||
|
||||
// Call a hook to handle token resource bar updates
|
||||
const allowed = Hooks.call("modifyTokenAttribute", { attribute, value, isDelta, isBar }, updates);
|
||||
return allowed !== false ? this.update(updates) : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll for a given check.
|
||||
* @param check - The check to perform
|
||||
* @param options - Additional options to customize the roll
|
||||
*/
|
||||
async rollCheck(
|
||||
check: Check,
|
||||
options: { speaker?: { token?: TokenDocument; alias?: string } } = {},
|
||||
): Promise<void> {
|
||||
const speaker = ChatMessage.getSpeaker({ actor: this, ...options.speaker });
|
||||
await createCheckRoll(this.data.data.checks[check], {
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
|
||||
flavor: "DS4.ActorCheckFlavor",
|
||||
flavorData: { actor: speaker.alias ?? this.name, check: DS4.i18nKeys.checks[check] },
|
||||
speaker,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll a generic check. A dialog is presented to select the combination of
|
||||
* Attribute and Trait to perform the check against.
|
||||
* @param options - Additional options to customize the roll
|
||||
*/
|
||||
async rollGenericCheck(options: { speaker?: { token?: TokenDocument; alias?: string } } = {}): Promise<void> {
|
||||
const attributeAndTrait = await this.selectAttributeAndTrait();
|
||||
if (!attributeAndTrait) {
|
||||
return;
|
||||
}
|
||||
const { attribute, trait } = attributeAndTrait;
|
||||
const checkTargetNumber = this.data.data.attributes[attribute].total + this.data.data.traits[trait].total;
|
||||
const speaker = ChatMessage.getSpeaker({ actor: this, ...options.speaker });
|
||||
await createCheckRoll(checkTargetNumber, {
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
|
||||
flavor: "DS4.ActorGenericCheckFlavor",
|
||||
flavorData: {
|
||||
actor: speaker.alias ?? this.name,
|
||||
attribute: DS4.i18n.attributes[attribute],
|
||||
trait: DS4.i18n.traits[trait],
|
||||
},
|
||||
speaker,
|
||||
});
|
||||
}
|
||||
|
||||
protected async selectAttributeAndTrait(): Promise<{
|
||||
attribute: keyof typeof DS4.i18n.attributes;
|
||||
trait: keyof typeof DS4.i18n.traits;
|
||||
} | null> {
|
||||
const attributeIdentifier = "attribute-trait-selection-attribute";
|
||||
const traitIdentifier = "attribute-trait-selection-trait";
|
||||
return Dialog.prompt({
|
||||
title: getGame().i18n.localize("DS4.DialogAttributeTraitSelection"),
|
||||
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
|
||||
selects: [
|
||||
{
|
||||
label: getGame().i18n.localize("DS4.Attribute"),
|
||||
identifier: attributeIdentifier,
|
||||
options: Object.fromEntries(
|
||||
(Object.entries(DS4.i18n.attributes) as [keyof typeof DS4.i18n.attributes, string][]).map(
|
||||
([attribute, translation]) => [
|
||||
attribute,
|
||||
`${translation} (${this.data.data.attributes[attribute].total})`,
|
||||
],
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
label: getGame().i18n.localize("DS4.Trait"),
|
||||
identifier: traitIdentifier,
|
||||
options: Object.fromEntries(
|
||||
(Object.entries(DS4.i18n.traits) as [keyof typeof DS4.i18n.traits, string][]).map(
|
||||
([trait, translation]) => [
|
||||
trait,
|
||||
`${translation} (${this.data.data.traits[trait].total})`,
|
||||
],
|
||||
),
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
label: getGame().i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
const selectedAttribute = html.find(`#${attributeIdentifier}`).val();
|
||||
if (!isAttribute(selectedAttribute)) {
|
||||
throw new Error(
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedAttribute", {
|
||||
actualAttribute: selectedAttribute,
|
||||
expectedTypes: Object.keys(DS4.i18n.attributes)
|
||||
.map((attribute) => `'${attribute}'`)
|
||||
.join(", "),
|
||||
}),
|
||||
);
|
||||
}
|
||||
const selectedTrait = html.find(`#${traitIdentifier}`).val();
|
||||
if (!isTrait(selectedTrait)) {
|
||||
throw new Error(
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedTrait", {
|
||||
actualTrait: selectedTrait,
|
||||
expectedTypes: Object.keys(DS4.i18n.traits)
|
||||
.map((attribute) => `'${attribute}'`)
|
||||
.join(", "),
|
||||
}),
|
||||
);
|
||||
}
|
||||
return {
|
||||
attribute: selectedAttribute,
|
||||
trait: selectedTrait,
|
||||
};
|
||||
},
|
||||
rejectClose: false,
|
||||
});
|
||||
}
|
||||
}
|
435
src/actor/sheets/actor-sheet.ts
Normal file
435
src/actor/sheets/actor-sheet.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
// SPDX-FileCopyrightText: 2021 Siegfried Krug
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4ActiveEffect } from "../../active-effect";
|
||||
import { ModifiableDataBaseTotal } from "../../common/common-data";
|
||||
import { DS4 } from "../../config";
|
||||
import { getCanvas, getGame } from "../../helpers";
|
||||
import type { DS4Item } from "../../item/item";
|
||||
import { DS4Settings, getDS4Settings } from "../../settings";
|
||||
import notifications from "../../ui/notifications";
|
||||
import { enforce } from "../../utils";
|
||||
import { isCheck } from "../actor-data-properties";
|
||||
|
||||
/**
|
||||
* The base sheet class for all {@link DS4Actor}s.
|
||||
*/
|
||||
export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetData> {
|
||||
/** @override */
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sheet", "ds4-actor-sheet"],
|
||||
height: 625,
|
||||
scrollY: [".ds4-sheet-body"],
|
||||
tabs: [{ navSelector: ".ds4-sheet-tab-nav", contentSelector: ".ds4-sheet-body", initial: "values" }],
|
||||
dragDrop: [
|
||||
{ dragSelector: ".item-list .item", dropSelector: null },
|
||||
{ dragSelector: ".effect-list .effect", dropSelector: null },
|
||||
{ dragSelector: ".ds4-check", dropSelector: null },
|
||||
],
|
||||
width: 650,
|
||||
});
|
||||
}
|
||||
|
||||
/** @override */
|
||||
get template(): string {
|
||||
const basePath = "systems/ds4/templates/sheets/actor";
|
||||
return `${basePath}/${this.actor.data.type}-sheet.hbs`;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async getData(): Promise<DS4ActorSheetData> {
|
||||
const itemsByType = Object.fromEntries(
|
||||
Object.entries(this.actor.itemTypes).map(([itemType, items]) => {
|
||||
return [itemType, items.map((item) => item.data).sort((a, b) => (a.sort || 0) - (b.sort || 0))];
|
||||
}),
|
||||
);
|
||||
|
||||
const enrichedEffectPromises = this.actor.effects.map(async (effect) => {
|
||||
return {
|
||||
...effect.toObject(),
|
||||
sourceName: await effect.getCurrentSourceName(),
|
||||
factor: effect.factor,
|
||||
isEffectivelyEnabled: !effect.data.disabled && !effect.isSurpressed,
|
||||
};
|
||||
});
|
||||
const enrichedEffects = await Promise.all(enrichedEffectPromises);
|
||||
|
||||
const data = {
|
||||
...this.addTooltipsToData(await super.getData()),
|
||||
config: DS4,
|
||||
itemsByType,
|
||||
enrichedEffects,
|
||||
settings: getDS4Settings(),
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tooltips to the attributes, traits, and combatValues of the actor data of the given {@link ActorSheet.Data}.
|
||||
*/
|
||||
protected addTooltipsToData(data: ActorSheet.Data): ActorSheet.Data {
|
||||
const valueGroups = [data.data.data.attributes, data.data.data.traits, data.data.data.combatValues];
|
||||
|
||||
valueGroups.forEach((valueGroup) => {
|
||||
Object.values(valueGroup).forEach((attribute: ModifiableDataBaseTotal<number> & { tooltip?: string }) => {
|
||||
attribute.tooltip = this.getTooltipForValue(attribute);
|
||||
});
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tooltip for a given attribute, trait, or combatValue.
|
||||
*/
|
||||
protected getTooltipForValue(value: ModifiableDataBaseTotal<number>): string {
|
||||
return `${value.base} (${getGame().i18n.localize("DS4.TooltipBaseValue")}) + ${
|
||||
value.mod
|
||||
} (${getGame().i18n.localize("DS4.TooltipModifier")}) ➞ ${getGame().i18n.localize("DS4.TooltipEffects")} ➞ ${
|
||||
value.total
|
||||
}`;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
activateListeners(html: JQuery): void {
|
||||
super.activateListeners(html);
|
||||
|
||||
if (!this.options.editable) return;
|
||||
|
||||
html.find(".control-item").on("click", this.onControlItem.bind(this));
|
||||
html.find(".change-item").on("change", this.onChangeItem.bind(this));
|
||||
|
||||
html.find(".control-effect").on("click", this.onControlEffect.bind(this));
|
||||
html.find(".change-effect").on("change", this.onChangeEffect.bind(this));
|
||||
|
||||
html.find(".rollable-item").on("click", this.onRollItem.bind(this));
|
||||
html.find(".rollable-check").on("click", this.onRollCheck.bind(this));
|
||||
|
||||
html.find(".sort-items").on("click", this.onSortItems.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a click on an element of this sheet to control an embedded item of the actor corresponding to this sheet.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onControlItem(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
switch (a.dataset["action"]) {
|
||||
case "create":
|
||||
return this.onCreateItem(event);
|
||||
case "edit":
|
||||
return this.onEditItem(event);
|
||||
case "delete":
|
||||
return this.onDeleteItem(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new embedded item using the initial data defined in the HTML dataset of the clicked element.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onCreateItem(event: JQuery.ClickEvent): void {
|
||||
const { type, ...data } = foundry.utils.deepClone(event.currentTarget.dataset);
|
||||
const name = getGame().i18n.localize(`DS4.New${type.capitalize()}Name`);
|
||||
const itemData = {
|
||||
name: name,
|
||||
type: type,
|
||||
data: data,
|
||||
};
|
||||
Item.create(itemData, { parent: this.actor, pack: this.actor.pack ?? undefined });
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the sheet of the embedded item corresponding to the clicked element.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onEditItem(event: JQuery.ClickEvent): void {
|
||||
const id = $(event.currentTarget)
|
||||
.parents(embeddedDocumentListEntryProperties.Item.selector)
|
||||
.data(embeddedDocumentListEntryProperties.Item.idDataAttribute);
|
||||
const item = this.actor.items.get(id);
|
||||
enforce(item, getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
enforce(item.sheet);
|
||||
item.sheet.render(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the embedded item corresponding to the clicked element.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onDeleteItem(event: JQuery.ClickEvent): void {
|
||||
const li = $(event.currentTarget).parents(embeddedDocumentListEntryProperties.Item.selector);
|
||||
this.actor.deleteEmbeddedDocuments("Item", [li.data(embeddedDocumentListEntryProperties.Item.idDataAttribute)]);
|
||||
li.slideUp(200, () => this.render(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a change to a property of an embedded item depending on the `data-property` attribute of the
|
||||
* {@link HTMLInputElement} that has been changed and its new value.
|
||||
*
|
||||
* @param event - The originating change event
|
||||
*/
|
||||
protected onChangeItem(event: JQuery.ChangeEvent): void {
|
||||
return this.onChangeEmbeddedDocument(event, "Item");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a click on an element of this sheet to control an embedded effect of the actor corresponding to this
|
||||
* sheet.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onControlEffect(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
switch (a.dataset["action"]) {
|
||||
case "create":
|
||||
return this.onCreateEffect();
|
||||
case "edit":
|
||||
return this.onEditEffect(event);
|
||||
case "delete":
|
||||
return this.onDeleteEffect(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new embedded effect.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onCreateEffect(): void {
|
||||
DS4ActiveEffect.createDefault(this.actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the sheet of the embedded effect corresponding to the clicked element.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onEditEffect(event: JQuery.ClickEvent): void {
|
||||
const id = $(event.currentTarget)
|
||||
.parents(embeddedDocumentListEntryProperties.ActiveEffect.selector)
|
||||
.data(embeddedDocumentListEntryProperties.ActiveEffect.idDataAttribute);
|
||||
const effect = this.actor.effects.get(id);
|
||||
enforce(effect, getGame().i18n.format("DS4.ErrorActorDoesNotHaveEffect", { id, actor: this.actor.name }));
|
||||
effect.sheet.render(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the embedded item corresponding to the clicked element.
|
||||
*
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onDeleteEffect(event: JQuery.ClickEvent): void {
|
||||
const li = $(event.currentTarget).parents(embeddedDocumentListEntryProperties.ActiveEffect.selector);
|
||||
const id = li.data(embeddedDocumentListEntryProperties.ActiveEffect.idDataAttribute);
|
||||
this.actor.deleteEmbeddedDocuments("ActiveEffect", [id]);
|
||||
li.slideUp(200, () => this.render(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a change to a property of an embedded effect depending on the `data-property` attribute of the
|
||||
* {@link HTMLInputElement} that has been changed and its new value.
|
||||
*
|
||||
* @param event - The originating change event
|
||||
*/
|
||||
protected onChangeEffect(event: JQuery.ChangeEvent): void {
|
||||
return this.onChangeEmbeddedDocument(event, "ActiveEffect");
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a change to a property of an embedded document of the actor belonging to this sheet. The change depends
|
||||
* on the `data-property` attribute of the {@link HTMLInputElement} that has been changed and its new value.
|
||||
*
|
||||
* @param event - The originating change event
|
||||
* @param documentName - The name of the embedded document to be changed.
|
||||
*/
|
||||
protected onChangeEmbeddedDocument(event: JQuery.ChangeEvent, documentName: "Item" | "ActiveEffect"): void {
|
||||
event.preventDefault();
|
||||
const element = $(event.currentTarget).get(0);
|
||||
enforce(element instanceof HTMLInputElement);
|
||||
if (element.disabled) return;
|
||||
|
||||
const documentElement = element.closest(embeddedDocumentListEntryProperties[documentName].selector);
|
||||
enforce(documentElement instanceof HTMLElement);
|
||||
const id = documentElement.dataset[embeddedDocumentListEntryProperties[documentName].idDataAttribute];
|
||||
const property = element.dataset["property"];
|
||||
enforce(property !== undefined, TypeError("HTML element does not provide 'data-property' attribute"));
|
||||
|
||||
const newValue = this.parseValue(element);
|
||||
this.actor.updateEmbeddedDocuments(documentName, [{ _id: id, [property]: newValue }]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the value of the given {@link HTMLInputElement} depending on the element's type
|
||||
* The value is parsed to:
|
||||
* - checkbox: `boolean`, if the attribute `data-inverted` is set to a truthy value, the parsed value is inverted
|
||||
* - text input: `string`
|
||||
* - number: `number`
|
||||
*
|
||||
* @param element - The input element to parse the value from
|
||||
*/
|
||||
protected parseValue(element: HTMLInputElement): boolean | string | number {
|
||||
switch (element.type) {
|
||||
case "checkbox": {
|
||||
const inverted = Boolean(element.dataset["inverted"]);
|
||||
const value: boolean = element.checked;
|
||||
return inverted ? !value : value;
|
||||
}
|
||||
case "text": {
|
||||
const value: string = element.value;
|
||||
return value;
|
||||
}
|
||||
case "number": {
|
||||
const value = Number(element.value.trim());
|
||||
return value;
|
||||
}
|
||||
default: {
|
||||
throw new TypeError(
|
||||
"Binding of item property to this type of HTML element not supported; given: " + element,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clickable item rolls.
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onRollItem(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const id = $(event.currentTarget)
|
||||
.parents(embeddedDocumentListEntryProperties.Item.selector)
|
||||
.data(embeddedDocumentListEntryProperties.Item.idDataAttribute);
|
||||
const item = this.actor.items.get(id);
|
||||
enforce(item, getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
item.roll().catch((e) => notifications.error(e, { log: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clickable check rolls.
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onRollCheck(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const check = event.currentTarget.dataset["check"];
|
||||
this.actor.rollCheck(check).catch((e) => notifications.error(e, { log: true }));
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onDragStart(event: DragEvent): void {
|
||||
const target = event.currentTarget as HTMLElement;
|
||||
if (!(target instanceof HTMLElement)) return super._onDragStart(event);
|
||||
|
||||
const check = target.dataset.check;
|
||||
if (!check) return super._onDragStart(event);
|
||||
|
||||
enforce(isCheck(check), getGame().i18n.format("DS4.ErrorCannotDragMissingCheck", { check }));
|
||||
|
||||
const dragData = {
|
||||
actorId: this.actor.id,
|
||||
sceneId: this.actor.isToken ? getCanvas().scene?.id : null,
|
||||
tokenId: this.actor.isToken ? this.actor.token?.id : null,
|
||||
type: "Check",
|
||||
data: check,
|
||||
};
|
||||
|
||||
event.dataTransfer?.setData("text/plain", JSON.stringify(dragData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort items according to the item list header that has been clicked.
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected onSortItems(event: JQuery.ClickEvent<unknown, unknown, HTMLElement>): void {
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget;
|
||||
const type = target.parentElement?.dataset["type"];
|
||||
enforce(type !== undefined, `Could not find property 'type' in the dataset of the parent of ${target}`);
|
||||
const dataPath = target.dataset["dataPath"];
|
||||
enforce(dataPath !== undefined, `Could not find property 'dataPath' in the dataset of ${target}`);
|
||||
const items = this.actor.items.filter((item) => item.type === type);
|
||||
items.sort((a, b) => a.data.sort - b.data.sort);
|
||||
|
||||
const sortFunction =
|
||||
(invert: boolean) =>
|
||||
(a: DS4Item, b: DS4Item): number => {
|
||||
const propertyA = getProperty(a.data, dataPath);
|
||||
const propertyB = getProperty(b.data, dataPath);
|
||||
if (typeof propertyA === "string" || typeof propertyB === "string") {
|
||||
return invert
|
||||
? (propertyB ?? "").localeCompare(propertyA ?? "")
|
||||
: (propertyA ?? "").localeCompare(propertyB ?? "");
|
||||
} else {
|
||||
return invert ? propertyB - propertyA : propertyA - propertyB;
|
||||
}
|
||||
};
|
||||
|
||||
const sortedItems = [...items].sort(sortFunction(false));
|
||||
const wasSortedAlready = !sortedItems.find((item, index) => item !== items[index]);
|
||||
|
||||
if (wasSortedAlready) {
|
||||
sortedItems.sort(sortFunction(true));
|
||||
}
|
||||
|
||||
const updates = sortedItems.map((item, i) => ({
|
||||
_id: item.id,
|
||||
sort: (i + 1) * CONST.SORT_INTEGER_DENSITY,
|
||||
}));
|
||||
|
||||
this.actor.updateEmbeddedDocuments("Item", updates);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
protected async _onDropItem(event: DragEvent, data: ActorSheet.DropData.Item): Promise<unknown> {
|
||||
const item = await Item.fromDropData(data);
|
||||
if (item && !this.actor.canOwnItemType(item.data.type)) {
|
||||
notifications.warn(
|
||||
getGame().i18n.format("DS4.WarningActorCannotOwnItem", {
|
||||
actorName: this.actor.name,
|
||||
actorType: this.actor.data.type,
|
||||
itemName: item.name,
|
||||
itemType: item.data.type,
|
||||
}),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return super._onDropItem(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
interface DS4ActorSheetData extends ActorSheet.Data {
|
||||
config: typeof DS4;
|
||||
itemsByType: Record<string, foundry.data.ItemData[]>;
|
||||
enrichedEffects: EnrichedActiveEffectDataSource[];
|
||||
settings: DS4Settings;
|
||||
}
|
||||
|
||||
type ActiveEffectDataSource = foundry.data.ActiveEffectData["_source"];
|
||||
|
||||
interface EnrichedActiveEffectDataSource extends ActiveEffectDataSource {
|
||||
sourceName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This object contains information about specific properties embedded document list entries for each different type.
|
||||
*/
|
||||
const embeddedDocumentListEntryProperties = Object.freeze({
|
||||
ActiveEffect: {
|
||||
selector: ".effect",
|
||||
idDataAttribute: "effectId",
|
||||
},
|
||||
Item: {
|
||||
selector: ".item",
|
||||
idDataAttribute: "itemId",
|
||||
},
|
||||
});
|
17
src/actor/sheets/character-sheet.ts
Normal file
17
src/actor/sheets/character-sheet.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4ActorSheet } from "./actor-sheet";
|
||||
|
||||
/**
|
||||
* The Sheet class for DS4 Character Actors
|
||||
*/
|
||||
export class DS4CharacterActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sheet", "ds4-actor-sheet", "ds4-character-sheet"],
|
||||
});
|
||||
}
|
||||
}
|
17
src/actor/sheets/creature-sheet.ts
Normal file
17
src/actor/sheets/creature-sheet.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4ActorSheet } from "./actor-sheet";
|
||||
|
||||
/**
|
||||
* The Sheet class for DS4 Creature Actors
|
||||
*/
|
||||
export class DS4CreatureActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sheet", "ds4-actor-sheet", "ds4-creature-sheet"],
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue