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

@ -12,6 +12,8 @@ module.exports = {
"plugin:prettier/recommended", // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. "plugin:prettier/recommended", // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
], ],
plugins: ["@typescript-eslint"],
rules: { rules: {
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off", // e.g. "@typescript-eslint/explicit-function-return-type": "off",

View file

@ -3,9 +3,8 @@
An implementation of the Dungeonslayers 4 game system for [Foundry Virtual An implementation of the Dungeonslayers 4 game system for [Foundry Virtual
Tabletop](http://foundryvtt.com). Tabletop](http://foundryvtt.com).
This system provides character sheet support for Actors and Items and mechanical This system provides sheet support for Actors and Items and mechanical support
support for dice and rules necessary to for dice and rules necessary to play games of Dungeonslayers 4.
play games of Dungeponslayers 4.
## Installation ## Installation
@ -17,7 +16,7 @@ https://git.f3l.de/dungeonslayers/ds4/-/raw/latest/src/system.json?inline=false
## Development ## Development
### Prerequisits ### Prerequisites
In order to build this system, recent versions of `node` and `npm` are required. In order to build this system, recent versions of `node` and `npm` are required.
We recommend using the latest lts version of `node`, which is `v14.15.4` at the We recommend using the latest lts version of `node`, which is `v14.15.4` at the
@ -83,16 +82,18 @@ npm test
## Contributing ## Contributing
Code and content contributions are accepted. Please feel free to submit issues Code and content contributions are accepted. Please feel free to submit issues
to the issue tracker or submit merge requests for code changes. To create an issue send a mail to [git+dungeonslayers-ds4-155-issue-@git.f3l.de](mailto:git+dungeonslayers-ds4-155-issue-@git.f3l.de). to the issue tracker or submit merge requests for code changes. To create an
issue, send a mail to [git+dungeonslayers-ds4-155-issue-@git.f3l.de](mailto:git+dungeonslayers-ds4-155-issue-@git.f3l.de).
## Licensing ## Licensing
[Dungeonslayers](http://dungeonslayers.de/) (© Christian Kennig) is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/de/deed.en). [Dungeonslayers](http://dungeonslayers.de/) (© Christian Kennig) is licensed
under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/de/deed.en).
The icons in [src/assets/icons/official](src/assets/icons/official) are slightly modifed The icons in [src/assets/icons/official](src/assets/icons/official) are slightly
versions of original Dungeonslayers icons, which have also been published under modified versions of original Dungeonslayers icons, which have also been
CC BY-NC-SA 3.0. Hence the modified icons are also published under this published under CC BY-NC-SA 3.0. Hence the modified icons are also published
license. A copy of this license can be found under under this license. A copy of this license can be found under
[src/assets/icons/official/LICENSE](src/assets/icons/official/LICENSE). [src/assets/icons/official/LICENSE](src/assets/icons/official/LICENSE).
Similarly, the compendium packs found in [src/packs](src/packs) are based on Similarly, the compendium packs found in [src/packs](src/packs) are based on

3458
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
{ {
"private": true, "private": true,
"name": "test", "name": "dungeonslayers4",
"description": "An implementation of the Dungeonslayers 4 game system for Foundry Virtual Tabletop.", "description": "An implementation of the Dungeonslayers 4 game system for Foundry Virtual Tabletop.",
"version": "0.2.0", "version": "0.2.1",
"license": "MIT", "license": "MIT",
"homepage": "https://git.f3l.de/dungeonslayers/ds4", "homepage": "https://git.f3l.de/dungeonslayers/ds4",
"repository": { "repository": {
@ -35,7 +35,7 @@
"build:watch": "gulp watch", "build:watch": "gulp watch",
"link": "gulp link", "link": "gulp link",
"clean": "gulp clean && gulp link --clean", "clean": "gulp clean && gulp link --clean",
"update": "npm install --save-dev git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f3l-fixes", "update": "npm install --save-dev github:League-of-Foundry-Developers/foundry-vtt-types#foundry-0.7.9",
"updateManifest": "gulp updateManifest", "updateManifest": "gulp updateManifest",
"lint": "eslint 'src/**/*.ts' --cache", "lint": "eslint 'src/**/*.ts' --cache",
"lint:fix": "eslint 'src/**/*.ts' --cache --fix", "lint:fix": "eslint 'src/**/*.ts' --cache --fix",
@ -45,28 +45,28 @@
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^9.0.6", "@types/fs-extra": "^9.0.6",
"@types/jasmine": "^3.6.2", "@types/jasmine": "^3.6.3",
"@typescript-eslint/eslint-plugin": "^4.14.0", "@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.0", "@typescript-eslint/parser": "^4.14.2",
"archiver": "^5.2.0", "archiver": "^5.2.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"eslint": "^7.18.0", "eslint": "^7.19.0",
"eslint-config-prettier": "^7.1.0", "eslint-config-prettier": "^7.2.0",
"eslint-plugin-prettier": "^3.3.1", "eslint-plugin-prettier": "^3.3.1",
"foundry-pc-types": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f3l-fixes", "foundry-vtt-types": "github:League-of-Foundry-Developers/foundry-vtt-types#foundry-0.7.9",
"fs-extra": "^9.0.1", "fs-extra": "^9.1.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-git": "^2.10.1", "gulp-git": "^2.10.1",
"gulp-less": "^4.0.1", "gulp-less": "^4.0.1",
"gulp-sass": "^4.1.0", "gulp-sass": "^4.1.0",
"gulp-typescript": "^6.0.0-alpha.1", "gulp-typescript": "^6.0.0-alpha.1",
"husky": "^4.3.8", "husky": "^4.3.8",
"jasmine": "^3.6.3", "jasmine": "^3.6.4",
"jasmine-xml-reporter": "^1.2.1", "jasmine-xml-reporter": "^1.2.1",
"json-stringify-pretty-compact": "^2.0.0", "json-stringify-pretty-compact": "^2.0.0",
"lint-staged": "^10.5.3", "lint-staged": "^10.5.4",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"sass": "^1.32.4", "sass": "^1.32.6",
"ts-node": "^9.1.1", "ts-node": "^9.1.1",
"typescript": "^4.1.3", "typescript": "^4.1.3",
"yargs": "^16.2.0" "yargs": "^16.2.0"

View file

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

View file

@ -2,6 +2,9 @@
"DS4.UserInteractionAddItem": "Add item", "DS4.UserInteractionAddItem": "Add item",
"DS4.UserInteractionEditItem": "Edit item", "DS4.UserInteractionEditItem": "Edit item",
"DS4.UserInteractionDeleteItem": "Delete item", "DS4.UserInteractionDeleteItem": "Delete item",
"DS4.UserInteractionAddEffect": "Add Effect",
"DS4.UserInteractionEditEffect": "Edit Effect",
"DS4.UserInteractionDeleteEffect": "Delete Effect",
"DS4.NotOwned": "No owner", "DS4.NotOwned": "No owner",
"DS4.HeadingBiography": "Biography", "DS4.HeadingBiography": "Biography",
"DS4.HeadingDetails": "Details", "DS4.HeadingDetails": "Details",
@ -144,9 +147,9 @@
"DS4.CharacterProfileBirthday": "Birthday", "DS4.CharacterProfileBirthday": "Birthday",
"DS4.CharacterProfileBirthplace": "Birthplace", "DS4.CharacterProfileBirthplace": "Birthplace",
"DS4.CharacterProfileAge": "Age", "DS4.CharacterProfileAge": "Age",
"DS4.CharacterProfileHeight": "Height", "DS4.CharacterProfileHeight": "Height [m]",
"DS4.CharacterProfileHairColor": "Hair Color", "DS4.CharacterProfileHairColor": "Hair Color",
"DS4.CharacterProfileWeight": "Weight", "DS4.CharacterProfileWeight": "Weight [kg]",
"DS4.CharacterProfileEyeColor": "Eye Color", "DS4.CharacterProfileEyeColor": "Eye Color",
"DS4.CharacterProfileSpecialCharacteristics": "Special Characteristics", "DS4.CharacterProfileSpecialCharacteristics": "Special Characteristics",
"DS4.CharacterCurrencyGold": "Gold", "DS4.CharacterCurrencyGold": "Gold",
@ -195,7 +198,8 @@
"DS4.RollDialogDefaultTitle": "Roll Options", "DS4.RollDialogDefaultTitle": "Roll Options",
"DS4.RollDialogOkButton": "Ok", "DS4.RollDialogOkButton": "Ok",
"DS4.RollDialogCancelButton": "Cancel", "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.RollDialogTargetLabel": "Check Target Number",
"DS4.RollDialogModifierLabel": "Game Master Modifier", "DS4.RollDialogModifierLabel": "Game Master Modifier",
"DS4.RollDialogCoupLabel": "Coup to", "DS4.RollDialogCoupLabel": "Coup to",

View file

@ -1,20 +1,31 @@
import { ModifiableData, ResourceData, UsableResource } from "../common/common-data"; 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 { type ActorType = keyof typeof DS4.i18n.actorTypes;
attributes: DS4ActorDataAttributes;
traits: DS4ActorDataTraits; interface DS4ActorDataHelper<T, U extends ActorType> extends Actor.Data<T, DS4ItemData> {
combatValues: DS4ActorDataCombatValues; 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>; body: ModifiableData<number>;
mobility: ModifiableData<number>; mobility: ModifiableData<number>;
mind: ModifiableData<number>; mind: ModifiableData<number>;
} }
interface DS4ActorDataTraits { interface DS4ActorDataDataTraits {
strength: ModifiableData<number>; strength: ModifiableData<number>;
constitution: ModifiableData<number>; constitution: ModifiableData<number>;
agility: ModifiableData<number>; agility: ModifiableData<number>;
@ -23,7 +34,7 @@ interface DS4ActorDataTraits {
aura: ModifiableData<number>; aura: ModifiableData<number>;
} }
interface DS4ActorDataCombatValues { interface DS4ActorDataDataCombatValues {
hitPoints: ResourceData<number>; hitPoints: ResourceData<number>;
defense: ModifiableData<number>; defense: ModifiableData<number>;
initiative: ModifiableData<number>; initiative: ModifiableData<number>;
@ -34,34 +45,32 @@ interface DS4ActorDataCombatValues {
targetedSpellcasting: ModifiableData<number>; targetedSpellcasting: ModifiableData<number>;
} }
interface DS4ActorDataCharacter extends DS4ActorDataBase { interface DS4CharacterDataData extends DS4ActorDataDataBase {
baseInfo: DS4ActorDataCharacterBaseInfo; baseInfo: DS4CharacterDataDataBaseInfo;
progression: DS4ActorDataCharacterProgression; progression: DS4CharacterDataDataProgression;
language: DS4ActorDataCharacterLanguage; language: DS4CharacterDataDataLanguage;
profile: DS4ActorDataCharacterProfile; profile: DS4CharacterDataDataProfile;
currency: DS4ActorDataCharacterCurrency; currency: DS4CharacterDataDataCurrency;
} }
interface DS4CharacterDataDataBaseInfo {
interface DS4ActorDataCharacterBaseInfo {
race: string; race: string;
class: string; class: string;
heroClass: string; heroClass: string;
culture: string; culture: string;
} }
interface DS4CharacterDataDataProgression {
interface DS4ActorDataCharacterProgression {
level: number; level: number;
experiencePoints: number; experiencePoints: number;
talentPoints: UsableResource<number>; talentPoints: UsableResource<number>;
progressPoints: UsableResource<number>; progressPoints: UsableResource<number>;
} }
interface DS4ActorDataCharacterLanguage { interface DS4CharacterDataDataLanguage {
languages: string; languages: string;
alphabets: string; alphabets: string;
} }
interface DS4ActorDataCharacterProfile { interface DS4CharacterDataDataProfile {
biography: string; biography: string;
gender: string; gender: string;
birthday: string; birthday: string;
@ -74,21 +83,21 @@ interface DS4ActorDataCharacterProfile {
specialCharacteristics: string; specialCharacteristics: string;
} }
interface DS4ActorDataCharacterCurrency { interface DS4CharacterDataDataCurrency {
gold: number; gold: number;
silver: number; silver: number;
copper: number; copper: number;
} }
interface DS4ActorDataCreature extends DS4ActorDataBase { interface DS4CreatureDataData extends DS4ActorDataDataBase {
baseInfo: DS4ActorDataCreatureBaseInfo; baseInfo: DS4CreatureDataDataBaseInfo;
} }
type CreatureType = "animal" | "construct" | "humanoid" | "magicalEntity" | "plantBeing" | "undead"; type CreatureType = "animal" | "construct" | "humanoid" | "magicalEntity" | "plantBeing" | "undead";
type SizeCategory = "tiny" | "small" | "normal" | "large" | "huge" | "colossal"; type SizeCategory = "tiny" | "small" | "normal" | "large" | "huge" | "colossal";
interface DS4ActorDataCreatureBaseInfo { interface DS4CreatureDataDataBaseInfo {
loot: string; loot: string;
foeFactor: number; foeFactor: number;
creatureType: CreatureType; creatureType: CreatureType;

View file

@ -1,15 +1,16 @@
import { ModifiableData } from "../common/common-data"; import { ModifiableData } from "../common/common-data";
import { DS4 } from "../config"; import { DS4 } from "../config";
import { DS4Item } from "../item/item"; import { DS4Item } from "../item/item";
import { DS4Armor, DS4EquippableItemDataType, DS4ItemDataType, DS4Shield, ItemType } from "../item/item-data"; import { DS4ItemData, ItemType } from "../item/item-data";
import { DS4ActorDataType } from "./actor-data"; import { DS4ActorData } from "./actor-data";
type DS4ActiveEffect = ActiveEffect<DS4ActorDataType, DS4ItemDataType, DS4Actor, DS4Item>; /**
* The Actor class for DS4
export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item> { */
export class DS4Actor extends Actor<DS4ActorData, DS4Item> {
/** @override */ /** @override */
prepareData(): void { 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.img) this.data.img = CONST.DEFAULT_TOKEN;
if (!this.data.name) this.data.name = "New " + this.entity; if (!this.data.name) this.data.name = "New " + this.entity;
this.prepareBaseData(); 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 * @param predicate The predicate that ActiveEffectChanges need to satisfy in order to be applied
*/ */
applyActiveEffectsFiltered(predicate: (change: ActiveEffectChange) => boolean): void { applyActiveEffectsFiltered(predicate: (change: ActiveEffectChange) => boolean): void {
const overrides = {}; const overrides: Record<string, unknown> = {};
// Organize non-disabled effects by their application priority // Organize non-disabled effects by their application priority
const changes = this.effects.reduce((changes: Array<ActiveEffectChange & { effect: DS4ActiveEffect }>, e) => { const changes = this.effects.reduce(
(changes: Array<ActiveEffectChange & { effect: ActiveEffect<DS4Actor> }>, e) => {
if (e.data.disabled) return changes; if (e.data.disabled) return changes;
return changes.concat( return changes.concat(
e.data.changes.filter(predicate).map((c) => { e.data.changes.filter(predicate).map((c) => {
c = duplicate(c); const duplicatedChange = duplicate(c) as ActiveEffect.Change;
c.priority = c.priority ?? c.mode * 10; duplicatedChange.priority = duplicatedChange.priority ?? duplicatedChange.mode * 10;
return { ...c, effect: e }; return { ...duplicatedChange, effect: e };
}), }),
); );
}, []); },
[],
);
changes.sort((a, b) => a.priority - b.priority); changes.sort((a, b) => a.priority - b.priority);
// Apply all changes // Apply all changes
@ -68,7 +72,7 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
} }
// Expand the set of final overrides // Expand the set of final overrides
this["overrides"] = expandObject({ ...flattenObject(this["overrides"] ?? {}), ...overrides }); this.overrides = expandObject({ ...flattenObject(this.overrides ?? {}), ...overrides });
} }
/** @override */ /** @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 */ /** The list of properties that are derived from others, given in dot notation */
get derivedDataProperties(): Array<string> { get derivedDataProperties(): Array<string> {
return Object.keys(DS4.combatValues) return Object.keys(DS4.i18n.combatValues)
.map((combatValue) => `data.combatValues.${combatValue}.total`) .map((combatValue) => `data.combatValues.${combatValue}.total`)
.concat("data.combatValues.hitPoints.max"); .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. * 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 { canOwnItemType(itemType: ItemType): boolean {
return this.ownableItemTypes.includes(itemType); return this.ownableItemTypes.includes(itemType);
@ -149,10 +153,13 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
*/ */
private _calculateArmorValueOfEquippedItems(): number { private _calculateArmorValueOfEquippedItems(): number {
return this.items return this.items
.filter((item) => ["armor", "shield"].includes(item.type)) .map((item) => {
.map((item) => item.data.data as DS4Armor | DS4Shield) if (item.data.type === "armor" || item.data.type === "shield") {
.filter((itemData) => itemData.equipped) return item.data.data.equipped ? item.data.data.armorValue : 0;
.map((itemData) => itemData.armorValue) } else {
return 0;
}
})
.reduce((a, b) => a + b, 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. * This only differs from the base implementation by also allowing negative values.
* @override * @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); const current = getProperty(this.data.data, attribute);
// Determine the updates to make to the actor data // Determine the updates to make to the actor data
@ -180,59 +187,77 @@ export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item>
} }
/** @override */ /** @override */
// TODO(types): Improve typing once it's fixed in upstream (arrays can be passed!)
createEmbeddedEntity( createEmbeddedEntity(
embeddedName: string, embeddedName: "OwnedItem",
createData: Record<string, unknown> | Array<Record<string, unknown>>, data: DeepPartial<DS4ItemData>,
options?: Record<string, unknown>, 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") { 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 * contained in the item
* @param itemData The data of the item to be created * @param itemData The data of the item to be created
*/ */
private _preCreateOwnedItem(itemData: ItemData<DS4ItemDataType>): void { protected _preCreateOwnedItem(itemData: DeepPartial<DS4ItemData>): void {
if ("equipped" in itemData.data) { if (itemData.data && "equipped" in itemData.data) {
itemData.effects = itemData.effects.map((effect) => ({ ...effect, disabled: true })); itemData.effects = itemData.effects?.map((effect) => ({ ...effect, disabled: true }));
const equippableUpdateData = itemData as ItemData<DS4EquippableItemDataType>; itemData.data.equipped = false;
equippableUpdateData.data.equipped = false;
} }
} }
/** @override */ /** @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( updateEmbeddedEntity(
embeddedName: string, embeddedName: string,
updateData: Record<string, unknown> | Array<Record<string, unknown>>, updateData: unknown | unknown[],
options?: Record<string, unknown>, options?: Record<string, unknown>,
): Promise<this> { ): Promise<unknown> {
if (embeddedName === "OwnedItem") { 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); return super.updateEmbeddedEntity(embeddedName, updateData, options);
} }
/** /**
* If the equipped flag of an item changed, update all ActiveEffects originating from that item accordingly. * If the equipped flag of one or more items changed, update all ActiveEffects originating from those items
* @param updateData The change that is going to be applied to the owned item * accordingly.
* @param updateData The change that is going to be applied to the owned item(s)
*/ */
private _preUpdateOwnedItem(updateData: Partial<ItemData<DS4ItemDataType>>): void { private _preUpdateOwnedItem(updateData: DeepPartial<DS4ItemData> | Array<DeepPartial<DS4ItemData>>): void {
if ("equipped" in updateData.data) { const dataArray = updateData instanceof Array ? updateData : [updateData];
const equippableUpdateData = updateData as Partial<ItemData<DS4EquippableItemDataType>>; dataArray.forEach((data) => {
const origin = `Actor.${this.id}.OwnedItem.${updateData._id}`; if (data.data && "equipped" in data.data) {
const equipped = data.data.equipped;
const origin = `Actor.${this.id}.OwnedItem.${data._id}`;
const effects = this.effects const effects = this.effects
.filter((e) => e.data.origin === origin) .filter((e) => e.data.origin === origin)
.map((e) => { .map((e) => {
const data = duplicate(e.data); const effectData = duplicate(e.data);
data.disabled = !equippableUpdateData.data.equipped; effectData.disabled = !(equipped ?? true);
return data; return effectData;
}); });
if (effects.length > 0) if (effects.length > 0)
this.updateEmbeddedEntity("ActiveEffect", (effects as unknown) as Record<string, unknown>); 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 { DS4Item } from "../../item/item";
import { DS4ItemDataType, ItemType } from "../../item/item-data"; import { DS4ItemData } from "../../item/item-data";
import { DS4Actor } from "../actor"; import { DS4Actor } from "../actor";
import { DS4ActorDataType } from "../actor-data";
/** /**
* Extend the basic ActorSheet with some very simple modifications * The base Sheet class for all DS4 Actors
* @extends {ActorSheet}
*/ */
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 */ /** @override */
static get defaultOptions(): FormApplicationOptions { static get defaultOptions(): BaseEntitySheet.Options {
return mergeObject(super.defaultOptions, { const superDefaultOptions = super.defaultOptions;
return mergeObject(superDefaultOptions, {
classes: ["ds4", "sheet", "actor"], classes: ["ds4", "sheet", "actor"],
width: 745, width: 745,
height: 600, height: 600,
scrollY: [".sheet-body"], 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`; return `${path}/${this.actor.data.type}-sheet.hbs`;
} }
/* -------------------------------------------- */
/** /**
* This method returns the data for the template of the actor sheet. * 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 * It explicitly adds the items of the object sorted by type in the
* object itemsByType. * 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 = { const data = {
...super.getData(), ...(await super.getData()),
// Add the localization config to the data: // Add the localization config to the data:
config: CONFIG.DS4, config: DS4,
// Add the items explicitly sorted by type to the data: // Add the items explicitly sorted by type to the data:
itemsByType: this.actor.itemTypes, itemsByType: this.actor.itemTypes,
}; };
return data; return data;
} }
/* -------------------------------------------- */
/** @override */ /** @override */
activateListeners(html: JQuery): void { activateListeners(html: JQuery): void {
super.activateListeners(html); super.activateListeners(html);
@ -59,7 +73,7 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
html.find(".item-edit").on("click", (ev) => { html.find(".item-edit").on("click", (ev) => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("itemId")); const item = this.actor.getOwnedItem(li.data("itemId"));
item.sheet.render(true); item.sheet?.render(true);
}); });
// Delete Inventory Item // Delete Inventory Item
@ -75,20 +89,16 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
html.find(".rollable").click(this._onRoll.bind(this)); 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 * 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 * @param event - The originating click event
* @private
*/ */
private _onItemCreate(event: JQuery.ClickEvent): Promise<Item> { protected _onItemCreate(event: JQuery.ClickEvent): Promise<DS4ItemData> {
event.preventDefault(); event.preventDefault();
const header = event.currentTarget; const header = event.currentTarget;
// Get the type of item to create. // Get the type of item to create.
const type = header.dataset.type;
// Grab any data associated with this control. // Grab any data associated with this control.
const data = duplicate(header.dataset); const { type, ...data } = duplicate(header.dataset);
// Initialize a default name. // Initialize a default name.
const name = `New ${type.capitalize()}`; const name = `New ${type.capitalize()}`;
// Prepare the item object. // Prepare the item object.
@ -97,8 +107,6 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
type: type, type: type,
data: data, data: data,
}; };
// Remove the type from the dataset since it's in the itemData.type prop.
delete itemData.data.type;
// Finally, create the item! // Finally, create the item!
return this.actor.createOwnedItem(itemData); 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. * Handle changes to properties of an Owned Item from within character sheet.
* Can currently properly bind: see getValue(). * Can currently properly bind: see getValue().
* Assumes the item property is given as the value of the HTML element property 'data-property'. * 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 * @param ev - The originating change event
* @private
*/ */
private _onItemChange(ev: JQuery.ChangeEvent<HTMLFormElement>): void { protected _onItemChange(ev: JQuery.ChangeEvent): void {
ev.preventDefault(); ev.preventDefault();
console.log("Current target:", $(ev.currentTarget).get(0)["name"]); console.log("Current target:", $(ev.currentTarget).get(0)["name"]);
const el: HTMLFormElement = $(ev.currentTarget).get(0); const el: HTMLFormElement = $(ev.currentTarget).get(0);
const id = $(ev.currentTarget).parents(".item").data("itemId"); 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"); const property: string | undefined = $(ev.currentTarget).data("property");
// Early return: // Early return:
@ -139,7 +146,7 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
* - Checkbox: boolean * - Checkbox: boolean
* - Text input: string * - Text input: string
* - Number: number * - 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 { private getValue(el: HTMLFormElement): boolean | string | number {
// One needs to differentiate between e.g. checkboxes (value="on") and select boxes etc. // 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. * Handle clickable rolls.
* @param {JQuery.ClickEvent} event The originating click event * @param event - The originating click event
* @private
*/ */
private _onRoll(event: JQuery.ClickEvent): void { protected _onRoll(event: JQuery.ClickEvent): void {
event.preventDefault(); event.preventDefault();
const element = event.currentTarget; const element = event.currentTarget;
const dataset = element.dataset; const dataset = element.dataset;
@ -209,10 +215,13 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
} }
/** @override */ /** @override */
async _onDropItem(event: DragEvent, data: Parameters<typeof DS4Item.fromDropData>[0]): Promise<unknown> { protected async _onDropItem(
const item = await Item.fromDropData(data); event: DragEvent,
if (item && !this.actor.canOwnItemType(item.data.type as ItemType)) { data: { type: "Item" } & (DeepPartial<ActorSheet.OwnedItemData<DS4Actor>> | { pack: string } | { id: string }),
ui.notifications.warn( ): 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", { game.i18n.format("DS4.WarningActorCannotOwnItem", {
actorName: this.actor.name, actorName: this.actor.name,
actorType: this.actor.data.type, actorType: this.actor.data.type,

View file

@ -1,11 +1,34 @@
import { DS4ActorSheet } from "./actor-sheet"; import { DS4ActorSheet } from "./actor-sheet";
/**
* The Sheet class for DS4 Character Actors
*/
export class DS4CharacterActorSheet extends DS4ActorSheet { export class DS4CharacterActorSheet extends DS4ActorSheet {
/** @override */ /** @override */
static get defaultOptions(): FormApplicationOptions { static get defaultOptions(): BaseEntitySheet.Options {
return mergeObject(super.defaultOptions, { const superDefaultOptions = super.defaultOptions;
return mergeObject(superDefaultOptions, {
classes: ["ds4", "sheet", "actor", "character"], classes: ["ds4", "sheet", "actor", "character"],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "inventory" }], 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"; import { DS4ActorSheet } from "./actor-sheet";
/**
* The Sheet class for DS4 Creature Actors
*/
export class DS4CreatureActorSheet extends DS4ActorSheet { export class DS4CreatureActorSheet extends DS4ActorSheet {
/** @override */ /** @override */
static get defaultOptions(): FormApplicationOptions { static get defaultOptions(): BaseEntitySheet.Options {
return mergeObject(super.defaultOptions, { const superDefaultOptions = super.defaultOptions;
return mergeObject(superDefaultOptions, {
classes: ["ds4", "sheet", "actor", "creature"], classes: ["ds4", "sheet", "actor", "creature"],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "inventory" }], 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

@ -8,6 +8,12 @@ export const DS4 = {
|____/ \___/|_| \_|\____|_____\___/|_| \_|____/|_____/_/ \_\_| |_____|_| \_\____/ |_| |____/ \___/|_| \_|\____|_____\___/|_| \_|____/|_____/_/ \_\_| |_____|_| \_\____/ |_|
=============================================================================================`, =============================================================================================`,
/**
* A dictionary of dictionaries each mapping keys to localized strings
* resp. their localization keys.
* The localization is assumed to take place on each reload.
*/
i18n: {
/** /**
* Define the set of acttack types that can be performed with weapon items * Define the set of acttack types that can be performed with weapon items
*/ */
@ -17,23 +23,6 @@ export const DS4 = {
meleeRanged: "DS4.AttackTypeMeleeRanged", meleeRanged: "DS4.AttackTypeMeleeRanged",
}, },
/**
* Define the file paths to icon images
*/
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",
},
/**
* 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 * Define the set of item availabilties
*/ */
@ -87,7 +76,7 @@ export const DS4 = {
}, },
/** /**
* Define the set of armor materials, used to determine if a characer may wear the armor without additional penalties * Define the set of armor materials, used to determine if a character may wear the armor without additional penalties
*/ */
armorMaterialTypes: { armorMaterialTypes: {
cloth: "DS4.ArmorMaterialTypeCloth", cloth: "DS4.ArmorMaterialTypeCloth",
@ -209,23 +198,6 @@ export const DS4 = {
eyeColor: "DS4.CharacterProfileEyeColor", eyeColor: "DS4.CharacterProfileEyeColor",
specialCharacteristics: "DS4.CharacterProfileSpecialCharacteristics", specialCharacteristics: "DS4.CharacterProfileSpecialCharacteristics",
}, },
/**
* Define the profile info types for handlebars of a character
*/
characterProfileDTypes: {
biography: "String",
gender: "String",
birthday: "String",
birthplace: "String",
age: "Number",
height: "Number",
hairColor: "String",
weight: "Number",
eyeColor: "String",
specialCharacteristics: "String",
},
/** /**
* Define currency elements of a character * Define currency elements of a character
*/ */
@ -319,4 +291,43 @@ export const DS4 = {
blindroll: "DS4.ChatVisibilityBlindRoll", blindroll: "DS4.ChatVisibilityBlindRoll",
selfroll: "DS4.ChatVisibilitySelfRoll", selfroll: "DS4.ChatVisibilitySelfRoll",
}, },
},
/**
* A dictionary of dictionaries mapping keys to icon file paths.
*/
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
*/
spellTypes: {
spellcasting: "systems/ds4/assets/icons/official/combat-values/spellcasting.png",
targetedSpellcasting: "systems/ds4/assets/icons/official/combat-values/targeted-spellcasting.png",
},
},
/**
* Define the profile info types for handlebars of a character
*/
characterProfileDTypes: {
biography: "String",
gender: "String",
birthday: "String",
birthplace: "String",
age: "Number",
height: "Number",
hairColor: "String",
weight: "Number",
eyeColor: "String",
specialCharacteristics: "String",
},
}; };

View file

@ -1,4 +1,3 @@
// Import Modules
import { DS4Actor } from "./actor/actor"; import { DS4Actor } from "./actor/actor";
import { DS4Item } from "./item/item"; import { DS4Item } from "./item/item";
import { DS4ItemSheet } from "./item/item-sheet"; import { DS4ItemSheet } from "./item/item-sheet";
@ -10,7 +9,7 @@ import { createCheckRoll } from "./rolls/check-factory";
import { registerSystemSettings } from "./settings"; import { registerSystemSettings } from "./settings";
import { migration } from "./migrations"; import { migration } from "./migrations";
Hooks.once("init", async function () { Hooks.once("init", async () => {
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`); console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
game.ds4 = { game.ds4 = {
@ -21,29 +20,19 @@ Hooks.once("init", async function () {
migration, migration,
}; };
// Record configuration
CONFIG.DS4 = DS4; CONFIG.DS4 = DS4;
// Define custom Entity classes CONFIG.Actor.entityClass = DS4Actor;
CONFIG.Actor.entityClass = DS4Actor as typeof Actor; CONFIG.Item.entityClass = DS4Item;
CONFIG.Item.entityClass = DS4Item as typeof Item;
// Define localized type labels CONFIG.Actor.typeLabels = DS4.i18n.actorTypes;
CONFIG.Actor.typeLabels = DS4.actorTypes; CONFIG.Item.typeLabels = DS4.i18n.itemTypes;
CONFIG.Item.typeLabels = DS4.itemTypes;
// Configure Dice CONFIG.Dice.types.push(DS4Check);
CONFIG.Dice.types = [Die, DS4Check]; CONFIG.Dice.terms.s = DS4Check;
CONFIG.Dice.terms = {
c: Coin,
d: Die,
s: DS4Check,
};
// Register system settings
registerSystemSettings(); registerSystemSettings();
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet); Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true }); Actors.registerSheet("ds4", DS4CharacterActorSheet, { types: ["character"], makeDefault: true });
Actors.registerSheet("ds4", DS4CreatureActorSheet, { types: ["creature"], 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/combat-values.hbs",
"systems/ds4/templates/actor/partials/profile.hbs", "systems/ds4/templates/actor/partials/profile.hbs",
"systems/ds4/templates/actor/partials/character-progression.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/character-inventory.hbs",
"systems/ds4/templates/actor/partials/creature-inventory.hbs", "systems/ds4/templates/actor/partials/creature-inventory.hbs",
]; ];
return loadTemplates(templatePaths); return loadTemplates(templatePaths);
} }
/* -------------------------------------------- */
/* Foundry VTT Setup */
/* -------------------------------------------- */
/** /**
* This function runs after game data has been requested and loaded from the servers, so entities exist * This function runs after game data has been requested and loaded from the servers, so entities exist
*/ */
Hooks.once("setup", function () { Hooks.once("setup", () => {
// Localize CONFIG objects once up-front localizeAndSortConfigObjects();
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("ready", function () { Hooks.once("ready", () => {
migration.migrate(); 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 { ModifiableData } from "../common/common-data";
import { DS4 } from "../config"; import { DS4 } from "../config";
export type ItemType = keyof typeof DS4.itemTypes; export type ItemType = keyof typeof DS4.i18n.itemTypes;
export type DS4ItemDataType = export type DS4ItemData =
| DS4Weapon | DS4WeaponData
| DS4Armor | DS4ArmorData
| DS4Shield | DS4ShieldData
| DS4Spell | DS4SpellData
| DS4Trinket | DS4TrinketData
| DS4Equipment | DS4EquipmentData
| DS4Talent | DS4TalentData
| DS4RacialAbility | DS4RacialAbilityData
| DS4Language | DS4LanguageData
| DS4Alphabet | DS4AlphabetData
| DS4SpecialCreatureAbility; | 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"; attackType: "melee" | "ranged" | "meleeRanged";
weaponBonus: number; weaponBonus: number;
opponentDefense: number; opponentDefense: number;
} }
export interface DS4Armor extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective { interface DS4ArmorDataData
extends DS4ItemDataDataBase,
DS4ItemDataDataPhysical,
DS4ItemDataDataEquipable,
DS4ItemDataDataProtective {
armorMaterialType: "cloth" | "leather" | "chain" | "plate"; armorMaterialType: "cloth" | "leather" | "chain" | "plate";
armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves"; armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves";
} }
export interface DS4Talent extends DS4ItemBase { interface DS4TalentDataData extends DS4ItemDataDataBase {
rank: DS4TalentRank; rank: DS4TalentRank;
} }
@ -39,7 +55,7 @@ interface DS4TalentRank extends ModifiableData<number> {
max: number; max: number;
} }
interface DS4Spell extends DS4ItemBase, DS4ItemEquipable { interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable {
spellType: "spellcasting" | "targetedSpellcasting"; spellType: "spellcasting" | "targetedSpellcasting";
bonus: string; bonus: string;
spellCategory: spellCategory:
@ -59,37 +75,41 @@ interface DS4Spell extends DS4ItemBase, DS4ItemEquipable {
scrollPrice: number; scrollPrice: number;
} }
export interface DS4Shield extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable, DS4ItemProtective {} interface DS4ShieldDataData
interface DS4Trinket extends DS4ItemBase, DS4ItemPhysical, DS4ItemEquipable {} extends DS4ItemDataDataBase,
interface DS4Equipment extends DS4ItemBase, DS4ItemPhysical {} DS4ItemDataDataPhysical,
type DS4RacialAbility = DS4ItemBase; DS4ItemDataDataEquipable,
type DS4Language = DS4ItemBase; DS4ItemDataDataProtective {}
type DS4Alphabet = DS4ItemBase; interface DS4TrinketDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {}
interface DS4SpecialCreatureAbility extends DS4ItemBase { interface DS4EquipmentDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical {}
type DS4RacialAbilityDataData = DS4ItemDataDataBase;
type DS4LanguageDataData = DS4ItemDataDataBase;
type DS4AlphabetDataData = DS4ItemDataDataBase;
interface DS4SpecialCreatureAbilityDataData extends DS4ItemDataDataBase {
experiencePoints: number; experiencePoints: number;
} }
// templates // templates
interface DS4ItemBase { interface DS4ItemDataDataBase {
description: string; description: string;
} }
interface DS4ItemPhysical { interface DS4ItemDataDataPhysical {
quantity: number; quantity: number;
price: number; price: number;
availability: "hamlet" | "village" | "city" | "elves" | "dwarves" | "nowhere" | "unset"; availability: "hamlet" | "village" | "city" | "elves" | "dwarves" | "nowhere" | "unset";
storageLocation: string; 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; return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input;
} }
interface DS4ItemEquipable { interface DS4ItemDataDataEquipable {
equipped: boolean; equipped: boolean;
} }
interface DS4ItemProtective { interface DS4ItemDataDataProtective {
armorValue: number; armorValue: number;
} }

View file

@ -1,19 +1,36 @@
import { DS4 } from "../config";
import { DS4Item } from "./item"; import { DS4Item } from "./item";
import { DS4ItemDataType, isDS4ItemDataTypePhysical } from "./item-data"; import { isDS4ItemDataTypePhysical } from "./item-data";
/** /**
* Extend the basic ItemSheet with some very simple modifications * The Sheet class for DS4 Items
* @extends {ItemSheet}
*/ */
export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> { export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
/** @override */ /** @override */
static get defaultOptions(): FormApplicationOptions { static get defaultOptions(): BaseEntitySheet.Options {
return mergeObject(super.defaultOptions, { const superDefaultOptions = super.defaultOptions;
return mergeObject(superDefaultOptions, {
width: 530, width: 530,
height: 400, height: 400,
classes: ["ds4", "sheet", "item"], classes: ["ds4", "sheet", "item"],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }], tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
scrollY: [".sheet-body"], 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`; return `${path}/${this.item.data.type}-sheet.hbs`;
} }
/* -------------------------------------------- */
/** @override */ /** @override */
getData(): ItemSheetData<DS4ItemDataType, DS4Item> { async getData(): Promise<ItemSheet.Data<DS4Item>> {
const data = { const data = {
...super.getData(), ...(await super.getData()),
config: CONFIG.DS4, config: DS4,
isOwned: this.item.isOwned, isOwned: this.item.isOwned,
actor: this.item.actor, actor: this.item.actor,
isPhysical: isDS4ItemDataTypePhysical(this.item.data.data), isPhysical: isDS4ItemDataTypePhysical(this.item.data.data),
@ -37,10 +52,8 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
return data; return data;
} }
/* -------------------------------------------- */
/** @override */ /** @override */
setPosition(options: ApplicationPosition = {}): ApplicationPosition { setPosition(options: Partial<Application.Position> = {}): Application.Position {
const position = super.setPosition(options); const position = super.setPosition(options);
if ("find" in this.element) { if ("find" in this.element) {
const sheetBody = this.element.find(".sheet-body"); const sheetBody = this.element.find(".sheet-body");
@ -52,8 +65,6 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
return position; return position;
} }
/* -------------------------------------------- */
/** @override */ /** @override */
activateListeners(html: JQuery): void { activateListeners(html: JQuery): void {
super.activateListeners(html); super.activateListeners(html);
@ -65,13 +76,13 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
/** /**
* Handle management of ActiveEffects. * 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(); event.preventDefault();
if (this.item.isOwned) { 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 a = event.currentTarget;
const li = $(a).parents(".effect"); const li = $(a).parents(".effect");
@ -81,7 +92,7 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
return this._createActiveEffect(); return this._createActiveEffect();
case "edit": case "edit":
const effect = this.item.effects.get(li.data("effectId")); const effect = this.item.effects.get(li.data("effectId"));
return effect.sheet.render(true); return effect?.sheet.render(true);
case "delete": { case "delete": {
return this.item.deleteEmbeddedEntity("ActiveEffect", li.data("effectId")); 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. * 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 label = `New Effect`;
const createData = { const createData = {
@ -101,7 +112,7 @@ export class DS4ItemSheet extends ItemSheet<DS4ItemDataType, DS4Item> {
transfer: true, transfer: true,
}; };
const effect = await ActiveEffect.create(createData, this.item); const effect = ActiveEffect.create(createData, this.item);
return effect.create({}); return effect.create({});
} }
} }

View file

@ -1,28 +1,20 @@
import { DS4Actor } from "../actor/actor"; import { DS4ItemData } from "./item-data";
import { DS4ActorDataType } from "../actor/actor-data";
import { DS4ItemDataType, DS4Talent } from "./item-data";
/** /**
* Extend the basic Item with some very simple modifications. * The Item class for DS4
* @extends {Item}
*/ */
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 { prepareData(): void {
super.prepareData(); super.prepareData();
this.prepareDerivedData(); this.prepareDerivedData();
// Get the Item's data
// const itemData = this.data;
// const actorData = this.actor ? this.actor.data : {};
// const data = itemData.data;
} }
prepareDerivedData(): void { prepareDerivedData(): void {
if (this.type === "talent") { if (this.data.type === "talent") {
const data = this.data.data as DS4Talent; const data = this.data.data;
data.rank.total = data.rank.base + data.rank.mod; data.rank.total = data.rank.base + data.rank.mod;
} }
} }

View file

@ -1,7 +1,7 @@
import { migrate as migrate001 } from "./migrations/001"; import { migrate as migrate001 } from "./migrations/001";
async function migrate(): Promise<void> { async function migrate(): Promise<void> {
if (!game.user.isGM) { if (!game.user?.isGM) {
return; return;
} }
@ -18,14 +18,14 @@ async function migrate(): Promise<void> {
} }
async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> { async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> {
if (!game.user.isGM) { if (!game.user?.isGM) {
return; return;
} }
const migrationsToExecute = migrations.slice(oldMigrationVersion, targetMigrationVersion); const migrationsToExecute = migrations.slice(oldMigrationVersion, targetMigrationVersion);
if (migrationsToExecute.length > 0) { if (migrationsToExecute.length > 0) {
ui.notifications.info( ui.notifications?.info(
game.i18n.format("DS4.InfoSystemUpdateStart", { game.i18n.format("DS4.InfoSystemUpdateStart", {
currentVersion: oldMigrationVersion, currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion, targetVersion: targetMigrationVersion,
@ -40,7 +40,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
await migration(); await migration();
game.settings.set("ds4", "systemMigrationVersion", currentMigrationVersion); game.settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
} catch (err) { } catch (err) {
ui.notifications.error( ui.notifications?.error(
game.i18n.format("DS4.ErrorDuringMigration", { game.i18n.format("DS4.ErrorDuringMigration", {
currentVersion: oldMigrationVersion, currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion, targetVersion: targetMigrationVersion,
@ -54,7 +54,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
} }
} }
ui.notifications.info( ui.notifications?.info(
game.i18n.format("DS4.InfoSystemUpdateCompleted", { game.i18n.format("DS4.InfoSystemUpdateCompleted", {
currentVersion: oldMigrationVersion, currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion, targetVersion: targetMigrationVersion,

View file

@ -1,5 +1,5 @@
export async function migrate(): Promise<void> { export async function migrate(): Promise<void> {
for (const a of game.actors.entities) { for (const a of game.actors?.entities ?? []) {
const updateData = getActorUpdateData(); const updateData = getActorUpdateData();
console.log(`Migrating actor ${a.name}`); console.log(`Migrating actor ${a.name}`);
await a.update(updateData, { enforceTypes: false }); await a.update(updateData, { enforceTypes: false });
@ -18,7 +18,7 @@ function getActorUpdateData(): Record<string, unknown> {
"rangedAttack", "rangedAttack",
"spellcasting", "spellcasting",
"targetedSpellcasting", "targetedSpellcasting",
].reduce((acc, curr) => { ].reduce((acc: Partial<Record<string, { "-=base": null }>>, curr) => {
acc[curr] = { "-=base": null }; acc[curr] = { "-=base": null };
return acc; return acc;
}, {}), }, {}),

View file

@ -4,13 +4,13 @@ import { DS4 } from "../config";
* Provides default values for all arguments the `CheckFactory` expects. * Provides default values for all arguments the `CheckFactory` expects.
*/ */
class DefaultCheckOptions implements DS4CheckFactoryOptions { class DefaultCheckOptions implements DS4CheckFactoryOptions {
maxCritSuccess = 1; readonly maxCritSuccess = 1;
minCritFailure = 20; readonly minCritFailure = 20;
useSlayingDice = false; readonly useSlayingDice = false;
rollMode: DS4RollMode = "roll"; readonly rollMode: DS4RollMode = "roll";
mergeWith(other: Partial<DS4CheckFactoryOptions>): DS4CheckFactoryOptions { mergeWith(other: Partial<DS4CheckFactoryOptions>): DS4CheckFactoryOptions {
return { ...this, ...other } as DS4CheckFactoryOptions; return { ...this, ...other };
} }
} }
@ -28,13 +28,13 @@ class CheckFactory {
private gmModifier: number, private gmModifier: number,
passedOptions: Partial<DS4CheckFactoryOptions> = {}, passedOptions: Partial<DS4CheckFactoryOptions> = {},
) { ) {
this.checkOptions = new DefaultCheckOptions().mergeWith(passedOptions); this.checkOptions = defaultCheckOptions.mergeWith(passedOptions);
} }
private checkOptions: DS4CheckFactoryOptions; private checkOptions: DS4CheckFactoryOptions;
async execute(): Promise<ChatMessage | unknown> { async execute(): Promise<ChatMessage | unknown> {
const rollCls: typeof Roll = CONFIG.Dice.rolls[0]; const rollCls = CONFIG.Dice.rolls[0];
const formula = [ const formula = [
"ds", "ds",
@ -45,7 +45,6 @@ class CheckFactory {
const roll = new rollCls(formula); const roll = new rollCls(formula);
const rollModeTemplate = this.checkOptions.rollMode; const rollModeTemplate = this.checkOptions.rollMode;
console.log(rollModeTemplate);
return roll.toMessage({}, { rollMode: rollModeTemplate, create: true }); 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. * 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 targetValue - The Check Target Number ("CTN")
* @param options {Partial<DS4CheckFactoryOptions>} Options changing the behaviour of the roll and message. * @param options - Options changing the behavior of the roll and message.
*/ */
export async function createCheckRoll( export async function createCheckRoll(
targetValue: number, targetValue: number,
@ -86,6 +85,8 @@ export async function createCheckRoll(
// Ask for additional required data; // Ask for additional required data;
const gmModifierData = await askGmModifier(targetValue, options); const gmModifierData = await askGmModifier(targetValue, options);
const newTargetValue = gmModifierData.checkTargetValue ?? targetValue;
const gmModifier = gmModifierData.gmModifier ?? 0;
const newOptions: Partial<DS4CheckFactoryOptions> = { const newOptions: Partial<DS4CheckFactoryOptions> = {
maxCritSuccess: gmModifierData.maxCritSuccess ?? options.maxCritSuccess ?? undefined, maxCritSuccess: gmModifierData.maxCritSuccess ?? options.maxCritSuccess ?? undefined,
minCritFailure: gmModifierData.minCritFailure ?? options.minCritFailure ?? undefined, minCritFailure: gmModifierData.minCritFailure ?? options.minCritFailure ?? undefined,
@ -94,7 +95,7 @@ export async function createCheckRoll(
}; };
// Create Factory // Create Factory
const cf = new CheckFactory(gmModifierData.checkTargetValue, gmModifierData.gmModifier, newOptions); const cf = new CheckFactory(newTargetValue, gmModifier, newOptions);
// Possibly additional processing // Possibly additional processing
@ -108,13 +109,13 @@ export async function createCheckRoll(
* @notes * @notes
* At the moment, this asks for more data than it will do after some iterations. * 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( async function askGmModifier(
targetValue: number, targetValue: number,
options: Partial<DS4CheckFactoryOptions> = {}, options: Partial<DS4CheckFactoryOptions> = {},
{ template, title }: { template?: string; title?: string } = {}, { template, title }: { template?: string; title?: string } = {},
): Promise<IntermediateGmModifierData> { ): Promise<Partial<IntermediateGmModifierData>> {
// Render model interface and return value // Render model interface and return value
const usedTemplate = template ?? "systems/ds4/templates/roll/roll-options.hbs"; const usedTemplate = template ?? "systems/ds4/templates/roll/roll-options.hbs";
const usedTitle = title ?? game.i18n.localize("DS4.RollDialogDefaultTitle"); const usedTitle = title ?? game.i18n.localize("DS4.RollDialogDefaultTitle");
@ -124,7 +125,7 @@ async function askGmModifier(
checkTargetValue: targetValue, checkTargetValue: targetValue,
maxCritSuccess: options.maxCritSuccess ?? defaultCheckOptions.maxCritSuccess, maxCritSuccess: options.maxCritSuccess ?? defaultCheckOptions.maxCritSuccess,
minCritFailure: options.minCritFailure ?? defaultCheckOptions.minCritFailure, minCritFailure: options.minCritFailure ?? defaultCheckOptions.minCritFailure,
rollModes: rollModes, rollMode: options.rollMode,
config: DS4, config: DS4,
}; };
const renderedHtml = await renderTemplate(usedTemplate, templateData); const renderedHtml = await renderTemplate(usedTemplate, templateData);
@ -133,14 +134,12 @@ async function askGmModifier(
new Dialog( new Dialog(
{ {
title: usedTitle, title: usedTitle,
close: () => {
// Don't do anything
},
content: renderedHtml, content: renderedHtml,
buttons: { buttons: {
ok: { ok: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("DS4.RollDialogOkButton"), label: game.i18n.localize("DS4.RollDialogOkButton"),
callback: (html: HTMLElement | JQuery) => { callback: (html) => {
if (!("jquery" in html)) { if (!("jquery" in html)) {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorUnexpectedHtmlType", { game.i18n.format("DS4.ErrorUnexpectedHtmlType", {
@ -150,39 +149,40 @@ async function askGmModifier(
); );
} else { } else {
const innerForm = html[0].querySelector("form"); const innerForm = html[0].querySelector("form");
if (!innerForm) {
throw new Error(
game.i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
);
}
resolve(innerForm); resolve(innerForm);
} }
}, },
}, },
cancel: { cancel: {
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("DS4.RollDialogCancelButton"), label: game.i18n.localize("DS4.RollDialogCancelButton"),
callback: () => {
// Don't do anything
},
}, },
}, },
default: "ok", default: "ok",
}, },
{}, { jQuery: true },
).render(true); ).render(true);
}); });
const dialogForm = await dialogPromise; const dialogForm = await dialogPromise;
return parseDialogFormData(dialogForm, targetValue); return parseDialogFormData(dialogForm);
} }
/** /**
* Extracts Dialog data from the returned DOM element. * Extracts Dialog data from the returned DOM element.
* @param formData {HTMLFormElement} The filed dialog * @param formData - The filed dialog
* @param targetValue {number} The previously known target value (slated for removal once data automation is available)
*/ */
function parseDialogFormData(formData: HTMLFormElement, targetValue: number): IntermediateGmModifierData { function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmModifierData> {
return { return {
checkTargetValue: parseInt(formData["ctv"]?.value) ?? targetValue, checkTargetValue: parseInt(formData["ctv"]?.value),
gmModifier: parseInt(formData["gmmod"]?.value) ?? 0, gmModifier: parseInt(formData["gmmod"]?.value),
maxCritSuccess: parseInt(formData["maxcoup"]?.value) ?? defaultCheckOptions.maxCritSuccess, maxCritSuccess: parseInt(formData["maxcoup"]?.value),
minCritFailure: parseInt(formData["minfumble"]?.value) ?? defaultCheckOptions.minCritFailure, minCritFailure: parseInt(formData["minfumble"]?.value),
useSlayingDice: false, rollMode: formData["visibility"]?.value,
rollMode: formData["visibility"]?.value ?? defaultCheckOptions.rollMode,
}; };
} }

View file

@ -56,8 +56,8 @@ export class DS4Check extends DiceTerm {
} }
} }
success = null; success: boolean | null = null;
failure = null; failure: boolean | null = null;
targetValue = DS4Check.DEFAULT_TARGET_VALUE; targetValue = DS4Check.DEFAULT_TARGET_VALUE;
minCritFailure = DS4Check.DEFAULT_MIN_CRIT_FAILURE; minCritFailure = DS4Check.DEFAULT_MIN_CRIT_FAILURE;
maxCritSuccess = DS4Check.DEFAULT_MAX_CRIT_SUCCESS; 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 // DS4 only allows recursive explosions
explode(modifier: string): this { explode(modifier: string): void {
const rgx = /[xX]/; const rgx = /[xX]/;
const match = modifier.match(rgx); const match = modifier.match(rgx);
if (!match) return this; if (!match) return;
this.results = (this.results as Array<RollResult>) this.results = (this.results as Array<RollResult>)
.map((r) => { .map((r) => {
@ -110,7 +105,7 @@ export class DS4Check extends DiceTerm {
let checked = 0; let checked = 0;
while (checked < intermediateResults.length) { while (checked < intermediateResults.length) {
const r = (intermediateResults as Array<RollResult>)[checked]; const r = intermediateResults[checked];
checked++; checked++;
if (!r.active) continue; if (!r.active) continue;
@ -135,7 +130,7 @@ export class DS4Check extends DiceTerm {
static DENOMINATION = "s"; static DENOMINATION = "s";
static MODIFIERS = { static MODIFIERS = {
x: "explode", x: "explode",
c: "noop", // Modifier is consumed in constructor for target value c: (): void => undefined, // Modifier is consumed in constructor for crit
v: "noop", // Modifier is consumed in constructor for target value 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; public slayingDiceRepetition = false;
mergeWith(other: Partial<RollOptions>): RollOptions { 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. * 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 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 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 dice - optional, pass already thrown dice that are used instead of rolling new ones.
*/ */
export function ds4roll( export function ds4roll(
checkTargetValue: number, checkTargetValue: number,
rollOptions: Partial<RollOptions> = {}, rollOptions: Partial<RollOptions> = {},
dice: Array<number> = null, dice: Array<number> = [],
): RollResult { ): RollResult {
if (checkTargetValue <= 20) { if (checkTargetValue <= 20) {
return rollCheckSingleDie(checkTargetValue, rollOptions, dice); return rollCheckSingleDie(checkTargetValue, rollOptions, dice);
@ -27,20 +27,20 @@ export function ds4roll(
* This is not intended for direct usage. Use * This is not intended for direct usage. Use
* {@link ds4roll | the function that is not bound to an amount of Dice} instead. * {@link ds4roll | the function that is not bound to an amount of Dice} instead.
* *
* @param {number} checkTargetValue - The target value to check against. * @param 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 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 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( export function rollCheckSingleDie(
checkTargetValue: number, checkTargetValue: number,
rollOptions: Partial<RollOptions>, rollOptions: Partial<RollOptions>,
dice: Array<number> = null, dice: Array<number> = [],
): RollResult { ): RollResult {
const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
if (dice?.length != 1) { if (dice.length != 1) {
dice = [new DS4RollProvider().getNextRoll()]; dice = [new DS4RollProvider().getNextRoll()];
} }
const usedDice = dice; const usedDice = dice;
@ -66,22 +66,22 @@ export function rollCheckSingleDie(
* This is not intended for direct usage. Use * This is not intended for direct usage. Use
* {@link ds4roll | the function that is not bound to an amount of Dice} instead. * {@link ds4roll | the function that is not bound to an amount of Dice} instead.
* *
* @param {number} targetValue- - The target value to check against. * @param 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 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 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( export function rollCheckMultipleDice(
targetValue: number, targetValue: number,
rollOptions: Partial<RollOptions>, rollOptions: Partial<RollOptions>,
dice: Array<number> = null, dice: Array<number> = [],
): RollResult { ): RollResult {
const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
const remainderTargetValue = targetValue % 20; const remainderTargetValue = targetValue % 20;
const numberOfDice = Math.ceil(targetValue / 20); const numberOfDice = Math.ceil(targetValue / 20);
if (!dice || dice.length != numberOfDice) { if (dice.length != numberOfDice) {
dice = new DS4RollProvider().getNextRolls(numberOfDice); dice = new DS4RollProvider().getNextRolls(numberOfDice);
} }
const usedDice = dice; const usedDice = dice;

View file

@ -1,3 +1,4 @@
import { partition, zip } from "../common/utils";
import { RollOptions } from "./roll-data"; import { RollOptions } from "./roll-data";
/** /**
@ -8,9 +9,9 @@ import { RollOptions } from "./roll-data";
* @private_remarks * @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! * 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 dice - The dice values.
* @param {RollOptions} usedOptions - Options that affect the check's behaviour. * @param usedOptions - Options that affect the check's behavior.
* @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. * @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 { export function separateCriticalHits(dice: Array<number>, usedOptions: RollOptions): CritsAndNonCrits {
const [critSuccesses, otherRolls] = partition(dice, (v: number) => { 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>]; 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. * Calculates if a critical success should be moved to the last position in order to maximize the check's result.
* *
* @example * @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. * 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: * This method would be called as follows:
* ``` * ```ts
* isDiceSwapNecessary([[1], [19]], 11) * 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 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. * @param 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. * @returns Bool indicating whether a critical success has to be used as the last dice.
*/ */
export function isDiceSwapNecessary( export function isDiceSwapNecessary(
[critSuccesses, otherRolls]: CritsAndNonCrits, [critSuccesses, otherRolls]: CritsAndNonCrits,
@ -81,7 +61,7 @@ export function isDiceSwapNecessary(
* *
* @internal * @internal
* *
* @param {RollOptions} opts the roll options to check against * @param opts - The roll options to check against
*/ */
export function isSlayingDiceRepetition(opts: RollOptions): boolean { export function isSlayingDiceRepetition(opts: RollOptions): boolean {
return opts.useSlayingDice && opts.slayingDiceRepetition; return opts.useSlayingDice && opts.slayingDiceRepetition;
@ -92,9 +72,9 @@ export function isSlayingDiceRepetition(opts: RollOptions): boolean {
* *
* @internal * @internal
* *
* @param assignedRollResults The dice values in the order of usage. * @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 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 rollOptions - Config object containing options that change the way dice results are handled.
* *
* @returns {number} The total check value. * @returns {number} The total check value.
*/ */
@ -118,22 +98,3 @@ export function calculateRollResult(
.map(([v]) => v) .map(([v]) => v)
.reduce((a, b) => a + b); .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", "name": "ds4",
"title": "Dungeonslayers 4", "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).", "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", "minimumCoreVersion": "0.7.9",
"compatibleCoreVersion": "0.7.9", "compatibleCoreVersion": "0.7.9",
"templateVersion": 2, "templateVersion": 2,
@ -43,7 +43,7 @@
"primaryTokenAttribute": "combatValues.hitPoints", "primaryTokenAttribute": "combatValues.hitPoints",
"url": "https://git.f3l.de/dungeonslayers/ds4", "url": "https://git.f3l.de/dungeonslayers/ds4",
"manifest": "https://git.f3l.de/dungeonslayers/ds4/-/raw/latest/src/system.json?inline=false", "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", "license": "MIT",
"initiative": "@combatValues.initiative.total" "initiative": "@combatValues.initiative.total"
} }

View file

@ -13,20 +13,20 @@
<div class="flexrow basic-properties"> <div class="flexrow basic-properties">
<div class="basic-property"> <div class="basic-property">
<label class="basic-property-label" <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}}" <input type="text" name="data.baseInfo.race" id="data.baseInfo.race" value="{{data.baseInfo.race}}"
data-dtype="String" /> data-dtype="String" />
</div> </div>
<div class="basic-property"> <div class="basic-property">
<label class="basic-property-label" <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" <input id="data.baseInfo.culture" type="text" name="data.baseInfo.culture"
value="{{data.baseInfo.culture}}" value="{{data.baseInfo.culture}}"
data-dtype="String" /> data-dtype="String" />
</div> </div>
<div class="basic-property flex125"> <div class="basic-property flex125">
<label class="basic-property-label" <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"> <div class="flexrow">
<input id="data.progression.progressPoints.used" type="number" <input id="data.progression.progressPoints.used" type="number"
name="data.progression.progressPoints.used" name="data.progression.progressPoints.used"
@ -43,7 +43,7 @@
</div> </div>
<div class="basic-property flex125"> <div class="basic-property flex125">
<label class="basic-property-label" <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"> <div class="flexrow">
<input type="number" name="data.progression.talentPoints.used" <input type="number" name="data.progression.talentPoints.used"
id="data.progression.talentPoints.used" id="data.progression.talentPoints.used"
@ -56,13 +56,13 @@
</div> </div>
<div class="basic-property"> <div class="basic-property">
<label class="basic-property-label" <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" <input type="text" id="data.baseInfo.class" name="data.baseInfo.class"
value="{{data.baseInfo.class}}" data-dtype="String" /> value="{{data.baseInfo.class}}" data-dtype="String" />
</div> </div>
<div class="basic-property"> <div class="basic-property">
<label class="basic-property-label" <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" <input type="text" id="data.baseInfo.heroClass" name="data.baseInfo.heroClass"
value="{{data.baseInfo.heroClass}}" value="{{data.baseInfo.heroClass}}"
data-dtype="String" /> data-dtype="String" />
@ -84,7 +84,9 @@
<a class="item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a> <a class="item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a>
</nav> </nav>
{{!-- Sheet Body --}} <!-- beautify ignore:start -->
<!-- prettier-ignore-start -->
{{!-- Sheet Body (remove indentation to avoid annoying Handlebars auto-indent) --}}
<section class="sheet-body"> <section class="sheet-body">
{{!-- Items Tab --}} {{!-- Items Tab --}}
{{> systems/ds4/templates/actor/partials/character-inventory.hbs}} {{> systems/ds4/templates/actor/partials/character-inventory.hbs}}
@ -104,4 +106,6 @@
editable=editable}} editable=editable}}
</div> </div>
</section> </section>
<!-- prettier-ignore-end -->
<!-- beautify ignore:end -->
</form> </form>

View file

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

View file

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

View file

@ -4,13 +4,13 @@
<h4 class="items-list-title">{{localize 'DS4.CharacterCurrency'}}</h4> <h4 class="items-list-title">{{localize 'DS4.CharacterCurrency'}}</h4>
<ol class="items-list"> <ol class="items-list">
<li class="item flexrow item-header"> <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" <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" /> 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" <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" /> 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" <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" /> id="data.currency.copper" value="{{data.currency.copper}}" data-dtype="Number" />
</li> </li>

View file

@ -1,6 +1,6 @@
<div class="progression flexrow"> <div class="progression flexrow">
<div class="progression-entry"> <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> </h2>
<label for="data.progression.level" class="hidden">Progression Level</label> <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}}" <input class="progression-value" type="number" name="data.progression.level" id="data.progression.level" value="{{data.progression.level}}"
@ -8,7 +8,7 @@
</div> </div>
<div class="progression-entry"> <div class="progression-entry">
<h2 class="progression-label"><label <h2 class="progression-label"><label
for="data.progression.experiencePoints">{{config.characterProgression.experiencePoints}}</label> for="data.progression.experiencePoints">{{config.i18n.characterProgression.experiencePoints}}</label>
</h2> </h2>
<label for="data.progression.experiencePoints" class="hidden">Experience Points</label> <label for="data.progression.experiencePoints" class="hidden">Experience Points</label>
<input class="progression-value" type="number" name="data.progression.experiencePoints" id="data.progression.experiencePoints" <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"> <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 combat-value-key=combat-value-key combat-value-data=(lookup ../data.combatValues
combat-value-key)}} combat-value-key)}}
{{/each}} {{/each}}

View file

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

View file

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

View file

@ -1,15 +1,21 @@
<div class="tab profile" data-group="primary" data-tab="profile"> <div class="tab profile" data-group="primary" data-tab="profile">
<div class="grid grid-2col"> <div class="grid grid-2col">
{{#each data.profile as |profile-data-value profile-data-key|}} {{#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"> <div class="profile-entry">
<label for="data.profile.{{profile-data-key}}"> <label for="data.profile.{{profile-data-key}}">
{{lookup ../config.characterProfile profile-data-key}} {{lookup ../config.i18n.characterProfile profile-data-key}}
</label> </label>
<input type="text" name="data.profile.{{profile-data-key}}" value="{{profile-data-value}}" <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> </div>
{{/if}} {{/if}}
{{/each}} {{/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>
</div> </div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
<img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}" /> <img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}" />
<div class="header-fields flexrow"> <div class="header-fields flexrow">
<h1 class="charname"><input name="name" type="text" value="{{item.name}}" placeholder="Name" /></h1> <h1 class="charname"><input name="name" type="text" value="{{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}} {{> @partial-block}}
</div> </div>
</header> </header>

View file

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

View file

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

View file

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

View file

@ -1,8 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017", "target": "ES2020",
"lib": ["DOM", "ES6", "ES2017"], "lib": ["DOM", "ES2020"],
"types": ["foundry-pc-types"], "types": ["foundry-vtt-types"],
"esModuleInterop": true "esModuleInterop": true,
} "moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"strict": true
},
"include": ["src"]
} }