Merge branch '068-enable-strict-mode' into 050-basic-active-effects

This commit is contained in:
Johannes Loher 2021-02-08 03:24:36 +01:00
commit b74ee5ec7c
45 changed files with 3088 additions and 2119 deletions

View file

@ -2,6 +2,9 @@
"DS4.UserInteractionAddItem": "Neu",
"DS4.UserInteractionEditItem": "Bearbeiten",
"DS4.UserInteractionDeleteItem": "Löschen",
"DS4.UserInteractionAddEffect": "Neuer Effekt",
"DS4.UserInteractionEditEffect": "Effekt bearbeiten",
"DS4.UserInteractionDeleteEffect": "Effekt löschen",
"DS4.NotOwned": "Nicht besessen",
"DS4.HeadingBiography": "Biografie",
"DS4.HeadingDetails": "Details",
@ -12,11 +15,11 @@
"DS4.HeadingSpells": "Zaubersprüche",
"DS4.HeadingDescription": "Beschreibung",
"DS4.HeadingSpecialCreatureAbilites": "Besondere Fähigkeiten",
"DS4.AttackType": "Angriffs Typ",
"DS4.AttackType": "Angriffstyp",
"DS4.AttackTypeAbbr": "AT",
"DS4.WeaponBonus": "Waffen Bonus",
"DS4.WeaponBonus": "Waffenbonus",
"DS4.WeaponBonusAbbr": "WB",
"DS4.OpponentDefense": "Gegner Abwehr",
"DS4.OpponentDefense": "Gegnerabwehr",
"DS4.OpponentDefenseAbbr": "GA",
"DS4.AttackTypeMelee": "Schlagen",
"DS4.AttackTypeRanged": "Schießen",
@ -61,9 +64,9 @@
"DS4.ItemTypeSpecialCreatureAbilityPlural": "Besondere Kreaturenfähigkeiten",
"DS4.ArmorType": "Panzerungstyp",
"DS4.ArmorTypeAbbr": "PAT",
"DS4.ArmorMaterialType": "Material Typ",
"DS4.ArmorMaterialType": "Materialtyp",
"DS4.ArmorMaterialTypeAbbr": "Mat.",
"DS4.ArmorValue": "Panzerungs Wert",
"DS4.ArmorValue": "Panzerungswert",
"DS4.ArmorValueAbbr": "PA",
"DS4.ArmorTypeBody": "Körper",
"DS4.ArmorTypeBodyAbbr": "Körper",
@ -125,7 +128,7 @@
"DS4.CombatValuesTargetedSpellcasting": "Zielzaubern",
"DS4.CharacterBaseInfoRace": "Volk",
"DS4.CharacterBaseInfoClass": "Klasse",
"DS4.CharacterBaseInfoHeroClass": "Helden Klasse",
"DS4.CharacterBaseInfoHeroClass": "Heldenklasse",
"DS4.CharacterBaseInfoCulture": "Kultur",
"DS4.CharacterProgressionLevel": "Stufe",
"DS4.CharacterProgressionExperiencePoints": "Erfahrungspunkte",
@ -144,9 +147,9 @@
"DS4.CharacterProfileBirthday": "Geburtstag",
"DS4.CharacterProfileBirthplace": "Geburtsort",
"DS4.CharacterProfileAge": "Alter",
"DS4.CharacterProfileHeight": "Größe",
"DS4.CharacterProfileHeight": "Größe [cm]",
"DS4.CharacterProfileHairColor": "Haarfarbe",
"DS4.CharacterProfileWeight": "Gewicht",
"DS4.CharacterProfileWeight": "Gewicht [kg]",
"DS4.CharacterProfileEyeColor": "Augenfarbe",
"DS4.CharacterProfileSpecialCharacteristics": "Besondere Eigenschaften",
"DS4.CharacterCurrencyGold": "Gold",
@ -193,9 +196,10 @@
"DS4.UnitCustom": "individuell",
"DS4.UnitCustomAbbr": " ",
"DS4.RollDialogDefaultTitle": "Proben-Optionen",
"DS4.RollDialogOkButton": "Ok",
"DS4.RollDialogOkButton": "OK",
"DS4.RollDialogCancelButton": "Abbrechen",
"DS4.ErrorUnexpectedHtmlType": "Typfehler: Erwartet wurde {exType}, tatsächlich erhalten wurde {realType}",
"DS4.ErrorUnexpectedHtmlType": "Typfehler: Erwartet wurde '{exType}', tatsächlich erhalten wurde '{realType}'.",
"DS4.ErrorCouldNotFindForm": "Konnte HTML Element '{htmlElement}' nicht finden.",
"DS4.RollDialogTargetLabel": "Probenwert",
"DS4.RollDialogModifierLabel": "SL-Modifikator",
"DS4.RollDialogCoupLabel": "Immersieg bis",

