Merge branch '068-enable-strict-mode' into 050-basic-active-effects
This commit is contained in:
commit
b74ee5ec7c
45 changed files with 3088 additions and 2119 deletions
|
@ -1,20 +1,31 @@
|
|||
import { ModifiableData, ResourceData, UsableResource } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import { DS4ItemData } from "../item/item-data";
|
||||
|
||||
export type DS4ActorDataType = DS4ActorDataCharacter | DS4ActorDataCreature;
|
||||
export type DS4ActorData = DS4CharacterData | DS4CreatureData;
|
||||
|
||||
interface DS4ActorDataBase {
|
||||
attributes: DS4ActorDataAttributes;
|
||||
traits: DS4ActorDataTraits;
|
||||
combatValues: DS4ActorDataCombatValues;
|
||||
type ActorType = keyof typeof DS4.i18n.actorTypes;
|
||||
|
||||
interface DS4ActorDataHelper<T, U extends ActorType> extends Actor.Data<T, DS4ItemData> {
|
||||
type: U;
|
||||
}
|
||||
|
||||
interface DS4ActorDataAttributes {
|
||||
type DS4CharacterData = DS4ActorDataHelper<DS4CharacterDataData, "character">;
|
||||
type DS4CreatureData = DS4ActorDataHelper<DS4CreatureDataData, "creature">;
|
||||
|
||||
interface DS4ActorDataDataBase {
|
||||
attributes: DS4ActorDataDataAttributes;
|
||||
traits: DS4ActorDataDataTraits;
|
||||
combatValues: DS4ActorDataDataCombatValues;
|
||||
}
|
||||
|
||||
interface DS4ActorDataDataAttributes {
|
||||
body: ModifiableData<number>;
|
||||
mobility: ModifiableData<number>;
|
||||
mind: ModifiableData<number>;
|
||||
}
|
||||
|
||||
interface DS4ActorDataTraits {
|
||||
interface DS4ActorDataDataTraits {
|
||||
strength: ModifiableData<number>;
|
||||
constitution: ModifiableData<number>;
|
||||
agility: ModifiableData<number>;
|
||||
|
@ -23,7 +34,7 @@ interface DS4ActorDataTraits {
|
|||
aura: ModifiableData<number>;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCombatValues {
|
||||
interface DS4ActorDataDataCombatValues {
|
||||
hitPoints: ResourceData<number>;
|
||||
defense: ModifiableData<number>;
|
||||
initiative: ModifiableData<number>;
|
||||
|
@ -34,34 +45,32 @@ interface DS4ActorDataCombatValues {
|
|||
targetedSpellcasting: ModifiableData<number>;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCharacter extends DS4ActorDataBase {
|
||||
baseInfo: DS4ActorDataCharacterBaseInfo;
|
||||
progression: DS4ActorDataCharacterProgression;
|
||||
language: DS4ActorDataCharacterLanguage;
|
||||
profile: DS4ActorDataCharacterProfile;
|
||||
currency: DS4ActorDataCharacterCurrency;
|
||||
interface DS4CharacterDataData extends DS4ActorDataDataBase {
|
||||
baseInfo: DS4CharacterDataDataBaseInfo;
|
||||
progression: DS4CharacterDataDataProgression;
|
||||
language: DS4CharacterDataDataLanguage;
|
||||
profile: DS4CharacterDataDataProfile;
|
||||
currency: DS4CharacterDataDataCurrency;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCharacterBaseInfo {
|
||||
interface DS4CharacterDataDataBaseInfo {
|
||||
race: string;
|
||||
class: string;
|
||||
heroClass: string;
|
||||
culture: string;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCharacterProgression {
|
||||
interface DS4CharacterDataDataProgression {
|
||||
level: number;
|
||||
experiencePoints: number;
|
||||
talentPoints: UsableResource<number>;
|
||||
progressPoints: UsableResource<number>;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCharacterLanguage {
|
||||
interface DS4CharacterDataDataLanguage {
|
||||
languages: string;
|
||||
alphabets: string;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCharacterProfile {
|
||||
interface DS4CharacterDataDataProfile {
|
||||
biography: string;
|
||||
gender: string;
|
||||
birthday: string;
|
||||
|
@ -74,21 +83,21 @@ interface DS4ActorDataCharacterProfile {
|
|||
specialCharacteristics: string;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCharacterCurrency {
|
||||
interface DS4CharacterDataDataCurrency {
|
||||
gold: number;
|
||||
silver: number;
|
||||
copper: number;
|
||||
}
|
||||
|
||||
interface DS4ActorDataCreature extends DS4ActorDataBase {
|
||||
baseInfo: DS4ActorDataCreatureBaseInfo;
|
||||
interface DS4CreatureDataData extends DS4ActorDataDataBase {
|
||||
baseInfo: DS4CreatureDataDataBaseInfo;
|
||||
}
|
||||
|
||||
type CreatureType = "animal" | "construct" | "humanoid" | "magicalEntity" | "plantBeing" | "undead";
|
||||
|
||||
type SizeCategory = "tiny" | "small" | "normal" | "large" | "huge" | "colossal";
|
||||
|
||||
interface DS4ActorDataCreatureBaseInfo {
|
||||
interface DS4CreatureDataDataBaseInfo {
|
||||
loot: string;
|
||||
foeFactor: number;
|
||||
creatureType: CreatureType;
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { ModifiableData } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { DS4Armor, DS4EquippableItemDataType, DS4ItemDataType, DS4Shield, ItemType } from "../item/item-data";
|
||||
import { DS4ActorDataType } from "./actor-data";
|
||||
import { DS4ItemData, ItemType } from "../item/item-data";
|
||||
import { DS4ActorData } from "./actor-data";
|
||||
|
||||
type DS4ActiveEffect = ActiveEffect<DS4ActorDataType, DS4ItemDataType, DS4Actor, DS4Item>;
|
||||
|
||||
export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item> {
|
||||
/**
|
||||
* The Actor class for DS4
|
||||
*/
|
||||
export class DS4Actor extends Actor<DS4ActorData, DS4Item> {
|
||||
/** @override */
|
||||
prepareData(): void {
|
||||
this.data = duplicate(this._data);
|
||||
this.data = duplicate(this._data) as DS4ActorData;
|
||||
if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN;
|
||||
if (!this.data.name) this.data.name = "New " + this.entity;
|
||||
this.prepareBaseData();
|
||||
|
@ -45,20 +46,23 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
|||
* @param predicate The predicate that ActiveEffectChanges need to satisfy in order to be applied
|
||||
*/
|
||||
applyActiveEffectsFiltered(predicate: (change: ActiveEffectChange) => boolean): void {
|
||||
const overrides = {};
|
||||
const overrides: Record<string, unknown> = {};
|
||||
|
||||
// Organize non-disabled effects by their application priority
|
||||
const changes = this.effects.reduce((changes: Array<ActiveEffectChange & { effect: DS4ActiveEffect }>, e) => {
|
||||
if (e.data.disabled) return changes;
|
||||
const changes = this.effects.reduce(
|
||||
(changes: Array<ActiveEffectChange & { effect: ActiveEffect<DS4Actor> }>, e) => {
|
||||
if (e.data.disabled) return changes;
|
||||
|
||||
return changes.concat(
|
||||
e.data.changes.filter(predicate).map((c) => {
|
||||
c = duplicate(c);
|
||||
c.priority = c.priority ?? c.mode * 10;
|
||||
return { ...c, effect: e };
|
||||
}),
|
||||
);
|
||||
}, []);
|
||||
return changes.concat(
|
||||
e.data.changes.filter(predicate).map((c) => {
|
||||
const duplicatedChange = duplicate(c) as ActiveEffect.Change;
|
||||
duplicatedChange.priority = duplicatedChange.priority ?? duplicatedChange.mode * 10;
|
||||
return { ...duplicatedChange, effect: e };
|
||||
}),
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
changes.sort((a, b) => a.priority - b.priority);
|
||||
|
||||
// Apply all changes
|
||||
|
@ -68,7 +72,7 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
|||
}
|
||||
|
||||
// Expand the set of final overrides
|
||||
this["overrides"] = expandObject({ ...flattenObject(this["overrides"] ?? {}), ...overrides });
|
||||
this.overrides = expandObject({ ...flattenObject(this.overrides ?? {}), ...overrides });
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
@ -78,7 +82,7 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
|||
|
||||
/** The list of properties that are derived from others, given in dot notation */
|
||||
get derivedDataProperties(): Array<string> {
|
||||
return Object.keys(DS4.combatValues)
|
||||
return Object.keys(DS4.i18n.combatValues)
|
||||
.map((combatValue) => `data.combatValues.${combatValue}.total`)
|
||||
.concat("data.combatValues.hitPoints.max");
|
||||
}
|
||||
|
@ -110,7 +114,7 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
|||
|
||||
/**
|
||||
* Checks whether or not the given item type can be owned by the actor.
|
||||
* @param itemType the item type to check
|
||||
* @param itemType - The item type to check
|
||||
*/
|
||||
canOwnItemType(itemType: ItemType): boolean {
|
||||
return this.ownableItemTypes.includes(itemType);
|
||||
|
@ -149,10 +153,13 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
|||
*/
|
||||
private _calculateArmorValueOfEquippedItems(): number {
|
||||
return this.items
|
||||
.filter((item) => ["armor", "shield"].includes(item.type))
|
||||
.map((item) => item.data.data as DS4Armor | DS4Shield)
|
||||
.filter((itemData) => itemData.equipped)
|
||||
.map((itemData) => itemData.armorValue)
|
||||
.map((item) => {
|
||||
if (item.data.type === "armor" || item.data.type === "shield") {
|
||||
return item.data.data.equipped ? item.data.data.armorValue : 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
|
@ -161,7 +168,7 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
|||
* This only differs from the base implementation by also allowing negative values.
|
||||
* @override
|
||||
*/
|
||||
async modifyTokenAttribute(attribute: string, value: number, isDelta = false, isBar = true): Promise<DS4Actor> {
|
||||
async modifyTokenAttribute(attribute: string, value: number, isDelta = false, isBar = true): Promise<this> {
|
||||
const current = getProperty(this.data.data, attribute);
|
||||
|
||||
// Determine the updates to make to the actor data
|
||||
|
@ -180,59 +187,77 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
|
|||
}
|
||||
|
||||
/** @override */
|
||||
// TODO(types): Improve typing once it's fixed in upstream (arrays can be passed!)
|
||||
createEmbeddedEntity(
|
||||
embeddedName: string,
|
||||
createData: Record<string, unknown> | Array<Record<string, unknown>>,
|
||||
embeddedName: "OwnedItem",
|
||||
data: DeepPartial<DS4ItemData>,
|
||||
options?: Record<string, unknown>,
|
||||
): Promise<this> {
|
||||
): Promise<DS4ItemData>;
|
||||
createEmbeddedEntity(
|
||||
embeddedName: "ActiveEffect",
|
||||
data: DeepPartial<DS4ItemData>,
|
||||
options?: Record<string, unknown>,
|
||||
): Promise<ActiveEffect.Data>;
|
||||
createEmbeddedEntity(
|
||||
embeddedName: "OwnedItem" | "ActiveEffect",
|
||||
data: DeepPartial<DS4ItemData> | DeepPartial<ActiveEffect.Data>,
|
||||
options?: Record<string, unknown>,
|
||||
): Promise<DS4ItemData> | Promise<ActiveEffect.Data> {
|
||||
if (embeddedName === "OwnedItem") {
|
||||
this._preCreateOwnedItem((createData as unknown) as ItemData<DS4ItemDataType>);
|
||||
this._preCreateOwnedItem(data);
|
||||
return super.createEmbeddedEntity(embeddedName, data, options);
|
||||
}
|
||||
return super.createEmbeddedEntity(embeddedName, createData, options);
|
||||
return super.createEmbeddedEntity(embeddedName, data, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the item that is going to be created is equippable, set it to be non equipped and disable all ActiveEffects
|
||||
* If the item that is going to be created is equipable, set it to be non equipped and disable all ActiveEffects
|
||||
* contained in the item
|
||||
* @param itemData The data of the item to be created
|
||||
*/
|
||||
private _preCreateOwnedItem(itemData: ItemData<DS4ItemDataType>): void {
|
||||
if ("equipped" in itemData.data) {
|
||||
itemData.effects = itemData.effects.map((effect) => ({ ...effect, disabled: true }));
|
||||
const equippableUpdateData = itemData as ItemData<DS4EquippableItemDataType>;
|
||||
equippableUpdateData.data.equipped = false;
|
||||
protected _preCreateOwnedItem(itemData: DeepPartial<DS4ItemData>): void {
|
||||
if (itemData.data && "equipped" in itemData.data) {
|
||||
itemData.effects = itemData.effects?.map((effect) => ({ ...effect, disabled: true }));
|
||||
itemData.data.equipped = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
// TODO(types): Improve typing once it's fixed in upstream
|
||||
updateEmbeddedEntity(embeddedName: string, data: unknown[], options?: Entity.UpdateOptions): Promise<unknown[]>;
|
||||
updateEmbeddedEntity(embeddedName: string, data: unknown, options?: Entity.UpdateOptions): Promise<unknown>;
|
||||
updateEmbeddedEntity(
|
||||
embeddedName: string,
|
||||
updateData: Record<string, unknown> | Array<Record<string, unknown>>,
|
||||
updateData: unknown | unknown[],
|
||||
options?: Record<string, unknown>,
|
||||
): Promise<this> {
|
||||
): Promise<unknown> {
|
||||
if (embeddedName === "OwnedItem") {
|
||||
this._preUpdateOwnedItem(updateData as Partial<ItemData<DS4ItemDataType>>);
|
||||
this._preUpdateOwnedItem(updateData as DeepPartial<DS4ItemData> | Array<DeepPartial<DS4ItemData>>);
|
||||
}
|
||||
return super.updateEmbeddedEntity(embeddedName, updateData, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the equipped flag of an item changed, update all ActiveEffects originating from that item accordingly.
|
||||
* @param updateData The change that is going to be applied to the owned item
|
||||
* If the equipped flag of one or more items changed, update all ActiveEffects originating from those items
|
||||
* accordingly.
|
||||
* @param updateData The change that is going to be applied to the owned item(s)
|
||||
*/
|
||||
private _preUpdateOwnedItem(updateData: Partial<ItemData<DS4ItemDataType>>): void {
|
||||
if ("equipped" in updateData.data) {
|
||||
const equippableUpdateData = updateData as Partial<ItemData<DS4EquippableItemDataType>>;
|
||||
const origin = `Actor.${this.id}.OwnedItem.${updateData._id}`;
|
||||
const effects = this.effects
|
||||
.filter((e) => e.data.origin === origin)
|
||||
.map((e) => {
|
||||
const data = duplicate(e.data);
|
||||
data.disabled = !equippableUpdateData.data.equipped;
|
||||
return data;
|
||||
});
|
||||
if (effects.length > 0)
|
||||
this.updateEmbeddedEntity("ActiveEffect", (effects as unknown) as Record<string, unknown>);
|
||||
}
|
||||
private _preUpdateOwnedItem(updateData: DeepPartial<DS4ItemData> | Array<DeepPartial<DS4ItemData>>): void {
|
||||
const dataArray = updateData instanceof Array ? updateData : [updateData];
|
||||
dataArray.forEach((data) => {
|
||||
if (data.data && "equipped" in data.data) {
|
||||
const equipped = data.data.equipped;
|
||||
const origin = `Actor.${this.id}.OwnedItem.${data._id}`;
|
||||
const effects = this.effects
|
||||
.filter((e) => e.data.origin === origin)
|
||||
.map((e) => {
|
||||
const effectData = duplicate(e.data);
|
||||
effectData.disabled = !(equipped ?? true);
|
||||
return effectData;
|
||||
});
|
||||
if (effects.length > 0)
|
||||
this.updateEmbeddedEntity("ActiveEffect", (effects as unknown) as Record<string, unknown>);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,38 @@
|
|||
import { DS4 } from "../../config";
|
||||
import { DS4Item } from "../../item/item";
|
||||
import { DS4ItemDataType, ItemType } from "../../item/item-data";
|
||||
import { DS4ItemData } from "../../item/item-data";
|
||||
import { DS4Actor } from "../actor";
|
||||
import { DS4ActorDataType } from "../actor-data";
|
||||
|
||||
/**
|
||||
* Extend the basic ActorSheet with some very simple modifications
|
||||
* @extends {ActorSheet}
|
||||
* The base Sheet class for all DS4 Actors
|
||||
*/
|
||||
export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4ItemDataType> {
|
||||
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)
|
||||
/** @override */
|
||||
static get defaultOptions(): FormApplicationOptions {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor"],
|
||||
width: 745,
|
||||
height: 600,
|
||||
scrollY: [".sheet-body"],
|
||||
template: superDefaultOptions.template,
|
||||
viewPermission: superDefaultOptions.viewPermission,
|
||||
closeOnSubmit: superDefaultOptions.closeOnSubmit,
|
||||
submitOnChange: superDefaultOptions.submitOnChange,
|
||||
submitOnClose: superDefaultOptions.submitOnClose,
|
||||
editable: superDefaultOptions.editable,
|
||||
baseApplication: superDefaultOptions.baseApplication,
|
||||
top: superDefaultOptions.top,
|
||||
left: superDefaultOptions.left,
|
||||
popOut: superDefaultOptions.popOut,
|
||||
minimizable: superDefaultOptions.minimizable,
|
||||
resizable: superDefaultOptions.resizable,
|
||||
id: superDefaultOptions.id,
|
||||
dragDrop: superDefaultOptions.dragDrop,
|
||||
filters: superDefaultOptions.filters,
|
||||
title: superDefaultOptions.title,
|
||||
tabs: superDefaultOptions.tabs,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -24,27 +42,23 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
return `${path}/${this.actor.data.type}-sheet.hbs`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* This method returns the data for the template of the actor sheet.
|
||||
* It explicitly adds the items of the object sorted by type in the
|
||||
* object itemsByType.
|
||||
* @returns the data fed to the template of the actor sheet
|
||||
* @returns The data fed to the template of the actor sheet
|
||||
*/
|
||||
getData(): ActorSheetData<DS4ActorDataType, DS4Actor> {
|
||||
async getData(): Promise<ActorSheet.Data<DS4Actor>> {
|
||||
const data = {
|
||||
...super.getData(),
|
||||
...(await super.getData()),
|
||||
// Add the localization config to the data:
|
||||
config: CONFIG.DS4,
|
||||
config: DS4,
|
||||
// Add the items explicitly sorted by type to the data:
|
||||
itemsByType: this.actor.itemTypes,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html: JQuery): void {
|
||||
super.activateListeners(html);
|
||||
|
@ -59,7 +73,7 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
html.find(".item-edit").on("click", (ev) => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
const item = this.actor.getOwnedItem(li.data("itemId"));
|
||||
item.sheet.render(true);
|
||||
item.sheet?.render(true);
|
||||
});
|
||||
|
||||
// Delete Inventory Item
|
||||
|
@ -75,20 +89,16 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
html.find(".rollable").click(this._onRoll.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
|
||||
* @param {JQuery.ClickEvent} event The originating click event
|
||||
* @private
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
private _onItemCreate(event: JQuery.ClickEvent): Promise<Item> {
|
||||
protected _onItemCreate(event: JQuery.ClickEvent): Promise<DS4ItemData> {
|
||||
event.preventDefault();
|
||||
const header = event.currentTarget;
|
||||
// Get the type of item to create.
|
||||
const type = header.dataset.type;
|
||||
// Grab any data associated with this control.
|
||||
const data = duplicate(header.dataset);
|
||||
const { type, ...data } = duplicate(header.dataset);
|
||||
// Initialize a default name.
|
||||
const name = `New ${type.capitalize()}`;
|
||||
// Prepare the item object.
|
||||
|
@ -97,8 +107,6 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
type: type,
|
||||
data: data,
|
||||
};
|
||||
// Remove the type from the dataset since it's in the itemData.type prop.
|
||||
delete itemData.data.type;
|
||||
|
||||
// Finally, create the item!
|
||||
return this.actor.createOwnedItem(itemData);
|
||||
|
@ -108,15 +116,14 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
* Handle changes to properties of an Owned Item from within character sheet.
|
||||
* Can currently properly bind: see getValue().
|
||||
* Assumes the item property is given as the value of the HTML element property 'data-property'.
|
||||
* @param {JQuery.ChangeEvent<HTMLFormElement>} ev The originating change event
|
||||
* @private
|
||||
* @param ev - The originating change event
|
||||
*/
|
||||
private _onItemChange(ev: JQuery.ChangeEvent<HTMLFormElement>): void {
|
||||
protected _onItemChange(ev: JQuery.ChangeEvent): void {
|
||||
ev.preventDefault();
|
||||
console.log("Current target:", $(ev.currentTarget).get(0)["name"]);
|
||||
const el: HTMLFormElement = $(ev.currentTarget).get(0);
|
||||
const id = $(ev.currentTarget).parents(".item").data("itemId");
|
||||
const item = duplicate(this.actor.getOwnedItem(id)); // getOwnedItem is typed incorrectly, it actually returns a ItemData<DS4ItemDataType>, not an Item
|
||||
const item = duplicate<DS4Item, "lenient">(this.actor.getOwnedItem(id));
|
||||
const property: string | undefined = $(ev.currentTarget).data("property");
|
||||
|
||||
// Early return:
|
||||
|
@ -139,7 +146,7 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
* - Checkbox: boolean
|
||||
* - Text input: string
|
||||
* - Number: number
|
||||
* @param el the input element to collect the value of
|
||||
* @param el - The input element to collect the value of
|
||||
*/
|
||||
private getValue(el: HTMLFormElement): boolean | string | number {
|
||||
// One needs to differentiate between e.g. checkboxes (value="on") and select boxes etc.
|
||||
|
@ -190,10 +197,9 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
|
||||
/**
|
||||
* Handle clickable rolls.
|
||||
* @param {JQuery.ClickEvent} event The originating click event
|
||||
* @private
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
private _onRoll(event: JQuery.ClickEvent): void {
|
||||
protected _onRoll(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const element = event.currentTarget;
|
||||
const dataset = element.dataset;
|
||||
|
@ -209,10 +215,13 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
|
|||
}
|
||||
|
||||
/** @override */
|
||||
async _onDropItem(event: DragEvent, data: Parameters<typeof DS4Item.fromDropData>[0]): Promise<unknown> {
|
||||
const item = await Item.fromDropData(data);
|
||||
if (item && !this.actor.canOwnItemType(item.data.type as ItemType)) {
|
||||
ui.notifications.warn(
|
||||
protected async _onDropItem(
|
||||
event: DragEvent,
|
||||
data: { type: "Item" } & (DeepPartial<ActorSheet.OwnedItemData<DS4Actor>> | { pack: string } | { id: string }),
|
||||
): Promise<boolean | undefined | ActorSheet.OwnedItemData<DS4Actor>> {
|
||||
const item = ((await Item.fromDropData(data)) as unknown) as DS4Item;
|
||||
if (item && !this.actor.canOwnItemType(item.data.type)) {
|
||||
ui.notifications?.warn(
|
||||
game.i18n.format("DS4.WarningActorCannotOwnItem", {
|
||||
actorName: this.actor.name,
|
||||
actorType: this.actor.data.type,
|
||||
|
|
|
@ -1,11 +1,34 @@
|
|||
import { DS4ActorSheet } from "./actor-sheet";
|
||||
|
||||
/**
|
||||
* The Sheet class for DS4 Character Actors
|
||||
*/
|
||||
export class DS4CharacterActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): FormApplicationOptions {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor", "character"],
|
||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "inventory" }],
|
||||
template: superDefaultOptions.template,
|
||||
viewPermission: superDefaultOptions.viewPermission,
|
||||
closeOnSubmit: superDefaultOptions.closeOnSubmit,
|
||||
submitOnChange: superDefaultOptions.submitOnChange,
|
||||
submitOnClose: superDefaultOptions.submitOnClose,
|
||||
editable: superDefaultOptions.editable,
|
||||
baseApplication: superDefaultOptions.baseApplication,
|
||||
top: superDefaultOptions.top,
|
||||
left: superDefaultOptions.left,
|
||||
popOut: superDefaultOptions.popOut,
|
||||
minimizable: superDefaultOptions.minimizable,
|
||||
resizable: superDefaultOptions.resizable,
|
||||
id: superDefaultOptions.id,
|
||||
dragDrop: superDefaultOptions.dragDrop,
|
||||
filters: superDefaultOptions.filters,
|
||||
title: superDefaultOptions.title,
|
||||
width: superDefaultOptions.width,
|
||||
height: superDefaultOptions.height,
|
||||
scrollY: superDefaultOptions.scrollY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,34 @@
|
|||
import { DS4ActorSheet } from "./actor-sheet";
|
||||
|
||||
/**
|
||||
* The Sheet class for DS4 Creature Actors
|
||||
*/
|
||||
export class DS4CreatureActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): FormApplicationOptions {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor", "creature"],
|
||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "inventory" }],
|
||||
template: superDefaultOptions.template,
|
||||
viewPermission: superDefaultOptions.viewPermission,
|
||||
closeOnSubmit: superDefaultOptions.closeOnSubmit,
|
||||
submitOnChange: superDefaultOptions.submitOnChange,
|
||||
submitOnClose: superDefaultOptions.submitOnClose,
|
||||
editable: superDefaultOptions.editable,
|
||||
baseApplication: superDefaultOptions.baseApplication,
|
||||
top: superDefaultOptions.top,
|
||||
left: superDefaultOptions.left,
|
||||
popOut: superDefaultOptions.popOut,
|
||||
minimizable: superDefaultOptions.minimizable,
|
||||
resizable: superDefaultOptions.resizable,
|
||||
id: superDefaultOptions.id,
|
||||
dragDrop: superDefaultOptions.dragDrop,
|
||||
filters: superDefaultOptions.filters,
|
||||
title: superDefaultOptions.title,
|
||||
width: superDefaultOptions.width,
|
||||
height: superDefaultOptions.height,
|
||||
scrollY: superDefaultOptions.scrollY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
37
src/module/common/utils.ts
Normal file
37
src/module/common/utils.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Partition an array into two, following a predicate.
|
||||
* @param input - The Array to split.
|
||||
* @param predicate - The predicate by which to split.
|
||||
* @returns A tuple of two arrays, the first one containing all elements from `input` that match the predicate, the second one containing those that do not.
|
||||
*/
|
||||
export function partition<T>(input: Array<T>, predicate: (v: T) => boolean): [T[], T[]] {
|
||||
return input.reduce(
|
||||
(p: [Array<T>, Array<T>], cur: T) => {
|
||||
if (predicate(cur)) {
|
||||
p[0].push(cur);
|
||||
} else {
|
||||
p[1].push(cur);
|
||||
}
|
||||
return p;
|
||||
},
|
||||
[[], []],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zips two Arrays to an array of pairs of elements with corresponding indices. Excessive elements are dropped.
|
||||
* @param a1 - First array to zip.
|
||||
* @param a2 - Second array to zip.
|
||||
*
|
||||
* @typeParam T - Type of elements contained in `a1`.
|
||||
* @typeParam U - Type of elements contained in `a2`.
|
||||
*
|
||||
* @returns The array of pairs that had the same index in their source array.
|
||||
*/
|
||||
export function zip<T, U>(a1: Array<T>, a2: Array<U>): Array<[T, U]> {
|
||||
if (a1.length <= a2.length) {
|
||||
return a1.map((e1, i) => [e1, a2[i]]);
|
||||
} else {
|
||||
return a2.map((e2, i) => [a1[i], e2]);
|
||||
}
|
||||
}
|
|
@ -9,205 +9,310 @@ export const DS4 = {
|
|||
=============================================================================================`,
|
||||
|
||||
/**
|
||||
* Define the set of acttack types that can be performed with weapon items
|
||||
* A dictionary of dictionaries each mapping keys to localized strings
|
||||
* resp. their localization keys.
|
||||
* The localization is assumed to take place on each reload.
|
||||
*/
|
||||
attackTypes: {
|
||||
melee: "DS4.AttackTypeMelee",
|
||||
ranged: "DS4.AttackTypeRanged",
|
||||
meleeRanged: "DS4.AttackTypeMeleeRanged",
|
||||
i18n: {
|
||||
/**
|
||||
* Define the set of acttack types that can be performed with weapon items
|
||||
*/
|
||||
attackTypes: {
|
||||
melee: "DS4.AttackTypeMelee",
|
||||
ranged: "DS4.AttackTypeRanged",
|
||||
meleeRanged: "DS4.AttackTypeMeleeRanged",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of item availabilties
|
||||
*/
|
||||
itemAvailabilities: {
|
||||
unset: "DS4.ItemAvailabilityUnset",
|
||||
hamlet: "DS4.ItemAvailabilityHamlet",
|
||||
village: "DS4.ItemAvailabilityVilage",
|
||||
city: "DS4.ItemAvailabilityCity",
|
||||
elves: "DS4.ItemAvailabilityElves",
|
||||
dwarves: "DS4.ItemAvailabilityDwarves",
|
||||
nowhere: "DS4.ItemAvailabilityNowhere",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of item types
|
||||
*/
|
||||
itemTypes: {
|
||||
weapon: "DS4.ItemTypeWeapon",
|
||||
armor: "DS4.ItemTypeArmor",
|
||||
shield: "DS4.ItemTypeShield",
|
||||
spell: "DS4.ItemTypeSpell",
|
||||
trinket: "DS4.ItemTypeTrinket",
|
||||
equipment: "DS4.ItemTypeEquipment",
|
||||
talent: "DS4.ItemTypeTalent",
|
||||
racialAbility: "DS4.ItemTypeRacialAbility",
|
||||
language: "DS4.ItemTypeLanguage",
|
||||
alphabet: "DS4.ItemTypeAlphabet",
|
||||
specialCreatureAbility: "DS4.ItemTypeSpecialCreatureAbility",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of armor types, a character may only wear one item of each at any given time
|
||||
*/
|
||||
armorTypes: {
|
||||
body: "DS4.ArmorTypeBody",
|
||||
helmet: "DS4.ArmorTypeHelmet",
|
||||
vambrace: "DS4.ArmorTypeVambrace",
|
||||
greaves: "DS4.ArmorTypeGreaves",
|
||||
vambraceGreaves: "DS4.ArmorTypeVambraceGreaves",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define abbreviations for the armor types
|
||||
*/
|
||||
armorTypesAbbr: {
|
||||
body: "DS4.ArmorTypeBodyAbbr",
|
||||
helmet: "DS4.ArmorTypeHelmetAbbr",
|
||||
vambrace: "DS4.ArmorTypeVambraceAbbr",
|
||||
greaves: "DS4.ArmorTypeGreavesAbbr",
|
||||
vambraceGreaves: "DS4.ArmorTypeVambraceGreavesAbbr",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of armor materials, used to determine if a character may wear the armor without additional penalties
|
||||
*/
|
||||
armorMaterialTypes: {
|
||||
cloth: "DS4.ArmorMaterialTypeCloth",
|
||||
leather: "DS4.ArmorMaterialTypeLeather",
|
||||
chain: "DS4.ArmorMaterialTypeChain",
|
||||
plate: "DS4.ArmorMaterialTypePlate",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the abbreviations of armor materials
|
||||
*/
|
||||
armorMaterialTypesAbbr: {
|
||||
cloth: "DS4.ArmorMaterialTypeClothAbbr",
|
||||
leather: "DS4.ArmorMaterialTypeLeatherAbbr",
|
||||
chain: "DS4.ArmorMaterialTypeChainAbbr",
|
||||
plate: "DS4.ArmorMaterialTypePlateAbbr",
|
||||
},
|
||||
|
||||
spellTypes: {
|
||||
spellcasting: "DS4.SpellTypeSpellcasting",
|
||||
targetedSpellcasting: "DS4.SpellTypeTargetedSpellcasting",
|
||||
},
|
||||
|
||||
spellCategories: {
|
||||
healing: "DS4.SpellCategoryHealing",
|
||||
fire: "DS4.SpellCategoryFire",
|
||||
ice: "DS4.SpellCategoryIce",
|
||||
light: "DS4.SpellCategoryLight",
|
||||
darkness: "DS4.SpellCategoryDarkness",
|
||||
mindAffecting: "DS4.SpellCategoryMindAffecting",
|
||||
electricity: "DS4.SpellCategoryElectricity",
|
||||
none: "DS4.SpellCategoryNone",
|
||||
unset: "DS4.SpellCategoryUnset",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of actor types
|
||||
*/
|
||||
actorTypes: {
|
||||
character: "DS4.ActorTypeCharacter",
|
||||
creature: "DS4.ActorTypeCreature",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of attributes an actor has
|
||||
*/
|
||||
attributes: {
|
||||
body: "DS4.AttributeBody",
|
||||
mobility: "DS4.AttributeMobility",
|
||||
mind: "DS4.AttributeMind",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of traits an actor has
|
||||
*/
|
||||
traits: {
|
||||
strength: "DS4.TraitStrength",
|
||||
agility: "DS4.TraitAgility",
|
||||
intellect: "DS4.TraitIntellect",
|
||||
constitution: "DS4.TraitConstitution",
|
||||
dexterity: "DS4.TraitDexterity",
|
||||
aura: "DS4.TraitAura",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of combat values an actor has
|
||||
*/
|
||||
combatValues: {
|
||||
hitPoints: "DS4.CombatValuesHitPoints",
|
||||
defense: "DS4.CombatValuesDefense",
|
||||
initiative: "DS4.CombatValuesInitiative",
|
||||
movement: "DS4.CombatValuesMovement",
|
||||
meleeAttack: "DS4.CombatValuesMeleeAttack",
|
||||
rangedAttack: "DS4.CombatValuesRangedAttack",
|
||||
spellcasting: "DS4.CombatValuesSpellcasting",
|
||||
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the base info of a character
|
||||
*/
|
||||
characterBaseInfo: {
|
||||
race: "DS4.CharacterBaseInfoRace",
|
||||
class: "DS4.CharacterBaseInfoClass",
|
||||
heroClass: "DS4.CharacterBaseInfoHeroClass",
|
||||
culture: "DS4.CharacterBaseInfoCulture",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the progression info of a character
|
||||
*/
|
||||
characterProgression: {
|
||||
level: "DS4.CharacterProgressionLevel",
|
||||
experiencePoints: "DS4.CharacterProgressionExperiencePoints",
|
||||
talentPoints: "DS4.CharacterProgressionTalentPoints",
|
||||
progressPoints: "DS4.CharacterProgressionProgressPoints",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the language info of a character
|
||||
*/
|
||||
characterLanguage: {
|
||||
languages: "DS4.CharacterLanguageLanguages",
|
||||
alphabets: "DS4.CharacterLanguageAlphabets",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the profile info of a character
|
||||
*/
|
||||
characterProfile: {
|
||||
biography: "DS4.CharacterProfileBiography",
|
||||
gender: "DS4.CharacterProfileGender",
|
||||
birthday: "DS4.CharacterProfileBirthday",
|
||||
birthplace: "DS4.CharacterProfileBirthplace",
|
||||
age: "DS4.CharacterProfileAge",
|
||||
height: "DS4.CharacterProfileHeight",
|
||||
hairColor: "DS4.CharacterProfileHairColor",
|
||||
weight: "DS4.CharacterProfileWeight",
|
||||
eyeColor: "DS4.CharacterProfileEyeColor",
|
||||
specialCharacteristics: "DS4.CharacterProfileSpecialCharacteristics",
|
||||
},
|
||||
/**
|
||||
* Define currency elements of a character
|
||||
*/
|
||||
characterCurrency: {
|
||||
gold: "DS4.CharacterCurrencyGold",
|
||||
silver: "DS4.CharacterCurrencySilver",
|
||||
copper: "DS4.CharacterCurrencyCopper",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the different creature types a creature can be
|
||||
*/
|
||||
creatureTypes: {
|
||||
animal: "DS4.CreatureTypeAnimal",
|
||||
construct: "DS4.CreatureTypeConstruct",
|
||||
humanoid: "DS4.CreatureTypeHumanoid",
|
||||
magicalEntity: "DS4.CreatureTypeMagicalEntity",
|
||||
plantBeing: "DS4.CreatureTypePlantBeing",
|
||||
undead: "DS4.CreatureTypeUndead",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the different size categories creatures fall into
|
||||
*/
|
||||
creatureSizeCategories: {
|
||||
tiny: "DS4.CreatureSizeCategoryTiny",
|
||||
small: "DS4.CreatureSizeCategorySmall",
|
||||
normal: "DS4.CreatureSizeCategoryNormal",
|
||||
large: "DS4.CreatureSizeCategoryLarge",
|
||||
huge: "DS4.CreatureSizeCategoryHuge",
|
||||
colossal: "DS4.CreatureSizeCategoryColossal",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the base info of a creature
|
||||
*/
|
||||
creatureBaseInfo: {
|
||||
loot: "DS4.CreatureBaseInfoLoot",
|
||||
foeFactor: "DS4.CreatureBaseInfoFoeFactor",
|
||||
creatureType: "DS4.CreatureBaseInfoCreatureType",
|
||||
sizeCategory: "DS4.CreatureBaseInfoSizeCategory",
|
||||
experiencePoints: "DS4.CreatureBaseInfoExperiencePoints",
|
||||
description: "DS4.CreatureBaseInfoDescription",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define translations for available distance units
|
||||
*/
|
||||
distanceUnits: {
|
||||
meter: "DS4.UnitMeters",
|
||||
kilometer: "DS4.UnitKilometers",
|
||||
custom: "DS4.UnitCustom",
|
||||
},
|
||||
/**
|
||||
* Define abbreviations for available distance units
|
||||
*/
|
||||
distanceUnitsAbbr: {
|
||||
meter: "DS4.UnitMetersAbbr",
|
||||
kilometer: "DS4.UnitKilometersAbbr",
|
||||
custom: "DS4.UnitCustomAbbr",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define translations for available distance units
|
||||
*/
|
||||
temporalUnits: {
|
||||
rounds: "DS4.UnitRounds",
|
||||
minutes: "DS4.UnitMinutes",
|
||||
hours: "DS4.UnitHours",
|
||||
days: "DS4.UnitDays",
|
||||
custom: "DS4.UnitCustom",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define abbreviations for available units
|
||||
*/
|
||||
temporalUnitsAbbr: {
|
||||
rounds: "DS4.UnitRoundsAbbr",
|
||||
minutes: "DS4.UnitMinutesAbbr",
|
||||
hours: "DS4.UnitHoursAbbr",
|
||||
days: "DS4.UnitDaysAbbr",
|
||||
custom: "DS4.UnitCustomAbbr",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define localization strings for Chat Visibility
|
||||
*/
|
||||
chatVisibilities: {
|
||||
roll: "DS4.ChatVisibilityRoll",
|
||||
gmroll: "DS4.ChatVisibilityGmRoll",
|
||||
blindroll: "DS4.ChatVisibilityBlindRoll",
|
||||
selfroll: "DS4.ChatVisibilitySelfRoll",
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the file paths to icon images
|
||||
* A dictionary of dictionaries mapping keys to icon file paths.
|
||||
*/
|
||||
attackTypesIcons: {
|
||||
melee: "systems/ds4/assets/icons/official/combat-values/melee-attack.png",
|
||||
meleeRanged: "systems/ds4/assets/icons/official/combat-values/melee-ranged-attack.png",
|
||||
ranged: "systems/ds4/assets/icons/official/combat-values/ranged-attack.png",
|
||||
},
|
||||
icons: {
|
||||
/**
|
||||
* Define the file paths to icon images
|
||||
*/
|
||||
attackTypes: {
|
||||
melee: "systems/ds4/assets/icons/official/combat-values/melee-attack.png",
|
||||
meleeRanged: "systems/ds4/assets/icons/official/combat-values/melee-ranged-attack.png",
|
||||
ranged: "systems/ds4/assets/icons/official/combat-values/ranged-attack.png",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the file paths to icon images
|
||||
*/
|
||||
spellTypesIcons: {
|
||||
spellcasting: "systems/ds4/assets/icons/official/combat-values/spellcasting.png",
|
||||
targetedSpellcasting: "systems/ds4/assets/icons/official/combat-values/targeted-spellcasting.png",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of item availabilties
|
||||
*/
|
||||
itemAvailabilities: {
|
||||
unset: "DS4.ItemAvailabilityUnset",
|
||||
hamlet: "DS4.ItemAvailabilityHamlet",
|
||||
village: "DS4.ItemAvailabilityVilage",
|
||||
city: "DS4.ItemAvailabilityCity",
|
||||
elves: "DS4.ItemAvailabilityElves",
|
||||
dwarves: "DS4.ItemAvailabilityDwarves",
|
||||
nowhere: "DS4.ItemAvailabilityNowhere",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of item types
|
||||
*/
|
||||
itemTypes: {
|
||||
weapon: "DS4.ItemTypeWeapon",
|
||||
armor: "DS4.ItemTypeArmor",
|
||||
shield: "DS4.ItemTypeShield",
|
||||
spell: "DS4.ItemTypeSpell",
|
||||
trinket: "DS4.ItemTypeTrinket",
|
||||
equipment: "DS4.ItemTypeEquipment",
|
||||
talent: "DS4.ItemTypeTalent",
|
||||
racialAbility: "DS4.ItemTypeRacialAbility",
|
||||
language: "DS4.ItemTypeLanguage",
|
||||
alphabet: "DS4.ItemTypeAlphabet",
|
||||
specialCreatureAbility: "DS4.ItemTypeSpecialCreatureAbility",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of armor types, a character may only wear one item of each at any given time
|
||||
*/
|
||||
armorTypes: {
|
||||
body: "DS4.ArmorTypeBody",
|
||||
helmet: "DS4.ArmorTypeHelmet",
|
||||
vambrace: "DS4.ArmorTypeVambrace",
|
||||
greaves: "DS4.ArmorTypeGreaves",
|
||||
vambraceGreaves: "DS4.ArmorTypeVambraceGreaves",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define abbreviations for the armor types
|
||||
*/
|
||||
armorTypesAbbr: {
|
||||
body: "DS4.ArmorTypeBodyAbbr",
|
||||
helmet: "DS4.ArmorTypeHelmetAbbr",
|
||||
vambrace: "DS4.ArmorTypeVambraceAbbr",
|
||||
greaves: "DS4.ArmorTypeGreavesAbbr",
|
||||
vambraceGreaves: "DS4.ArmorTypeVambraceGreavesAbbr",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of armor materials, used to determine if a characer may wear the armor without additional penalties
|
||||
*/
|
||||
armorMaterialTypes: {
|
||||
cloth: "DS4.ArmorMaterialTypeCloth",
|
||||
leather: "DS4.ArmorMaterialTypeLeather",
|
||||
chain: "DS4.ArmorMaterialTypeChain",
|
||||
plate: "DS4.ArmorMaterialTypePlate",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the abbreviations of armor materials
|
||||
*/
|
||||
armorMaterialTypesAbbr: {
|
||||
cloth: "DS4.ArmorMaterialTypeClothAbbr",
|
||||
leather: "DS4.ArmorMaterialTypeLeatherAbbr",
|
||||
chain: "DS4.ArmorMaterialTypeChainAbbr",
|
||||
plate: "DS4.ArmorMaterialTypePlateAbbr",
|
||||
},
|
||||
|
||||
spellTypes: {
|
||||
spellcasting: "DS4.SpellTypeSpellcasting",
|
||||
targetedSpellcasting: "DS4.SpellTypeTargetedSpellcasting",
|
||||
},
|
||||
|
||||
spellCategories: {
|
||||
healing: "DS4.SpellCategoryHealing",
|
||||
fire: "DS4.SpellCategoryFire",
|
||||
ice: "DS4.SpellCategoryIce",
|
||||
light: "DS4.SpellCategoryLight",
|
||||
darkness: "DS4.SpellCategoryDarkness",
|
||||
mindAffecting: "DS4.SpellCategoryMindAffecting",
|
||||
electricity: "DS4.SpellCategoryElectricity",
|
||||
none: "DS4.SpellCategoryNone",
|
||||
unset: "DS4.SpellCategoryUnset",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of actor types
|
||||
*/
|
||||
actorTypes: {
|
||||
character: "DS4.ActorTypeCharacter",
|
||||
creature: "DS4.ActorTypeCreature",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of attributes an actor has
|
||||
*/
|
||||
attributes: {
|
||||
body: "DS4.AttributeBody",
|
||||
mobility: "DS4.AttributeMobility",
|
||||
mind: "DS4.AttributeMind",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of traits an actor has
|
||||
*/
|
||||
traits: {
|
||||
strength: "DS4.TraitStrength",
|
||||
agility: "DS4.TraitAgility",
|
||||
intellect: "DS4.TraitIntellect",
|
||||
constitution: "DS4.TraitConstitution",
|
||||
dexterity: "DS4.TraitDexterity",
|
||||
aura: "DS4.TraitAura",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the set of combat values an actor has
|
||||
*/
|
||||
combatValues: {
|
||||
hitPoints: "DS4.CombatValuesHitPoints",
|
||||
defense: "DS4.CombatValuesDefense",
|
||||
initiative: "DS4.CombatValuesInitiative",
|
||||
movement: "DS4.CombatValuesMovement",
|
||||
meleeAttack: "DS4.CombatValuesMeleeAttack",
|
||||
rangedAttack: "DS4.CombatValuesRangedAttack",
|
||||
spellcasting: "DS4.CombatValuesSpellcasting",
|
||||
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the base info of a character
|
||||
*/
|
||||
characterBaseInfo: {
|
||||
race: "DS4.CharacterBaseInfoRace",
|
||||
class: "DS4.CharacterBaseInfoClass",
|
||||
heroClass: "DS4.CharacterBaseInfoHeroClass",
|
||||
culture: "DS4.CharacterBaseInfoCulture",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the progression info of a character
|
||||
*/
|
||||
characterProgression: {
|
||||
level: "DS4.CharacterProgressionLevel",
|
||||
experiencePoints: "DS4.CharacterProgressionExperiencePoints",
|
||||
talentPoints: "DS4.CharacterProgressionTalentPoints",
|
||||
progressPoints: "DS4.CharacterProgressionProgressPoints",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the language info of a character
|
||||
*/
|
||||
characterLanguage: {
|
||||
languages: "DS4.CharacterLanguageLanguages",
|
||||
alphabets: "DS4.CharacterLanguageAlphabets",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the profile info of a character
|
||||
*/
|
||||
characterProfile: {
|
||||
biography: "DS4.CharacterProfileBiography",
|
||||
gender: "DS4.CharacterProfileGender",
|
||||
birthday: "DS4.CharacterProfileBirthday",
|
||||
birthplace: "DS4.CharacterProfileBirthplace",
|
||||
age: "DS4.CharacterProfileAge",
|
||||
height: "DS4.CharacterProfileHeight",
|
||||
hairColor: "DS4.CharacterProfileHairColor",
|
||||
weight: "DS4.CharacterProfileWeight",
|
||||
eyeColor: "DS4.CharacterProfileEyeColor",
|
||||
specialCharacteristics: "DS4.CharacterProfileSpecialCharacteristics",
|
||||
/**
|
||||
* Define the file paths to icon images
|
||||
*/
|
||||
spellTypes: {
|
||||
spellcasting: "systems/ds4/assets/icons/official/combat-values/spellcasting.png",
|
||||
targetedSpellcasting: "systems/ds4/assets/icons/official/combat-values/targeted-spellcasting.png",
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -225,98 +330,4 @@ export const DS4 = {
|
|||
eyeColor: "String",
|
||||
specialCharacteristics: "String",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define currency elements of a character
|
||||
*/
|
||||
characterCurrency: {
|
||||
gold: "DS4.CharacterCurrencyGold",
|
||||
silver: "DS4.CharacterCurrencySilver",
|
||||
copper: "DS4.CharacterCurrencyCopper",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the different creature types a creature can be
|
||||
*/
|
||||
creatureTypes: {
|
||||
animal: "DS4.CreatureTypeAnimal",
|
||||
construct: "DS4.CreatureTypeConstruct",
|
||||
humanoid: "DS4.CreatureTypeHumanoid",
|
||||
magicalEntity: "DS4.CreatureTypeMagicalEntity",
|
||||
plantBeing: "DS4.CreatureTypePlantBeing",
|
||||
undead: "DS4.CreatureTypeUndead",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the different size categories creatures fall into
|
||||
*/
|
||||
creatureSizeCategories: {
|
||||
tiny: "DS4.CreatureSizeCategoryTiny",
|
||||
small: "DS4.CreatureSizeCategorySmall",
|
||||
normal: "DS4.CreatureSizeCategoryNormal",
|
||||
large: "DS4.CreatureSizeCategoryLarge",
|
||||
huge: "DS4.CreatureSizeCategoryHuge",
|
||||
colossal: "DS4.CreatureSizeCategoryColossal",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the base info of a creature
|
||||
*/
|
||||
creatureBaseInfo: {
|
||||
loot: "DS4.CreatureBaseInfoLoot",
|
||||
foeFactor: "DS4.CreatureBaseInfoFoeFactor",
|
||||
creatureType: "DS4.CreatureBaseInfoCreatureType",
|
||||
sizeCategory: "DS4.CreatureBaseInfoSizeCategory",
|
||||
experiencePoints: "DS4.CreatureBaseInfoExperiencePoints",
|
||||
description: "DS4.CreatureBaseInfoDescription",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define translations for available distance units
|
||||
*/
|
||||
distanceUnits: {
|
||||
meter: "DS4.UnitMeters",
|
||||
kilometer: "DS4.UnitKilometers",
|
||||
custom: "DS4.UnitCustom",
|
||||
},
|
||||
/**
|
||||
* Define abbreviations for available distance units
|
||||
*/
|
||||
distanceUnitsAbbr: {
|
||||
meter: "DS4.UnitMetersAbbr",
|
||||
kilometer: "DS4.UnitKilometersAbbr",
|
||||
custom: "DS4.UnitCustomAbbr",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define translations for available distance units
|
||||
*/
|
||||
temporalUnits: {
|
||||
rounds: "DS4.UnitRounds",
|
||||
minutes: "DS4.UnitMinutes",
|
||||
hours: "DS4.UnitHours",
|
||||
days: "DS4.UnitDays",
|
||||
custom: "DS4.UnitCustom",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define abbreviations for available units
|
||||
*/
|
||||
temporalUnitsAbbr: {
|
||||
rounds: "DS4.UnitRoundsAbbr",
|
||||
minutes: "DS4.UnitMinutesAbbr",
|
||||
hours: "DS4.UnitHoursAbbr",
|
||||
days: "DS4.UnitDaysAbbr",
|
||||
custom: "DS4.UnitCustomAbbr",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define localization strings for Chat Visibility
|
||||
*/
|
||||
chatVisibilities: {
|
||||
roll: "DS4.ChatVisibilityRoll",
|
||||
gmroll: "DS4.ChatVisibilityGmRoll",
|
||||
blindroll: "DS4.ChatVisibilityBlindRoll",
|
||||
selfroll: "DS4.ChatVisibilitySelfRoll",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// Import Modules
|
||||
import { DS4Actor } from "./actor/actor";
|
||||
import { DS4Item } from "./item/item";
|
||||
import { DS4ItemSheet } from "./item/item-sheet";
|
||||
|
@ -10,7 +9,7 @@ import { createCheckRoll } from "./rolls/check-factory";
|
|||
import { registerSystemSettings } from "./settings";
|
||||
import { migration } from "./migrations";
|
||||
|
||||
Hooks.once("init", async function () {
|
||||
Hooks.once("init", async () => {
|
||||
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
|
||||
|
||||
game.ds4 = {
|
||||
|
@ -21,29 +20,19 @@ Hooks.once("init", async function () {
|
|||
migration,
|
||||
};
|
||||
|
||||
// Record configuration
|
||||
CONFIG.DS4 = DS4;
|
||||
|
||||
// Define custom Entity classes
|
||||
CONFIG.Actor.entityClass = DS4Actor as typeof Actor;
|
||||
CONFIG.Item.entityClass = DS4Item as typeof Item;
|
||||
CONFIG.Actor.entityClass = DS4Actor;
|
||||
CONFIG.Item.entityClass = DS4Item;
|
||||
|
||||
// Define localized type labels
|
||||
CONFIG.Actor.typeLabels = DS4.actorTypes;
|
||||
CONFIG.Item.typeLabels = DS4.itemTypes;
|
||||
CONFIG.Actor.typeLabels = DS4.i18n.actorTypes;
|
||||
CONFIG.Item.typeLabels = DS4.i18n.itemTypes;
|
||||
|
||||
// Configure Dice
|
||||
CONFIG.Dice.types = [Die, DS4Check];
|
||||
CONFIG.Dice.terms = {
|
||||
c: Coin,
|
||||
d: Die,
|
||||
s: DS4Check,
|
||||
};
|
||||
CONFIG.Dice.types.push(DS4Check);
|
||||
CONFIG.Dice.terms.s = DS4Check;
|
||||
|
||||
// Register system settings
|
||||
registerSystemSettings();
|
||||
|
||||
// Register sheet application classes
|
||||
Actors.unregisterSheet("core", ActorSheet);
|
||||
Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true });
|
||||
Actors.registerSheet("ds4", DS4CreatureActorSheet, { types: ["creature"], makeDefault: true });
|
||||
|
@ -69,67 +58,59 @@ async function registerHandlebarsPartials() {
|
|||
"systems/ds4/templates/actor/partials/combat-values.hbs",
|
||||
"systems/ds4/templates/actor/partials/profile.hbs",
|
||||
"systems/ds4/templates/actor/partials/character-progression.hbs",
|
||||
"systems/ds4/templates/actor/partials/special-creature-abilites-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/special-creature-abilities-overview.hbs",
|
||||
"systems/ds4/templates/actor/partials/character-inventory.hbs",
|
||||
"systems/ds4/templates/actor/partials/creature-inventory.hbs",
|
||||
];
|
||||
return loadTemplates(templatePaths);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Foundry VTT Setup */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* This function runs after game data has been requested and loaded from the servers, so entities exist
|
||||
*/
|
||||
Hooks.once("setup", function () {
|
||||
// Localize CONFIG objects once up-front
|
||||
const toLocalize = [
|
||||
"attackTypes",
|
||||
"itemAvailabilities",
|
||||
"itemTypes",
|
||||
"armorTypes",
|
||||
"armorTypesAbbr",
|
||||
"armorMaterialTypes",
|
||||
"armorMaterialTypesAbbr",
|
||||
"armorMaterialTypes",
|
||||
"spellTypes",
|
||||
"spellCategories",
|
||||
"attributes",
|
||||
"traits",
|
||||
"combatValues",
|
||||
"characterBaseInfo",
|
||||
"characterProgression",
|
||||
"characterLanguage",
|
||||
"characterProfile",
|
||||
"characterCurrency",
|
||||
"creatureTypes",
|
||||
"creatureSizeCategories",
|
||||
"creatureBaseInfo",
|
||||
"temporalUnits",
|
||||
"temporalUnitsAbbr",
|
||||
"distanceUnits",
|
||||
"distanceUnitsAbbr",
|
||||
"chatVisibilities",
|
||||
];
|
||||
|
||||
// Exclude some from sorting where the default order matters
|
||||
const noSort = ["attributes", "traits", "combatValues", "creatureSizeCategories"];
|
||||
|
||||
// Localize and sort CONFIG objects
|
||||
for (const o of toLocalize) {
|
||||
const localized = Object.entries(CONFIG.DS4[o]).map((e) => {
|
||||
return [e[0], game.i18n.localize(e[1] as string)];
|
||||
});
|
||||
if (!noSort.includes(o)) localized.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
CONFIG.DS4[o] = localized.reduce((obj, e) => {
|
||||
obj[e[0]] = e[1];
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
Hooks.once("setup", () => {
|
||||
localizeAndSortConfigObjects();
|
||||
});
|
||||
|
||||
Hooks.once("ready", function () {
|
||||
Hooks.once("ready", () => {
|
||||
migration.migrate();
|
||||
});
|
||||
|
||||
/**
|
||||
* Select the text of input elements in given sheets via onfocus listener.
|
||||
* The hook names are of the form "render"+sheet_superclassname and are called within
|
||||
* the render() method of the foundry Application class.
|
||||
* Note: The render hooks of all classes in the class hierarchy are called,
|
||||
* so e.g. for a Dialog, both "renderDialog" and "renderApplication" are called
|
||||
* (in this order).
|
||||
*/
|
||||
["renderApplication", "renderActorSheet", "renderItemSheet"].forEach((hookName: string) => {
|
||||
Hooks.on(hookName, (app: Dialog, html: JQueryStatic) => {
|
||||
$(html)
|
||||
.find("input")
|
||||
.on("focus", (ev: JQuery.FocusEvent<HTMLInputElement>) => {
|
||||
ev.currentTarget.select();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Localizes all objects in {@link DS4.i18n} and sorts them unless they are explicitly excluded.
|
||||
*/
|
||||
function localizeAndSortConfigObjects() {
|
||||
const noSort = ["attributes", "traits", "combatValues", "creatureSizeCategories"];
|
||||
|
||||
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)];
|
||||
});
|
||||
if (sort) localized.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
return Object.fromEntries(localized);
|
||||
};
|
||||
|
||||
DS4.i18n = Object.fromEntries(
|
||||
Object.entries(DS4.i18n).map(([key, value]) => {
|
||||
return [key, localizeObject(value, !noSort.includes(key))];
|
||||
}),
|
||||
) as typeof DS4.i18n;
|
||||
}
|
||||
|
|
|
@ -1,37 +1,53 @@
|
|||
import { ModifiableData } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
|
||||
export type ItemType = keyof typeof DS4.itemTypes;
|
||||
export type ItemType = keyof typeof DS4.i18n.itemTypes;
|
||||
|
||||
export type DS4ItemDataType =
|
||||
| DS4Weapon
|
||||
| DS4Armor
|
||||
| DS4Shield
|
||||
| DS4Spell
|
||||
| DS4Trinket
|
||||
| DS4Equipment
|
||||
| DS4Talent
|
||||
| DS4RacialAbility
|
||||
| DS4Language
|
||||
| DS4Alphabet
|
||||
| DS4SpecialCreatureAbility;
|
||||
export type DS4ItemData =
|
||||
| DS4WeaponData
|
||||
| DS4ArmorData
|
||||
| DS4ShieldData
|
||||
| DS4SpellData
|
||||
| DS4TrinketData
|
||||
| DS4EquipmentData
|
||||
| DS4TalentData
|
||||
| DS4RacialAbilityData
|
||||
| DS4LanguageData
|
||||
| DS4AlphabetData
|
||||
| DS4SpecialCreatureAbilityData;
|
||||
|
||||
export type DS4EquippableItemDataType = DS4Weapon | DS4Armor | DS4Shield | DS4Trinket;
|
||||
interface DS4ItemDataHelper<T, U extends ItemType> extends Item.Data<T> {
|
||||
type: U;
|
||||
}
|
||||
|
||||
// types
|
||||
type DS4WeaponData = DS4ItemDataHelper<DS4WeaponDataData, "weapon">;
|
||||
type DS4ArmorData = DS4ItemDataHelper<DS4ArmorDataData, "armor">;
|
||||
type DS4ShieldData = DS4ItemDataHelper<DS4ShieldDataData, "shield">;
|
||||
type DS4SpellData = DS4ItemDataHelper<DS4SpellDataData, "spell">;
|
||||
type DS4TrinketData = DS4ItemDataHelper<DS4TrinketDataData, "trinket">;
|
||||
type DS4EquipmentData = DS4ItemDataHelper<DS4EquipmentDataData, "equipment">;
|
||||
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">;
|
||||
|
||||
interface DS4Weapon extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {
|
||||
interface DS4WeaponDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {
|
||||
attackType: "melee" | "ranged" | "meleeRanged";
|
||||
weaponBonus: number;
|
||||
opponentDefense: number;
|
||||
}
|
||||
|
||||
export interface DS4Armor extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {
|
||||
interface DS4ArmorDataData
|
||||
extends DS4ItemDataDataBase,
|
||||
DS4ItemDataDataPhysical,
|
||||
DS4ItemDataDataEquipable,
|
||||
DS4ItemDataDataProtective {
|
||||
armorMaterialType: "cloth" | "leather" | "chain" | "plate";
|
||||
armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves";
|
||||
}
|
||||
|
||||
export interface DS4Talent extends DS4ItemBase {
|
||||
interface DS4TalentDataData extends DS4ItemDataDataBase {
|
||||
rank: DS4TalentRank;
|
||||
}
|
||||
|
||||
|
@ -39,7 +55,7 @@ interface DS4TalentRank extends ModifiableData<number> {
|
|||
max: number;
|
||||
}
|
||||
|
||||
interface DS4Spell extends DS4ItemBase, DS4ItemEquipable {
|
||||
interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable {
|
||||
spellType: "spellcasting" | "targetedSpellcasting";
|
||||
bonus: string;
|
||||
spellCategory:
|
||||
|
@ -59,37 +75,41 @@ interface DS4Spell extends DS4ItemBase, DS4ItemEquipable {
|
|||
scrollPrice: number;
|
||||
}
|
||||
|
||||
export interface DS4Shield extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {}
|
||||
interface DS4Trinket extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {}
|
||||
interface DS4Equipment extends DS4ItemBase, DS4ItemPhysical {}
|
||||
type DS4RacialAbility = DS4ItemBase;
|
||||
type DS4Language = DS4ItemBase;
|
||||
type DS4Alphabet = DS4ItemBase;
|
||||
interface DS4SpecialCreatureAbility extends DS4ItemBase {
|
||||
interface DS4ShieldDataData
|
||||
extends DS4ItemDataDataBase,
|
||||
DS4ItemDataDataPhysical,
|
||||
DS4ItemDataDataEquipable,
|
||||
DS4ItemDataDataProtective {}
|
||||
interface DS4TrinketDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {}
|
||||
interface DS4EquipmentDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical {}
|
||||
type DS4RacialAbilityDataData = DS4ItemDataDataBase;
|
||||
type DS4LanguageDataData = DS4ItemDataDataBase;
|
||||
type DS4AlphabetDataData = DS4ItemDataDataBase;
|
||||
interface DS4SpecialCreatureAbilityDataData extends DS4ItemDataDataBase {
|
||||
experiencePoints: number;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemBase {
|
||||
interface DS4ItemDataDataBase {
|
||||
description: string;
|
||||
}
|
||||
interface DS4ItemPhysical {
|
||||
interface DS4ItemDataDataPhysical {
|
||||
quantity: number;
|
||||
price: number;
|
||||
availability: "hamlet" | "village" | "city" | "elves" | "dwarves" | "nowhere" | "unset";
|
||||
storageLocation: string;
|
||||
}
|
||||
|
||||
export function isDS4ItemDataTypePhysical(input: DS4ItemDataType): boolean {
|
||||
export function isDS4ItemDataTypePhysical(input: DS4ItemData["data"]): boolean {
|
||||
return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input;
|
||||
}
|
||||
|
||||
interface DS4ItemEquipable {
|
||||
interface DS4ItemDataDataEquipable {
|
||||
equipped: boolean;
|
||||
}
|
||||
|
||||
interface DS4ItemProtective {
|
||||
interface DS4ItemDataDataProtective {
|
||||
armorValue: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,36 @@
|
|||
import { DS4 } from "../config";
|
||||
import { DS4Item } from "./item";
|
||||
import { DS4ItemDataType, isDS4ItemDataTypePhysical } from "./item-data";
|
||||
import { isDS4ItemDataTypePhysical } from "./item-data";
|
||||
|
||||
/**
|
||||
* Extend the basic ItemSheet with some very simple modifications
|
||||
* @extends {ItemSheet}
|
||||
* The Sheet class for DS4 Items
|
||||
*/
|
||||
export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
||||
export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
||||
/** @override */
|
||||
static get defaultOptions(): FormApplicationOptions {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
width: 530,
|
||||
height: 400,
|
||||
classes: ["ds4", "sheet", "item"],
|
||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
|
||||
scrollY: [".sheet-body"],
|
||||
template: superDefaultOptions.template,
|
||||
viewPermission: superDefaultOptions.viewPermission,
|
||||
closeOnSubmit: superDefaultOptions.closeOnSubmit,
|
||||
submitOnChange: superDefaultOptions.submitOnChange,
|
||||
submitOnClose: superDefaultOptions.submitOnClose,
|
||||
editable: superDefaultOptions.editable,
|
||||
baseApplication: superDefaultOptions.baseApplication,
|
||||
top: superDefaultOptions.top,
|
||||
left: superDefaultOptions.left,
|
||||
popOut: superDefaultOptions.popOut,
|
||||
minimizable: superDefaultOptions.minimizable,
|
||||
resizable: superDefaultOptions.resizable,
|
||||
id: superDefaultOptions.id,
|
||||
dragDrop: superDefaultOptions.dragDrop,
|
||||
filters: superDefaultOptions.filters,
|
||||
title: superDefaultOptions.title,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -23,13 +40,11 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
|||
return `${path}/${this.item.data.type}-sheet.hbs`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(): ItemSheetData<DS4ItemDataType, DS4Item> {
|
||||
async getData(): Promise<ItemSheet.Data<DS4Item>> {
|
||||
const data = {
|
||||
...super.getData(),
|
||||
config: CONFIG.DS4,
|
||||
...(await super.getData()),
|
||||
config: DS4,
|
||||
isOwned: this.item.isOwned,
|
||||
actor: this.item.actor,
|
||||
isPhysical: isDS4ItemDataTypePhysical(this.item.data.data),
|
||||
|
@ -37,10 +52,8 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
|||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
setPosition(options: ApplicationPosition = {}): ApplicationPosition {
|
||||
setPosition(options: Partial<Application.Position> = {}): Application.Position {
|
||||
const position = super.setPosition(options);
|
||||
if ("find" in this.element) {
|
||||
const sheetBody = this.element.find(".sheet-body");
|
||||
|
@ -52,8 +65,6 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
|||
return position;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html: JQuery): void {
|
||||
super.activateListeners(html);
|
||||
|
@ -65,13 +76,13 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
|||
|
||||
/**
|
||||
* Handle management of ActiveEffects.
|
||||
* @param {Event} event The originating click event
|
||||
* @param event - he originating click event
|
||||
*/
|
||||
private async _onManageActiveEffect(event: JQuery.ClickEvent): Promise<unknown> {
|
||||
protected async _onManageActiveEffect(event: JQuery.ClickEvent): Promise<unknown> {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.item.isOwned) {
|
||||
return ui.notifications.warn(game.i18n.localize("DS4.WarningManageActiveEffectOnOwnedItem"));
|
||||
return ui.notifications?.warn(game.i18n.localize("DS4.WarningManageActiveEffectOnOwnedItem"));
|
||||
}
|
||||
const a = event.currentTarget;
|
||||
const li = $(a).parents(".effect");
|
||||
|
@ -81,7 +92,7 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
|||
return this._createActiveEffect();
|
||||
case "edit":
|
||||
const effect = this.item.effects.get(li.data("effectId"));
|
||||
return effect.sheet.render(true);
|
||||
return effect?.sheet.render(true);
|
||||
case "delete": {
|
||||
return this.item.deleteEmbeddedEntity("ActiveEffect", li.data("effectId"));
|
||||
}
|
||||
|
@ -91,7 +102,7 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
|||
/**
|
||||
* Create a new ActiveEffect for the item using default data.
|
||||
*/
|
||||
private async _createActiveEffect(): Promise<unknown> {
|
||||
protected async _createActiveEffect(): Promise<ActiveEffect.Data> {
|
||||
const label = `New Effect`;
|
||||
|
||||
const createData = {
|
||||
|
@ -101,7 +112,7 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
|
|||
transfer: true,
|
||||
};
|
||||
|
||||
const effect = await ActiveEffect.create(createData, this.item);
|
||||
const effect = ActiveEffect.create(createData, this.item);
|
||||
return effect.create({});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,20 @@
|
|||
import { DS4Actor } from "../actor/actor";
|
||||
import { DS4ActorDataType } from "../actor/actor-data";
|
||||
import { DS4ItemDataType, DS4Talent } from "./item-data";
|
||||
import { DS4ItemData } from "./item-data";
|
||||
|
||||
/**
|
||||
* Extend the basic Item with some very simple modifications.
|
||||
* @extends {Item}
|
||||
* The Item class for DS4
|
||||
*/
|
||||
export class DS4Item extends Item<DS4ItemDataType, DS4ActorDataType, DS4Actor> {
|
||||
export class DS4Item extends Item<DS4ItemData> {
|
||||
/**
|
||||
* Augment the basic Item data model with additional dynamic data.
|
||||
* @override
|
||||
*/
|
||||
prepareData(): void {
|
||||
super.prepareData();
|
||||
this.prepareDerivedData();
|
||||
|
||||
// Get the Item's data
|
||||
// const itemData = this.data;
|
||||
// const actorData = this.actor ? this.actor.data : {};
|
||||
// const data = itemData.data;
|
||||
}
|
||||
|
||||
prepareDerivedData(): void {
|
||||
if (this.type === "talent") {
|
||||
const data = this.data.data as DS4Talent;
|
||||
if (this.data.type === "talent") {
|
||||
const data = this.data.data;
|
||||
data.rank.total = data.rank.base + data.rank.mod;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { migrate as migrate001 } from "./migrations/001";
|
||||
|
||||
async function migrate(): Promise<void> {
|
||||
if (!game.user.isGM) {
|
||||
if (!game.user?.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -18,14 +18,14 @@ async function migrate(): Promise<void> {
|
|||
}
|
||||
|
||||
async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> {
|
||||
if (!game.user.isGM) {
|
||||
if (!game.user?.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
const migrationsToExecute = migrations.slice(oldMigrationVersion, targetMigrationVersion);
|
||||
|
||||
if (migrationsToExecute.length > 0) {
|
||||
ui.notifications.info(
|
||||
ui.notifications?.info(
|
||||
game.i18n.format("DS4.InfoSystemUpdateStart", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
|
@ -40,7 +40,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
await migration();
|
||||
game.settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
|
||||
} catch (err) {
|
||||
ui.notifications.error(
|
||||
ui.notifications?.error(
|
||||
game.i18n.format("DS4.ErrorDuringMigration", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
|
@ -54,7 +54,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
}
|
||||
}
|
||||
|
||||
ui.notifications.info(
|
||||
ui.notifications?.info(
|
||||
game.i18n.format("DS4.InfoSystemUpdateCompleted", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export async function migrate(): Promise<void> {
|
||||
for (const a of game.actors.entities) {
|
||||
for (const a of game.actors?.entities ?? []) {
|
||||
const updateData = getActorUpdateData();
|
||||
console.log(`Migrating actor ${a.name}`);
|
||||
await a.update(updateData, { enforceTypes: false });
|
||||
|
@ -18,7 +18,7 @@ function getActorUpdateData(): Record<string, unknown> {
|
|||
"rangedAttack",
|
||||
"spellcasting",
|
||||
"targetedSpellcasting",
|
||||
].reduce((acc, curr) => {
|
||||
].reduce((acc: Partial<Record<string, { "-=base": null }>>, curr) => {
|
||||
acc[curr] = { "-=base": null };
|
||||
return acc;
|
||||
}, {}),
|
||||
|
|
|
@ -4,13 +4,13 @@ import { DS4 } from "../config";
|
|||
* Provides default values for all arguments the `CheckFactory` expects.
|
||||
*/
|
||||
class DefaultCheckOptions implements DS4CheckFactoryOptions {
|
||||
maxCritSuccess = 1;
|
||||
minCritFailure = 20;
|
||||
useSlayingDice = false;
|
||||
rollMode: DS4RollMode = "roll";
|
||||
readonly maxCritSuccess = 1;
|
||||
readonly minCritFailure = 20;
|
||||
readonly useSlayingDice = false;
|
||||
readonly rollMode: DS4RollMode = "roll";
|
||||
|
||||
mergeWith(other: Partial<DS4CheckFactoryOptions>): DS4CheckFactoryOptions {
|
||||
return { ...this, ...other } as DS4CheckFactoryOptions;
|
||||
return { ...this, ...other };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,13 @@ class CheckFactory {
|
|||
private gmModifier: number,
|
||||
passedOptions: Partial<DS4CheckFactoryOptions> = {},
|
||||
) {
|
||||
this.checkOptions = new DefaultCheckOptions().mergeWith(passedOptions);
|
||||
this.checkOptions = defaultCheckOptions.mergeWith(passedOptions);
|
||||
}
|
||||
|
||||
private checkOptions: DS4CheckFactoryOptions;
|
||||
|
||||
async execute(): Promise<ChatMessage | unknown> {
|
||||
const rollCls: typeof Roll = CONFIG.Dice.rolls[0];
|
||||
const rollCls = CONFIG.Dice.rolls[0];
|
||||
|
||||
const formula = [
|
||||
"ds",
|
||||
|
@ -45,7 +45,6 @@ class CheckFactory {
|
|||
const roll = new rollCls(formula);
|
||||
|
||||
const rollModeTemplate = this.checkOptions.rollMode;
|
||||
console.log(rollModeTemplate);
|
||||
return roll.toMessage({}, { rollMode: rollModeTemplate, create: true });
|
||||
}
|
||||
|
||||
|
@ -76,8 +75,8 @@ class CheckFactory {
|
|||
|
||||
/**
|
||||
* Asks the user for all unknown/necessary information and passes them on to perform a roll.
|
||||
* @param targetValue {number} The Check Target Number ("CTN")
|
||||
* @param options {Partial<DS4CheckFactoryOptions>} Options changing the behaviour of the roll and message.
|
||||
* @param targetValue - The Check Target Number ("CTN")
|
||||
* @param options - Options changing the behavior of the roll and message.
|
||||
*/
|
||||
export async function createCheckRoll(
|
||||
targetValue: number,
|
||||
|
@ -86,6 +85,8 @@ export async function createCheckRoll(
|
|||
// Ask for additional required data;
|
||||
const gmModifierData = await askGmModifier(targetValue, options);
|
||||
|
||||
const newTargetValue = gmModifierData.checkTargetValue ?? targetValue;
|
||||
const gmModifier = gmModifierData.gmModifier ?? 0;
|
||||
const newOptions: Partial<DS4CheckFactoryOptions> = {
|
||||
maxCritSuccess: gmModifierData.maxCritSuccess ?? options.maxCritSuccess ?? undefined,
|
||||
minCritFailure: gmModifierData.minCritFailure ?? options.minCritFailure ?? undefined,
|
||||
|
@ -94,7 +95,7 @@ export async function createCheckRoll(
|
|||
};
|
||||
|
||||
// Create Factory
|
||||
const cf = new CheckFactory(gmModifierData.checkTargetValue, gmModifierData.gmModifier, newOptions);
|
||||
const cf = new CheckFactory(newTargetValue, gmModifier, newOptions);
|
||||
|
||||
// Possibly additional processing
|
||||
|
||||
|
@ -108,13 +109,13 @@ export async function createCheckRoll(
|
|||
* @notes
|
||||
* At the moment, this asks for more data than it will do after some iterations.
|
||||
*
|
||||
* @returns {Promise<IntermediateGmModifierData>} The data given by the user.
|
||||
* @returns The data given by the user.
|
||||
*/
|
||||
async function askGmModifier(
|
||||
targetValue: number,
|
||||
options: Partial<DS4CheckFactoryOptions> = {},
|
||||
{ template, title }: { template?: string; title?: string } = {},
|
||||
): Promise<IntermediateGmModifierData> {
|
||||
): Promise<Partial<IntermediateGmModifierData>> {
|
||||
// Render model interface and return value
|
||||
const usedTemplate = template ?? "systems/ds4/templates/roll/roll-options.hbs";
|
||||
const usedTitle = title ?? game.i18n.localize("DS4.RollDialogDefaultTitle");
|
||||
|
@ -124,7 +125,7 @@ async function askGmModifier(
|
|||
checkTargetValue: targetValue,
|
||||
maxCritSuccess: options.maxCritSuccess ?? defaultCheckOptions.maxCritSuccess,
|
||||
minCritFailure: options.minCritFailure ?? defaultCheckOptions.minCritFailure,
|
||||
rollModes: rollModes,
|
||||
rollMode: options.rollMode,
|
||||
config: DS4,
|
||||
};
|
||||
const renderedHtml = await renderTemplate(usedTemplate, templateData);
|
||||
|
@ -133,14 +134,12 @@ async function askGmModifier(
|
|||
new Dialog(
|
||||
{
|
||||
title: usedTitle,
|
||||
close: () => {
|
||||
// Don't do anything
|
||||
},
|
||||
content: renderedHtml,
|
||||
buttons: {
|
||||
ok: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize("DS4.RollDialogOkButton"),
|
||||
callback: (html: HTMLElement | JQuery) => {
|
||||
callback: (html) => {
|
||||
if (!("jquery" in html)) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedHtmlType", {
|
||||
|
@ -150,39 +149,40 @@ async function askGmModifier(
|
|||
);
|
||||
} else {
|
||||
const innerForm = html[0].querySelector("form");
|
||||
if (!innerForm) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
|
||||
);
|
||||
}
|
||||
resolve(innerForm);
|
||||
}
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("DS4.RollDialogCancelButton"),
|
||||
callback: () => {
|
||||
// Don't do anything
|
||||
},
|
||||
},
|
||||
},
|
||||
default: "ok",
|
||||
},
|
||||
{},
|
||||
{ jQuery: true },
|
||||
).render(true);
|
||||
});
|
||||
const dialogForm = await dialogPromise;
|
||||
return parseDialogFormData(dialogForm, targetValue);
|
||||
return parseDialogFormData(dialogForm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts Dialog data from the returned DOM element.
|
||||
* @param formData {HTMLFormElement} The filed dialog
|
||||
* @param targetValue {number} The previously known target value (slated for removal once data automation is available)
|
||||
* @param formData - The filed dialog
|
||||
*/
|
||||
function parseDialogFormData(formData: HTMLFormElement, targetValue: number): IntermediateGmModifierData {
|
||||
function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmModifierData> {
|
||||
return {
|
||||
checkTargetValue: parseInt(formData["ctv"]?.value) ?? targetValue,
|
||||
gmModifier: parseInt(formData["gmmod"]?.value) ?? 0,
|
||||
maxCritSuccess: parseInt(formData["maxcoup"]?.value) ?? defaultCheckOptions.maxCritSuccess,
|
||||
minCritFailure: parseInt(formData["minfumble"]?.value) ?? defaultCheckOptions.minCritFailure,
|
||||
useSlayingDice: false,
|
||||
rollMode: formData["visibility"]?.value ?? defaultCheckOptions.rollMode,
|
||||
checkTargetValue: parseInt(formData["ctv"]?.value),
|
||||
gmModifier: parseInt(formData["gmmod"]?.value),
|
||||
maxCritSuccess: parseInt(formData["maxcoup"]?.value),
|
||||
minCritFailure: parseInt(formData["minfumble"]?.value),
|
||||
rollMode: formData["visibility"]?.value,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -56,8 +56,8 @@ export class DS4Check extends DiceTerm {
|
|||
}
|
||||
}
|
||||
|
||||
success = null;
|
||||
failure = null;
|
||||
success: boolean | null = null;
|
||||
failure: boolean | null = null;
|
||||
targetValue = DS4Check.DEFAULT_TARGET_VALUE;
|
||||
minCritFailure = DS4Check.DEFAULT_MIN_CRIT_FAILURE;
|
||||
maxCritSuccess = DS4Check.DEFAULT_MAX_CRIT_SUCCESS;
|
||||
|
@ -93,16 +93,11 @@ export class DS4Check extends DiceTerm {
|
|||
}
|
||||
}
|
||||
|
||||
/** Term Modifiers */
|
||||
noop(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
// DS4 only allows recursive explosions
|
||||
explode(modifier: string): this {
|
||||
explode(modifier: string): void {
|
||||
const rgx = /[xX]/;
|
||||
const match = modifier.match(rgx);
|
||||
if (!match) return this;
|
||||
if (!match) return;
|
||||
|
||||
this.results = (this.results as Array<RollResult>)
|
||||
.map((r) => {
|
||||
|
@ -110,7 +105,7 @@ export class DS4Check extends DiceTerm {
|
|||
|
||||
let checked = 0;
|
||||
while (checked < intermediateResults.length) {
|
||||
const r = (intermediateResults as Array<RollResult>)[checked];
|
||||
const r = intermediateResults[checked];
|
||||
checked++;
|
||||
if (!r.active) continue;
|
||||
|
||||
|
@ -135,7 +130,7 @@ export class DS4Check extends DiceTerm {
|
|||
static DENOMINATION = "s";
|
||||
static MODIFIERS = {
|
||||
x: "explode",
|
||||
c: "noop", // Modifier is consumed in constructor for target value
|
||||
v: "noop", // Modifier is consumed in constructor for target value
|
||||
c: (): void => undefined, // Modifier is consumed in constructor for crit
|
||||
v: (): void => undefined, // Modifier is consumed in constructor for target value
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export class DefaultRollOptions implements RollOptions {
|
|||
public slayingDiceRepetition = false;
|
||||
|
||||
mergeWith(other: Partial<RollOptions>): RollOptions {
|
||||
return { ...this, ...other } as RollOptions;
|
||||
return { ...this, ...other };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@ import { calculateRollResult, isDiceSwapNecessary, isSlayingDiceRepetition, sepa
|
|||
|
||||
/**
|
||||
* Performs a roll against a check target number, e.g. for usage in battle, but not for herbs.
|
||||
* @param {number} checkTargetValue the final CTN, including all static modifiers.
|
||||
* @param {Partial<RollOptions>} rollOptions optional, final option override that affect the checks outcome, e.g. different values for crits or whether slaying dice are used.
|
||||
* @param {Array<number>} dice optional, pass already thrown dice that are used instead of rolling new ones.
|
||||
* @param checkTargetValue - the final CTN, including all static modifiers.
|
||||
* @param rollOptions - optional, final option override that affect the checks outcome, e.g. different values for crits or whether slaying dice are used.
|
||||
* @param dice - optional, pass already thrown dice that are used instead of rolling new ones.
|
||||
*/
|
||||
export function ds4roll(
|
||||
checkTargetValue: number,
|
||||
rollOptions: Partial<RollOptions> = {},
|
||||
dice: Array<number> = null,
|
||||
dice: Array<number> = [],
|
||||
): RollResult {
|
||||
if (checkTargetValue <= 20) {
|
||||
return rollCheckSingleDie(checkTargetValue, rollOptions, dice);
|
||||
|
@ -27,20 +27,20 @@ export function ds4roll(
|
|||
* This is not intended for direct usage. Use
|
||||
* {@link ds4roll | the function that is not bound to an amount of Dice} instead.
|
||||
*
|
||||
* @param {number} checkTargetValue - The target value to check against.
|
||||
* @param {RollOptions} rollOptions - Options that affect the checks outcome, e.g. different values for crits or whether slaying dice are used.
|
||||
* @param {Array<number>} dice optional, pass already thrown dice that are used instead of rolling new ones.
|
||||
* @param checkTargetValue - The target value to check against.
|
||||
* @param rollOptions - Options that affect the checks outcome, e.g. different values for crits or whether slaying dice are used.
|
||||
* @param dice - optional, pass already thrown dice that are used instead of rolling new ones.
|
||||
*
|
||||
* @returns {RollResult} An object containing detailed information on the roll result.
|
||||
* @returns An object containing detailed information on the roll result.
|
||||
*/
|
||||
export function rollCheckSingleDie(
|
||||
checkTargetValue: number,
|
||||
rollOptions: Partial<RollOptions>,
|
||||
dice: Array<number> = null,
|
||||
dice: Array<number> = [],
|
||||
): RollResult {
|
||||
const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
|
||||
|
||||
if (dice?.length != 1) {
|
||||
if (dice.length != 1) {
|
||||
dice = [new DS4RollProvider().getNextRoll()];
|
||||
}
|
||||
const usedDice = dice;
|
||||
|
@ -66,22 +66,22 @@ export function rollCheckSingleDie(
|
|||
* This is not intended for direct usage. Use
|
||||
* {@link ds4roll | the function that is not bound to an amount of Dice} instead.
|
||||
*
|
||||
* @param {number} targetValue- - The target value to check against.
|
||||
* @param {RollOptions} rollOptions - Options that affect the checks outcome, e.g. different values for crits or whether slaying dice are used.
|
||||
* @param {Array<number>} dice - Optional array of dice values to consider instead of rolling new ones.
|
||||
* @param targetValue - The target value to check against.
|
||||
* @param rollOptions - Options that affect the checks outcome, e.g. different values for crits or whether slaying dice are used.
|
||||
* @param dice - Optional array of dice values to consider instead of rolling new ones.
|
||||
*
|
||||
* @returns {RollResult} An object containing detailed information on the roll result.
|
||||
* @returns An object containing detailed information on the roll result.
|
||||
*/
|
||||
export function rollCheckMultipleDice(
|
||||
targetValue: number,
|
||||
rollOptions: Partial<RollOptions>,
|
||||
dice: Array<number> = null,
|
||||
dice: Array<number> = [],
|
||||
): RollResult {
|
||||
const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
|
||||
const remainderTargetValue = targetValue % 20;
|
||||
const numberOfDice = Math.ceil(targetValue / 20);
|
||||
|
||||
if (!dice || dice.length != numberOfDice) {
|
||||
if (dice.length != numberOfDice) {
|
||||
dice = new DS4RollProvider().getNextRolls(numberOfDice);
|
||||
}
|
||||
const usedDice = dice;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { partition, zip } from "../common/utils";
|
||||
import { RollOptions } from "./roll-data";
|
||||
|
||||
/**
|
||||
|
@ -8,9 +9,9 @@ import { RollOptions } from "./roll-data";
|
|||
* @private_remarks
|
||||
* This uses an internal implementation of a `partition` method. Don't let typescript fool you, it will tell you that a partition method is available for Arrays, but that one's imported globally from foundry's declarations and not available during the test stage!
|
||||
*
|
||||
* @param {Array<number>} dice - The dice values.
|
||||
* @param {RollOptions} usedOptions - Options that affect the check's behaviour.
|
||||
* @returns {[Array<number>, Array<number>]} A tuple containing two arrays of dice values, the first one containing all critical hits, the second one containing all others. Both arrays are sorted descendingby value.
|
||||
* @param dice - The dice values.
|
||||
* @param usedOptions - Options that affect the check's behavior.
|
||||
* @returns A tuple containing two arrays of dice values, the first one containing all critical hits, the second one containing all others. Both arrays are sorted descending by value.
|
||||
*/
|
||||
export function separateCriticalHits(dice: Array<number>, usedOptions: RollOptions): CritsAndNonCrits {
|
||||
const [critSuccesses, otherRolls] = partition(dice, (v: number) => {
|
||||
|
@ -25,40 +26,19 @@ export function separateCriticalHits(dice: Array<number>, usedOptions: RollOptio
|
|||
*/
|
||||
type CritsAndNonCrits = [Array<number>, Array<number>];
|
||||
|
||||
/**
|
||||
* Partition an array into two, following a predicate.
|
||||
* @param {Array<T>} input The Array to split.
|
||||
* @param {(T) => boolean} predicate The predicate by which to split.
|
||||
* @returns A tuple of two arrays, the first one containing all elements from `input` that matched the predicate, the second one containing those that don't.
|
||||
*/
|
||||
// TODO: Move to generic utils method?
|
||||
function partition<T>(input: Array<T>, predicate: (v: T) => boolean) {
|
||||
return input.reduce(
|
||||
(p: [Array<T>, Array<T>], cur: T) => {
|
||||
if (predicate(cur)) {
|
||||
p[0].push(cur);
|
||||
} else {
|
||||
p[1].push(cur);
|
||||
}
|
||||
return p;
|
||||
},
|
||||
[[], []],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates if a critical success should be moved to the last position in order to maximize the check's result.
|
||||
*
|
||||
* @example
|
||||
* With regular dice rolling rules and a check target number of 31, the two dice 1 and 19 can get to a check result of 30.
|
||||
* This method would be called as follows:
|
||||
* ```
|
||||
* ```ts
|
||||
* isDiceSwapNecessary([[1], [19]], 11)
|
||||
* ```
|
||||
*
|
||||
* @param {[Array<number>, Array<number>]} critsAndNonCrits the dice values thrown. It is assumed that both critical successes and other rolls are sorted descending.
|
||||
* @param {number} remainingTargetValue the target value for the last dice, that is the only one that can be less than 20.
|
||||
* @returns {boolean} Bool indicating whether a critical success has to be used as the last dice.
|
||||
* @param critsAndNonCrits - The dice values thrown. It is assumed that both critical successes and other rolls are sorted descending.
|
||||
* @param remainingTargetValue - The target value for the last dice, that is the only one that can be less than 20.
|
||||
* @returns Bool indicating whether a critical success has to be used as the last dice.
|
||||
*/
|
||||
export function isDiceSwapNecessary(
|
||||
[critSuccesses, otherRolls]: CritsAndNonCrits,
|
||||
|
@ -81,7 +61,7 @@ export function isDiceSwapNecessary(
|
|||
*
|
||||
* @internal
|
||||
*
|
||||
* @param {RollOptions} opts the roll options to check against
|
||||
* @param opts - The roll options to check against
|
||||
*/
|
||||
export function isSlayingDiceRepetition(opts: RollOptions): boolean {
|
||||
return opts.useSlayingDice && opts.slayingDiceRepetition;
|
||||
|
@ -92,9 +72,9 @@ export function isSlayingDiceRepetition(opts: RollOptions): boolean {
|
|||
*
|
||||
* @internal
|
||||
*
|
||||
* @param assignedRollResults The dice values in the order of usage.
|
||||
* @param remainderTargetValue Target value for the last dice (the only one differing from `20`).
|
||||
* @param rollOptions Config object containing options that change the way dice results are handled.
|
||||
* @param assignedRollResults - The dice values in the order of usage.
|
||||
* @param remainderTargetValue - Target value for the last dice (the only one differing from `20`).
|
||||
* @param rollOptions - Config object containing options that change the way dice results are handled.
|
||||
*
|
||||
* @returns {number} The total check value.
|
||||
*/
|
||||
|
@ -118,22 +98,3 @@ export function calculateRollResult(
|
|||
.map(([v]) => v)
|
||||
.reduce((a, b) => a + b);
|
||||
}
|
||||
|
||||
// TODO: Move to generic utils method?
|
||||
/**
|
||||
* Zips two Arrays to an array of pairs of elements with corresponding indices. Excessive elements are dropped.
|
||||
* @param {Array<T>} a1 First array to zip.
|
||||
* @param {Array<U>} a2 Second array to zip.
|
||||
*
|
||||
* @typeParam T - Type of elements contained in `a1`.
|
||||
* @typeParam U - Type of elements contained in `a2`.
|
||||
*
|
||||
* @returns {Array<[T,U]>} The array of pairs that had the same index in their source array.
|
||||
*/
|
||||
function zip<T, U>(a1: Array<T>, a2: Array<U>): Array<[T, U]> {
|
||||
if (a1.length <= a2.length) {
|
||||
return a1.map((e1, i) => [e1, a2[i]]);
|
||||
} else {
|
||||
return a2.map((e2, i) => [a1[i], e2]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue