Merge branch '074-update-to-foundry-0.8.x' into 'master'
Update to 0.8.x Closes #74 See merge request dungeonslayers/ds4!117
This commit is contained in:
commit
a07dd207c3
66 changed files with 2505 additions and 2792 deletions
4
.husky/.gitignore
vendored
4
.husky/.gitignore
vendored
|
@ -1,5 +1 @@
|
|||
# SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
_
|
||||
|
|
3
.husky/.gitignore.license
Normal file
3
.husky/.gitignore.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
|
@ -52,7 +52,7 @@
|
|||
"postinstall": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@league-of-foundry-developers/foundry-vtt-types": "^0.7.10-0",
|
||||
"@league-of-foundry-developers/foundry-vtt-types": "^0.8.8-0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/jest": "^26.0.23",
|
||||
|
|
|
@ -5,7 +5,23 @@
|
|||
|
||||
import evaluateCheck from "../../src/module/rolls/check-evaluation";
|
||||
|
||||
Object.defineProperty(globalThis, "game", { value: { i18n: { localize: (key: string) => key } } });
|
||||
class StubGame {
|
||||
constructor() {
|
||||
this.i18n = {
|
||||
localize: (key: string) => key,
|
||||
};
|
||||
}
|
||||
i18n: {
|
||||
localize: (key: string) => string;
|
||||
};
|
||||
}
|
||||
|
||||
const game = new StubGame();
|
||||
|
||||
Object.defineProperties(globalThis, {
|
||||
game: { value: game },
|
||||
Game: { value: StubGame },
|
||||
});
|
||||
|
||||
describe("evaluateCheck with no dice", () => {
|
||||
it("should throw an error.", () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"DS4.UserInteractionAddEffect": "Neuer Effekt",
|
||||
"DS4.UserInteractionEditEffect": "Effekt bearbeiten",
|
||||
"DS4.UserInteractionDeleteEffect": "Effekt löschen",
|
||||
"DS4.EntityImageAltText": "Bild von {name}",
|
||||
"DS4.DocumentImageAltText": "Bild von {name}",
|
||||
"DS4.RollableImageRollableTitle": "Für {name} würfeln",
|
||||
"DS4.DiceOverlayImageAltText": "Bild eines W20",
|
||||
"DS4.NotOwned": "Nicht besessen",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"DS4.UserInteractionAddEffect": "Add Effect",
|
||||
"DS4.UserInteractionEditEffect": "Edit Effect",
|
||||
"DS4.UserInteractionDeleteEffect": "Delete Effect",
|
||||
"DS4.EntityImageAltText": "Image of {name}",
|
||||
"DS4.DocumentImageAltText": "Image of {name}",
|
||||
"DS4.RollableImageRollableTitle": "Roll for {name}",
|
||||
"DS4.DiceOverlayImageAltText": "Image of a d20",
|
||||
"DS4.NotOwned": "No owner",
|
||||
|
|
87
src/module/actor/actor-data-properties.ts
Normal file
87
src/module/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;
|
||||
}
|
|
@ -7,50 +7,56 @@
|
|||
|
||||
import { ModifiableData, ModifiableDataBase, ResourceData, UsableResource } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import { DS4ItemData } from "../item/item-data";
|
||||
|
||||
export type DS4ActorData = DS4CharacterData | DS4CreatureData;
|
||||
|
||||
type ActorType = keyof typeof DS4.i18n.actorTypes;
|
||||
|
||||
export interface DS4ActorDataHelper<T, U extends ActorType> extends Actor.Data<T, DS4ItemData> {
|
||||
type: U;
|
||||
declare global {
|
||||
interface SourceConfig {
|
||||
Actor: DS4ActorDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
type DS4CharacterData = DS4ActorDataHelper<DS4CharacterDataData, "character">;
|
||||
type DS4CreatureData = DS4ActorDataHelper<DS4CreatureDataData, "creature">;
|
||||
export type DS4ActorDataSource = DS4CharacterDataSource | DS4CreatureDataSource;
|
||||
|
||||
interface DS4CharacterDataSource {
|
||||
type: "character";
|
||||
data: DS4CharacterDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4CreatureDataSource {
|
||||
type: "creature";
|
||||
data: DS4CreatureDataSourceData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ActorDataDataBase {
|
||||
attributes: DS4ActorDataDataAttributes;
|
||||
traits: DS4ActorDataDataTraits;
|
||||
combatValues: DS4ActorDataDataCombatValues;
|
||||
interface DS4ActorDataSourceDataBase {
|
||||
attributes: DS4ActorDataSourceDataAttributes;
|
||||
traits: DS4ActorDataSourceDataTraits;
|
||||
combatValues: DS4ActorDataSourceDataCombatValues;
|
||||
}
|
||||
|
||||
type DS4ActorDataDataAttributes = { [Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBase<number> };
|
||||
type DS4ActorDataSourceDataAttributes = { [Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBase<number> };
|
||||
|
||||
type Attribute = keyof DS4ActorDataDataAttributes;
|
||||
type Attribute = keyof DS4ActorDataSourceDataAttributes;
|
||||
|
||||
export function isAttribute(value: unknown): value is Attribute {
|
||||
return (Object.keys(DS4.i18n.attributes) as Array<unknown>).includes(value);
|
||||
}
|
||||
|
||||
type DS4ActorDataDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBase<number> };
|
||||
type DS4ActorDataSourceDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBase<number> };
|
||||
|
||||
type Trait = keyof DS4ActorDataDataTraits;
|
||||
type Trait = keyof DS4ActorDataSourceDataTraits;
|
||||
|
||||
export function isTrait(value: unknown): value is Trait {
|
||||
return (Object.keys(DS4.i18n.traits) as Array<unknown>).includes(value);
|
||||
}
|
||||
|
||||
type DS4ActorDataDataCombatValues = {
|
||||
type DS4ActorDataSourceDataCombatValues = {
|
||||
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
|
||||
? ResourceData<number>
|
||||
: ModifiableData<number>;
|
||||
};
|
||||
|
||||
type CombatValue = keyof DS4ActorDataDataCombatValues;
|
||||
type CombatValue = keyof DS4ActorDataSourceDataCombatValues;
|
||||
|
||||
export function isCombatValue(value: string): value is CombatValue {
|
||||
return (Object.keys(DS4.i18n.combatValues) as Array<unknown>).includes(value);
|
||||
|
@ -58,33 +64,44 @@ export function isCombatValue(value: string): value is CombatValue {
|
|||
|
||||
// types
|
||||
|
||||
interface DS4CharacterDataData extends DS4ActorDataDataBase {
|
||||
baseInfo: DS4CharacterDataDataBaseInfo;
|
||||
progression: DS4CharacterDataDataProgression;
|
||||
language: DS4CharacterDataDataLanguage;
|
||||
profile: DS4CharacterDataDataProfile;
|
||||
currency: DS4CharacterDataDataCurrency;
|
||||
slayerPoints: DS4CharacterDataDataSlayerPoints;
|
||||
interface DS4CreatureDataSourceData extends DS4ActorDataSourceDataBase {
|
||||
baseInfo: DS4CreatureDataSourceDataBaseInfo;
|
||||
}
|
||||
export interface DS4CharacterDataDataBaseInfo {
|
||||
|
||||
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 DS4CharacterDataDataProgression {
|
||||
export interface DS4CharacterDataSourceDataProgression {
|
||||
level: number;
|
||||
experiencePoints: number;
|
||||
talentPoints: UsableResource<number>;
|
||||
progressPoints: UsableResource<number>;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataLanguage {
|
||||
languages: string;
|
||||
alphabets: string;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataProfile {
|
||||
export interface DS4CharacterDataSourceDataProfile {
|
||||
biography: string;
|
||||
gender: string;
|
||||
birthday: string;
|
||||
|
@ -97,29 +114,12 @@ export interface DS4CharacterDataDataProfile {
|
|||
specialCharacteristics: string;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataCurrency {
|
||||
export interface DS4CharacterDataSourceDataCurrency {
|
||||
gold: number;
|
||||
silver: number;
|
||||
copper: number;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataSlayerPoints {
|
||||
export interface DS4CharacterDataSourceDataSlayerPoints {
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface DS4CreatureDataData extends DS4ActorDataDataBase {
|
||||
baseInfo: DS4CreatureDataDataBaseInfo;
|
||||
}
|
||||
|
||||
export interface DS4CreatureDataDataBaseInfo {
|
||||
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;
|
|
@ -1,77 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseTotal, ResourceDataBaseTotalMax } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import {
|
||||
DS4ActorDataHelper,
|
||||
DS4CharacterDataDataBaseInfo,
|
||||
DS4CharacterDataDataCurrency,
|
||||
DS4CharacterDataDataLanguage,
|
||||
DS4CharacterDataDataProfile,
|
||||
DS4CharacterDataDataProgression,
|
||||
DS4CharacterDataDataSlayerPoints,
|
||||
DS4CreatureDataDataBaseInfo,
|
||||
} from "./actor-data";
|
||||
|
||||
export type DS4ActorPreparedData = DS4CharacterPreparedData | DS4CreaturePreparedData;
|
||||
|
||||
type DS4CharacterPreparedData = DS4ActorDataHelper<DS4CharacterPreparedDataData, "character">;
|
||||
type DS4CreaturePreparedData = DS4ActorDataHelper<DS4CreaturePreparedDataData, "creature">;
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ActorPreparedDataDataBase {
|
||||
attributes: DS4ActorPreparedDataDataAttributes;
|
||||
traits: DS4ActorPreparedDataDataTraits;
|
||||
combatValues: DS4ActorPreparedDataDataCombatValues;
|
||||
rolling: DS4ActorPreparedDataDataRolling;
|
||||
checks: DS4ActorPreparedDataDataChecks;
|
||||
}
|
||||
|
||||
type DS4ActorPreparedDataDataAttributes = {
|
||||
[Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
type DS4ActorPreparedDataDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal<number> };
|
||||
|
||||
type DS4ActorPreparedDataDataCombatValues = {
|
||||
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
|
||||
? ResourceDataBaseTotalMax<number>
|
||||
: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
interface DS4ActorPreparedDataDataRolling {
|
||||
maximumCoupResult: number;
|
||||
minimumFumbleResult: 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);
|
||||
}
|
||||
|
||||
type DS4ActorPreparedDataDataChecks = {
|
||||
[key in Check]: number;
|
||||
};
|
||||
|
||||
// types
|
||||
|
||||
interface DS4CharacterPreparedDataData extends DS4ActorPreparedDataDataBase {
|
||||
baseInfo: DS4CharacterDataDataBaseInfo;
|
||||
progression: DS4CharacterDataDataProgression;
|
||||
language: DS4CharacterDataDataLanguage;
|
||||
profile: DS4CharacterDataDataProfile;
|
||||
currency: DS4CharacterDataDataCurrency;
|
||||
slayerPoints: DS4CharacterPreparedDataDataSlayerPoints;
|
||||
}
|
||||
|
||||
export interface DS4CharacterPreparedDataDataSlayerPoints extends DS4CharacterDataDataSlayerPoints {
|
||||
max: number;
|
||||
}
|
||||
|
||||
interface DS4CreaturePreparedDataData extends DS4ActorPreparedDataDataBase {
|
||||
baseInfo: DS4CreatureDataDataBaseInfo;
|
||||
}
|
|
@ -5,22 +5,27 @@
|
|||
|
||||
import { ModifiableDataBaseTotal } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { ItemType } from "../item/item-data";
|
||||
import { DS4ArmorPreparedData, DS4ShieldPreparedData } from "../item/item-prepared-data";
|
||||
import { DS4ArmorDataProperties, DS4ShieldDataProperties } from "../item/item-data-properties";
|
||||
import { ItemType } from "../item/item-data-source";
|
||||
import { createCheckRoll } from "../rolls/check-factory";
|
||||
import { DS4ActorData, isAttribute, isTrait } from "./actor-data";
|
||||
import { Check, DS4ActorPreparedData } from "./actor-prepared-data";
|
||||
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<DS4ActorData, DS4Item, DS4ActorPreparedData> {
|
||||
export class DS4Actor extends Actor {
|
||||
/** @override */
|
||||
prepareData(): void {
|
||||
this.data = duplicate(this._data) as DS4ActorPreparedData;
|
||||
if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN;
|
||||
if (!this.data.name) this.data.name = "New " + this.entity;
|
||||
this.data.reset();
|
||||
this.prepareBaseData();
|
||||
this.prepareEmbeddedEntities();
|
||||
this.applyActiveEffectsToBaseData();
|
||||
|
@ -49,6 +54,15 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* We override this with an empty implementation because we have our own custom way of applying
|
||||
* {@link ActiveEffect}s and {@link Actor#prepareEmbeddedEntities} 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 = {};
|
||||
|
@ -68,32 +82,30 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
*
|
||||
* @param predicate - The predicate that ActiveEffectChanges need to satisfy in order to be applied
|
||||
*/
|
||||
applyActiveEffectsFiltered(predicate: (change: ActiveEffectChange) => boolean): void {
|
||||
applyActiveEffectsFiltered(predicate: (change: foundry.data.ActiveEffectData["changes"][number]) => boolean): void {
|
||||
const overrides: Record<string, unknown> = {};
|
||||
|
||||
// Organize non-disabled effects by their application priority
|
||||
const changes = this.effects.reduce(
|
||||
(changes: Array<ActiveEffectChange & { effect: ActiveEffect<DS4Actor> }>, e) => {
|
||||
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) return changes;
|
||||
const item = this._getOriginatingItemOfActiveEffect(e);
|
||||
const item = this.getOriginatingItemOfActiveEffect(e);
|
||||
if (item?.isNonEquippedEuipable()) return changes;
|
||||
|
||||
const factor = item?.activeEffectFactor ?? 1;
|
||||
|
||||
return changes.concat(
|
||||
e.data.changes.filter(predicate).flatMap((c) => {
|
||||
const duplicatedChange = duplicate(c);
|
||||
duplicatedChange.priority = duplicatedChange.priority ?? duplicatedChange.mode * 10;
|
||||
return Array(factor).fill({
|
||||
...duplicatedChange,
|
||||
effect: e,
|
||||
const newChanges = e.data.changes.filter(predicate).flatMap((c) => {
|
||||
const changeSource = c.toObject();
|
||||
changeSource.priority = changeSource.priority ?? changeSource.mode * 10;
|
||||
return Array(factor).fill({ ...changeSource, effect: e });
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return changes.concat(newChanges);
|
||||
},
|
||||
[],
|
||||
);
|
||||
changes.sort((a, b) => a.priority - b.priority);
|
||||
changes.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
||||
|
||||
// Apply all changes
|
||||
for (const change of changes) {
|
||||
|
@ -102,11 +114,11 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
}
|
||||
|
||||
// Expand the set of final overrides
|
||||
this.overrides = expandObject({ ...flattenObject(this.overrides), ...overrides });
|
||||
this.overrides = foundry.utils.expandObject({ ...foundry.utils.flattenObject(this.overrides), ...overrides });
|
||||
}
|
||||
|
||||
protected _getOriginatingItemOfActiveEffect(effect: ActiveEffect<DS4Actor>): DS4Item | undefined {
|
||||
return this.items.find((item) => item.uuid === effect.data.origin) ?? undefined;
|
||||
protected getOriginatingItemOfActiveEffect(effect: ActiveEffect): DS4Item | undefined {
|
||||
return this.items.find((item) => item.uuid === effect.data.origin);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,7 +228,7 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
return this.items
|
||||
.map((item) => item.data)
|
||||
.filter(
|
||||
(data): data is DS4ArmorPreparedData | DS4ShieldPreparedData =>
|
||||
(data): data is foundry.data.ItemData & (DS4ArmorDataProperties | DS4ShieldDataProperties) =>
|
||||
data.type === "armor" || data.type === "shield",
|
||||
)
|
||||
.filter((data) => data.data.equipped)
|
||||
|
@ -267,8 +279,13 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
* 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> {
|
||||
const current = getProperty(this.data.data, attribute);
|
||||
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>;
|
||||
|
@ -291,10 +308,10 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
*/
|
||||
async rollCheck(check: Check): Promise<void> {
|
||||
await createCheckRoll(this.data.data.checks[check], {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ActorCheckFlavor", { actor: this.name, check: DS4.i18n.checks[check] }),
|
||||
flavor: getGame().i18n.format("DS4.ActorCheckFlavor", { actor: this.name, check: DS4.i18n.checks[check] }),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -306,10 +323,10 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
const { attribute, trait } = await this.selectAttributeAndTrait();
|
||||
const checkTargetNumber = this.data.data.attributes[attribute].total + this.data.data.traits[trait].total;
|
||||
await createCheckRoll(checkTargetNumber, {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ActorGenericCheckFlavor", {
|
||||
flavor: getGame().i18n.format("DS4.ActorGenericCheckFlavor", {
|
||||
actor: this.name,
|
||||
attribute: DS4.i18n.attributes[attribute],
|
||||
trait: DS4.i18n.traits[trait],
|
||||
|
@ -324,27 +341,27 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
const attributeIdentifier = "attribute-trait-selection-attribute";
|
||||
const traitIdentifier = "attribute-trait-selection-trait";
|
||||
return Dialog.prompt({
|
||||
title: game.i18n.localize("DS4.DialogAttributeTraitSelection"),
|
||||
title: getGame().i18n.localize("DS4.DialogAttributeTraitSelection"),
|
||||
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
|
||||
selects: [
|
||||
{
|
||||
label: game.i18n.localize("DS4.Attribute"),
|
||||
label: getGame().i18n.localize("DS4.Attribute"),
|
||||
identifier: attributeIdentifier,
|
||||
options: DS4.i18n.attributes,
|
||||
},
|
||||
{
|
||||
label: game.i18n.localize("DS4.Trait"),
|
||||
label: getGame().i18n.localize("DS4.Trait"),
|
||||
identifier: traitIdentifier,
|
||||
options: DS4.i18n.traits,
|
||||
},
|
||||
],
|
||||
}),
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
const selectedAttribute = html.find(`#${attributeIdentifier}`).val();
|
||||
if (!isAttribute(selectedAttribute)) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedAttribute", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedAttribute", {
|
||||
actualAttribute: selectedAttribute,
|
||||
expectedTypes: Object.keys(DS4.i18n.attributes)
|
||||
.map((attribute) => `'${attribute}'`)
|
||||
|
@ -355,7 +372,7 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
const selectedTrait = html.find(`#${traitIdentifier}`).val();
|
||||
if (!isTrait(selectedTrait)) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedTrait", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedTrait", {
|
||||
actualTrait: selectedTrait,
|
||||
expectedTypes: Object.keys(DS4.i18n.traits)
|
||||
.map((attribute) => `'${attribute}'`)
|
||||
|
|
|
@ -7,24 +7,19 @@
|
|||
|
||||
import { ModifiableDataBaseTotal } from "../../common/common-data";
|
||||
import { DS4 } from "../../config";
|
||||
import { getCanvas } from "../../helpers";
|
||||
import { getCanvas, getGame } from "../../helpers";
|
||||
import { DS4Item } from "../../item/item";
|
||||
import { DS4ItemData } from "../../item/item-data";
|
||||
import { getDS4Settings } from "../../settings";
|
||||
import { DS4Settings, getDS4Settings } from "../../settings";
|
||||
import notifications from "../../ui/notifications";
|
||||
import { DS4Actor } from "../actor";
|
||||
import { isCheck } from "../actor-prepared-data";
|
||||
import { isCheck } from "../actor-data-properties";
|
||||
|
||||
/**
|
||||
* The base Sheet class for all DS4 Actors
|
||||
*/
|
||||
export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
||||
// TODO(types): Improve mergeObject in upstream so that it isn't necessary to provide all parameters (see https://github.com/League-of-Foundry-Developers/foundry-vtt-types/issues/272)
|
||||
export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetData> {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor"],
|
||||
height: 620,
|
||||
scrollY: [
|
||||
|
@ -57,14 +52,14 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* object itemsByType.
|
||||
* @returns The data fed to the template of the actor sheet
|
||||
*/
|
||||
async getData(): Promise<ActorSheet.Data<DS4Actor>> {
|
||||
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 data = {
|
||||
...this._addTooltipsToData(await super.getData()),
|
||||
...this.addTooltipsToData(await super.getData()),
|
||||
// Add the localization config to the data:
|
||||
config: DS4,
|
||||
// Add the items explicitly sorted by type to the data:
|
||||
|
@ -74,21 +69,23 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
return data;
|
||||
}
|
||||
|
||||
protected _addTooltipsToData(data: ActorSheet.Data<DS4Actor>): ActorSheet.Data<DS4Actor> {
|
||||
const valueGroups = [data.data.attributes, data.data.traits, data.data.combatValues];
|
||||
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);
|
||||
attribute.tooltip = this.getTooltipForValue(attribute);
|
||||
});
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
protected _getTooltipForValue(value: ModifiableDataBaseTotal<number>): string {
|
||||
return `${value.base} (${game.i18n.localize("DS4.TooltipBaseValue")}) + ${value.mod} (${game.i18n.localize(
|
||||
"DS4.TooltipModifier",
|
||||
)}) ➞ ${game.i18n.localize("DS4.TooltipEffects")} ➞ ${value.total}`;
|
||||
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 */
|
||||
|
@ -99,18 +96,18 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
if (!this.options.editable) return;
|
||||
|
||||
// Add Inventory Item
|
||||
html.find(".item-create").on("click", this._onItemCreate.bind(this));
|
||||
html.find(".item-create").on("click", this.onItemCreate.bind(this));
|
||||
|
||||
// Update Inventory Item
|
||||
html.find(".item-edit").on("click", (ev) => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
const id = li.data("itemId");
|
||||
const item = this.actor.getOwnedItem(id);
|
||||
const item = this.actor.items.get(id);
|
||||
if (!item) {
|
||||
throw new Error(game.i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
}
|
||||
if (!item.sheet) {
|
||||
throw new Error(game.i18n.localize("DS4.ErrorUnexpectedError"));
|
||||
throw new Error(getGame().i18n.localize("DS4.ErrorUnexpectedError"));
|
||||
}
|
||||
item.sheet.render(true);
|
||||
});
|
||||
|
@ -118,38 +115,36 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
// Delete Inventory Item
|
||||
html.find(".item-delete").on("click", (ev) => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
this.actor.deleteOwnedItem(li.data("itemId"));
|
||||
this.actor.deleteEmbeddedDocuments("Item", [li.data("itemId")]);
|
||||
li.slideUp(200, () => this.render(false));
|
||||
});
|
||||
|
||||
html.find(".item-change").on("change", this._onItemChange.bind(this));
|
||||
html.find(".item-change").on("change", this.onItemChange.bind(this));
|
||||
|
||||
html.find(".rollable-item").on("click", this._onRollItem.bind(this));
|
||||
html.find(".rollable-item").on("click", this.onRollItem.bind(this));
|
||||
|
||||
html.find(".rollable-check").on("click", this._onRollCheck.bind(this));
|
||||
html.find(".rollable-check").on("click", this.onRollCheck.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
|
||||
* Handle creating a new embedded Item for the actor using initial data defined in the HTML dataset
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected _onItemCreate(event: JQuery.ClickEvent): Promise<DS4ItemData> {
|
||||
protected onItemCreate(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const header = event.currentTarget;
|
||||
// Get the type of item to create.
|
||||
// Grab any data associated with this control.
|
||||
const { type, ...data } = duplicate(header.dataset);
|
||||
// Initialize a default name.
|
||||
|
||||
const { type, ...data } = foundry.utils.deepClone(header.dataset);
|
||||
|
||||
const name = `New ${type.capitalize()}`;
|
||||
// Prepare the item object.
|
||||
|
||||
const itemData = {
|
||||
name: name,
|
||||
type: type,
|
||||
data: data,
|
||||
};
|
||||
|
||||
// Finally, create the item!
|
||||
return this.actor.createOwnedItem(itemData);
|
||||
DS4Item.create(itemData, { parent: this.actor });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,11 +153,15 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* Assumes the item property is given as the value of the HTML element property 'data-property'.
|
||||
* @param ev - The originating change event
|
||||
*/
|
||||
protected _onItemChange(ev: JQuery.ChangeEvent): void {
|
||||
protected onItemChange(ev: JQuery.ChangeEvent): void {
|
||||
ev.preventDefault();
|
||||
const el: HTMLFormElement = $(ev.currentTarget).get(0);
|
||||
const id = $(ev.currentTarget).parents(".item").data("itemId");
|
||||
const item = duplicate(this.actor.getOwnedItem(id));
|
||||
const item = this.actor.items.get(id);
|
||||
if (!item) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
}
|
||||
const itemObject = item.toObject();
|
||||
const property: string | undefined = $(ev.currentTarget).data("property");
|
||||
|
||||
// Early return:
|
||||
|
@ -175,8 +174,8 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
|
||||
// Set new value
|
||||
const newValue = this.getValue(el);
|
||||
setProperty(item, property, newValue);
|
||||
this.actor.updateOwnedItem(item);
|
||||
foundry.utils.setProperty(itemObject, property, newValue);
|
||||
item.update({ ...itemObject });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -238,10 +237,13 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* Handle clickable item rolls.
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected _onRollItem(event: JQuery.ClickEvent): void {
|
||||
protected onRollItem(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const id = $(event.currentTarget).parents(".item").data("itemId");
|
||||
const item = this.actor.getOwnedItem(id);
|
||||
const item = this.actor.items.get(id);
|
||||
if (!item) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
}
|
||||
item.roll().catch((e) => notifications.error(e, { log: true }));
|
||||
}
|
||||
|
||||
|
@ -249,7 +251,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* Handle clickable check rolls.
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected _onRollCheck(event: JQuery.ClickEvent): void {
|
||||
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 }));
|
||||
|
@ -263,7 +265,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
const check = target.dataset.check;
|
||||
if (!check) return super._onDragStart(event);
|
||||
|
||||
if (!isCheck(check)) throw new Error(game.i18n.format("DS4.ErrorCannotDragMissingCheck", { check }));
|
||||
if (!isCheck(check)) throw new Error(getGame().i18n.format("DS4.ErrorCannotDragMissingCheck", { check }));
|
||||
|
||||
const dragData = {
|
||||
actorId: this.actor.id,
|
||||
|
@ -277,18 +279,11 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
protected async _onDropItem(
|
||||
event: DragEvent,
|
||||
data: { type: "Item" } & (
|
||||
| { data: DeepPartial<ActorSheet.OwnedItemData<DS4Actor>> }
|
||||
| { pack: string }
|
||||
| { id: string }
|
||||
),
|
||||
): Promise<boolean | undefined | ActorSheet.OwnedItemData<DS4Actor>> {
|
||||
protected async _onDropItem(event: DragEvent, data: ActorSheet.DropData.Item): Promise<unknown> {
|
||||
const item = await DS4Item.fromDropData(data);
|
||||
if (item && !this.actor.canOwnItemType(item.data.type)) {
|
||||
notifications.warn(
|
||||
game.i18n.format("DS4.WarningActorCannotOwnItem", {
|
||||
getGame().i18n.format("DS4.WarningActorCannotOwnItem", {
|
||||
actorName: this.actor.name,
|
||||
actorType: this.actor.data.type,
|
||||
itemName: item.name,
|
||||
|
@ -300,3 +295,9 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
return super._onDropItem(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
interface DS4ActorSheetData extends ActorSheet.Data<ActorSheet.Options> {
|
||||
config: typeof DS4;
|
||||
itemsByType: Record<string, foundry.data.ItemData[]>;
|
||||
settings: DS4Settings;
|
||||
}
|
||||
|
|
|
@ -9,10 +9,8 @@ import { DS4ActorSheet } from "./actor-sheet";
|
|||
*/
|
||||
export class DS4CharacterActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor", "character"],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,10 +9,8 @@ import { DS4ActorSheet } from "./actor-sheet";
|
|||
*/
|
||||
export class DS4CreatureActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor", "creature"],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ export interface HasTotal<T> {
|
|||
total: T;
|
||||
}
|
||||
|
||||
export interface ModifiableDataTotal<T> extends ModifiableData<T>, HasTotal<T> {}
|
||||
|
||||
export interface ModifiableDataBaseTotal<T> extends ModifiableDataBase<T>, HasTotal<T> {}
|
||||
|
||||
export interface ResourceData<T> extends ModifiableData<T> {
|
||||
|
@ -27,6 +25,10 @@ export interface HasMax<T> {
|
|||
max: T;
|
||||
}
|
||||
|
||||
export interface ModifiableDataBaseMax<T> extends ModifiableDataBase<T>, HasMax<T> {}
|
||||
|
||||
export interface ModifiableDataBaseTotalMax<T> extends ModifiableDataBaseMax<T>, HasTotal<T> {}
|
||||
|
||||
export interface ResourceDataBaseTotalMax<T> extends ResourceData<T>, HasBase<T>, HasTotal<T>, HasMax<T> {}
|
||||
|
||||
export interface UsableResource<T> {
|
||||
|
|
12
src/module/global.d.ts
vendored
12
src/module/global.d.ts
vendored
|
@ -2,10 +2,20 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
declare namespace ClientSettings {
|
||||
declare global {
|
||||
namespace ClientSettings {
|
||||
interface Values {
|
||||
"ds4.systemMigrationVersion": number;
|
||||
"ds4.useSlayingDiceForAutomatedChecks": boolean;
|
||||
"ds4.showSlayerPoints": boolean;
|
||||
}
|
||||
}
|
||||
|
||||
namespace PoolTerm {
|
||||
interface Modifiers {
|
||||
x: (this: PoolTerm, modifier: string) => void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
|
||||
export function getCanvas(): Canvas {
|
||||
if (!(canvas instanceof Canvas) || !canvas.ready) {
|
||||
throw new Error(game.i18n.localize("DS4.ErrorCanvasIsNotInitialized"));
|
||||
throw new Error(getGame().i18n.localize("DS4.ErrorCanvasIsNotInitialized"));
|
||||
}
|
||||
return canvas;
|
||||
}
|
||||
|
||||
export function getGame(): Game {
|
||||
if (!(game instanceof Game)) {
|
||||
throw new Error("Game is not initialized yet."); // Cannot localize this as we would need to access game to do this.
|
||||
}
|
||||
return game;
|
||||
}
|
||||
|
|
|
@ -2,25 +2,27 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { isCheck } from "../actor/actor-prepared-data";
|
||||
import { isCheck } from "../actor/actor-data-properties";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { DS4ItemData } from "../item/item-data";
|
||||
import { createRollCheckMacro } from "../macros/roll-check";
|
||||
import { createRollItemMacro } from "../macros/roll-item";
|
||||
import notifications from "../ui/notifications";
|
||||
|
||||
export default function registerForHotbarDropHook(): void {
|
||||
Hooks.on("hotbarDrop", async (hotbar: Hotbar, data: { type: string } & Record<string, unknown>, slot: string) => {
|
||||
Hooks.on("hotbarDrop", async (hotbar: Hotbar, data: HotbarDropData, slot: string) => {
|
||||
switch (data.type) {
|
||||
case "Item": {
|
||||
if (!("data" in data)) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems"));
|
||||
if (!isItemDropData(data) || !("data" in data)) {
|
||||
return notifications.warn(
|
||||
getGame().i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems"),
|
||||
);
|
||||
}
|
||||
const itemData = data.data as DS4ItemData;
|
||||
const itemData = data.data;
|
||||
|
||||
if (!DS4Item.rollableItemTypes.includes(itemData.type)) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningItemIsNotRollable", {
|
||||
getGame().i18n.format("DS4.WarningItemIsNotRollable", {
|
||||
name: itemData.name,
|
||||
id: itemData._id,
|
||||
type: itemData.type,
|
||||
|
@ -31,10 +33,16 @@ export default function registerForHotbarDropHook(): void {
|
|||
}
|
||||
case "Check": {
|
||||
if (!("data" in data) || typeof data.data !== "string" || !isCheck(data.data)) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningInvalidCheckDropped"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningInvalidCheckDropped"));
|
||||
}
|
||||
return createRollCheckMacro(data.data, slot);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
type HotbarDropData = ActorSheet.DropData.Item | ({ type: string } & Partial<Record<string, unknown>>);
|
||||
|
||||
function isItemDropData(dropData: HotbarDropData): dropData is ActorSheet.DropData.Item {
|
||||
return dropData.type === "Item";
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { DS4CreatureActorSheet } from "../actor/sheets/creature-sheet";
|
|||
import { DS4 } from "../config";
|
||||
import registerHandlebarsHelpers from "../handlebars/handlebars-helpers";
|
||||
import registerHandlebarsPartials from "../handlebars/handlebars-partials";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { DS4ItemSheet } from "../item/item-sheet";
|
||||
import logger from "../logger";
|
||||
|
@ -28,7 +29,7 @@ export default function registerForInitHook(): void {
|
|||
async function init() {
|
||||
logger.info(`Initializing the DS4 Game System\n${DS4.ASCII}`);
|
||||
|
||||
game.ds4 = {
|
||||
getGame().ds4 = {
|
||||
DS4Actor,
|
||||
DS4Item,
|
||||
DS4,
|
||||
|
@ -39,8 +40,8 @@ async function init() {
|
|||
|
||||
CONFIG.DS4 = DS4;
|
||||
|
||||
CONFIG.Actor.entityClass = DS4Actor;
|
||||
CONFIG.Item.entityClass = DS4Item;
|
||||
CONFIG.Actor.documentClass = DS4Actor;
|
||||
CONFIG.Item.documentClass = DS4Item;
|
||||
|
||||
CONFIG.Actor.typeLabels = DS4.i18n.actorTypes;
|
||||
CONFIG.Item.typeLabels = DS4.i18n.itemTypes;
|
||||
|
@ -63,3 +64,20 @@ async function init() {
|
|||
await registerHandlebarsPartials();
|
||||
registerHandlebarsHelpers();
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Game {
|
||||
ds4: {
|
||||
DS4Actor: typeof DS4Actor;
|
||||
DS4Item: typeof DS4Item;
|
||||
DS4: typeof DS4;
|
||||
createCheckRoll: typeof createCheckRoll;
|
||||
migration: typeof migration;
|
||||
macros: typeof macros;
|
||||
};
|
||||
}
|
||||
|
||||
interface CONFIG {
|
||||
DS4: typeof DS4;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
|
||||
export default function registerForSetupHooks(): void {
|
||||
Hooks.once("setup", () => {
|
||||
|
@ -21,7 +22,7 @@ function localizeAndSortConfigObjects() {
|
|||
|
||||
const localizeObject = <T extends { [s: string]: string }>(obj: T, sort = true): T => {
|
||||
const localized = Object.entries(obj).map(([key, value]) => {
|
||||
return [key, game.i18n.localize(value)];
|
||||
return [key, getGame().i18n.localize(value)];
|
||||
});
|
||||
if (sort) localized.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
return Object.fromEntries(localized);
|
||||
|
|
130
src/module/item/item-data-properties.ts
Normal file
130
src/module/item/item-data-properties.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseTotalMax } from "../common/common-data";
|
||||
import {
|
||||
DS4AlphabetDataSourceData,
|
||||
DS4ArmorDataSourceData,
|
||||
DS4EquipmentDataSourceData,
|
||||
DS4LanguageDataSourceData,
|
||||
DS4LootDataSourceData,
|
||||
DS4RacialAbilityDataSourceData,
|
||||
DS4ShieldDataSourceData,
|
||||
DS4SpecialCreatureAbilityDataSourceData,
|
||||
DS4SpellDataSourceData,
|
||||
DS4TalentDataSourceData,
|
||||
DS4WeaponDataSourceData,
|
||||
} from "./item-data-source";
|
||||
|
||||
declare global {
|
||||
interface DataConfig {
|
||||
Item: DS4ItemDataProperties;
|
||||
}
|
||||
}
|
||||
|
||||
export type DS4ItemDataProperties =
|
||||
| DS4WeaponDataProperties
|
||||
| DS4ArmorDataProperties
|
||||
| DS4ShieldDataProperties
|
||||
| DS4SpellDataProperties
|
||||
| DS4EquipmentDataProperties
|
||||
| DS4LootDataProperties
|
||||
| DS4TalentDataProperties
|
||||
| DS4RacialAbilityDataProperties
|
||||
| DS4LanguageDataProperties
|
||||
| DS4AlphabetDataProperties
|
||||
| DS4SpecialCreatureAbilityDataProperties;
|
||||
|
||||
export interface DS4WeaponDataProperties {
|
||||
type: "weapon";
|
||||
data: DS4WeaponDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4ArmorDataProperties {
|
||||
type: "armor";
|
||||
data: DS4ArmorDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4ShieldDataProperties {
|
||||
type: "shield";
|
||||
data: DS4ShieldDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4SpellDataProperties {
|
||||
type: "spell";
|
||||
data: DS4SpellDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4EquipmentDataProperties {
|
||||
type: "equipment";
|
||||
data: DS4EquipmentDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4LootDataProperties {
|
||||
type: "loot";
|
||||
data: DS4LootDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4TalentDataProperties {
|
||||
type: "talent";
|
||||
data: DS4TalentDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4RacialAbilityDataProperties {
|
||||
type: "racialAbility";
|
||||
data: DS4RacialAbilityDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4LanguageDataProperties {
|
||||
type: "language";
|
||||
data: DS4LanguageDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4AlphabetDataProperties {
|
||||
type: "alphabet";
|
||||
data: DS4AlphabetDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4SpecialCreatureAbilityDataProperties {
|
||||
type: "specialCreatureAbility";
|
||||
data: DS4SpecialCreatureAbilityDataPropertiesData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemDataPropertiesDataRollable {
|
||||
rollable: boolean;
|
||||
}
|
||||
|
||||
//types
|
||||
|
||||
interface DS4WeaponDataPropertiesData extends DS4WeaponDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4ArmorDataPropertiesData extends DS4ArmorDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4ShieldDataPropertiesData extends DS4ShieldDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4SpellDataPropertiesData extends DS4SpellDataSourceData, DS4ItemDataPropertiesDataRollable {
|
||||
price: number | null;
|
||||
}
|
||||
|
||||
interface DS4EquipmentDataPropertiesData extends DS4EquipmentDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4LootDataPropertiesData extends DS4LootDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4TalentDataPropertiesData extends DS4TalentDataSourceData, DS4ItemDataPropertiesDataRollable {
|
||||
rank: ModifiableDataBaseTotalMax<number>;
|
||||
}
|
||||
|
||||
interface DS4RacialAbilityDataPropertiesData
|
||||
extends DS4RacialAbilityDataSourceData,
|
||||
DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4LanguageDataPropertiesData extends DS4LanguageDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4AlphabetDataPropertiesData extends DS4AlphabetDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4SpecialCreatureAbilityDataPropertiesData
|
||||
extends DS4SpecialCreatureAbilityDataSourceData,
|
||||
DS4ItemDataPropertiesDataRollable {}
|
184
src/module/item/item-data-source.ts
Normal file
184
src/module/item/item-data-source.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseMax } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
|
||||
declare global {
|
||||
interface SourceConfig {
|
||||
Item: DS4ItemDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
export type ItemType = keyof typeof DS4.i18n.itemTypes;
|
||||
|
||||
export type DS4ItemDataSource =
|
||||
| DS4WeaponDataSource
|
||||
| DS4ArmorDataSource
|
||||
| DS4ShieldDataSource
|
||||
| DS4SpellDataSource
|
||||
| DS4EquipmentDataSource
|
||||
| DS4LootDataSource
|
||||
| DS4TalentDataSource
|
||||
| DS4RacialAbilityDataSource
|
||||
| DS4LanguageDataSource
|
||||
| DS4AlphabetDataSource
|
||||
| DS4SpecialCreatureAbilityDataSource;
|
||||
|
||||
interface DS4WeaponDataSource {
|
||||
type: "weapon";
|
||||
data: DS4WeaponDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4ArmorDataSource {
|
||||
type: "armor";
|
||||
data: DS4ArmorDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4ShieldDataSource {
|
||||
type: "shield";
|
||||
data: DS4ShieldDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4SpellDataSource {
|
||||
type: "spell";
|
||||
data: DS4SpellDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4EquipmentDataSource {
|
||||
type: "equipment";
|
||||
data: DS4EquipmentDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4LootDataSource {
|
||||
type: "loot";
|
||||
data: DS4LootDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4TalentDataSource {
|
||||
type: "talent";
|
||||
data: DS4TalentDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4RacialAbilityDataSource {
|
||||
type: "racialAbility";
|
||||
data: DS4RacialAbilityDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4LanguageDataSource {
|
||||
type: "language";
|
||||
data: DS4LanguageDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4AlphabetDataSource {
|
||||
type: "alphabet";
|
||||
data: DS4AlphabetDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4SpecialCreatureAbilityDataSource {
|
||||
type: "specialCreatureAbility";
|
||||
data: DS4SpecialCreatureAbilityDataSourceData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemDataSourceDataBase {
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface DS4ItemDataSourceDataPhysical {
|
||||
quantity: number;
|
||||
price: number;
|
||||
availability: keyof typeof DS4.i18n.itemAvailabilities;
|
||||
storageLocation: string;
|
||||
}
|
||||
|
||||
export function isDS4ItemDataTypePhysical(input: foundry.data.ItemData["data"]): boolean {
|
||||
return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input;
|
||||
}
|
||||
|
||||
interface DS4ItemDataSourceDataEquipable {
|
||||
equipped: boolean;
|
||||
}
|
||||
|
||||
interface DS4ItemDataSourceDataProtective {
|
||||
armorValue: number;
|
||||
}
|
||||
|
||||
// types
|
||||
|
||||
export interface DS4WeaponDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable {
|
||||
attackType: AttackType;
|
||||
weaponBonus: number;
|
||||
opponentDefense: number;
|
||||
}
|
||||
|
||||
export type AttackType = keyof typeof DS4.i18n.attackTypes;
|
||||
|
||||
export interface DS4ArmorDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable,
|
||||
DS4ItemDataSourceDataProtective {
|
||||
armorMaterialType: keyof typeof DS4.i18n.armorMaterialTypes;
|
||||
armorType: keyof typeof DS4.i18n.armorTypes;
|
||||
}
|
||||
|
||||
export interface DS4ShieldDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable,
|
||||
DS4ItemDataSourceDataProtective {}
|
||||
|
||||
export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataEquipable {
|
||||
spellType: keyof typeof DS4.i18n.spellTypes;
|
||||
bonus: string;
|
||||
spellCategory: keyof typeof DS4.i18n.spellCategories;
|
||||
maxDistance: UnitData<DistanceUnit>;
|
||||
effectRadius: UnitData<DistanceUnit>;
|
||||
duration: UnitData<CustomTemporalUnit>;
|
||||
cooldownDuration: UnitData<TemporalUnit>;
|
||||
minimumLevels: {
|
||||
healer: number | null;
|
||||
wizard: number | null;
|
||||
sorcerer: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UnitData<UnitType> {
|
||||
value: string;
|
||||
unit: UnitType;
|
||||
}
|
||||
|
||||
type DistanceUnit = keyof typeof DS4.i18n.distanceUnits;
|
||||
|
||||
type CustomTemporalUnit = keyof typeof DS4.i18n.customTemporalUnits;
|
||||
|
||||
export type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
|
||||
|
||||
export interface DS4EquipmentDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable {}
|
||||
|
||||
export interface DS4LootDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataPhysical {}
|
||||
|
||||
export interface DS4TalentDataSourceData extends DS4ItemDataSourceDataBase {
|
||||
rank: ModifiableDataBaseMax<number>;
|
||||
}
|
||||
|
||||
export type DS4RacialAbilityDataSourceData = DS4ItemDataSourceDataBase;
|
||||
|
||||
export type DS4LanguageDataSourceData = DS4ItemDataSourceDataBase;
|
||||
|
||||
export type DS4AlphabetDataSourceData = DS4ItemDataSourceDataBase;
|
||||
|
||||
export interface DS4SpecialCreatureAbilityDataSourceData extends DS4ItemDataSourceDataBase {
|
||||
experiencePoints: number;
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBase } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
|
||||
export type ItemType = keyof typeof DS4.i18n.itemTypes;
|
||||
|
||||
export type DS4ItemData =
|
||||
| DS4WeaponData
|
||||
| DS4ArmorData
|
||||
| DS4ShieldData
|
||||
| DS4SpellData
|
||||
| DS4EquipmentData
|
||||
| DS4LootData
|
||||
| DS4TalentData
|
||||
| DS4RacialAbilityData
|
||||
| DS4LanguageData
|
||||
| DS4AlphabetData
|
||||
| DS4SpecialCreatureAbilityData;
|
||||
|
||||
export interface DS4ItemDataHelper<T, U extends ItemType> extends Item.Data<T> {
|
||||
type: U;
|
||||
}
|
||||
|
||||
type DS4WeaponData = DS4ItemDataHelper<DS4WeaponDataData, "weapon">;
|
||||
type DS4ArmorData = DS4ItemDataHelper<DS4ArmorDataData, "armor">;
|
||||
type DS4ShieldData = DS4ItemDataHelper<DS4ShieldDataData, "shield">;
|
||||
type DS4SpellData = DS4ItemDataHelper<DS4SpellDataData, "spell">;
|
||||
type DS4EquipmentData = DS4ItemDataHelper<DS4EquipmentDataData, "equipment">;
|
||||
type DS4LootData = DS4ItemDataHelper<DS4LootDataData, "loot">;
|
||||
type DS4TalentData = DS4ItemDataHelper<DS4TalentDataData, "talent">;
|
||||
type DS4RacialAbilityData = DS4ItemDataHelper<DS4RacialAbilityDataData, "racialAbility">;
|
||||
type DS4LanguageData = DS4ItemDataHelper<DS4LanguageDataData, "language">;
|
||||
type DS4AlphabetData = DS4ItemDataHelper<DS4AlphabetDataData, "alphabet">;
|
||||
type DS4SpecialCreatureAbilityData = DS4ItemDataHelper<DS4SpecialCreatureAbilityDataData, "specialCreatureAbility">;
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemDataDataBase {
|
||||
description: string;
|
||||
}
|
||||
interface DS4ItemDataDataPhysical {
|
||||
quantity: number;
|
||||
price: number;
|
||||
availability: keyof typeof DS4.i18n.itemAvailabilities;
|
||||
storageLocation: string;
|
||||
}
|
||||
|
||||
export function isDS4ItemDataTypePhysical(input: DS4ItemData["data"]): boolean {
|
||||
return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input;
|
||||
}
|
||||
|
||||
interface DS4ItemDataDataEquipable {
|
||||
equipped: boolean;
|
||||
}
|
||||
|
||||
interface DS4ItemDataDataProtective {
|
||||
armorValue: number;
|
||||
}
|
||||
|
||||
export interface UnitData<UnitType> {
|
||||
value: string;
|
||||
unit: UnitType;
|
||||
}
|
||||
export type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
|
||||
type CustomTemporalUnit = keyof typeof DS4.i18n.customTemporalUnits;
|
||||
type DistanceUnit = keyof typeof DS4.i18n.distanceUnits;
|
||||
|
||||
// types
|
||||
|
||||
export interface DS4WeaponDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {
|
||||
attackType: AttackType;
|
||||
weaponBonus: number;
|
||||
opponentDefense: number;
|
||||
}
|
||||
|
||||
export type AttackType = keyof typeof DS4.i18n.attackTypes;
|
||||
|
||||
export interface DS4ArmorDataData
|
||||
extends DS4ItemDataDataBase,
|
||||
DS4ItemDataDataPhysical,
|
||||
DS4ItemDataDataEquipable,
|
||||
DS4ItemDataDataProtective {
|
||||
armorMaterialType: keyof typeof DS4.i18n.armorMaterialTypes;
|
||||
armorType: keyof typeof DS4.i18n.armorTypes;
|
||||
}
|
||||
|
||||
export interface DS4TalentDataData extends DS4ItemDataDataBase {
|
||||
rank: DS4TalentRank;
|
||||
}
|
||||
|
||||
export interface DS4TalentRank extends ModifiableDataBase<number> {
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable {
|
||||
spellType: keyof typeof DS4.i18n.spellTypes;
|
||||
bonus: string;
|
||||
spellCategory: keyof typeof DS4.i18n.spellCategories;
|
||||
maxDistance: UnitData<DistanceUnit>;
|
||||
effectRadius: UnitData<DistanceUnit>;
|
||||
duration: UnitData<CustomTemporalUnit>;
|
||||
cooldownDuration: UnitData<TemporalUnit>;
|
||||
minimumLevels: {
|
||||
healer: number | null;
|
||||
wizard: number | null;
|
||||
sorcerer: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DS4ShieldDataData
|
||||
extends DS4ItemDataDataBase,
|
||||
DS4ItemDataDataPhysical,
|
||||
DS4ItemDataDataEquipable,
|
||||
DS4ItemDataDataProtective {}
|
||||
|
||||
export interface DS4EquipmentDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {}
|
||||
|
||||
export interface DS4LootDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical {}
|
||||
|
||||
export type DS4RacialAbilityDataData = DS4ItemDataDataBase;
|
||||
|
||||
export type DS4LanguageDataData = DS4ItemDataDataBase;
|
||||
|
||||
export type DS4AlphabetDataData = DS4ItemDataDataBase;
|
||||
|
||||
export interface DS4SpecialCreatureAbilityDataData extends DS4ItemDataDataBase {
|
||||
experiencePoints: number;
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { HasTotal } from "../common/common-data";
|
||||
import {
|
||||
DS4AlphabetDataData,
|
||||
DS4ArmorDataData,
|
||||
DS4EquipmentDataData,
|
||||
DS4ItemDataHelper,
|
||||
DS4LanguageDataData,
|
||||
DS4LootDataData,
|
||||
DS4RacialAbilityDataData,
|
||||
DS4ShieldDataData,
|
||||
DS4SpecialCreatureAbilityDataData,
|
||||
DS4SpellDataData,
|
||||
DS4TalentDataData,
|
||||
DS4TalentRank,
|
||||
DS4WeaponDataData,
|
||||
} from "./item-data";
|
||||
|
||||
export type DS4ItemPreparedData =
|
||||
| DS4WeaponPreparedData
|
||||
| DS4ArmorPreparedData
|
||||
| DS4ShieldPreparedData
|
||||
| DS4SpellPreparedData
|
||||
| DS4EquipmentPreparedData
|
||||
| DS4LootPreparedData
|
||||
| DS4TalentPreparedData
|
||||
| DS4RacialAbilityPreparedData
|
||||
| DS4LanguagePreparedData
|
||||
| DS4AlphabetPreparedData
|
||||
| DS4SpecialCreatureAbilityPreparedData;
|
||||
|
||||
export type DS4WeaponPreparedData = DS4ItemDataHelper<DS4WeaponPreparedDataData, "weapon">;
|
||||
export type DS4ArmorPreparedData = DS4ItemDataHelper<DS4ArmorPreparedDataData, "armor">;
|
||||
export type DS4ShieldPreparedData = DS4ItemDataHelper<DS4ShieldPreparedDataData, "shield">;
|
||||
export type DS4SpellPreparedData = DS4ItemDataHelper<DS4SpellPreparedDataData, "spell">;
|
||||
export type DS4EquipmentPreparedData = DS4ItemDataHelper<DS4EquipmentPreparedDataData, "equipment">;
|
||||
export type DS4LootPreparedData = DS4ItemDataHelper<DS4LootPreparedDataData, "loot">;
|
||||
export type DS4TalentPreparedData = DS4ItemDataHelper<DS4TalentPreparedDataData, "talent">;
|
||||
export type DS4RacialAbilityPreparedData = DS4ItemDataHelper<DS4RacialAbilityPreparedDataData, "racialAbility">;
|
||||
export type DS4LanguagePreparedData = DS4ItemDataHelper<DS4LanguagePreparedDataData, "language">;
|
||||
export type DS4AlphabetPreparedData = DS4ItemDataHelper<DS4AlphabetPreparedDataData, "alphabet">;
|
||||
export type DS4SpecialCreatureAbilityPreparedData = DS4ItemDataHelper<
|
||||
DS4SpecialCreatureAbilityPreparedDataData,
|
||||
"specialCreatureAbility"
|
||||
>;
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemPreparedDataDataRollable {
|
||||
rollable: boolean;
|
||||
}
|
||||
|
||||
//types
|
||||
|
||||
interface DS4WeaponPreparedDataData extends DS4WeaponDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4ArmorPreparedDataData extends DS4ArmorDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4ShieldPreparedDataData extends DS4ShieldDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4SpellPreparedDataData extends DS4SpellDataData, DS4ItemPreparedDataDataRollable {
|
||||
price: number | null;
|
||||
}
|
||||
|
||||
interface DS4EquipmentPreparedDataData extends DS4EquipmentDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4LootPreparedDataData extends DS4LootDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4TalentPreparedDataData extends DS4TalentDataData, DS4ItemPreparedDataDataRollable {
|
||||
rank: DS4TalentPreparedRank;
|
||||
}
|
||||
|
||||
interface DS4TalentPreparedRank extends DS4TalentRank, HasTotal<number> {}
|
||||
|
||||
interface DS4RacialAbilityPreparedDataData extends DS4RacialAbilityDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4LanguagePreparedDataData extends DS4LanguageDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4AlphabetPreparedDataData extends DS4AlphabetDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4SpecialCreatureAbilityPreparedDataData
|
||||
extends DS4SpecialCreatureAbilityDataData,
|
||||
DS4ItemPreparedDataDataRollable {}
|
|
@ -5,19 +5,17 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { DS4Item } from "./item";
|
||||
import { isDS4ItemDataTypePhysical } from "./item-data";
|
||||
import { isDS4ItemDataTypePhysical } from "./item-data-source";
|
||||
|
||||
/**
|
||||
* The Sheet class for DS4 Items
|
||||
*/
|
||||
export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
||||
export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData> {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ItemSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
width: 540,
|
||||
height: 400,
|
||||
classes: ["ds4", "sheet", "item"],
|
||||
|
@ -33,7 +31,7 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
async getData(): Promise<ItemSheet.Data<DS4Item>> {
|
||||
async getData(): Promise<DS4ItemSheetData> {
|
||||
const data = {
|
||||
...(await super.getData()),
|
||||
config: DS4,
|
||||
|
@ -45,11 +43,14 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
setPosition(options: Partial<Application.Position> = {}): Application.Position & { height: number } {
|
||||
setPosition(options: Partial<Application.Position> = {}): (Application.Position & { height: number }) | undefined {
|
||||
const position = super.setPosition(options);
|
||||
if (position) {
|
||||
const sheetBody = this.element.find(".sheet-body");
|
||||
const bodyHeight = position.height - 192;
|
||||
sheetBody.css("height", bodyHeight);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
@ -70,23 +71,25 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
event.preventDefault();
|
||||
|
||||
if (this.item.isOwned) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningManageActiveEffectOnOwnedItem"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningManageActiveEffectOnOwnedItem"));
|
||||
}
|
||||
const a = event.currentTarget;
|
||||
const li = $(a).parents(".effect");
|
||||
|
||||
switch (a.dataset["action"]) {
|
||||
case "create":
|
||||
return this._createActiveEffect();
|
||||
return this.createActiveEffect();
|
||||
case "edit":
|
||||
const id = li.data("effectId");
|
||||
const effect = this.item.effects.get(id);
|
||||
if (!effect) {
|
||||
throw new Error(game.i18n.format("DS4.ErrorItemDoesNotHaveEffect", { id, item: this.item.name }));
|
||||
throw new Error(
|
||||
getGame().i18n.format("DS4.ErrorItemDoesNotHaveEffect", { id, item: this.item.name }),
|
||||
);
|
||||
}
|
||||
return effect.sheet.render(true);
|
||||
case "delete": {
|
||||
return this.item.deleteEmbeddedEntity("ActiveEffect", li.data("effectId"));
|
||||
return this.item.deleteEmbeddedDocuments("ActiveEffect", [li.data("effectId")]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,17 +97,19 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
/**
|
||||
* Create a new ActiveEffect for the item using default data.
|
||||
*/
|
||||
protected async _createActiveEffect(): Promise<ActiveEffect.Data> {
|
||||
const label = `New Effect`;
|
||||
|
||||
protected async createActiveEffect(): Promise<ActiveEffect | undefined> {
|
||||
const createData = {
|
||||
label: label,
|
||||
changes: [],
|
||||
duration: {},
|
||||
transfer: true,
|
||||
label: "New Effect",
|
||||
icon: "icons/svg/aura.svg",
|
||||
};
|
||||
|
||||
const effect = ActiveEffect.create(createData, this.item);
|
||||
return effect.create({});
|
||||
return ActiveEffect.create({ ...createData }, { parent: this.item });
|
||||
}
|
||||
}
|
||||
|
||||
interface DS4ItemSheetData extends ItemSheet.Data<ItemSheet.Options> {
|
||||
config: typeof DS4;
|
||||
isOwned: boolean;
|
||||
actor: DS4ItemSheet["item"]["actor"];
|
||||
isPhysical: boolean;
|
||||
}
|
||||
|
|
|
@ -3,26 +3,29 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import { createCheckRoll } from "../rolls/check-factory";
|
||||
import notifications from "../ui/notifications";
|
||||
import { AttackType, DS4ItemData, ItemType } from "./item-data";
|
||||
import { DS4ItemPreparedData } from "./item-prepared-data";
|
||||
import { AttackType, ItemType } from "./item-data-source";
|
||||
import { calculateSpellPrice } from "./type-specific-helpers/spell";
|
||||
|
||||
declare global {
|
||||
interface DocumentClassConfig {
|
||||
Item: typeof DS4Item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Item class for DS4
|
||||
*/
|
||||
export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
export class DS4Item extends Item {
|
||||
/** @override */
|
||||
prepareData(): void {
|
||||
super.prepareData();
|
||||
this.prepareDerivedData();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
prepareDerivedData(): void {
|
||||
if (this.data.type === "talent") {
|
||||
const data = this.data.data;
|
||||
|
@ -63,24 +66,22 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
* Roll a check for an action with this item.
|
||||
*/
|
||||
async roll(): Promise<void> {
|
||||
if (!this.isOwnedItem()) {
|
||||
throw new Error(game.i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
|
||||
}
|
||||
|
||||
switch (this.data.type) {
|
||||
case "weapon":
|
||||
return this.rollWeapon();
|
||||
case "spell":
|
||||
return this.rollSpell();
|
||||
default:
|
||||
throw new Error(game.i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type }));
|
||||
throw new Error(
|
||||
getGame().i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected async rollWeapon(this: this & { readonly isOwned: true }): Promise<void> {
|
||||
protected async rollWeapon(): Promise<void> {
|
||||
if (!(this.data.type === "weapon")) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorWrongItemType", {
|
||||
getGame().i18n.format("DS4.ErrorWrongItemType", {
|
||||
actualType: this.data.type,
|
||||
expectedType: "weapon",
|
||||
id: this.id,
|
||||
|
@ -91,7 +92,7 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
|
||||
if (!this.data.data.equipped) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
getGame().i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
type: this.data.type,
|
||||
|
@ -99,24 +100,27 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
);
|
||||
}
|
||||
|
||||
const actor = this.actor as unknown as DS4Actor; // TODO(types): Improve so that the concrete Actor type is known here
|
||||
const ownerDataData = actor.data.data;
|
||||
if (!this.actor) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
|
||||
}
|
||||
|
||||
const ownerDataData = this.actor.data.data;
|
||||
const weaponBonus = this.data.data.weaponBonus;
|
||||
const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType);
|
||||
const checkTargetNumber = ownerDataData.combatValues[combatValue].total + weaponBonus;
|
||||
|
||||
await createCheckRoll(checkTargetNumber, {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ItemWeaponCheckFlavor", { actor: actor.name, weapon: this.name }),
|
||||
flavor: getGame().i18n.format("DS4.ItemWeaponCheckFlavor", { actor: this.actor.name, weapon: this.name }),
|
||||
});
|
||||
}
|
||||
|
||||
protected async rollSpell(): Promise<void> {
|
||||
if (!(this.data.type === "spell")) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorWrongItemType", {
|
||||
getGame().i18n.format("DS4.ErrorWrongItemType", {
|
||||
actualType: this.data.type,
|
||||
expectedType: "spell",
|
||||
id: this.id,
|
||||
|
@ -127,7 +131,7 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
|
||||
if (!this.data.data.equipped) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
getGame().i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
type: this.data.type,
|
||||
|
@ -135,12 +139,15 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
);
|
||||
}
|
||||
|
||||
const actor = this.actor as unknown as DS4Actor; // TODO(types): Improve so that the concrete Actor type is known here
|
||||
const ownerDataData = actor.data.data;
|
||||
if (!this.actor) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
|
||||
}
|
||||
|
||||
const ownerDataData = this.actor.data.data;
|
||||
const spellBonus = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined;
|
||||
if (spellBonus === undefined) {
|
||||
notifications.info(
|
||||
game.i18n.format("DS4.InfoManuallyEnterSpellBonus", {
|
||||
getGame().i18n.format("DS4.InfoManuallyEnterSpellBonus", {
|
||||
name: this.name,
|
||||
spellBonus: this.data.data.bonus,
|
||||
}),
|
||||
|
@ -150,10 +157,10 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
const checkTargetNumber = ownerDataData.combatValues[spellType].total + (spellBonus ?? 0);
|
||||
|
||||
await createCheckRoll(checkTargetNumber, {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ItemSpellCheckFlavor", { actor: actor.name, spell: this.name }),
|
||||
flavor: getGame().i18n.format("DS4.ItemSpellCheckFlavor", { actor: this.actor.name, spell: this.name }),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -162,22 +169,22 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
const { melee, ranged } = { ...DS4.i18n.attackTypes };
|
||||
const identifier = "attack-type-selection";
|
||||
return Dialog.prompt({
|
||||
title: game.i18n.localize("DS4.DialogAttackTypeSelection"),
|
||||
title: getGame().i18n.localize("DS4.DialogAttackTypeSelection"),
|
||||
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
|
||||
selects: [
|
||||
{
|
||||
label: game.i18n.localize("DS4.AttackType"),
|
||||
label: getGame().i18n.localize("DS4.AttackType"),
|
||||
identifier,
|
||||
options: { melee, ranged },
|
||||
},
|
||||
],
|
||||
}),
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
const selectedAttackType = html.find(`#${identifier}`).val();
|
||||
if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedAttackType", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedAttackType", {
|
||||
actualType: selectedAttackType,
|
||||
expectedTypes: "'melee', 'ranged'",
|
||||
}),
|
||||
|
@ -190,11 +197,4 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
return `${attackType}Attack` as const;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-guarding variant to check if the item is owned.
|
||||
*/
|
||||
isOwnedItem(): this is this & { readonly isOwned: true } {
|
||||
return this.isOwned;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { hoursPerDay, minutesPerHour, secondsPerMinute, secondsPerRound } from "../../common/time-helpers";
|
||||
import { DS4SpellDataData, TemporalUnit, UnitData } from "../item-data";
|
||||
import { DS4SpellDataSourceData, TemporalUnit, UnitData } from "../item-data-source";
|
||||
|
||||
export function calculateSpellPrice(data: DS4SpellDataData): number | null {
|
||||
export function calculateSpellPrice(data: DS4SpellDataSourceData): number | null {
|
||||
const spellPriceFactor = calculateSpellPriceFactor(data.cooldownDuration);
|
||||
const baseSpellPrices = [
|
||||
data.minimumLevels.healer !== null ? 10 + (data.minimumLevels.healer - 1) * 35 : null,
|
||||
|
|
|
@ -8,24 +8,17 @@ const loggingSeparator = "|";
|
|||
type LogLevel = "debug" | "info" | "warning" | "error";
|
||||
type LoggingFunction = (...data: unknown[]) => void;
|
||||
|
||||
class Logger {
|
||||
readonly debug: LoggingFunction;
|
||||
readonly info: LoggingFunction;
|
||||
readonly warn: LoggingFunction;
|
||||
readonly error: LoggingFunction;
|
||||
|
||||
constructor() {
|
||||
this.debug = this.getLoggingFunction("debug");
|
||||
this.info = this.getLoggingFunction("info");
|
||||
this.warn = this.getLoggingFunction("warning");
|
||||
this.error = this.getLoggingFunction("error");
|
||||
}
|
||||
|
||||
getLoggingFunction(type: LogLevel = "info") {
|
||||
const getLoggingFunction = (type: LogLevel = "info"): LoggingFunction => {
|
||||
const log = { debug: console.debug, info: console.info, warning: console.warn, error: console.error }[type];
|
||||
return (...data: unknown[]) => log(loggingContext, loggingSeparator, ...data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const logger = Object.freeze({
|
||||
debug: getLoggingFunction("debug"),
|
||||
info: getLoggingFunction("info"),
|
||||
warn: getLoggingFunction("warning"),
|
||||
error: getLoggingFunction("error"),
|
||||
getLoggingFunction,
|
||||
});
|
||||
|
||||
const logger = new Logger();
|
||||
export default logger;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { getCanvas } from "../helpers";
|
||||
import { getCanvas, getGame } from "../helpers";
|
||||
|
||||
/**
|
||||
* Gets the currently active actor based on how {@link ChatMessage} determines
|
||||
|
@ -15,11 +15,11 @@ export function getActiveActor(): DS4Actor | undefined {
|
|||
|
||||
const speakerToken = speaker.token ? getCanvas().tokens.get(speaker.token) : undefined;
|
||||
if (speakerToken) {
|
||||
return speakerToken.actor as DS4Actor;
|
||||
return speakerToken.actor ?? undefined;
|
||||
}
|
||||
|
||||
const speakerActor = speaker.actor ? game.actors?.get(speaker.actor) : undefined;
|
||||
const speakerActor = speaker.actor ? getGame().actors?.get(speaker.actor) : undefined;
|
||||
if (speakerActor) {
|
||||
return speakerActor as DS4Actor;
|
||||
return speakerActor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Check } from "../actor/actor-prepared-data";
|
||||
import { Check } from "../actor/actor-data-properties";
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { getActiveActor } from "./helpers";
|
||||
|
||||
|
@ -15,13 +16,13 @@ import { getActiveActor } from "./helpers";
|
|||
*/
|
||||
export async function createRollCheckMacro(check: Check, slot: string): Promise<void> {
|
||||
const macro = await getOrCreateRollCheckMacro(check);
|
||||
game.user?.assignHotbarMacro(macro, slot);
|
||||
getGame().user?.assignHotbarMacro(macro ?? null, slot);
|
||||
}
|
||||
|
||||
async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | null> {
|
||||
async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | undefined> {
|
||||
const command = `game.ds4.macros.rollCheck("${check}");`;
|
||||
|
||||
const existingMacro = game.macros?.entities.find(
|
||||
const existingMacro = getGame().macros?.find(
|
||||
(m) => m.name === DS4.i18n.checks[check] && m.data.command === command,
|
||||
);
|
||||
if (existingMacro) {
|
||||
|
@ -36,7 +37,7 @@ async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | null> {
|
|||
img: DS4.icons.checks[check],
|
||||
flags: { "ds4.checkMacro": true },
|
||||
},
|
||||
{ displaySheet: false },
|
||||
{ renderSheet: false },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -46,7 +47,7 @@ async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | null> {
|
|||
export async function rollCheck(check: Check): Promise<void> {
|
||||
const actor = getActiveActor();
|
||||
if (!actor) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
}
|
||||
|
||||
return actor.rollCheck(check).catch((e) => notifications.error(e, { log: true }));
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { getActiveActor } from "./helpers";
|
||||
|
||||
/**
|
||||
* Executes the roll generic check macro.
|
||||
*/
|
||||
export async function rollGenericCheck(): Promise<void> {
|
||||
const actor = getActiveActor();
|
||||
if (!actor) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
}
|
||||
|
||||
return actor.rollGenericCheck().catch((e) => notifications.error(e, { log: true }));
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4ItemData } from "../item/item-data";
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { getActiveActor } from "./helpers";
|
||||
|
||||
|
@ -12,15 +12,15 @@ import { getActiveActor } from "./helpers";
|
|||
* @param itemData - The item data
|
||||
* @param slot - The hotbar slot to use
|
||||
*/
|
||||
export async function createRollItemMacro(itemData: DS4ItemData, slot: string): Promise<void> {
|
||||
export async function createRollItemMacro(itemData: foundry.data.ItemData["_source"], slot: string): Promise<void> {
|
||||
const macro = await getOrCreateRollItemMacro(itemData);
|
||||
game.user?.assignHotbarMacro(macro, slot);
|
||||
getGame().user?.assignHotbarMacro(macro ?? null, slot);
|
||||
}
|
||||
|
||||
async function getOrCreateRollItemMacro(itemData: DS4ItemData): Promise<Macro | null> {
|
||||
async function getOrCreateRollItemMacro(itemData: foundry.data.ItemData["_source"]): Promise<Macro | undefined> {
|
||||
const command = `game.ds4.macros.rollItem("${itemData._id}");`;
|
||||
|
||||
const existingMacro = game.macros?.entities.find((m) => m.name === itemData.name && m.data.command === command);
|
||||
const existingMacro = getGame().macros?.find((m) => m.name === itemData.name && m.data.command === command);
|
||||
if (existingMacro) {
|
||||
return existingMacro;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ async function getOrCreateRollItemMacro(itemData: DS4ItemData): Promise<Macro |
|
|||
img: itemData.img,
|
||||
flags: { "ds4.itemMacro": true },
|
||||
},
|
||||
{ displaySheet: false },
|
||||
{ renderSheet: false },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -43,13 +43,13 @@ async function getOrCreateRollItemMacro(itemData: DS4ItemData): Promise<Macro |
|
|||
export async function rollItem(itemId: string): Promise<void> {
|
||||
const actor = getActiveActor();
|
||||
if (!actor) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollItemMacro"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningMustControlActorToUseRollItemMacro"));
|
||||
}
|
||||
|
||||
const item = actor.items?.get(itemId);
|
||||
if (!item) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningControlledActorDoesNotHaveItem", {
|
||||
getGame().i18n.format("DS4.WarningControlledActorDoesNotHaveItem", {
|
||||
actorName: actor.name,
|
||||
actorId: actor.id,
|
||||
itemId,
|
||||
|
|
|
@ -2,25 +2,25 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "./helpers";
|
||||
import logger from "./logger";
|
||||
import { migrate as migrate001 } from "./migrations/001";
|
||||
import { migrate as migrate002 } from "./migrations/002";
|
||||
import { migrate as migrate003 } from "./migrations/003";
|
||||
import { migrate as migrate004 } from "./migrations/004";
|
||||
|
||||
import notifications from "./ui/notifications";
|
||||
|
||||
async function migrate(): Promise<void> {
|
||||
if (!game.user?.isGM) {
|
||||
if (!getGame().user?.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldMigrationVersion = game.settings.get("ds4", "systemMigrationVersion");
|
||||
const oldMigrationVersion = getGame().settings.get("ds4", "systemMigrationVersion");
|
||||
|
||||
const targetMigrationVersion = migrations.length;
|
||||
|
||||
if (isFirstWorldStart(oldMigrationVersion)) {
|
||||
game.settings.set("ds4", "systemMigrationVersion", targetMigrationVersion);
|
||||
getGame().settings.set("ds4", "systemMigrationVersion", targetMigrationVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ async function migrate(): Promise<void> {
|
|||
}
|
||||
|
||||
async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> {
|
||||
if (!game.user?.isGM) {
|
||||
if (!getGame().user?.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
|
||||
if (migrationsToExecute.length > 0) {
|
||||
notifications.info(
|
||||
game.i18n.format("DS4.InfoSystemUpdateStart", {
|
||||
getGame().i18n.format("DS4.InfoSystemUpdateStart", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
}),
|
||||
|
@ -48,10 +48,10 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
logger.info("executing migration script ", currentMigrationVersion);
|
||||
try {
|
||||
await migration();
|
||||
game.settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
|
||||
getGame().settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
|
||||
} catch (err) {
|
||||
notifications.error(
|
||||
game.i18n.format("DS4.ErrorDuringMigration", {
|
||||
getGame().i18n.format("DS4.ErrorDuringMigration", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
migrationVersion: currentMigrationVersion,
|
||||
|
@ -65,7 +65,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
}
|
||||
|
||||
notifications.info(
|
||||
game.i18n.format("DS4.InfoSystemUpdateCompleted", {
|
||||
getGame().i18n.format("DS4.InfoSystemUpdateCompleted", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
}),
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
for (const a of game.actors?.entities ?? []) {
|
||||
const updateData = getActorUpdateData();
|
||||
logger.info(`Migrating actor ${a.name}`);
|
||||
await a.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
function getActorUpdateData(): Record<string, unknown> {
|
||||
|
@ -32,3 +36,6 @@ function getActorUpdateData(): Record<string, unknown> {
|
|||
};
|
||||
return updateData;
|
||||
}
|
||||
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator({ getActorUpdateData, getSceneUpdateData });
|
||||
|
|
|
@ -2,142 +2,33 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getActorUpdateDataGetter,
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateItems,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
await migrateItems();
|
||||
await migrateActors();
|
||||
await migrateScenes();
|
||||
await migrateCompendiums();
|
||||
await migrateItems(getItemUpdateData);
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
async function migrateItems() {
|
||||
for (const item of game.items?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item entity ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item entity ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
||||
function getItemUpdateData(
|
||||
itemData: Partial<foundry.data.ItemData["_source"]>,
|
||||
): DeepPartial<foundry.data.ItemData["_source"]> | undefined {
|
||||
if (!["equipment", "trinket"].includes(itemData.type ?? "")) return undefined;
|
||||
return { type: itemData.type === "equipment" ? "loot" : "equipment" };
|
||||
return { type: itemData.type === "equipment" ? ("loot" as const) : ("equipment" as const) };
|
||||
}
|
||||
|
||||
async function migrateActors() {
|
||||
for (const actor of game.actors?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor entity ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor entity ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActorUpdateData(actorData: DeepPartial<Actor.Data>) {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = itemData ? getItemUpdateData(itemData) : undefined;
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return { ...itemData, ...update };
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
return hasItemUpdates ? { items } : undefined;
|
||||
}
|
||||
|
||||
async function migrateScenes() {
|
||||
for (const scene of game.scenes?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene entity ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene entity ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSceneUpdateData(sceneData: Scene.Data) {
|
||||
let hasTokenUpdates = false;
|
||||
const tokens = sceneData.tokens.map((tokenData) => {
|
||||
if (!tokenData.actorId || tokenData.actorLink || tokenData.actorData.data) {
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
return tokenData;
|
||||
}
|
||||
const token = new Token(tokenData);
|
||||
if (!token.actor) {
|
||||
tokenData.actorId = null as unknown as string;
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
} else if (!tokenData.actorLink) {
|
||||
const actorUpdateData = getActorUpdateData(token.data.actorData);
|
||||
tokenData.actorData = mergeObject(token.data.actorData, actorUpdateData);
|
||||
hasTokenUpdates = true;
|
||||
}
|
||||
return tokenData;
|
||||
});
|
||||
if (!hasTokenUpdates) return undefined;
|
||||
return hasTokenUpdates ? { tokens } : undefined;
|
||||
}
|
||||
|
||||
async function migrateCompendiums() {
|
||||
for (const compendium of game.packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCompendium(compendium: Compendium) {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
|
||||
const content = await compendium.getContent();
|
||||
|
||||
for (const entity of content) {
|
||||
try {
|
||||
const getUpdateData = (entity: Entity) => {
|
||||
switch (entityName) {
|
||||
case "Item":
|
||||
return getItemUpdateData(entity._data);
|
||||
case "Actor":
|
||||
return getActorUpdateData(entity._data);
|
||||
case "Scene":
|
||||
return getSceneUpdateData(entity._data as Scene.Data);
|
||||
}
|
||||
};
|
||||
const updateData = getUpdateData(entity);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}`);
|
||||
await compendium.updateEntity({ ...updateData, _id: entity._id });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
await compendium.migrate({});
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
}
|
||||
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator(
|
||||
{ getItemUpdateData, getActorUpdateData, getSceneUpdateData },
|
||||
{ migrateToTemplateEarly: false },
|
||||
);
|
||||
|
|
|
@ -2,31 +2,24 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getActorUpdateDataGetter,
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateItems,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
await migrateItems();
|
||||
await migrateActors();
|
||||
await migrateScenes();
|
||||
await migrateCompendiums();
|
||||
await migrateItems(getItemUpdateData);
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
async function migrateItems() {
|
||||
for (const item of game.items?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item entity ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item entity ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
||||
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
|
||||
if (!["loot"].includes(itemData.type ?? "")) return undefined;
|
||||
return {
|
||||
data: {
|
||||
|
@ -35,113 +28,9 @@ function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
|||
};
|
||||
}
|
||||
|
||||
async function migrateActors() {
|
||||
for (const actor of game.actors?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor entity ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor entity ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActorUpdateData(actorData: DeepPartial<Actor.Data>) {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = itemData ? getItemUpdateData(itemData) : undefined;
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return mergeObject(itemData, update, { enforceTypes: false, inplace: false });
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
return hasItemUpdates ? { items } : undefined;
|
||||
}
|
||||
|
||||
async function migrateScenes() {
|
||||
for (const scene of game.scenes?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene entity ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene entity ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSceneUpdateData(sceneData: Scene.Data) {
|
||||
let hasTokenUpdates = false;
|
||||
const tokens = sceneData.tokens.map((tokenData) => {
|
||||
if (!tokenData.actorId || tokenData.actorLink || tokenData.actorData.data) {
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
return tokenData;
|
||||
}
|
||||
const token = new Token(tokenData);
|
||||
if (!token.actor) {
|
||||
tokenData.actorId = null as unknown as string;
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
} else if (!tokenData.actorLink) {
|
||||
const actorUpdateData = getActorUpdateData(token.data.actorData);
|
||||
tokenData.actorData = mergeObject(token.data.actorData, actorUpdateData);
|
||||
hasTokenUpdates = true;
|
||||
}
|
||||
return tokenData;
|
||||
});
|
||||
if (!hasTokenUpdates) return undefined;
|
||||
return hasTokenUpdates ? { tokens } : undefined;
|
||||
}
|
||||
|
||||
async function migrateCompendiums() {
|
||||
for (const compendium of game.packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCompendium(compendium: Compendium) {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
|
||||
const content = await compendium.getContent();
|
||||
|
||||
for (const entity of content) {
|
||||
try {
|
||||
const getUpdateData = (entity: Entity) => {
|
||||
switch (entityName) {
|
||||
case "Item":
|
||||
return getItemUpdateData(entity._data);
|
||||
case "Actor":
|
||||
return getActorUpdateData(entity._data);
|
||||
case "Scene":
|
||||
return getSceneUpdateData(entity._data as Scene.Data);
|
||||
}
|
||||
};
|
||||
const updateData = getUpdateData(entity);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}`);
|
||||
await compendium.updateEntity({ ...updateData, _id: entity._id });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
await compendium.migrate({});
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
}
|
||||
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator(
|
||||
{ getItemUpdateData, getActorUpdateData },
|
||||
{ migrateToTemplateEarly: false },
|
||||
);
|
||||
|
|
|
@ -2,157 +2,39 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4SpellDataData } from "../item/item-data";
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getActorUpdateDataGetter,
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateItems,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
await migrateItems();
|
||||
await migrateActors();
|
||||
await migrateScenes();
|
||||
await migrateCompendiums();
|
||||
await migrateItems(getItemUpdateData);
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
async function migrateItems() {
|
||||
for (const item of game.items?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item entity ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item entity ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
|
||||
if (itemData.type !== "spell") return;
|
||||
const cooldownDurationUnit: string | undefined = itemData.data?.cooldownDuration.unit;
|
||||
|
||||
function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
||||
if (!["spell"].includes(itemData.type ?? "")) return undefined;
|
||||
const updateData: Record<string, unknown> = {
|
||||
"-=data.scrollPrice": null,
|
||||
"data.minimumLevels": { healer: null, wizard: null, sorcerer: null },
|
||||
data: {
|
||||
"-=scrollPrice": null,
|
||||
minimumLevels: { healer: null, wizard: null, sorcerer: null },
|
||||
cooldownDuration: {
|
||||
unit: cooldownDurationUnit === "custom" ? "rounds" : cooldownDurationUnit,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (((itemData.data as DS4SpellDataData).cooldownDuration.unit as string) === "custom") {
|
||||
updateData["data.cooldownDuration.unit"] = "rounds";
|
||||
}
|
||||
return updateData;
|
||||
}
|
||||
|
||||
async function migrateActors() {
|
||||
for (const actor of game.actors?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor entity ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor entity ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActorUpdateData(actorData: DeepPartial<Actor.Data>) {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = itemData ? getItemUpdateData(itemData) : undefined;
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return mergeObject(itemData, update, { enforceTypes: false, inplace: false });
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (actorData.type === "character") {
|
||||
updateData["data.slayerPoints"] = { value: 0 };
|
||||
}
|
||||
if (hasItemUpdates) {
|
||||
updateData["items"] = items;
|
||||
}
|
||||
return updateData;
|
||||
}
|
||||
|
||||
async function migrateScenes() {
|
||||
for (const scene of game.scenes?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene entity ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene entity ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSceneUpdateData(sceneData: Scene.Data) {
|
||||
let hasTokenUpdates = false;
|
||||
const tokens = sceneData.tokens.map((tokenData) => {
|
||||
if (!tokenData.actorId || tokenData.actorLink || tokenData.actorData.data) {
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
return tokenData;
|
||||
}
|
||||
const token = new Token(tokenData);
|
||||
if (!token.actor) {
|
||||
tokenData.actorId = null as unknown as string;
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
} else if (!tokenData.actorLink) {
|
||||
const actorUpdateData = getActorUpdateData(token.data.actorData);
|
||||
tokenData.actorData = mergeObject(token.data.actorData, actorUpdateData);
|
||||
hasTokenUpdates = true;
|
||||
}
|
||||
return tokenData;
|
||||
});
|
||||
if (!hasTokenUpdates) return undefined;
|
||||
return hasTokenUpdates ? { tokens } : undefined;
|
||||
}
|
||||
|
||||
async function migrateCompendiums() {
|
||||
for (const compendium of game.packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCompendium(compendium: Compendium) {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
await compendium.migrate({});
|
||||
|
||||
const content = await compendium.getContent();
|
||||
|
||||
for (const entity of content) {
|
||||
try {
|
||||
const getUpdateData = (entity: Entity) => {
|
||||
switch (entityName) {
|
||||
case "Item":
|
||||
return getItemUpdateData(entity._data);
|
||||
case "Actor":
|
||||
return getActorUpdateData(entity._data);
|
||||
case "Scene":
|
||||
return getSceneUpdateData(entity._data as Scene.Data);
|
||||
}
|
||||
};
|
||||
const updateData = getUpdateData(entity);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}`);
|
||||
await compendium.updateEntity({ ...updateData, _id: entity._id });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
}
|
||||
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
|
||||
|
|
178
src/module/migrations/migrationHelpers.ts
Normal file
178
src/module/migrations/migrationHelpers.ts
Normal file
|
@ -0,0 +1,178 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import logger from "../logger";
|
||||
|
||||
type ItemUpdateDataGetter = (
|
||||
itemData: Partial<foundry.data.ItemData["_source"]>,
|
||||
) => DeepPartial<foundry.data.ItemData["_source"]> | Record<string, unknown> | undefined;
|
||||
|
||||
export async function migrateItems(getItemUpdateData: ItemUpdateDataGetter): Promise<void> {
|
||||
for (const item of getGame().items ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item.toObject());
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item document ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item document ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ActorUpdateDataGetter = (
|
||||
itemData: Partial<foundry.data.ActorData["_source"]>,
|
||||
) => DeepPartial<foundry.data.ActorData["_source"]> | undefined;
|
||||
|
||||
export async function migrateActors(getActorUpdateData: ActorUpdateDataGetter): Promise<void> {
|
||||
for (const actor of getGame().actors ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor.toObject());
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor document ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData);
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor document ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SceneUpdateDataGetter = (
|
||||
sceneData: foundry.documents.BaseScene["data"],
|
||||
) => DeepPartial<foundry.documents.BaseScene["data"]["_source"]>;
|
||||
|
||||
export async function migrateScenes(getSceneUpdateData: SceneUpdateDataGetter): Promise<void> {
|
||||
for (const scene of getGame().scenes ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene.data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene document ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData);
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene document ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CompendiumMigrator = (compendium: CompendiumCollection<CompendiumCollection.Metadata>) => Promise<void>;
|
||||
|
||||
export async function migrateCompendiums(migrateCompendium: CompendiumMigrator): Promise<void> {
|
||||
for (const compendium of getGame().packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
export function getActorUpdateDataGetter(getItemUpdateData: ItemUpdateDataGetter): ActorUpdateDataGetter {
|
||||
return (
|
||||
actorData: Partial<foundry.data.ActorData["_source"]>,
|
||||
): DeepPartial<foundry.data.ActorData["_source"]> | undefined => {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = getItemUpdateData(itemData);
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return { ...itemData, ...update };
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
return hasItemUpdates ? { items } : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export function getSceneUpdateDataGetter(getActorUpdateData: ActorUpdateDataGetter): SceneUpdateDataGetter {
|
||||
return (sceneData: foundry.documents.BaseScene["data"]) => {
|
||||
const tokens = (sceneData.tokens as Collection<TokenDocument>).map((token: TokenDocument) => {
|
||||
const t = token.toObject();
|
||||
if (!t.actorId || t.actorLink) {
|
||||
t.actorData = {};
|
||||
} else if (!getGame().actors?.has(t.actorId)) {
|
||||
t.actorId = null;
|
||||
t.actorData = {};
|
||||
} else if (!t.actorLink) {
|
||||
const actorData = foundry.utils.deepClone(t.actorData);
|
||||
actorData.type = token.actor?.type;
|
||||
const update = getActorUpdateData(actorData);
|
||||
if (update !== undefined) {
|
||||
["items" as const, "effects" as const].forEach((embeddedName) => {
|
||||
const embeddedUpdates = update[embeddedName];
|
||||
if (embeddedUpdates === undefined || !embeddedUpdates.length) return;
|
||||
const updates = new Map(embeddedUpdates.flatMap((u) => (u && u._id ? [[u._id, u]] : [])));
|
||||
const originals = t.actorData[embeddedName];
|
||||
if (!originals) return;
|
||||
originals.forEach((original) => {
|
||||
if (!original._id) return;
|
||||
const update = updates.get(original._id);
|
||||
if (update) foundry.utils.mergeObject(original, update);
|
||||
});
|
||||
delete update[embeddedName];
|
||||
});
|
||||
foundry.utils.mergeObject(t.actorData, update);
|
||||
}
|
||||
}
|
||||
return t;
|
||||
});
|
||||
return { tokens };
|
||||
};
|
||||
}
|
||||
|
||||
export function getCompendiumMigrator(
|
||||
{
|
||||
getItemUpdateData,
|
||||
getActorUpdateData,
|
||||
getSceneUpdateData,
|
||||
}: {
|
||||
getItemUpdateData?: ItemUpdateDataGetter;
|
||||
getActorUpdateData?: ActorUpdateDataGetter;
|
||||
getSceneUpdateData?: SceneUpdateDataGetter;
|
||||
} = {},
|
||||
{ migrateToTemplateEarly = true } = {},
|
||||
) {
|
||||
return async (compendium: CompendiumCollection<CompendiumCollection.Metadata>): Promise<void> => {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
if (migrateToTemplateEarly) {
|
||||
await compendium.migrate();
|
||||
}
|
||||
|
||||
const documents = await compendium.getDocuments();
|
||||
|
||||
for (const doc of documents) {
|
||||
try {
|
||||
logger.info(`Migrating document ${doc.name} (${doc.id}) in compendium ${compendium.collection}`);
|
||||
if (doc instanceof DS4Item && getItemUpdateData) {
|
||||
const updateData = getItemUpdateData(doc.toObject());
|
||||
updateData && (await doc.update(updateData));
|
||||
} else if (doc instanceof DS4Actor && getActorUpdateData) {
|
||||
const updateData = getActorUpdateData(doc.toObject());
|
||||
updateData && (await doc.update(updateData));
|
||||
} else if (doc instanceof Scene && getSceneUpdateData) {
|
||||
const updateData = getSceneUpdateData(doc.data);
|
||||
updateData && (await doc.update(updateData));
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of document ${doc.name} (${doc.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (!migrateToTemplateEarly) {
|
||||
await compendium.migrate();
|
||||
}
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
};
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
|
||||
export default function evaluateCheck(
|
||||
dice: number[],
|
||||
checkTargetNumber: number,
|
||||
|
@ -39,7 +41,7 @@ function assignSubChecksToDice(
|
|||
const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);
|
||||
|
||||
if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) {
|
||||
throw new Error(game.i18n.localize("DS4.ErrorInvalidNumberOfDice"));
|
||||
throw new Error(getGame().i18n.localize("DS4.ErrorInvalidNumberOfDice"));
|
||||
}
|
||||
|
||||
const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);
|
||||
|
@ -86,11 +88,7 @@ function shouldUseCoupForLastSubCheck(
|
|||
);
|
||||
}
|
||||
|
||||
interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {
|
||||
success?: boolean;
|
||||
failure?: boolean;
|
||||
count?: number;
|
||||
}
|
||||
interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {}
|
||||
|
||||
function evaluateDiceWithSubChecks(
|
||||
results: DieWithSubCheck[],
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
|
||||
/**
|
||||
* Provides default values for all arguments the `CheckFactory` expects.
|
||||
*/
|
||||
|
@ -10,7 +12,7 @@ class DefaultCheckOptions implements DS4CheckFactoryOptions {
|
|||
readonly maximumCoupResult = 1;
|
||||
readonly minimumFumbleResult = 20;
|
||||
readonly useSlayingDice = false;
|
||||
readonly rollMode: Const.DiceRollMode = "roll";
|
||||
readonly rollMode: foundry.CONST.DiceRollMode = "roll";
|
||||
readonly flavor: undefined;
|
||||
|
||||
mergeWith(other: Partial<DS4CheckFactoryOptions>): DS4CheckFactoryOptions {
|
||||
|
@ -37,15 +39,16 @@ class CheckFactory {
|
|||
|
||||
private options: DS4CheckFactoryOptions;
|
||||
|
||||
async execute(): Promise<ChatMessage> {
|
||||
async execute(): Promise<ChatMessage | undefined> {
|
||||
const innerFormula = ["ds", this.createCheckTargetNumberModifier(), this.createCoupFumbleModifier()].filterJoin(
|
||||
"",
|
||||
);
|
||||
const formula = this.options.useSlayingDice ? `{${innerFormula}}x` : innerFormula;
|
||||
const roll = Roll.create(formula);
|
||||
const speaker = ChatMessage.getSpeaker();
|
||||
|
||||
return roll.toMessage(
|
||||
{ speaker: ChatMessage.getSpeaker(), flavor: this.options.flavor },
|
||||
{ speaker, flavor: this.options.flavor },
|
||||
{ rollMode: this.options.rollMode, create: true },
|
||||
);
|
||||
}
|
||||
|
@ -85,7 +88,7 @@ export async function createCheckRoll(
|
|||
const newOptions: Partial<DS4CheckFactoryOptions> = {
|
||||
maximumCoupResult: gmModifierData.maximumCoupResult ?? options.maximumCoupResult,
|
||||
minimumFumbleResult: gmModifierData.minimumFumbleResult ?? options.minimumFumbleResult,
|
||||
useSlayingDice: game.settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
useSlayingDice: getGame().settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
rollMode: gmModifierData.rollMode ?? options.rollMode,
|
||||
flavor: options.flavor,
|
||||
};
|
||||
|
@ -113,13 +116,13 @@ async function askGmModifier(
|
|||
{ template, title }: { template?: string; title?: string } = {},
|
||||
): Promise<Partial<IntermediateGmModifierData>> {
|
||||
const usedTemplate = template ?? "systems/ds4/templates/dialogs/roll-options.hbs";
|
||||
const usedTitle = title ?? game.i18n.localize("DS4.DialogRollOptionsDefaultTitle");
|
||||
const usedTitle = title ?? getGame().i18n.localize("DS4.DialogRollOptionsDefaultTitle");
|
||||
const templateData = {
|
||||
title: usedTitle,
|
||||
checkTargetNumber: checkTargetNumber,
|
||||
maximumCoupResult: options.maximumCoupResult ?? defaultCheckOptions.maximumCoupResult,
|
||||
minimumFumbleResult: options.minimumFumbleResult ?? defaultCheckOptions.minimumFumbleResult,
|
||||
rollMode: options.rollMode ?? game.settings.get("core", "rollMode"),
|
||||
rollMode: options.rollMode ?? getGame().settings.get("core", "rollMode"),
|
||||
rollModes: CONFIG.Dice.rollModes,
|
||||
};
|
||||
const renderedHtml = await renderTemplate(usedTemplate, templateData);
|
||||
|
@ -131,11 +134,11 @@ async function askGmModifier(
|
|||
buttons: {
|
||||
ok: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
if (!("jquery" in html)) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedHtmlType", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedHtmlType", {
|
||||
exType: "JQuery",
|
||||
realType: "HTMLElement",
|
||||
}),
|
||||
|
@ -144,7 +147,7 @@ async function askGmModifier(
|
|||
const innerForm = html[0].querySelector("form");
|
||||
if (!innerForm) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
|
||||
getGame().i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
|
||||
);
|
||||
}
|
||||
resolve(innerForm);
|
||||
|
@ -153,7 +156,7 @@ async function askGmModifier(
|
|||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("DS4.GenericCancelButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericCancelButton"),
|
||||
},
|
||||
},
|
||||
default: "ok",
|
||||
|
@ -174,13 +177,11 @@ function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmM
|
|||
const chosenMinimumFumbleResult = parseInt(formData["minimum-fumble-result"]?.value);
|
||||
const chosenRollMode = formData["roll-mode"]?.value;
|
||||
|
||||
const invalidNumbers = [NaN, Infinity, -Infinity];
|
||||
|
||||
return {
|
||||
checkTargetNumber: invalidNumbers.includes(chosenCheckTargetNumber) ? undefined : chosenCheckTargetNumber,
|
||||
gmModifier: invalidNumbers.includes(chosenGMModifier) ? undefined : chosenGMModifier,
|
||||
maximumCoupResult: invalidNumbers.includes(chosenMaximumCoupResult) ? undefined : chosenMaximumCoupResult,
|
||||
minimumFumbleResult: invalidNumbers.includes(chosenMinimumFumbleResult) ? undefined : chosenMinimumFumbleResult,
|
||||
checkTargetNumber: Number.isSafeInteger(chosenCheckTargetNumber) ? chosenCheckTargetNumber : undefined,
|
||||
gmModifier: Number.isSafeInteger(chosenGMModifier) ? chosenGMModifier : undefined,
|
||||
maximumCoupResult: Number.isSafeInteger(chosenMaximumCoupResult) ? chosenMaximumCoupResult : undefined,
|
||||
minimumFumbleResult: Number.isSafeInteger(chosenMinimumFumbleResult) ? chosenMinimumFumbleResult : undefined,
|
||||
rollMode: Object.values(CONST.DICE_ROLL_MODES).includes(chosenRollMode) ? chosenRollMode : undefined,
|
||||
};
|
||||
}
|
||||
|
@ -190,7 +191,7 @@ function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmM
|
|||
*/
|
||||
interface GmModifierData {
|
||||
gmModifier: number;
|
||||
rollMode: Const.DiceRollMode;
|
||||
rollMode: foundry.CONST.DiceRollMode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,6 +222,6 @@ export interface DS4CheckFactoryOptions {
|
|||
maximumCoupResult: number;
|
||||
minimumFumbleResult: number;
|
||||
useSlayingDice: boolean;
|
||||
rollMode: Const.DiceRollMode;
|
||||
rollMode: foundry.CONST.DiceRollMode;
|
||||
flavor?: string;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation";
|
||||
|
||||
/**
|
||||
|
@ -15,11 +16,12 @@ import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation";
|
|||
* - Roll a check with a racial ability that makes `5` a coup and default fumble: `/r dsv19c5`
|
||||
*/
|
||||
export class DS4Check extends DiceTerm {
|
||||
constructor({ modifiers = [], options }: Partial<DiceTerm.TermData> = {}) {
|
||||
constructor({ modifiers = [], results = [], options }: Partial<DiceTerm.TermData> = {}) {
|
||||
super({
|
||||
faces: 20,
|
||||
modifiers: modifiers,
|
||||
options: options,
|
||||
results,
|
||||
modifiers,
|
||||
options,
|
||||
});
|
||||
|
||||
// Parse and store check target number
|
||||
|
@ -49,7 +51,7 @@ export class DS4Check extends DiceTerm {
|
|||
? parseInt(parseMinimumFumbleResult)
|
||||
: DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
|
||||
if (this.minimumFumbleResult <= this.maximumCoupResult)
|
||||
throw new SyntaxError(game.i18n.localize("DS4.ErrorDiceCoupFumbleOverlap"));
|
||||
throw new SyntaxError(getGame().i18n.localize("DS4.ErrorDiceCoupFumbleOverlap"));
|
||||
}
|
||||
|
||||
// Parse and store no fumble
|
||||
|
@ -57,6 +59,10 @@ export class DS4Check extends DiceTerm {
|
|||
if (noFumbleModifier) {
|
||||
this.canFumble = false;
|
||||
}
|
||||
|
||||
if (this.results.length > 0) {
|
||||
this.evaluateResults();
|
||||
}
|
||||
}
|
||||
|
||||
coup: boolean | null = null;
|
||||
|
@ -72,14 +78,14 @@ export class DS4Check extends DiceTerm {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
get total(): number | null {
|
||||
get total(): string | number | null | undefined {
|
||||
if (this.fumble) return 0;
|
||||
return super.total;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
evaluate({ minimize = false, maximize = false } = {}): this {
|
||||
super.evaluate({ minimize, maximize });
|
||||
_evaluateSync({ minimize = false, maximize = false } = {}): this {
|
||||
super._evaluateSync({ minimize, maximize });
|
||||
this.evaluateResults();
|
||||
return this;
|
||||
}
|
||||
|
@ -102,17 +108,14 @@ export class DS4Check extends DiceTerm {
|
|||
this.fumble = results[0].failure ?? false;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static fromResults<T extends DS4Check>(
|
||||
this: ConstructorOf<T>,
|
||||
options: Partial<DiceTerm.TermData>,
|
||||
results: DiceTerm.Result[],
|
||||
): T {
|
||||
const term = new this(options);
|
||||
term.results = results;
|
||||
term.evaluateResults();
|
||||
term._evaluated = true;
|
||||
return term;
|
||||
/**
|
||||
* @override
|
||||
* @remarks "min" and "max" are filtered out because they are irrelevant for
|
||||
* {@link DS4Check}s and only result in some dice rolls being highlighted
|
||||
* incorrectly.
|
||||
*/
|
||||
getResultCSS(result: DiceTerm.Result): (string | null)[] {
|
||||
return super.getResultCSS(result).filter((cssClass) => cssClass !== "min" && cssClass !== "max");
|
||||
}
|
||||
|
||||
static readonly DEFAULT_CHECK_TARGET_NUMBER = 10;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Check } from "./check";
|
||||
|
||||
export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>> extends Roll<D> {
|
||||
|
@ -12,10 +13,10 @@ export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>
|
|||
* template if the first dice term is a ds4 check.
|
||||
* @override
|
||||
*/
|
||||
async render(chatOptions: Roll.ChatOptions = {}): Promise<string> {
|
||||
chatOptions = mergeObject(
|
||||
async render(chatOptions: Parameters<Roll["render"]>[0] = {}): Promise<string> {
|
||||
chatOptions = foundry.utils.mergeObject(
|
||||
{
|
||||
user: game.user?._id,
|
||||
user: getGame().user?.id,
|
||||
flavor: null,
|
||||
template: DS4Roll.CHAT_TEMPLATE,
|
||||
blind: false,
|
||||
|
@ -25,7 +26,7 @@ export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>
|
|||
const isPrivate = chatOptions.isPrivate;
|
||||
|
||||
// Execute the roll, if needed
|
||||
if (!this._rolled) this.roll();
|
||||
if (!this._evaluated) this.evaluate();
|
||||
|
||||
// Define chat data
|
||||
const firstDiceTerm = this.dice[0];
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Check } from "./check";
|
||||
|
||||
export default function registerSlayingDiceModifier(): void {
|
||||
DicePool.MODIFIERS.x = slay;
|
||||
DicePool.POOL_REGEX = /^{([^}]+)}([A-z]([A-z0-9<=>]+)?)?$/;
|
||||
PoolTerm.MODIFIERS.x = slay;
|
||||
}
|
||||
|
||||
function slay(this: DicePool, modifier: string): void {
|
||||
function slay(this: PoolTerm, modifier: string): void {
|
||||
const rgx = /[xX]/;
|
||||
const match = modifier.match(rgx);
|
||||
if (!match || !this.rolls) return;
|
||||
|
@ -21,11 +21,12 @@ function slay(this: DicePool, modifier: string): void {
|
|||
checked++;
|
||||
if (diceTerm instanceof DS4Check && diceTerm.coup) {
|
||||
const formula = `dsv${diceTerm.checkTargetNumber}c${diceTerm.maximumCoupResult}:${diceTerm.minimumFumbleResult}n`;
|
||||
const additionalRoll = Roll.create(formula).evaluate();
|
||||
const additionalRoll = Roll.create(formula).evaluate({ async: false });
|
||||
|
||||
this.rolls.push(additionalRoll);
|
||||
this.results.push({ result: additionalRoll.total ?? 0, active: true });
|
||||
this.terms.push(formula);
|
||||
}
|
||||
if (checked > 1000) throw new Error(game.i18n.localize("DS4.ErrorSlayingDiceRecursionLimitExceeded"));
|
||||
if (checked > 1000) throw new Error(getGame().i18n.localize("DS4.ErrorSlayingDiceRecursionLimitExceeded"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "./helpers";
|
||||
|
||||
export function registerSystemSettings(): void {
|
||||
/**
|
||||
* Track the migrations version of the latest migration that has been applied
|
||||
*/
|
||||
game.settings.register("ds4", "systemMigrationVersion", {
|
||||
getGame().settings.register("ds4", "systemMigrationVersion", {
|
||||
name: "System Migration Version",
|
||||
scope: "world",
|
||||
config: false,
|
||||
|
@ -14,7 +16,7 @@ export function registerSystemSettings(): void {
|
|||
default: -1,
|
||||
});
|
||||
|
||||
game.settings.register("ds4", "useSlayingDiceForAutomatedChecks", {
|
||||
getGame().settings.register("ds4", "useSlayingDiceForAutomatedChecks", {
|
||||
name: "DS4.SettingUseSlayingDiceForAutomatedChecksName",
|
||||
hint: "DS4.SettingUseSlayingDiceForAutomatedChecksHint",
|
||||
scope: "world",
|
||||
|
@ -23,7 +25,7 @@ export function registerSystemSettings(): void {
|
|||
default: false,
|
||||
});
|
||||
|
||||
game.settings.register("ds4", "showSlayerPoints", {
|
||||
getGame().settings.register("ds4", "showSlayerPoints", {
|
||||
name: "DS4.SettingShowSlayerPointsName",
|
||||
hint: "DS4.SettingShowSlayerPointsHint",
|
||||
scope: "world",
|
||||
|
@ -33,7 +35,7 @@ export function registerSystemSettings(): void {
|
|||
});
|
||||
}
|
||||
|
||||
interface DS4Settings {
|
||||
export interface DS4Settings {
|
||||
systemMigrationVersion: number;
|
||||
useSlayingDiceForAutomatedChecks: boolean;
|
||||
showSlayerPoints: boolean;
|
||||
|
@ -41,8 +43,8 @@ interface DS4Settings {
|
|||
|
||||
export function getDS4Settings(): DS4Settings {
|
||||
return {
|
||||
systemMigrationVersion: game.settings.get("ds4", "systemMigrationVersion"),
|
||||
useSlayingDiceForAutomatedChecks: game.settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"),
|
||||
systemMigrationVersion: getGame().settings.get("ds4", "systemMigrationVersion"),
|
||||
useSlayingDiceForAutomatedChecks: getGame().settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
showSlayerPoints: getGame().settings.get("ds4", "showSlayerPoints"),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"title": "Dungeonslayers 4",
|
||||
"description": "The Dungeonslayers 4 system for FoundryVTT. Dungeonslayers by Christian Kennig is licensed under CC BY-NC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/). The icons by the authors of Game-icons.net are licensed under CC BY 3.0 (https://creativecommons.org/licenses/by/3.0/).",
|
||||
"version": "0.8.0",
|
||||
"minimumCoreVersion": "0.7.9",
|
||||
"compatibleCoreVersion": "0.7.10",
|
||||
"minimumCoreVersion": "0.8.8",
|
||||
"compatibleCoreVersion": "0.8.8",
|
||||
"templateVersion": 6,
|
||||
"author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug, Max Tharr, Sascha Martens",
|
||||
"authors": [
|
||||
|
|
|
@ -170,29 +170,6 @@
|
|||
"shield": {
|
||||
"templates": ["base", "physical", "equipable", "protective"]
|
||||
},
|
||||
"equipment": {
|
||||
"templates": ["base", "physical", "equipable"]
|
||||
},
|
||||
"loot": {
|
||||
"templates": ["base", "physical"]
|
||||
},
|
||||
"talent": {
|
||||
"templates": ["base"],
|
||||
"rank": {
|
||||
"base": 0,
|
||||
"max": 0,
|
||||
"mod": 0
|
||||
}
|
||||
},
|
||||
"racialAbility": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"language": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"alphabet": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"spell": {
|
||||
"templates": ["base", "equipable"],
|
||||
"spellType": "spellcasting",
|
||||
|
@ -220,6 +197,29 @@
|
|||
"sorcerer": null
|
||||
}
|
||||
},
|
||||
"equipment": {
|
||||
"templates": ["base", "physical", "equipable"]
|
||||
},
|
||||
"loot": {
|
||||
"templates": ["base", "physical"]
|
||||
},
|
||||
"talent": {
|
||||
"templates": ["base"],
|
||||
"rank": {
|
||||
"base": 0,
|
||||
"max": 0,
|
||||
"mod": 0
|
||||
}
|
||||
},
|
||||
"racialAbility": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"language": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"alphabet": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"specialCreatureAbility": {
|
||||
"templates": ["base"],
|
||||
"experiencePoints": 0
|
||||
|
|
|
@ -9,12 +9,12 @@ SPDX-License-Identifier: MIT
|
|||
<form class="{{cssClass}} flexcol" autocomplete="off">
|
||||
{{!-- Sheet Header --}}
|
||||
<header class="sheet-header">
|
||||
<img class="profile-img" src="{{actor.img}}" data-edit="img" alt="Actor Icon" title="{{actor.name}}"
|
||||
<img class="profile-img" src="{{data.img}}" data-edit="img" alt="Actor Icon" title="{{data.name}}"
|
||||
height="100" width="100" />
|
||||
<div class="header-fields flexrow">
|
||||
<h1 class="charname">
|
||||
<label for="actor.name" class="hidden">Name</label>
|
||||
<input name="name" type="text" id="actor.name" value="{{actor.name}}" placeholder="Name" />
|
||||
<label for="name" class="hidden">Name</label>
|
||||
<input name="name" type="text" id="name" value="{{data.name}}" placeholder="Name" />
|
||||
</h1>
|
||||
{{> systems/ds4/templates/sheets/actor/components/character-progression.hbs}}
|
||||
|
||||
|
@ -22,28 +22,28 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.race">{{config.i18n.characterBaseInfo.race}}</label>
|
||||
<input type="text" name="data.baseInfo.race" id="data.baseInfo.race" value="{{data.baseInfo.race}}"
|
||||
data-dtype="String" />
|
||||
<input type="text" name="data.baseInfo.race" id="data.baseInfo.race"
|
||||
value="{{data.data.baseInfo.race}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.culture">{{config.i18n.characterBaseInfo.culture}}</label>
|
||||
<input id="data.baseInfo.culture" type="text" name="data.baseInfo.culture"
|
||||
value="{{data.baseInfo.culture}}" data-dtype="String" />
|
||||
value="{{data.data.baseInfo.culture}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property flex125">
|
||||
<label class="basic-property-label"
|
||||
for="data.progression.progressPoints.used">{{config.i18n.characterProgression.progressPoints}}</label>
|
||||
<div class="flexrow">
|
||||
<input id="data.progression.progressPoints.used" type="number"
|
||||
name="data.progression.progressPoints.used" value="{{data.progression.progressPoints.used}}"
|
||||
data-dtype="Number" />
|
||||
name="data.progression.progressPoints.used"
|
||||
value="{{data.data.progression.progressPoints.used}}" data-dtype="Number" />
|
||||
<span class="input-divider"> / </span>
|
||||
<label class="hidden" for="data.progression.progressPoints.total">Total
|
||||
Progression Points</label>
|
||||
<input type="number" id="data.progression.progressPoints.total"
|
||||
name="data.progression.progressPoints.total"
|
||||
value="{{data.progression.progressPoints.total}}" data-dtype="Number" />
|
||||
value="{{data.data.progression.progressPoints.total}}" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="basic-property flex125">
|
||||
|
@ -51,26 +51,26 @@ SPDX-License-Identifier: MIT
|
|||
for="data.progression.talentPoints.used">{{config.i18n.characterProgression.talentPoints}}</label>
|
||||
<div class="flexrow">
|
||||
<input type="number" name="data.progression.talentPoints.used"
|
||||
id="data.progression.talentPoints.used" value="{{data.progression.talentPoints.used}}"
|
||||
id="data.progression.talentPoints.used" value="{{data.data.progression.talentPoints.used}}"
|
||||
data-dtype="Number" />
|
||||
<span class="input-divider"> / </span>
|
||||
<label for="data.progression.talentPoints.total" class="hidden">Total Talent Points</label>
|
||||
<input type="number" name="data.progression.talentPoints.total"
|
||||
id="data.progression.talentPoints.total" value="{{data.progression.talentPoints.total}}"
|
||||
data-dtype="Number" />
|
||||
id="data.progression.talentPoints.total"
|
||||
value="{{data.data.progression.talentPoints.total}}" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.class">{{config.i18n.characterBaseInfo.class}}</label>
|
||||
<input type="text" id="data.baseInfo.class" name="data.baseInfo.class"
|
||||
value="{{data.baseInfo.class}}" data-dtype="String" />
|
||||
value="{{data.data.baseInfo.class}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.heroClass">{{config.i18n.characterBaseInfo.heroClass}}</label>
|
||||
<input type="text" id="data.baseInfo.heroClass" name="data.baseInfo.heroClass"
|
||||
value="{{data.baseInfo.heroClass}}" data-dtype="String" />
|
||||
value="{{data.data.baseInfo.heroClass}}" data-dtype="String" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,8 @@ SPDX-License-Identifier: MIT
|
|||
"DS4.CombatValuesHitPointsCurrentAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value" type="number" name="data.combatValues.hitPoints.value"
|
||||
id="data.combatValues.hitPoints.value" value="{{data.combatValues.hitPoints.value}}" data-dtype="Number" />
|
||||
id="data.combatValues.hitPoints.value" value="{{data.data.combatValues.hitPoints.value}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
{{#if (eq actor.type "character")}}
|
||||
{{#if settings.showSlayerPoints}}
|
||||
|
@ -21,9 +22,9 @@ SPDX-License-Identifier: MIT
|
|||
<h2 class="progression-label"><label for="data.slayersPoints.value"
|
||||
title="{{localize 'DS4.CharacterSlayerPoints'}}">{{localize "DS4.CharacterSlayerPointsAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value progression-value--slayer-points" type="number" max="{{data.slayerPoints.max}}"
|
||||
min="0" step="1" name="data.slayerPoints.value" id="data.slayersPoints.value"
|
||||
value="{{data.slayerPoints.value}}" data-dtype="Number" />
|
||||
<input class="progression-value progression-value--slayer-points" type="number"
|
||||
max="{{data.data.slayerPoints.max}}" min="0" step="1" name="data.slayerPoints.value"
|
||||
id="data.slayersPoints.value" value="{{data.data.slayerPoints.value}}" data-dtype="Number" />
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="progression-entry">
|
||||
|
@ -32,7 +33,7 @@ SPDX-License-Identifier: MIT
|
|||
"DS4.CharacterProgressionLevelAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value" type="number" min="0" name="data.progression.level" id="data.progression.level"
|
||||
value="{{data.progression.level}}" data-dtype="Number" />
|
||||
value="{{data.data.progression.level}}" data-dtype="Number" />
|
||||
</div>
|
||||
<div class="progression-entry">
|
||||
<h2 class="progression-label"><label for="data.progression.experiencePoints"
|
||||
|
@ -40,7 +41,8 @@ SPDX-License-Identifier: MIT
|
|||
"DS4.CharacterProgressionExperiencePointsAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value" type="number" min="0" name="data.progression.experiencePoints"
|
||||
id="data.progression.experiencePoints" value="{{data.progression.experiencePoints}}" data-dtype="Number" />
|
||||
id="data.progression.experiencePoints" value="{{data.data.progression.experiencePoints}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,6 @@ SPDX-License-Identifier: MIT
|
|||
<div class="ds4-checks">
|
||||
{{#each config.i18n.checks as |check-label check-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/check.hbs check-key=check-key check-target-number=(lookup
|
||||
../data.checks check-key) check-label=check-label}}
|
||||
../data.data.checks check-key) check-label=check-label}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,6 @@ SPDX-License-Identifier: MIT
|
|||
<div class="ds4-combat-values">
|
||||
{{#each config.i18n.combatValues as |combat-value-label combat-value-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/combat-value.hbs combat-value-key=combat-value-key
|
||||
combat-value-data=(lookup ../data.combatValues combat-value-key) combat-value-label=combat-value-label}}
|
||||
combat-value-data=(lookup ../data.data.combatValues combat-value-key) combat-value-label=combat-value-label}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -8,12 +8,12 @@ SPDX-License-Identifier: MIT
|
|||
<div class="ds4-core-values">
|
||||
{{#each config.i18n.attributes as |attribute-label attribute-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=attribute-label
|
||||
core-value-key=attribute-key core-value-data=(lookup ../data.attributes
|
||||
core-value-key=attribute-key core-value-data=(lookup ../data.data.attributes
|
||||
attribute-key) core-value-variant="attribute"}}
|
||||
{{/each}}
|
||||
{{#each config.i18n.traits as |trait-label trait-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=trait-label
|
||||
core-value-key=trait-key
|
||||
core-value-data=(lookup ../data.traits trait-key) core-value-variant="trait"}}
|
||||
core-value-data=(lookup ../data.data.traits trait-key) core-value-variant="trait"}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
<h4 class="ds4-currency-title">{{localize 'DS4.CharacterCurrency'}}</h4>
|
||||
<div class="ds4-currency">
|
||||
{{#each data.currency as |value key|}}
|
||||
{{#each data.data.currency as |value key|}}
|
||||
<label for="data.currency.{{key}}" class="flex05">{{lookup ../config.i18n.characterCurrency key}}</label>
|
||||
<input class="ds4-currency__value ds4-currency__value--{{key}} item-change" type="number" min="0" step="1"
|
||||
name="data.currency.{{key}}" id="data.currency.{{key}}" value="{{value}}" data-dtype="Number" />
|
||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
{{!-- image --}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/rollable-image.hbs rollable=itemData.data.rollable
|
||||
src=itemData.img alt=(localize "DS4.EntityImageAltText" name=itemData.name) title=itemData.name
|
||||
src=itemData.img alt=(localize "DS4.DocumentImageAltText" name=itemData.name) title=itemData.name
|
||||
rollableTitle=(localize "DS4.RollableImageRollableTitle" name=itemData.name) rollableClass="rollable-item"}}
|
||||
|
||||
{{!-- amount --}}
|
||||
|
|
|
@ -9,11 +9,11 @@ SPDX-License-Identifier: MIT
|
|||
<form class="{{cssClass}} flexcol" autocomplete="off">
|
||||
{{!-- Sheet Header --}}
|
||||
<header class="sheet-header">
|
||||
<img class="profile-img" src="{{actor.img}}" data-edit="img" title="{{actor.name}}" height="100" width="100" />
|
||||
<img class="profile-img" src="{{data.img}}" data-edit="img" title="{{data.name}}" height="100" width="100" />
|
||||
<div class="header-fields flexrow">
|
||||
<h1 class="charname">
|
||||
<label for="actor.name" class="hidden">Name</label>
|
||||
<input name="name" type="text" id="actor.name" value="{{actor.name}}" placeholder="Name" />
|
||||
<label for="name" class="hidden">Name</label>
|
||||
<input name="name" type="text" id="name" value="{{data.name}}" placeholder="Name" />
|
||||
</h1>
|
||||
{{> systems/ds4/templates/sheets/actor/components/character-progression.hbs}}
|
||||
|
||||
|
@ -21,7 +21,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label>{{config.i18n.creatureBaseInfo.creatureType}}</label>
|
||||
<select name="data.baseInfo.creatureType" data-type="String">
|
||||
{{#select data.baseInfo.creatureType}}
|
||||
{{#select data.data.baseInfo.creatureType}}
|
||||
{{#each config.i18n.creatureTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -31,18 +31,19 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.loot">{{config.i18n.creatureBaseInfo.loot}}</label>
|
||||
<input type="text" name="data.baseInfo.loot" value="{{data.baseInfo.loot}}" data-dtype="String" />
|
||||
<input type="text" name="data.baseInfo.loot" value="{{data.data.baseInfo.loot}}"
|
||||
data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.foeFactor">{{config.i18n.creatureBaseInfo.foeFactor}}</label>
|
||||
<input type="text" name="data.baseInfo.foeFactor" value="{{data.baseInfo.foeFactor}}"
|
||||
<input type="text" name="data.baseInfo.foeFactor" value="{{data.data.baseInfo.foeFactor}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{config.i18n.creatureBaseInfo.sizeCategory}}</label>
|
||||
<select name="data.baseInfo.sizeCategory" data-type="String">
|
||||
{{#select data.baseInfo.sizeCategory}}
|
||||
{{#select data.data.baseInfo.sizeCategory}}
|
||||
{{#each config.i18n.creatureSizeCategories as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -52,8 +53,8 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.experiencePoints">{{config.i18n.creatureBaseInfo.experiencePoints}}</label>
|
||||
<input type="text" name="data.baseInfo.experiencePoints" value="{{data.baseInfo.experiencePoints}}"
|
||||
data-dtype="Number" />
|
||||
<input type="text" name="data.baseInfo.experiencePoints"
|
||||
value="{{data.data.baseInfo.experiencePoints}}" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,6 @@ SPDX-License-Identifier: MIT
|
|||
--}}
|
||||
|
||||
<div class="tab biography" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.profile.biography target="data.profile.biography" button=true owner=owner
|
||||
{{editor content=data.data.profile.biography target="data.profile.biography" button=true owner=owner
|
||||
editable=editable}}
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
<div class="tab profile" data-group="primary" data-tab="profile">
|
||||
<div class="grid grid-2col">
|
||||
{{#each data.profile as |profile-data-value profile-data-key|}}
|
||||
{{#each data.data.profile as |profile-data-value profile-data-key|}}
|
||||
{{#if (and (ne profile-data-key 'biography') (ne profile-data-key 'specialCharacteristics'))}}
|
||||
<div class="profile-entry">
|
||||
<label for="data.profile.{{profile-data-key}}">
|
||||
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: MIT
|
|||
{{lookup config.i18n.characterProfile 'specialCharacteristics'}}
|
||||
</label>
|
||||
<textarea name="data.profile.specialCharacteristics" data-dtype="String"
|
||||
rows="4">{{data.profile.specialCharacteristics}}</textarea>
|
||||
rows="4">{{data.data.profile.specialCharacteristics}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorType"}}</label>
|
||||
<select name="data.armorType" data-type="String">
|
||||
{{#select data.armorType}}
|
||||
{{#select data.data.armorType}}
|
||||
{{#each config.i18n.armorTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -21,7 +21,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label for="data.armorMaterialType">{{localize "DS4.ArmorMaterialType"}}</label>
|
||||
<select name="data.armorMaterialType" data-type="String">
|
||||
{{#select data.armorMaterialType}}
|
||||
{{#select data.data.armorMaterialType}}
|
||||
{{#each config.i18n.armorMaterialTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -30,12 +30,13 @@ SPDX-License-Identifier: MIT
|
|||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorValue"}}</label>
|
||||
<input type="text" name="data.armorValue" value="{{data.armorValue}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
<input type="text" name="data.armorValue" value="{{data.data.armorValue}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
</form>
|
||||
|
|
|
@ -6,9 +6,9 @@ SPDX-License-Identifier: MIT
|
|||
--}}
|
||||
|
||||
<header class="sheet-header">
|
||||
<img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}" />
|
||||
<img class="profile-img" src="{{data.img}}" data-edit="img" title="{{data.name}}" />
|
||||
<div class="header-fields flexrow">
|
||||
<h1 class="charname"><input name="name" type="text" value="{{item.name}}" placeholder="Name" /></h1>
|
||||
<h1 class="charname"><input name="name" type="text" value="{{data.name}}" placeholder="Name" /></h1>
|
||||
<h2 class="item-type">{{lookup config.i18n.itemTypes item.type}}</h2>
|
||||
{{> @partial-block}}
|
||||
</div>
|
||||
|
|
|
@ -10,12 +10,13 @@ SPDX-License-Identifier: MIT
|
|||
<div class="grid grid-1col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorValue"}}</label>
|
||||
<input type="text" name="data.armorValue" value="{{data.armorValue}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
<input type="text" name="data.armorValue" value="{{data.data.armorValue}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
</form>
|
||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="grid grid-3col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.SpecialCreatureAbilityExperiencePoints"}}</label>
|
||||
<input type="number" min="0" step="1" name="data.experiencePoints" value="{{data.experiencePoints}}"
|
||||
<input type="number" min="0" step="1" name="data.experiencePoints" value="{{data.data.experiencePoints}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,9 +16,9 @@ SPDX-License-Identifier: MIT
|
|||
<label>{{localize localizeString}}</label>
|
||||
<div class="unit-data-pair">
|
||||
<input class="item-num-val" type="text" data-dtype="String" name="data.{{property}}.value"
|
||||
value="{{lookup (lookup data property) 'value'}}" />
|
||||
value="{{lookup (lookup data.data property) 'value'}}" />
|
||||
<select name="data.{{property}}.unit" data-type="String">
|
||||
{{#select (lookup (lookup data property) 'unit')}}
|
||||
{{#select (lookup (lookup data.data property) 'unit')}}
|
||||
{{#if (eq unitType 'temporal')}}
|
||||
{{#each (lookup config.i18n 'temporalUnitsAbbr') as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label for="data.spellType">{{localize "DS4.SpellType"}}</label>
|
||||
<select id="data.spellType" name="data.spellType" data-type="String">
|
||||
{{#select data.spellType}}
|
||||
{{#select data.data.spellType}}
|
||||
{{#each config.i18n.spellTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -57,7 +57,7 @@ SPDX-License-Identifier: MIT
|
|||
</div>
|
||||
<div class="basic-property">
|
||||
<label for="data.bonus">{{localize "DS4.SpellBonus"}}</label>
|
||||
<input id="data.bonus" type="text" name="data.bonus" value="{{data.bonus}}" data-dtype="String" />
|
||||
<input id="data.bonus" type="text" name="data.bonus" value="{{data.data.bonus}}" data-dtype="String" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
@ -67,7 +67,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="side-property">
|
||||
<label for="data.spellCategory">{{localize "DS4.SpellCategory"}}</label>
|
||||
<select id="data.spellCategory" name="data.spellCategory" data-type="String">
|
||||
{{#select data.spellCategory}}
|
||||
{{#select data.data.spellCategory}}
|
||||
{{#each config.i18n.spellCategories as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -82,21 +82,21 @@ SPDX-License-Identifier: MIT
|
|||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsHealer'}}">
|
||||
<label for="data.minimumLevels.healer">{{localize "DS4.SpellMinimumLevelsHealerAbbr"}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.healer"
|
||||
id="data.minimumLevels.healer" value="{{data.minimumLevels.healer}}" />
|
||||
id="data.minimumLevels.healer" value="{{data.data.minimumLevels.healer}}" />
|
||||
</div>
|
||||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsWizard'}}">
|
||||
<label for="data.minimumLevels.wizard">{{localize "DS4.SpellMinimumLevelsWizardAbbr"}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.wizard"
|
||||
id="data.minimumLevels.wizard" value="{{data.minimumLevels.wizard}}" />
|
||||
id="data.minimumLevels.wizard" value="{{data.data.minimumLevels.wizard}}" />
|
||||
</div>
|
||||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsSorcerer'}}">
|
||||
<label for="data.minimumLevels.sorcerer">{{localize "DS4.SpellMinimumLevelsSorcererAbbr"}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.sorcerer"
|
||||
id="data.minimumLevels.sorcerer" value="{{data.minimumLevels.sorcerer}}" />
|
||||
id="data.minimumLevels.sorcerer" value="{{data.data.minimumLevels.sorcerer}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.price">{{localize "DS4.SpellPrice"}}</label>
|
||||
<span name="data.price" id="data.price">{{data.price}}</span>
|
||||
<span name="data.price" id="data.price">{{data.data.price}}</span>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
|
||||
|
|
|
@ -13,24 +13,26 @@ Additional elements of the side-properties div can be handed over via the @parti
|
|||
<div class="tab flexrow description" data-group="primary" data-tab="description">
|
||||
<div class="side-properties">
|
||||
{{#if isOwned}}
|
||||
{{#if (ne data.equipped undefined)}}<div class="side-property">
|
||||
{{#if (ne data.data.equipped undefined)}}<div class="side-property">
|
||||
<label for="data.equipped">{{localize 'DS4.ItemEquipped'}}</label>
|
||||
<input type="checkbox" name="data.equipped" data-dtype="Boolean" {{checked data.equipped}} title="{{localize 'DS4.ItemEquipped'}}">
|
||||
<input type="checkbox" name="data.equipped" data-dtype="Boolean" {{checked data.data.equipped}}
|
||||
title="{{localize 'DS4.ItemEquipped'}}">
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="side-property">
|
||||
<label for="data.actor">{{localize 'DS4.ItemOwner'}}</label>
|
||||
<a class="entity-link" draggable="true" data-entity="Actor" data-id="{{actor._id}}"><i
|
||||
<a class="entity-link" draggable="true" data-entity="Actor" data-id="{{actor.id}}"><i
|
||||
class="fas fa-user"></i>{{actor.name}}</a>
|
||||
</div>
|
||||
{{#if isPhysical}}
|
||||
<div class="side-property">
|
||||
<label for="data.quantity">{{localize 'DS4.Quantity'}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.quantity" value="{{data.quantity}}" />
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.quantity"
|
||||
value="{{data.data.quantity}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.storageLocation">{{localize 'DS4.StorageLocation'}}</label>
|
||||
<input type="text" data-dtype="String" name="data.storageLocation" value="{{data.storageLocation}}" />
|
||||
<input type="text" data-dtype="String" name="data.storageLocation" value="{{data.data.storageLocation}}" />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
|
@ -39,6 +41,6 @@ Additional elements of the side-properties div can be handed over via the @parti
|
|||
{{> @partial-block}}
|
||||
</div>
|
||||
<div class="description" title="{{localize 'DS4.HeadingDescription'}}">
|
||||
{{editor content=data.description target="data.description" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.data.description target="data.description" button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,13 +11,13 @@ SPDX-License-Identifier: MIT
|
|||
<div class="side-properties">
|
||||
<div class="side-property">
|
||||
<label for="data.price">{{localize "DS4.PriceGold"}}</label>
|
||||
<input type="number" min="0" max="99999" step="0.01" data-dtype="Number"
|
||||
name="data.price" value="{{data.price}}" />
|
||||
<input type="number" min="0" max="99999" step="0.01" data-dtype="Number" name="data.price"
|
||||
value="{{data.data.price}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.availability">{{localize "DS4.ItemAvailability"}}</label>
|
||||
<select name="data.availability" data-type="String">
|
||||
{{#select data.availability}}
|
||||
{{#select data.data.availability}}
|
||||
{{#each config.i18n.itemAvailabilities as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: MIT
|
|||
</div>
|
||||
</li>
|
||||
{{#each item.effects as |effect id|}}
|
||||
<li class="effect flexrow" data-effect-id="{{effect._id}}">
|
||||
<h4 class="effect-name">{{effect.label}}</h4>
|
||||
<li class="effect flexrow" data-effect-id="{{effect.id}}">
|
||||
<h4 class="effect-name">{{effect.data.label}}</h4>
|
||||
<div class="effect-controls">
|
||||
<a class="effect-control" data-action="edit" title="{{localize 'DS4.UserInteractionEditEffect'}}">
|
||||
<i class="fas fa-edit"></i></a>
|
||||
|
|
|
@ -13,9 +13,9 @@ SPDX-License-Identifier: MIT
|
|||
{{#*inline "talentRankBasicProperty" }}
|
||||
<div class="basic-property">
|
||||
<label for="data.rank.{{property}}">{{localize localizeString}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" {{disabled}}
|
||||
{{#if (eq property 'base') }}max="{{data.rank.max}}"{{/if}}
|
||||
name="data.rank.{{property}}" value="{{lookup data.rank property}}" />
|
||||
<input type="number" min="0" step="1" data-dtype="Number" {{disabled}} {{#if (eq property 'base' )
|
||||
}}max="{{data.data.rank.max}}" {{/if}} name="data.rank.{{property}}"
|
||||
value="{{lookup data.data.rank property}}" />
|
||||
</div>
|
||||
{{/inline}}
|
||||
|
||||
|
@ -29,11 +29,13 @@ SPDX-License-Identifier: MIT
|
|||
{{> talentRankBasicProperty data=data property='base' localizeString='DS4.TalentRankBase' }}
|
||||
{{> talentRankBasicProperty data=data property='max' localizeString='DS4.TalentRankMax'}}
|
||||
{{> talentRankBasicProperty data=data property='mod' localizeString='DS4.TalentRankMod'}}
|
||||
{{> talentRankBasicProperty data=data property='total' localizeString='DS4.TalentRankTotal' disabled='disabled'}}
|
||||
{{> talentRankBasicProperty data=data property='total' localizeString='DS4.TalentRankTotal'
|
||||
disabled='disabled'}}
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
|
||||
</form>
|
||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label>{{localize "DS4.AttackType"}}</label>
|
||||
<select name="data.attackType" data-type="String">
|
||||
{{#select data.attackType}}
|
||||
{{#select data.data.attackType}}
|
||||
{{#each config.i18n.attackTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -20,17 +20,18 @@ SPDX-License-Identifier: MIT
|
|||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.WeaponBonus"}}</label>
|
||||
<input type="number" name="data.weaponBonus" value="{{data.weaponBonus}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
<input type="number" name="data.weaponBonus" value="{{data.data.weaponBonus}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.OpponentDefense"}}</label>
|
||||
<input type="number" name="data.opponentDefense"
|
||||
value="{{data.opponentDefense}}" placeholder="0" data-dtype="Number" />
|
||||
<input type="number" name="data.opponentDefense" value="{{data.data.opponentDefense}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
</form>
|
||||
|
|
Loading…
Reference in a new issue