View file

@ -2,6 +2,9 @@
"DS4.UserInteractionAddItem": "Add item",
"DS4.UserInteractionEditItem": "Edit item",
"DS4.UserInteractionDeleteItem": "Delete item",
"DS4.UserInteractionAddEffect": "Add Effect",
"DS4.UserInteractionEditEffect": "Edit Effect",
"DS4.UserInteractionDeleteEffect": "Delete Effect",
"DS4.NotOwned": "No owner",
"DS4.HeadingBiography": "Biography",
"DS4.HeadingDetails": "Details",
@ -144,9 +147,9 @@
"DS4.CharacterProfileBirthday": "Birthday",
"DS4.CharacterProfileBirthplace": "Birthplace",
"DS4.CharacterProfileAge": "Age",
"DS4.CharacterProfileHeight": "Height",
"DS4.CharacterProfileHeight": "Height [m]",
"DS4.CharacterProfileHairColor": "Hair Color",
"DS4.CharacterProfileWeight": "Weight",
"DS4.CharacterProfileWeight": "Weight [kg]",
"DS4.CharacterProfileEyeColor": "Eye Color",
"DS4.CharacterProfileSpecialCharacteristics": "Special Characteristics",
"DS4.CharacterCurrencyGold": "Gold",
@ -195,7 +198,8 @@
"DS4.RollDialogDefaultTitle": "Roll Options",
"DS4.RollDialogOkButton": "Ok",
"DS4.RollDialogCancelButton": "Cancel",
"DS4.ErrorUnexpectedHtmlType": "Type Error: Expected {exType}, got {realType}",
"DS4.ErrorUnexpectedHtmlType": "Type Error: Expected '{exType}' but got '{realType}'.",
"DS4.ErrorCouldNotFindForm": "Could not find html element '{htmlElement}'.",
"DS4.RollDialogTargetLabel": "Check Target Number",
"DS4.RollDialogModifierLabel": "Game Master Modifier",
"DS4.RollDialogCoupLabel": "Coup to",

View file

@ -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;

View file

@ -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>);
}
});
}
}

View file

@ -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,

View file

@ -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,
});
}
}

View file

@ -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,
});
}
}

View 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]);
}
}

View file

@ -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",
},
};

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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({});
}
}

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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;
}, {}),

View file

@ -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,
};
}

View file

@ -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
};
}

View file

@ -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 };
}
}

View file

@ -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;

View file

@ -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]);
}
}

View file

@ -2,7 +2,7 @@
"name": "ds4",
"title": "Dungeonslayers 4",
"description": "The Dungeonslayers 4 system for FoundryVTT. Dungeonslayers (© Christian Kennig) is licensed under CC BY-NC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/de/deed.en).",
"version": "0.2.0",
"version": "0.2.1",
"minimumCoreVersion": "0.7.9",
"compatibleCoreVersion": "0.7.9",
"templateVersion": 2,
@ -43,7 +43,7 @@
"primaryTokenAttribute": "combatValues.hitPoints",
"url": "https://git.f3l.de/dungeonslayers/ds4",
"manifest": "https://git.f3l.de/dungeonslayers/ds4/-/raw/latest/src/system.json?inline=false",
"download": "https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/0.2.0/download?job=build",
"download": "https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/0.2.1/download?job=build",
"license": "MIT",
"initiative": "@combatValues.initiative.total"
}

View file

@ -13,20 +13,20 @@
<div class="flexrow basic-properties">
<div class="basic-property">
<label class="basic-property-label"
for="data.baseInfo.race">{{config.characterBaseInfo.race}}</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" />
</div>
<div class="basic-property">
<label class="basic-property-label"
for="data.baseInfo.culture">{{config.characterBaseInfo.culture}}</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" />
</div>
<div class="basic-property flex125">
<label class="basic-property-label"
for="data.progression.progressPoints.used">{{config.characterProgression.progressPoints}}</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"
@ -43,7 +43,7 @@
</div>
<div class="basic-property flex125">
<label class="basic-property-label"
for="data.progression.talentPoints.used">{{config.characterProgression.talentPoints}}</label>
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"
@ -56,13 +56,13 @@
</div>
<div class="basic-property">
<label class="basic-property-label"
for="data.baseInfo.class">{{config.characterBaseInfo.class}}</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" />
</div>
<div class="basic-property">
<label class="basic-property-label"
for="data.baseInfo.heroClass">{{config.characterBaseInfo.heroClass}}</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" />
@ -84,24 +84,28 @@
<a class="item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a>
</nav>
{{!-- Sheet Body --}}
<section class="sheet-body">
{{!-- Items Tab --}}
{{> systems/ds4/templates/actor/partials/character-inventory.hbs}}
<!-- beautify ignore:start -->
<!-- prettier-ignore-start -->
{{!-- Sheet Body (remove indentation to avoid annoying Handlebars auto-indent) --}}
<section class="sheet-body">
{{!-- Items Tab --}}
{{> systems/ds4/templates/actor/partials/character-inventory.hbs}}
{{!-- Spells Tab --}}
{{> systems/ds4/templates/actor/partials/spells-overview.hbs}}
{{!-- Spells Tab --}}
{{> systems/ds4/templates/actor/partials/spells-overview.hbs}}
{{!-- Talents Tab --}}
{{> systems/ds4/templates/actor/partials/talents-abilities-overview.hbs}}
{{!-- Talents Tab --}}
{{> systems/ds4/templates/actor/partials/talents-abilities-overview.hbs}}
{{! Profile Tab --}}
{{> systems/ds4/templates/actor/partials/profile.hbs}}
{{! Profile Tab --}}
{{> systems/ds4/templates/actor/partials/profile.hbs}}
{{!-- Biography Tab --}}
<div class="tab biography" data-group="primary" data-tab="biography">
{{editor content=data.profile.biography target="data.profile.biography" button=true owner=owner
editable=editable}}
</div>
</section>
{{!-- Biography Tab --}}
<div class="tab biography" data-group="primary" data-tab="biography">
{{editor content=data.profile.biography target="data.profile.biography" button=true owner=owner
editable=editable}}
</div>
</section>
<!-- prettier-ignore-end -->
<!-- beautify ignore:end -->
</form>

View file

@ -6,10 +6,10 @@
<h1 class="charname"><input name="name" type="text" value="{{actor.name}}" placeholder="Name" /></h1>
<div class="flexrow basic-properties">
<div class="basic-property">
<label>{{config.creatureBaseInfo.creatureType}}</label>
<label>{{config.i18n.creatureBaseInfo.creatureType}}</label>
<select name="data.baseInfo.creatureType" data-type="String">
{{#select data.baseInfo.creatureType}}
{{#each config.creatureTypes as |value key|}}
{{#each config.i18n.creatureTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}
@ -17,20 +17,20 @@
</div>
<div class="basic-property">
<label class="basic-property-label"
for="data.baseInfo.loot">{{config.creatureBaseInfo.loot}}</label>
for="data.baseInfo.loot">{{config.i18n.creatureBaseInfo.loot}}</label>
<input type="text" name="data.baseInfo.loot" value="{{data.baseInfo.loot}}" data-dtype="String" />
</div>
<div class="basic-property">
<label class="basic-property-label"
for="data.baseInfo.foeFactor">{{config.creatureBaseInfo.foeFactor}}</label>
for="data.baseInfo.foeFactor">{{config.i18n.creatureBaseInfo.foeFactor}}</label>
<input type="text" name="data.baseInfo.foeFactor" value="{{data.baseInfo.foeFactor}}"
data-dtype="Number" />
</div>
<div class="basic-property">
<label>{{config.creatureBaseInfo.sizeCategory}}</label>
<label>{{config.i18n.creatureBaseInfo.sizeCategory}}</label>
<select name="data.baseInfo.sizeCategory" data-type="String">
{{#select data.baseInfo.sizeCategory}}
{{#each config.creatureSizeCategories as |value key|}}
{{#each config.i18n.creatureSizeCategories as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}
@ -38,7 +38,7 @@
</div>
<div class="basic-property">
<label class="basic-property-label"
for="data.baseInfo.experiencePoints">{{config.creatureBaseInfo.experiencePoints}}</label>
for="data.baseInfo.experiencePoints">{{config.i18n.creatureBaseInfo.experiencePoints}}</label>
<input type="text" name="data.baseInfo.experiencePoints" value="{{data.baseInfo.experiencePoints}}"
data-dtype="Number" />
</div>
@ -64,7 +64,7 @@
{{> systems/ds4/templates/actor/partials/creature-inventory.hbs}}
{{!-- Special Creature Abilities Tab --}}
{{> systems/ds4/templates/actor/partials/special-creature-abilites-overview.hbs}}
{{> systems/ds4/templates/actor/partials/special-creature-abilities-overview.hbs}}
{{!-- Spells Tab --}}
{{> systems/ds4/templates/actor/partials/spells-overview.hbs}}

View file

@ -42,11 +42,11 @@
{{!-- ======================================================================== --}}
<div class="attributes-traits grid grid-3col">
{{#each config.attributes as |attribute-label attribute-key|}}
{{#each config.i18n.attributes as |attribute-label attribute-key|}}
{{> attribute attribute-label=attribute-label attribute-key=attribute-key attribute-data=(lookup ../data.attributes
attribute-key)}}
{{/each}}
{{#each config.traits as |trait-label trait-key|}}
{{#each config.i18n.traits as |trait-label trait-key|}}
{{> trait trait-label=trait-label trait-key=trait-key trait-data=(lookup ../data.traits trait-key)}}
{{/each}}
</div>

View file

@ -4,13 +4,13 @@
<h4 class="items-list-title">{{localize 'DS4.CharacterCurrency'}}</h4>
<ol class="items-list">
<li class="item flexrow item-header">
<label for="data.currency.gold" class="flex05">{{config.characterCurrency.gold}}</label>
<label for="data.currency.gold" class="flex05">{{config.i18n.characterCurrency.gold}}</label>
<input class="flex3 item-num-val item-change" type="number" min="0" step="1" name="data.currency.gold"
id="data.currency.gold" value="{{data.currency.gold}}" data-dtype="Number" />
<label for="data.currency.silver" class="flex05">{{config.characterCurrency.silver}}</label>
<label for="data.currency.silver" class="flex05">{{config.i18n.characterCurrency.silver}}</label>
<input class="flex3 item-num-val item-change" type="number" min="0" step="1" name="data.currency.silver"
id="data.currency.silver" value="{{data.currency.silver}}" data-dtype="Number" />
<label for="data.currency.copper" class="flex05">{{config.characterCurrency.copper}}</label>
<label for="data.currency.copper" class="flex05">{{config.i18n.characterCurrency.copper}}</label>
<input class="flex3 item-num-val item-change" type="number" min="0" step="1" name="data.currency.copper"
id="data.currency.copper" value="{{data.currency.copper}}" data-dtype="Number" />
</li>

View file

@ -1,6 +1,6 @@
<div class="progression flexrow">
<div class="progression-entry">
<h2 class="progression-label"><label for="data.progression.level">{{config.characterProgression.level}}</label>
<h2 class="progression-label"><label for="data.progression.level">{{config.i18n.characterProgression.level}}</label>
</h2>
<label for="data.progression.level" class="hidden">Progression Level</label>
<input class="progression-value" type="number" name="data.progression.level" id="data.progression.level" value="{{data.progression.level}}"
@ -8,7 +8,7 @@
</div>
<div class="progression-entry">
<h2 class="progression-label"><label
for="data.progression.experiencePoints">{{config.characterProgression.experiencePoints}}</label>
for="data.progression.experiencePoints">{{config.i18n.characterProgression.experiencePoints}}</label>
</h2>
<label for="data.progression.experiencePoints" class="hidden">Experience Points</label>
<input class="progression-value" type="number" name="data.progression.experiencePoints" id="data.progression.experiencePoints"

View file

@ -23,7 +23,7 @@
{{!-- ======================================================================== --}}
<div class="combat-values flexrow flex-between">
{{#each config.combatValues as |combat-value-label combat-value-key|}}
{{#each config.i18n.combatValues as |combat-value-label combat-value-key|}}
{{> combat-value combat-value-key=combat-value-key combat-value-data=(lookup ../data.combatValues
combat-value-key)}}
{{/each}}

View file

@ -15,9 +15,8 @@
{{#*inline "ifHasItemOfType"}}
{{#if (and (ne itemsArray undefined) (gt itemsArray.length 0))}}
{{> @partial-block}}
{{else}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }}
{{/if}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }}
{{/inline}}
@ -48,8 +47,8 @@
{{> @partial-block }}
{{!-- description --}}
<div class="flex4">{{localize 'DS4.Description'}}</div>
{{!-- add button --}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }}
{{!-- control buttons placeholder --}}
<div></div>
</li>
{{/inline}}
@ -110,8 +109,8 @@
{{#each itemsByType.weapon as |item id|}}
{{#> itemListEntry item=item}}
<div class="flex05 item-image">
<img src="{{lookup ../../config.attackTypesIcons item.data.data.attackType}}"
title="{{lookup ../../config.attackTypes item.data.data.attackType}}" width="24" height="24" />
<img src="{{lookup ../../config.icons.attackTypes item.data.data.attackType}}"
title="{{lookup ../../config.i18n.attackTypes item.data.data.attackType}}" width="24" height="24" />
</div>
<div class="flex05 item-num-val">{{ item.data.data.weaponBonus}}</div>
<div class="flex05 item-num-val">{{ item.data.data.opponentDefense}}</div>
@ -135,11 +134,11 @@
{{/itemListHeader}}
{{#each itemsByType.armor as |item id|}}
{{#> itemListEntry item=item }}
<div title="{{lookup ../../config.armorMaterialTypes item.data.data.armorMaterialType}}">
{{lookup ../../config.armorMaterialTypesAbbr item.data.data.armorMaterialType}}
<div title="{{lookup ../../config.i18n.armorMaterialTypes item.data.data.armorMaterialType}}">
{{lookup ../../config.i18n.armorMaterialTypesAbbr item.data.data.armorMaterialType}}
</div>
<div title="{{lookup ../../config.armorTypes item.data.data.armorType}}">
{{lookup ../../config.armorTypesAbbr item.data.data.armorType}}
<div title="{{lookup ../../config.i18n.armorTypes item.data.data.armorType}}">
{{lookup ../../config.i18n.armorTypesAbbr item.data.data.armorType}}
</div>
<div class="flex05 item-num-val">{{ item.data.data.armorValue}}</div>
{{/itemListEntry}}

View file

@ -3,6 +3,6 @@
!-- The current item is defined by the data-item-id HTML property of the parent li element.
--}}
<div class="item-controls">
<a class="item-control item-edit" title="Edit Item"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="Delete Item"><i class="fas fa-trash"></i></a>
<a class="item-control item-edit" title="{{localize 'DS4.UserInteractionEditItem'}}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="{{localize 'DS4.UserInteractionDeleteItem'}}"><i class="fas fa-trash"></i></a>
</div>

View file

@ -1,15 +1,21 @@
<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|}}
{{#if (ne profile-data-key 'biography')}}
{{#if (and (ne profile-data-key 'biography') (ne profile-data-key 'specialCharacteristics'))}}
<div class="profile-entry">
<label for="data.profile.{{profile-data-key}}">
{{lookup ../config.characterProfile profile-data-key}}
{{lookup ../config.i18n.characterProfile profile-data-key}}
</label>
<input type="text" name="data.profile.{{profile-data-key}}" value="{{profile-data-value}}"
data-dtype="{{lookup ../config/characterProfileDTypes profile-data-key}}" />
data-dtype="{{lookup ../config.i18n.characterProfileDTypes profile-data-key}}" />
</div>
{{/if}}
{{/each}}
<div>
<label for="data.profile.specialCharacteristics">
{{lookup config.i18n.characterProfile 'specialCharacteristics'}}
</label>
<textarea name="data.profile.specialCharacteristics" data-dtype="String" rows="4">{{data.profile.specialCharacteristics}}</textarea>
</div>
</div>
</div>

View file

@ -42,8 +42,8 @@
<div class="flex1 item-name">{{localize 'DS4.ItemName'}}</div>
{{!-- description --}}
<div class="flex3">{{localize 'DS4.Description'}}</div>
{{!-- add button --}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }}
{{!-- control buttons placeholder --}}
<div></div>
</li>
{{/inline}}
@ -58,4 +58,5 @@
{{> baseItemListEntry item=item}}
{{/each}}
</ol>
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType='specialCreatureAbility' }}
</div>

View file

@ -13,7 +13,7 @@
<div class="unit-data-pair item-num-val"
title="{{localize localizationString}} [{{lookup unitNames unitDatum.unit}}]" >
{{#if unitDatum.value }}
{{unitDatum.value}}{{lookup unitAbbrs unitDatum.unit}}
{{unitDatum.value}}&thinsp;{{lookup unitAbbrs unitDatum.unit}}
{{else}}-{{/if}}
</div>
{{/inline}}
@ -23,11 +23,11 @@
!-- directly handing over the latter two.
--}}
{{#*inline "temporalUnit"}}
{{> unit unitNames=config.temporalUnits unitAbbrs=config.temporalUnitsAbbr unitDatum=unitDatum localizationString=localizationString}}
{{> unit unitNames=config.i18n.temporalUnits unitAbbrs=config.i18n.temporalUnitsAbbr unitDatum=unitDatum localizationString=localizationString}}
{{/inline}}
{{#*inline "distanceUnit"}}
{{> unit unitNames=config.distanceUnits unitAbbrs=config.distanceUnitsAbbr unitDatum=unitDatum localizationString=localizationString}}
{{> unit unitNames=config.i18n.distanceUnits unitAbbrs=config.i18n.distanceUnitsAbbr unitDatum=unitDatum localizationString=localizationString}}
{{/inline}}
@ -55,8 +55,8 @@
<div class="item-num-val" title="{{localize 'DS4.SpellCooldownDuration'}}"><i class="fas fa-hourglass-half"></i></div>
{{!-- description --}}
{{!-- <div class="flex3">{{localize 'DS4.Description'}}</div> --}}
{{!-- add button --}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType='spell' }}
{{!-- control buttons placeholder --}}
<div></div>
</li>
{{#each itemsByType.spell as |item id|}}
<li class="item flexrow" data-item-id="{{item._id}}">
@ -71,8 +71,8 @@
data-property="name" title="{{localize 'DS4.ItemName'}}" />
{{!-- spell type --}}
<div class="flex05 item-image">
<img src="{{lookup ../config.spellTypesIcons item.data.data.spellType}}"
title="{{lookup ../config.spellTypes item.data.data.spellType}}" width="24" height="24" />
<img src="{{lookup ../config.icons.spellTypes item.data.data.spellType}}"
title="{{lookup ../config.i18n.spellTypes item.data.data.spellType}}" width="24" height="24" />
</div>
{{!-- spell bonus --}}
<input class="item-num-val item-change" type="text" data-dtype="String"
@ -90,4 +90,6 @@
</li>
{{/each}}
</ol>
{{!-- add button --}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType='spell' }}
</div>

View file

@ -15,9 +15,8 @@
{{#*inline "ifHasItemOfType"}}
{{#if (and (ne itemsArray undefined) (gt itemsArray.length 0))}}
{{> @partial-block}}
{{else}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }}
{{/if}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }}
{{/inline}}
@ -98,7 +97,7 @@
{{/inline}}
{{!--
!-- Render a list header for a base item list entries from a given item.
!-- Render a list header for a base item list entry from a given item.
!-- The partial assumes a variable dataType to be given in the context.
!--
!-- @param dataType: the string item type for the list
@ -111,8 +110,8 @@
<div class="flex1 item-name">{{localize 'DS4.ItemName'}}</div>
{{!-- description --}}
<div class="flex3">{{localize 'DS4.Description'}}</div>
{{!-- add button --}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType=dataType }}
{{!-- control buttons placeholder --}}
<div></div>
</li>
{{/inline}}
@ -133,8 +132,8 @@
<div class="flex3">{{localize 'DS4.TalentRank'}}</div>
{{!-- description --}}
<div class="flex4">{{localize 'DS4.Description'}}</div>
{{!-- add button --}}
{{> systems/ds4/templates/actor/partials/overview-add-button.hbs dataType='talent' }}
{{!-- control buttons placeholder --}}
<div></div>
</li>
{{#each itemsByType.talent as |item id|}}
{{> talentListEntry item=item}}

View file

@ -5,7 +5,7 @@
<label>{{localize "DS4.ArmorType"}}</label>
<select name="data.armorType" data-type="String">
{{#select data.armorType}}
{{#each config.armorTypes as |value key|}}
{{#each config.i18n.armorTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}
@ -15,7 +15,7 @@
<label for="data.armorMaterialType">{{localize "DS4.ArmorMaterialType"}}</label>
<select name="data.armorMaterialType" data-type="String">
{{#select data.armorMaterialType}}
{{#each config.armorMaterialTypes as |value key|}}
{{#each config.i18n.armorMaterialTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}

View file

@ -11,7 +11,7 @@
<label for="data.availability">{{localize "DS4.ItemAvailability"}}</label>
<select name="data.availability" data-type="String">
{{#select data.availability}}
{{#each config.itemAvailabilities as |value key|}}
{{#each config.i18n.itemAvailabilities as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}

View file

@ -5,16 +5,18 @@
<div class="effect-image"></div>
<div class="effect-name">Name</div>
<div class="effect-controls">
<a class="effect-control" data-action="create" title="Create Effect"><i class="fas fa-plus"></i> Add
effect</a>
<a class="effect-control" data-action="create" title="{{localize 'DS4.UserInteractionAddEffect'}}">
<i class="fas fa-plus"></i> {{localize 'DS4.UserInteractionAddEffect'}}</a>
</div>
</li>
{{#each item.effects as |effect id|}}
<li class="effect flexrow" data-effect-id="{{effect._id}}">
<h4 class="effect-name">{{effect.label}}</h4>
<div class="effect-controls">
<a class="effect-control" data-action="edit" title="Edit Effect"><i class="fas fa-edit"></i></a>
<a class="effect-control" data-action="delete" title="Delete Effect"><i class="fas fa-trash"></i></a>
<a class="effect-control" data-action="edit" title="{{localize 'DS4.UserInteractionEditEffect'}}">
<i class="fas fa-edit"></i></a>
<a class="effect-control" data-action="delete" title="{{localize 'DS4.UserInteractionDeleteEffect'}}">
<i class="fas fa-trash"></i></a>
</div>
</li>
{{/each}}

View file

@ -2,7 +2,7 @@
<img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}" />
<div class="header-fields flexrow">
<h1 class="charname"><input name="name" type="text" value="{{item.name}}" placeholder="Name" /></h1>
<h2 class="item-type">{{localize (lookup config.itemTypes item.type)}}</h2>
<h2 class="item-type">{{lookup config.i18n.itemTypes item.type}}</h2>
{{> @partial-block}}
</div>
</header>

View file

@ -12,9 +12,9 @@
<select name="data.{{property}}.unit" data-type="String">
{{#select (lookup (lookup data property) 'unit')}}
{{#if (eq unitType 'temporal')}}
{{#each (lookup config 'temporalUnitsAbbr') as |value key|}}<option value="{{key}}">{{value}}</option>{{/each}}
{{#each (lookup config.i18n 'temporalUnitsAbbr') as |value key|}}<option value="{{key}}">{{value}}</option>{{/each}}
{{else}}
{{#each (lookup config 'distanceUnitsAbbr') as |value key|}}<option value="{{key}}">{{value}}</option>{{/each}}
{{#each (lookup config.i18n 'distanceUnitsAbbr') as |value key|}}<option value="{{key}}">{{value}}</option>{{/each}}
{{/if}}
{{/select}}
</select>
@ -33,7 +33,7 @@
<label for="data.spellType">{{localize "DS4.SpellType"}}</label>
<select id="data.spellType" name="data.spellType" data-type="String">
{{#select data.spellType}}
{{#each config.spellTypes as |value key|}}
{{#each config.i18n.spellTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}
@ -52,7 +52,7 @@
<label for="data.spellCategory">{{localize "DS4.SpellCategory"}}</label>
<select id="data.spellCategory" name="data.spellCategory" data-type="String">
{{#select data.spellCategory}}
{{#each config.spellCategories as |value key|}}
{{#each config.i18n.spellCategories as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}

View file

@ -5,7 +5,7 @@
<label>{{localize "DS4.AttackType"}}</label>
<select name="data.attackType" data-type="String">
{{#select data.attackType}}
{{#each config.attackTypes as |value key|}}
{{#each config.i18n.attackTypes as |value key|}}
<option value="{{key}}">{{value}}</option>
{{/each}}
{{/select}}

View file

@ -2,15 +2,17 @@
<label for="ctv">{{localize "DS4.RollDialogTargetLabel"}}</label>
<input id="ctv" data-type="Number" type="number" name="ctv" value="{{checkTargetValue}}" />
<label for="gmmod">{{localize "DS4.RollDialogModifierLabel"}}</label>
<input id="gmmod" data-type="Number" type="number" name="gmmod" value="0" />
<input id="gmmod" data-type="Number" type="number" name="gmmod" value="0" autofocus />
<label for="maxcoup">{{localize "DS4.RollDialogCoupLabel"}}</label>
<input id="maxcoup" data-type="Number" type="number" name="maxcoup" value="{{maxCritSuccess}}" />
<label for="minfumble">{{localize "DS4.RollDialogFumbleLabel"}}</label>
<input id="minfumble" data-type="Number" type="number" name="minfumble" value="{{minCritFailure}}" />
<label for="visibility">{{localize "DS4.RollDialogVisibilityLabel"}}</label>
<select id="visibility" data-type="String">
{{#each rollModes as |rollMode|}}
<option value="{{rollMode}}">{{lookup ../config.chatVisibilities rollMode}}</option>
{{#select rollMode}}
{{#each config.i18n.chatVisibilities as |rollModeValue rollModeKey|}}
<option value="{{rollModeKey}}">{{rollModeValue}}</option>
{{/each}}
{{/select}}
</select>
</form>
</form>