Merge branch '55-creatures-compendium' of git.f3l.de:dungeonslayers/ds4 into 55-creatures-compendium
This commit is contained in:
commit
0d79e38898
96 changed files with 3236 additions and 3108 deletions
3
.husky/.gitignore.license
Normal file
3
.husky/.gitignore.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
24
package.json
24
package.json
|
@ -2,7 +2,7 @@
|
|||
"private": true,
|
||||
"name": "dungeonslayers4",
|
||||
"description": "An implementation of the Dungeonslayers 4 game system for Foundry Virtual Tabletop.",
|
||||
"version": "0.8.0",
|
||||
"version": "1.1.3",
|
||||
"license": "https://git.f3l.de/dungeonslayers/ds4#licensing",
|
||||
"homepage": "https://git.f3l.de/dungeonslayers/ds4",
|
||||
"repository": {
|
||||
|
@ -52,32 +52,32 @@
|
|||
"postinstall": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@league-of-foundry-developers/foundry-vtt-types": "^0.7.10-0",
|
||||
"@league-of-foundry-developers/foundry-vtt-types": "^0.8.8-5",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
||||
"@typescript-eslint/parser": "^4.28.0",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"chalk": "^4.1.1",
|
||||
"eslint": "^7.29.0",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-jest": "^24.3.6",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-sass": "^5.0.0",
|
||||
"husky": "^6.0.0",
|
||||
"jest": "^27.0.5",
|
||||
"husky": "^7.0.1",
|
||||
"jest": "^27.0.6",
|
||||
"jest-junit": "^12.2.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rollup": "^2.52.3",
|
||||
"rollup": "^2.53.1",
|
||||
"rollup-plugin-typescript2": "^0.30.0",
|
||||
"sass": "1.32.8",
|
||||
"sass": "1.35.2",
|
||||
"semver": "^7.3.5",
|
||||
"ts-jest": "^27.0.3",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "^4.3.4",
|
||||
"typescript": "^4.3.5",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
|
|
@ -5,7 +5,23 @@
|
|||
|
||||
import evaluateCheck from "../../src/module/rolls/check-evaluation";
|
||||
|
||||
Object.defineProperty(globalThis, "game", { value: { i18n: { localize: (key: string) => key } } });
|
||||
class StubGame {
|
||||
constructor() {
|
||||
this.i18n = {
|
||||
localize: (key: string) => key,
|
||||
};
|
||||
}
|
||||
i18n: {
|
||||
localize: (key: string) => string;
|
||||
};
|
||||
}
|
||||
|
||||
const game = new StubGame();
|
||||
|
||||
Object.defineProperties(globalThis, {
|
||||
game: { value: game },
|
||||
Game: { value: StubGame },
|
||||
});
|
||||
|
||||
describe("evaluateCheck with no dice", () => {
|
||||
it("should throw an error.", () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"DS4.UserInteractionAddEffect": "Neuer Effekt",
|
||||
"DS4.UserInteractionEditEffect": "Effekt bearbeiten",
|
||||
"DS4.UserInteractionDeleteEffect": "Effekt löschen",
|
||||
"DS4.EntityImageAltText": "Bild von {name}",
|
||||
"DS4.DocumentImageAltText": "Bild von {name}",
|
||||
"DS4.RollableImageRollableTitle": "Für {name} würfeln",
|
||||
"DS4.DiceOverlayImageAltText": "Bild eines W20",
|
||||
"DS4.NotOwned": "Nicht besessen",
|
||||
|
@ -15,7 +15,7 @@
|
|||
"DS4.HeadingEffects": "Effekte",
|
||||
"DS4.HeadingInventory": "Inventar",
|
||||
"DS4.HeadingProfile": "Profil",
|
||||
"DS4.HeadingTalentsAbilities": "Talente & Fähigkeiten",
|
||||
"DS4.HeadingAbilities": "Fähigkeiten",
|
||||
"DS4.HeadingSpells": "Zaubersprüche",
|
||||
"DS4.HeadingDescription": "Beschreibung",
|
||||
"DS4.HeadingSpecialCreatureAbilities": "Besondere Fähigkeiten",
|
||||
|
@ -121,6 +121,8 @@
|
|||
"DS4.SpellMinimumLevelsSorcerer": "Zugangsstufe für Schwarzmagier",
|
||||
"DS4.SpellMinimumLevelsSorcererAbbr": "Zugangsstufe Sch",
|
||||
"DS4.SpellPrice": "Preis (Gold)",
|
||||
"DS4.ActorName": "Name",
|
||||
"DS4.ActorImageAltText": "Bild des Aktors",
|
||||
"DS4.ActorTypeCharacter": "Charakter",
|
||||
"DS4.ActorTypeCreature": "Kreatur",
|
||||
"DS4.Attribute": "Attribut",
|
||||
|
@ -144,6 +146,14 @@
|
|||
"DS4.CombatValuesRangedAttack": "Schießen",
|
||||
"DS4.CombatValuesSpellcasting": "Zaubern",
|
||||
"DS4.CombatValuesTargetedSpellcasting": "Zielzaubern",
|
||||
"DS4.CombatValuesHitPointsSheet": "Lebenskraft",
|
||||
"DS4.CombatValuesDefenseSheet": "Abwehr",
|
||||
"DS4.CombatValuesInitiativeSheet": "Initiative",
|
||||
"DS4.CombatValuesMovementSheet": "Laufen",
|
||||
"DS4.CombatValuesMeleeAttackSheet": "Schlagen",
|
||||
"DS4.CombatValuesRangedAttackSheet": "Schießen",
|
||||
"DS4.CombatValuesSpellcastingSheet": "Zaubern",
|
||||
"DS4.CombatValuesTargetedSpellcastingSheet": "Zielzaubern",
|
||||
"DS4.CharacterBaseInfoRace": "Volk",
|
||||
"DS4.CharacterBaseInfoClass": "Klasse",
|
||||
"DS4.CharacterBaseInfoHeroClass": "Heldenklasse",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"DS4.UserInteractionAddEffect": "Add Effect",
|
||||
"DS4.UserInteractionEditEffect": "Edit Effect",
|
||||
"DS4.UserInteractionDeleteEffect": "Delete Effect",
|
||||
"DS4.EntityImageAltText": "Image of {name}",
|
||||
"DS4.DocumentImageAltText": "Image of {name}",
|
||||
"DS4.RollableImageRollableTitle": "Roll for {name}",
|
||||
"DS4.DiceOverlayImageAltText": "Image of a d20",
|
||||
"DS4.NotOwned": "No owner",
|
||||
|
@ -15,7 +15,7 @@
|
|||
"DS4.HeadingEffects": "Effects",
|
||||
"DS4.HeadingInventory": "Inventory",
|
||||
"DS4.HeadingProfile": "Profile",
|
||||
"DS4.HeadingTalentsAbilities": "Talents & Abilities",
|
||||
"DS4.HeadingAbilities": "Abilities",
|
||||
"DS4.HeadingSpells": "Spells",
|
||||
"DS4.HeadingDescription": "Description",
|
||||
"DS4.HeadingSpecialCreatureAbilities": "Special Abilities",
|
||||
|
@ -121,6 +121,8 @@
|
|||
"DS4.SpellMinimumLevelsSorcerer": "Minimum level for Sorcerers",
|
||||
"DS4.SpellMinimumLevelsSorcererAbbr": "Min lvl SRC",
|
||||
"DS4.SpellPrice": "Price (Gold)",
|
||||
"DS4.ActorName": "Name",
|
||||
"DS4.ActorImageAltText": "Image of the Actor",
|
||||
"DS4.ActorTypeCharacter": "Character",
|
||||
"DS4.ActorTypeCreature": "Creature",
|
||||
"DS4.Attribute": "Attribute",
|
||||
|
@ -144,6 +146,14 @@
|
|||
"DS4.CombatValuesRangedAttack": "Ranged Attack",
|
||||
"DS4.CombatValuesSpellcasting": "Spellcasting",
|
||||
"DS4.CombatValuesTargetedSpellcasting": "Targeted Spellcasting",
|
||||
"DS4.CombatValuesHitPointsSheet": "Hit Points",
|
||||
"DS4.CombatValuesDefenseSheet": "Defense",
|
||||
"DS4.CombatValuesInitiativeSheet": "Initiative",
|
||||
"DS4.CombatValuesMovementSheet": "Movement",
|
||||
"DS4.CombatValuesMeleeAttackSheet": "Melee Attack",
|
||||
"DS4.CombatValuesRangedAttackSheet": "RAT",
|
||||
"DS4.CombatValuesSpellcastingSheet": "Spellcasting",
|
||||
"DS4.CombatValuesTargetedSpellcastingSheet": "TSC",
|
||||
"DS4.CharacterBaseInfoRace": "Race",
|
||||
"DS4.CharacterBaseInfoClass": "Class",
|
||||
"DS4.CharacterBaseInfoHeroClass": "Hero Class",
|
||||
|
|
24
src/module/active-effect.ts
Normal file
24
src/module/active-effect.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "./actor/actor";
|
||||
|
||||
declare global {
|
||||
interface DocumentClassConfig {
|
||||
ActiveEffect: typeof DS4ActiveEffect;
|
||||
}
|
||||
}
|
||||
|
||||
export class DS4ActiveEffect extends ActiveEffect {
|
||||
/** @override */
|
||||
apply(actor: DS4Actor, change: foundry.data.ActiveEffectData["changes"][number]): unknown {
|
||||
change.value = Roll.replaceFormulaData(change.value, actor.data);
|
||||
try {
|
||||
change.value = Roll.safeEval(change.value).toString();
|
||||
} catch (e) {
|
||||
// this is a valid case, e.g., if the effect change simply is a string
|
||||
}
|
||||
return super.apply(actor, change);
|
||||
}
|
||||
}
|
87
src/module/actor/actor-data-properties.ts
Normal file
87
src/module/actor/actor-data-properties.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseTotal, ResourceDataBaseTotalMax } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import {
|
||||
DS4CharacterDataSourceDataBaseInfo,
|
||||
DS4CharacterDataSourceDataCurrency,
|
||||
DS4CharacterDataSourceDataProfile,
|
||||
DS4CharacterDataSourceDataProgression,
|
||||
DS4CharacterDataSourceDataSlayerPoints,
|
||||
DS4CreatureDataSourceDataBaseInfo,
|
||||
} from "./actor-data-source";
|
||||
|
||||
declare global {
|
||||
interface DataConfig {
|
||||
Actor: DS4ActorDataProperties;
|
||||
}
|
||||
}
|
||||
|
||||
export type DS4ActorDataProperties = DS4CharacterDataProperties | DS4CreatureDataProperties;
|
||||
|
||||
interface DS4CharacterDataProperties {
|
||||
type: "character";
|
||||
data: DS4CharacterDataPropertiesData;
|
||||
}
|
||||
|
||||
interface DS4CreatureDataProperties {
|
||||
type: "creature";
|
||||
data: DS4CreatureDataPropertiesData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ActorDataPropertiesDataBase {
|
||||
attributes: DS4ActorDataPropertiesDataAttributes;
|
||||
traits: DS4ActorDataPropertiesDataTraits;
|
||||
combatValues: DS4ActorDataPropertiesDataCombatValues;
|
||||
rolling: DS4ActorDataPropertiesDataRolling;
|
||||
checks: DS4ActorDataPropertiesDataChecks;
|
||||
}
|
||||
|
||||
type DS4ActorDataPropertiesDataAttributes = {
|
||||
[Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
type DS4ActorDataPropertiesDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal<number> };
|
||||
|
||||
type DS4ActorDataPropertiesDataCombatValues = {
|
||||
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
|
||||
? ResourceDataBaseTotalMax<number>
|
||||
: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
interface DS4ActorDataPropertiesDataRolling {
|
||||
maximumCoupResult: number;
|
||||
minimumFumbleResult: number;
|
||||
}
|
||||
|
||||
type DS4ActorDataPropertiesDataChecks = {
|
||||
[key in Check]: number;
|
||||
};
|
||||
|
||||
export type Check = keyof typeof DS4.i18n.checks;
|
||||
|
||||
export function isCheck(value: string): value is Check {
|
||||
return Object.keys(DS4.i18n.checks).includes(value);
|
||||
}
|
||||
|
||||
// types
|
||||
|
||||
interface DS4CreatureDataPropertiesData extends DS4ActorDataPropertiesDataBase {
|
||||
baseInfo: DS4CreatureDataSourceDataBaseInfo;
|
||||
}
|
||||
|
||||
interface DS4CharacterDataPropertiesData extends DS4ActorDataPropertiesDataBase {
|
||||
baseInfo: DS4CharacterDataSourceDataBaseInfo;
|
||||
progression: DS4CharacterDataSourceDataProgression;
|
||||
profile: DS4CharacterDataSourceDataProfile;
|
||||
currency: DS4CharacterDataSourceDataCurrency;
|
||||
slayerPoints: DS4CharacterDataPropertiesDataSlayerPoints;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataPropertiesDataSlayerPoints extends DS4CharacterDataSourceDataSlayerPoints {
|
||||
max: number;
|
||||
}
|
|
@ -7,50 +7,56 @@
|
|||
|
||||
import { ModifiableData, ModifiableDataBase, ResourceData, UsableResource } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import { DS4ItemData } from "../item/item-data";
|
||||
|
||||
export type DS4ActorData = DS4CharacterData | DS4CreatureData;
|
||||
|
||||
type ActorType = keyof typeof DS4.i18n.actorTypes;
|
||||
|
||||
export interface DS4ActorDataHelper<T, U extends ActorType> extends Actor.Data<T, DS4ItemData> {
|
||||
type: U;
|
||||
declare global {
|
||||
interface SourceConfig {
|
||||
Actor: DS4ActorDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
type DS4CharacterData = DS4ActorDataHelper<DS4CharacterDataData, "character">;
|
||||
type DS4CreatureData = DS4ActorDataHelper<DS4CreatureDataData, "creature">;
|
||||
export type DS4ActorDataSource = DS4CharacterDataSource | DS4CreatureDataSource;
|
||||
|
||||
interface DS4CharacterDataSource {
|
||||
type: "character";
|
||||
data: DS4CharacterDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4CreatureDataSource {
|
||||
type: "creature";
|
||||
data: DS4CreatureDataSourceData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ActorDataDataBase {
|
||||
attributes: DS4ActorDataDataAttributes;
|
||||
traits: DS4ActorDataDataTraits;
|
||||
combatValues: DS4ActorDataDataCombatValues;
|
||||
interface DS4ActorDataSourceDataBase {
|
||||
attributes: DS4ActorDataSourceDataAttributes;
|
||||
traits: DS4ActorDataSourceDataTraits;
|
||||
combatValues: DS4ActorDataSourceDataCombatValues;
|
||||
}
|
||||
|
||||
type DS4ActorDataDataAttributes = { [Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBase<number> };
|
||||
type DS4ActorDataSourceDataAttributes = { [Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBase<number> };
|
||||
|
||||
type Attribute = keyof DS4ActorDataDataAttributes;
|
||||
type Attribute = keyof DS4ActorDataSourceDataAttributes;
|
||||
|
||||
export function isAttribute(value: unknown): value is Attribute {
|
||||
return (Object.keys(DS4.i18n.attributes) as Array<unknown>).includes(value);
|
||||
}
|
||||
|
||||
type DS4ActorDataDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBase<number> };
|
||||
type DS4ActorDataSourceDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBase<number> };
|
||||
|
||||
type Trait = keyof DS4ActorDataDataTraits;
|
||||
type Trait = keyof DS4ActorDataSourceDataTraits;
|
||||
|
||||
export function isTrait(value: unknown): value is Trait {
|
||||
return (Object.keys(DS4.i18n.traits) as Array<unknown>).includes(value);
|
||||
}
|
||||
|
||||
type DS4ActorDataDataCombatValues = {
|
||||
type DS4ActorDataSourceDataCombatValues = {
|
||||
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
|
||||
? ResourceData<number>
|
||||
: ModifiableData<number>;
|
||||
};
|
||||
|
||||
type CombatValue = keyof DS4ActorDataDataCombatValues;
|
||||
type CombatValue = keyof DS4ActorDataSourceDataCombatValues;
|
||||
|
||||
export function isCombatValue(value: string): value is CombatValue {
|
||||
return (Object.keys(DS4.i18n.combatValues) as Array<unknown>).includes(value);
|
||||
|
@ -58,33 +64,44 @@ export function isCombatValue(value: string): value is CombatValue {
|
|||
|
||||
// types
|
||||
|
||||
interface DS4CharacterDataData extends DS4ActorDataDataBase {
|
||||
baseInfo: DS4CharacterDataDataBaseInfo;
|
||||
progression: DS4CharacterDataDataProgression;
|
||||
language: DS4CharacterDataDataLanguage;
|
||||
profile: DS4CharacterDataDataProfile;
|
||||
currency: DS4CharacterDataDataCurrency;
|
||||
slayerPoints: DS4CharacterDataDataSlayerPoints;
|
||||
interface DS4CreatureDataSourceData extends DS4ActorDataSourceDataBase {
|
||||
baseInfo: DS4CreatureDataSourceDataBaseInfo;
|
||||
}
|
||||
export interface DS4CharacterDataDataBaseInfo {
|
||||
|
||||
export interface DS4CreatureDataSourceDataBaseInfo {
|
||||
loot: string;
|
||||
foeFactor: number;
|
||||
creatureType: CreatureType;
|
||||
sizeCategory: SizeCategory;
|
||||
experiencePoints: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
type CreatureType = keyof typeof DS4.i18n.creatureTypes;
|
||||
|
||||
type SizeCategory = keyof typeof DS4.i18n.creatureSizeCategories;
|
||||
|
||||
interface DS4CharacterDataSourceData extends DS4ActorDataSourceDataBase {
|
||||
baseInfo: DS4CharacterDataSourceDataBaseInfo;
|
||||
progression: DS4CharacterDataSourceDataProgression;
|
||||
profile: DS4CharacterDataSourceDataProfile;
|
||||
currency: DS4CharacterDataSourceDataCurrency;
|
||||
slayerPoints: DS4CharacterDataSourceDataSlayerPoints;
|
||||
}
|
||||
export interface DS4CharacterDataSourceDataBaseInfo {
|
||||
race: string;
|
||||
class: string;
|
||||
heroClass: string;
|
||||
culture: string;
|
||||
}
|
||||
export interface DS4CharacterDataDataProgression {
|
||||
export interface DS4CharacterDataSourceDataProgression {
|
||||
level: number;
|
||||
experiencePoints: number;
|
||||
talentPoints: UsableResource<number>;
|
||||
progressPoints: UsableResource<number>;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataLanguage {
|
||||
languages: string;
|
||||
alphabets: string;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataProfile {
|
||||
export interface DS4CharacterDataSourceDataProfile {
|
||||
biography: string;
|
||||
gender: string;
|
||||
birthday: string;
|
||||
|
@ -97,29 +114,12 @@ export interface DS4CharacterDataDataProfile {
|
|||
specialCharacteristics: string;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataCurrency {
|
||||
export interface DS4CharacterDataSourceDataCurrency {
|
||||
gold: number;
|
||||
silver: number;
|
||||
copper: number;
|
||||
}
|
||||
|
||||
export interface DS4CharacterDataDataSlayerPoints {
|
||||
export interface DS4CharacterDataSourceDataSlayerPoints {
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface DS4CreatureDataData extends DS4ActorDataDataBase {
|
||||
baseInfo: DS4CreatureDataDataBaseInfo;
|
||||
}
|
||||
|
||||
export interface DS4CreatureDataDataBaseInfo {
|
||||
loot: string;
|
||||
foeFactor: number;
|
||||
creatureType: CreatureType;
|
||||
sizeCategory: SizeCategory;
|
||||
experiencePoints: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
type CreatureType = keyof typeof DS4.i18n.creatureTypes;
|
||||
|
||||
type SizeCategory = keyof typeof DS4.i18n.creatureSizeCategories;
|
|
@ -1,77 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseTotal, ResourceDataBaseTotalMax } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import {
|
||||
DS4ActorDataHelper,
|
||||
DS4CharacterDataDataBaseInfo,
|
||||
DS4CharacterDataDataCurrency,
|
||||
DS4CharacterDataDataLanguage,
|
||||
DS4CharacterDataDataProfile,
|
||||
DS4CharacterDataDataProgression,
|
||||
DS4CharacterDataDataSlayerPoints,
|
||||
DS4CreatureDataDataBaseInfo,
|
||||
} from "./actor-data";
|
||||
|
||||
export type DS4ActorPreparedData = DS4CharacterPreparedData | DS4CreaturePreparedData;
|
||||
|
||||
type DS4CharacterPreparedData = DS4ActorDataHelper<DS4CharacterPreparedDataData, "character">;
|
||||
type DS4CreaturePreparedData = DS4ActorDataHelper<DS4CreaturePreparedDataData, "creature">;
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ActorPreparedDataDataBase {
|
||||
attributes: DS4ActorPreparedDataDataAttributes;
|
||||
traits: DS4ActorPreparedDataDataTraits;
|
||||
combatValues: DS4ActorPreparedDataDataCombatValues;
|
||||
rolling: DS4ActorPreparedDataDataRolling;
|
||||
checks: DS4ActorPreparedDataDataChecks;
|
||||
}
|
||||
|
||||
type DS4ActorPreparedDataDataAttributes = {
|
||||
[Key in keyof typeof DS4.i18n.attributes]: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
type DS4ActorPreparedDataDataTraits = { [Key in keyof typeof DS4.i18n.traits]: ModifiableDataBaseTotal<number> };
|
||||
|
||||
type DS4ActorPreparedDataDataCombatValues = {
|
||||
[Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
|
||||
? ResourceDataBaseTotalMax<number>
|
||||
: ModifiableDataBaseTotal<number>;
|
||||
};
|
||||
|
||||
interface DS4ActorPreparedDataDataRolling {
|
||||
maximumCoupResult: number;
|
||||
minimumFumbleResult: number;
|
||||
}
|
||||
|
||||
export type Check = keyof typeof DS4.i18n.checks;
|
||||
|
||||
export function isCheck(value: string): value is Check {
|
||||
return Object.keys(DS4.i18n.checks).includes(value);
|
||||
}
|
||||
|
||||
type DS4ActorPreparedDataDataChecks = {
|
||||
[key in Check]: number;
|
||||
};
|
||||
|
||||
// types
|
||||
|
||||
interface DS4CharacterPreparedDataData extends DS4ActorPreparedDataDataBase {
|
||||
baseInfo: DS4CharacterDataDataBaseInfo;
|
||||
progression: DS4CharacterDataDataProgression;
|
||||
language: DS4CharacterDataDataLanguage;
|
||||
profile: DS4CharacterDataDataProfile;
|
||||
currency: DS4CharacterDataDataCurrency;
|
||||
slayerPoints: DS4CharacterPreparedDataDataSlayerPoints;
|
||||
}
|
||||
|
||||
export interface DS4CharacterPreparedDataDataSlayerPoints extends DS4CharacterDataDataSlayerPoints {
|
||||
max: number;
|
||||
}
|
||||
|
||||
interface DS4CreaturePreparedDataData extends DS4ActorPreparedDataDataBase {
|
||||
baseInfo: DS4CreatureDataDataBaseInfo;
|
||||
}
|
|
@ -5,22 +5,27 @@
|
|||
|
||||
import { ModifiableDataBaseTotal } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { ItemType } from "../item/item-data";
|
||||
import { DS4ArmorPreparedData, DS4ShieldPreparedData } from "../item/item-prepared-data";
|
||||
import { DS4ArmorDataProperties, DS4ShieldDataProperties } from "../item/item-data-properties";
|
||||
import { ItemType } from "../item/item-data-source";
|
||||
import { createCheckRoll } from "../rolls/check-factory";
|
||||
import { DS4ActorData, isAttribute, isTrait } from "./actor-data";
|
||||
import { Check, DS4ActorPreparedData } from "./actor-prepared-data";
|
||||
import { Check } from "./actor-data-properties";
|
||||
import { isAttribute, isTrait } from "./actor-data-source";
|
||||
|
||||
declare global {
|
||||
interface DocumentClassConfig {
|
||||
Actor: typeof DS4Actor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Actor class for DS4
|
||||
*/
|
||||
export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData> {
|
||||
export class DS4Actor extends Actor {
|
||||
/** @override */
|
||||
prepareData(): void {
|
||||
this.data = duplicate(this._data) as DS4ActorPreparedData;
|
||||
if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN;
|
||||
if (!this.data.name) this.data.name = "New " + this.entity;
|
||||
this.data.reset();
|
||||
this.prepareBaseData();
|
||||
this.prepareEmbeddedEntities();
|
||||
this.applyActiveEffectsToBaseData();
|
||||
|
@ -49,6 +54,15 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* We override this with an empty implementation because we have our own custom way of applying
|
||||
* {@link ActiveEffect}s and {@link Actor#prepareEmbeddedEntities} calls this.
|
||||
*/
|
||||
applyActiveEffects(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
applyActiveEffectsToBaseData(): void {
|
||||
// reset overrides because our variant of applying active effects does not set them, it only adds overrides
|
||||
this.overrides = {};
|
||||
|
@ -68,32 +82,30 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
*
|
||||
* @param predicate - The predicate that ActiveEffectChanges need to satisfy in order to be applied
|
||||
*/
|
||||
applyActiveEffectsFiltered(predicate: (change: ActiveEffectChange) => boolean): void {
|
||||
applyActiveEffectsFiltered(predicate: (change: foundry.data.ActiveEffectData["changes"][number]) => boolean): void {
|
||||
const overrides: Record<string, unknown> = {};
|
||||
|
||||
// Organize non-disabled effects by their application priority
|
||||
const changes = this.effects.reduce(
|
||||
(changes: Array<ActiveEffectChange & { effect: ActiveEffect<DS4Actor> }>, e) => {
|
||||
if (e.data.disabled) return changes;
|
||||
const item = this._getOriginatingItemOfActiveEffect(e);
|
||||
if (item?.isNonEquippedEuipable()) return changes;
|
||||
const changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[] =
|
||||
this.effects.reduce(
|
||||
(changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[], e) => {
|
||||
if (e.data.disabled) return changes;
|
||||
const item = this.getOriginatingItemOfActiveEffect(e);
|
||||
if (item?.isNonEquippedEuipable()) return changes;
|
||||
|
||||
const factor = item?.activeEffectFactor ?? 1;
|
||||
const factor = item?.activeEffectFactor ?? 1;
|
||||
|
||||
return changes.concat(
|
||||
e.data.changes.filter(predicate).flatMap((c) => {
|
||||
const duplicatedChange = duplicate(c);
|
||||
duplicatedChange.priority = duplicatedChange.priority ?? duplicatedChange.mode * 10;
|
||||
return Array(factor).fill({
|
||||
...duplicatedChange,
|
||||
effect: e,
|
||||
});
|
||||
}),
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
changes.sort((a, b) => a.priority - b.priority);
|
||||
const newChanges = e.data.changes.filter(predicate).flatMap((c) => {
|
||||
const changeSource = c.toObject();
|
||||
changeSource.priority = changeSource.priority ?? changeSource.mode * 10;
|
||||
return Array(factor).fill({ ...changeSource, effect: e });
|
||||
});
|
||||
|
||||
return changes.concat(newChanges);
|
||||
},
|
||||
[],
|
||||
);
|
||||
changes.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
||||
|
||||
// Apply all changes
|
||||
for (const change of changes) {
|
||||
|
@ -102,11 +114,11 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
}
|
||||
|
||||
// Expand the set of final overrides
|
||||
this.overrides = expandObject({ ...flattenObject(this.overrides), ...overrides });
|
||||
this.overrides = foundry.utils.expandObject({ ...foundry.utils.flattenObject(this.overrides), ...overrides });
|
||||
}
|
||||
|
||||
protected _getOriginatingItemOfActiveEffect(effect: ActiveEffect<DS4Actor>): DS4Item | undefined {
|
||||
return this.items.find((item) => item.uuid === effect.data.origin) ?? undefined;
|
||||
protected getOriginatingItemOfActiveEffect(effect: ActiveEffect): DS4Item | undefined {
|
||||
return this.items.find((item) => item.uuid === effect.data.origin);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,7 +228,7 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
return this.items
|
||||
.map((item) => item.data)
|
||||
.filter(
|
||||
(data): data is DS4ArmorPreparedData | DS4ShieldPreparedData =>
|
||||
(data): data is foundry.data.ItemData & (DS4ArmorDataProperties | DS4ShieldDataProperties) =>
|
||||
data.type === "armor" || data.type === "shield",
|
||||
)
|
||||
.filter((data) => data.data.equipped)
|
||||
|
@ -267,8 +279,13 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
* This only differs from the base implementation by also allowing negative values.
|
||||
* @override
|
||||
*/
|
||||
async modifyTokenAttribute(attribute: string, value: number, isDelta = false, isBar = true): Promise<this> {
|
||||
const current = getProperty(this.data.data, attribute);
|
||||
async modifyTokenAttribute(
|
||||
attribute: string,
|
||||
value: number,
|
||||
isDelta = false,
|
||||
isBar = true,
|
||||
): Promise<this | undefined> {
|
||||
const current = foundry.utils.getProperty(this.data.data, attribute);
|
||||
|
||||
// Determine the updates to make to the actor data
|
||||
let updates: Record<string, number>;
|
||||
|
@ -291,10 +308,10 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
*/
|
||||
async rollCheck(check: Check): Promise<void> {
|
||||
await createCheckRoll(this.data.data.checks[check], {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ActorCheckFlavor", { actor: this.name, check: DS4.i18n.checks[check] }),
|
||||
flavor: getGame().i18n.format("DS4.ActorCheckFlavor", { actor: this.name, check: DS4.i18n.checks[check] }),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -306,10 +323,10 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
const { attribute, trait } = await this.selectAttributeAndTrait();
|
||||
const checkTargetNumber = this.data.data.attributes[attribute].total + this.data.data.traits[trait].total;
|
||||
await createCheckRoll(checkTargetNumber, {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: this.data.data.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ActorGenericCheckFlavor", {
|
||||
flavor: getGame().i18n.format("DS4.ActorGenericCheckFlavor", {
|
||||
actor: this.name,
|
||||
attribute: DS4.i18n.attributes[attribute],
|
||||
trait: DS4.i18n.traits[trait],
|
||||
|
@ -324,27 +341,27 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
const attributeIdentifier = "attribute-trait-selection-attribute";
|
||||
const traitIdentifier = "attribute-trait-selection-trait";
|
||||
return Dialog.prompt({
|
||||
title: game.i18n.localize("DS4.DialogAttributeTraitSelection"),
|
||||
title: getGame().i18n.localize("DS4.DialogAttributeTraitSelection"),
|
||||
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
|
||||
selects: [
|
||||
{
|
||||
label: game.i18n.localize("DS4.Attribute"),
|
||||
label: getGame().i18n.localize("DS4.Attribute"),
|
||||
identifier: attributeIdentifier,
|
||||
options: DS4.i18n.attributes,
|
||||
},
|
||||
{
|
||||
label: game.i18n.localize("DS4.Trait"),
|
||||
label: getGame().i18n.localize("DS4.Trait"),
|
||||
identifier: traitIdentifier,
|
||||
options: DS4.i18n.traits,
|
||||
},
|
||||
],
|
||||
}),
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
const selectedAttribute = html.find(`#${attributeIdentifier}`).val();
|
||||
if (!isAttribute(selectedAttribute)) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedAttribute", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedAttribute", {
|
||||
actualAttribute: selectedAttribute,
|
||||
expectedTypes: Object.keys(DS4.i18n.attributes)
|
||||
.map((attribute) => `'${attribute}'`)
|
||||
|
@ -355,7 +372,7 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
|
|||
const selectedTrait = html.find(`#${traitIdentifier}`).val();
|
||||
if (!isTrait(selectedTrait)) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedTrait", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedTrait", {
|
||||
actualTrait: selectedTrait,
|
||||
expectedTypes: Object.keys(DS4.i18n.traits)
|
||||
.map((attribute) => `'${attribute}'`)
|
||||
|
|
|
@ -7,31 +7,26 @@
|
|||
|
||||
import { ModifiableDataBaseTotal } from "../../common/common-data";
|
||||
import { DS4 } from "../../config";
|
||||
import { getCanvas } from "../../helpers";
|
||||
import { getCanvas, getGame } from "../../helpers";
|
||||
import { DS4Item } from "../../item/item";
|
||||
import { DS4ItemData } from "../../item/item-data";
|
||||
import { getDS4Settings } from "../../settings";
|
||||
import { DS4Settings, getDS4Settings } from "../../settings";
|
||||
import notifications from "../../ui/notifications";
|
||||
import { DS4Actor } from "../actor";
|
||||
import { isCheck } from "../actor-prepared-data";
|
||||
import { isCheck } from "../actor-data-properties";
|
||||
|
||||
/**
|
||||
* The base Sheet class for all DS4 Actors
|
||||
*/
|
||||
export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
||||
// TODO(types): Improve mergeObject in upstream so that it isn't necessary to provide all parameters (see https://github.com/League-of-Foundry-Developers/foundry-vtt-types/issues/272)
|
||||
export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetData> {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor"],
|
||||
height: 620,
|
||||
scrollY: [
|
||||
".values",
|
||||
".inventory",
|
||||
".spells",
|
||||
".talents-abilities",
|
||||
".abilities",
|
||||
".profile",
|
||||
".biography",
|
||||
".special-creature-abilities",
|
||||
|
@ -57,14 +52,14 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* object itemsByType.
|
||||
* @returns The data fed to the template of the actor sheet
|
||||
*/
|
||||
async getData(): Promise<ActorSheet.Data<DS4Actor>> {
|
||||
async getData(): Promise<DS4ActorSheetData> {
|
||||
const itemsByType = Object.fromEntries(
|
||||
Object.entries(this.actor.itemTypes).map(([itemType, items]) => {
|
||||
return [itemType, items.map((item) => item.data).sort((a, b) => (a.sort || 0) - (b.sort || 0))];
|
||||
}),
|
||||
);
|
||||
const data = {
|
||||
...this._addTooltipsToData(await super.getData()),
|
||||
...this.addTooltipsToData(await super.getData()),
|
||||
// Add the localization config to the data:
|
||||
config: DS4,
|
||||
// Add the items explicitly sorted by type to the data:
|
||||
|
@ -74,21 +69,23 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
return data;
|
||||
}
|
||||
|
||||
protected _addTooltipsToData(data: ActorSheet.Data<DS4Actor>): ActorSheet.Data<DS4Actor> {
|
||||
const valueGroups = [data.data.attributes, data.data.traits, data.data.combatValues];
|
||||
protected addTooltipsToData(data: ActorSheet.Data): ActorSheet.Data {
|
||||
const valueGroups = [data.data.data.attributes, data.data.data.traits, data.data.data.combatValues];
|
||||
|
||||
valueGroups.forEach((valueGroup) => {
|
||||
Object.values(valueGroup).forEach((attribute: ModifiableDataBaseTotal<number> & { tooltip?: string }) => {
|
||||
attribute.tooltip = this._getTooltipForValue(attribute);
|
||||
attribute.tooltip = this.getTooltipForValue(attribute);
|
||||
});
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
protected _getTooltipForValue(value: ModifiableDataBaseTotal<number>): string {
|
||||
return `${value.base} (${game.i18n.localize("DS4.TooltipBaseValue")}) + ${value.mod} (${game.i18n.localize(
|
||||
"DS4.TooltipModifier",
|
||||
)}) ➞ ${game.i18n.localize("DS4.TooltipEffects")} ➞ ${value.total}`;
|
||||
protected getTooltipForValue(value: ModifiableDataBaseTotal<number>): string {
|
||||
return `${value.base} (${getGame().i18n.localize("DS4.TooltipBaseValue")}) + ${
|
||||
value.mod
|
||||
} (${getGame().i18n.localize("DS4.TooltipModifier")}) ➞ ${getGame().i18n.localize("DS4.TooltipEffects")} ➞ ${
|
||||
value.total
|
||||
}`;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
|
@ -99,18 +96,18 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
if (!this.options.editable) return;
|
||||
|
||||
// Add Inventory Item
|
||||
html.find(".item-create").on("click", this._onItemCreate.bind(this));
|
||||
html.find(".item-create").on("click", this.onItemCreate.bind(this));
|
||||
|
||||
// Update Inventory Item
|
||||
html.find(".item-edit").on("click", (ev) => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
const id = li.data("itemId");
|
||||
const item = this.actor.getOwnedItem(id);
|
||||
const item = this.actor.items.get(id);
|
||||
if (!item) {
|
||||
throw new Error(game.i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
}
|
||||
if (!item.sheet) {
|
||||
throw new Error(game.i18n.localize("DS4.ErrorUnexpectedError"));
|
||||
throw new Error(getGame().i18n.localize("DS4.ErrorUnexpectedError"));
|
||||
}
|
||||
item.sheet.render(true);
|
||||
});
|
||||
|
@ -118,38 +115,36 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
// Delete Inventory Item
|
||||
html.find(".item-delete").on("click", (ev) => {
|
||||
const li = $(ev.currentTarget).parents(".item");
|
||||
this.actor.deleteOwnedItem(li.data("itemId"));
|
||||
this.actor.deleteEmbeddedDocuments("Item", [li.data("itemId")]);
|
||||
li.slideUp(200, () => this.render(false));
|
||||
});
|
||||
|
||||
html.find(".item-change").on("change", this._onItemChange.bind(this));
|
||||
html.find(".item-change").on("change", this.onItemChange.bind(this));
|
||||
|
||||
html.find(".rollable-item").on("click", this._onRollItem.bind(this));
|
||||
html.find(".rollable-item").on("click", this.onRollItem.bind(this));
|
||||
|
||||
html.find(".rollable-check").on("click", this._onRollCheck.bind(this));
|
||||
html.find(".rollable-check").on("click", this.onRollCheck.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
|
||||
* Handle creating a new embedded Item for the actor using initial data defined in the HTML dataset
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected _onItemCreate(event: JQuery.ClickEvent): Promise<DS4ItemData> {
|
||||
protected onItemCreate(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const header = event.currentTarget;
|
||||
// Get the type of item to create.
|
||||
// Grab any data associated with this control.
|
||||
const { type, ...data } = duplicate(header.dataset);
|
||||
// Initialize a default name.
|
||||
|
||||
const { type, ...data } = foundry.utils.deepClone(header.dataset);
|
||||
|
||||
const name = `New ${type.capitalize()}`;
|
||||
// Prepare the item object.
|
||||
|
||||
const itemData = {
|
||||
name: name,
|
||||
type: type,
|
||||
data: data,
|
||||
};
|
||||
|
||||
// Finally, create the item!
|
||||
return this.actor.createOwnedItem(itemData);
|
||||
DS4Item.create(itemData, { parent: this.actor });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,11 +153,15 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* Assumes the item property is given as the value of the HTML element property 'data-property'.
|
||||
* @param ev - The originating change event
|
||||
*/
|
||||
protected _onItemChange(ev: JQuery.ChangeEvent): void {
|
||||
protected onItemChange(ev: JQuery.ChangeEvent): void {
|
||||
ev.preventDefault();
|
||||
const el: HTMLFormElement = $(ev.currentTarget).get(0);
|
||||
const id = $(ev.currentTarget).parents(".item").data("itemId");
|
||||
const item = duplicate(this.actor.getOwnedItem(id));
|
||||
const item = this.actor.items.get(id);
|
||||
if (!item) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
}
|
||||
const itemObject = item.toObject();
|
||||
const property: string | undefined = $(ev.currentTarget).data("property");
|
||||
|
||||
// Early return:
|
||||
|
@ -175,8 +174,8 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
|
||||
// Set new value
|
||||
const newValue = this.getValue(el);
|
||||
setProperty(item, property, newValue);
|
||||
this.actor.updateOwnedItem(item);
|
||||
foundry.utils.setProperty(itemObject, property, newValue);
|
||||
item.update(itemObject);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -238,10 +237,13 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* Handle clickable item rolls.
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected _onRollItem(event: JQuery.ClickEvent): void {
|
||||
protected onRollItem(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const id = $(event.currentTarget).parents(".item").data("itemId");
|
||||
const item = this.actor.getOwnedItem(id);
|
||||
const item = this.actor.items.get(id);
|
||||
if (!item) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorActorDoesNotHaveItem", { id, actor: this.actor.name }));
|
||||
}
|
||||
item.roll().catch((e) => notifications.error(e, { log: true }));
|
||||
}
|
||||
|
||||
|
@ -249,7 +251,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
* Handle clickable check rolls.
|
||||
* @param event - The originating click event
|
||||
*/
|
||||
protected _onRollCheck(event: JQuery.ClickEvent): void {
|
||||
protected onRollCheck(event: JQuery.ClickEvent): void {
|
||||
event.preventDefault();
|
||||
const check = event.currentTarget.dataset["check"];
|
||||
this.actor.rollCheck(check).catch((e) => notifications.error(e, { log: true }));
|
||||
|
@ -263,7 +265,7 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
const check = target.dataset.check;
|
||||
if (!check) return super._onDragStart(event);
|
||||
|
||||
if (!isCheck(check)) throw new Error(game.i18n.format("DS4.ErrorCannotDragMissingCheck", { check }));
|
||||
if (!isCheck(check)) throw new Error(getGame().i18n.format("DS4.ErrorCannotDragMissingCheck", { check }));
|
||||
|
||||
const dragData = {
|
||||
actorId: this.actor.id,
|
||||
|
@ -277,18 +279,11 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
protected async _onDropItem(
|
||||
event: DragEvent,
|
||||
data: { type: "Item" } & (
|
||||
| { data: DeepPartial<ActorSheet.OwnedItemData<DS4Actor>> }
|
||||
| { pack: string }
|
||||
| { id: string }
|
||||
),
|
||||
): Promise<boolean | undefined | ActorSheet.OwnedItemData<DS4Actor>> {
|
||||
protected async _onDropItem(event: DragEvent, data: ActorSheet.DropData.Item): Promise<unknown> {
|
||||
const item = await DS4Item.fromDropData(data);
|
||||
if (item && !this.actor.canOwnItemType(item.data.type)) {
|
||||
notifications.warn(
|
||||
game.i18n.format("DS4.WarningActorCannotOwnItem", {
|
||||
getGame().i18n.format("DS4.WarningActorCannotOwnItem", {
|
||||
actorName: this.actor.name,
|
||||
actorType: this.actor.data.type,
|
||||
itemName: item.name,
|
||||
|
@ -300,3 +295,9 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
|
|||
return super._onDropItem(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
interface DS4ActorSheetData extends ActorSheet.Data<ActorSheet.Options> {
|
||||
config: typeof DS4;
|
||||
itemsByType: Record<string, foundry.data.ItemData[]>;
|
||||
settings: DS4Settings;
|
||||
}
|
||||
|
|
|
@ -9,10 +9,8 @@ import { DS4ActorSheet } from "./actor-sheet";
|
|||
*/
|
||||
export class DS4CharacterActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor", "character"],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,10 +9,8 @@ import { DS4ActorSheet } from "./actor-sheet";
|
|||
*/
|
||||
export class DS4CreatureActorSheet extends DS4ActorSheet {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ActorSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["ds4", "sheet", "actor", "creature"],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ export interface HasTotal<T> {
|
|||
total: T;
|
||||
}
|
||||
|
||||
export interface ModifiableDataTotal<T> extends ModifiableData<T>, HasTotal<T> {}
|
||||
|
||||
export interface ModifiableDataBaseTotal<T> extends ModifiableDataBase<T>, HasTotal<T> {}
|
||||
|
||||
export interface ResourceData<T> extends ModifiableData<T> {
|
||||
|
@ -27,6 +25,10 @@ export interface HasMax<T> {
|
|||
max: T;
|
||||
}
|
||||
|
||||
export interface ModifiableDataBaseMax<T> extends ModifiableDataBase<T>, HasMax<T> {}
|
||||
|
||||
export interface ModifiableDataBaseTotalMax<T> extends ModifiableDataBaseMax<T>, HasTotal<T> {}
|
||||
|
||||
export interface ResourceDataBaseTotalMax<T> extends ResourceData<T>, HasBase<T>, HasTotal<T>, HasMax<T> {}
|
||||
|
||||
export interface UsableResource<T> {
|
||||
|
|
|
@ -162,6 +162,20 @@ export const DS4 = {
|
|||
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting",
|
||||
},
|
||||
|
||||
/**
|
||||
* The what do display in the actor sheets for the combat value text (in some languages, abbreviations are necessary)
|
||||
*/
|
||||
combatValuesSheet: {
|
||||
hitPoints: "DS4.CombatValuesHitPointsSheet",
|
||||
defense: "DS4.CombatValuesDefenseSheet",
|
||||
initiative: "DS4.CombatValuesInitiativeSheet",
|
||||
movement: "DS4.CombatValuesMovementSheet",
|
||||
meleeAttack: "DS4.CombatValuesMeleeAttackSheet",
|
||||
rangedAttack: "DS4.CombatValuesRangedAttackSheet",
|
||||
spellcasting: "DS4.CombatValuesSpellcastingSheet",
|
||||
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcastingSheet",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define the base info of a character
|
||||
*/
|
||||
|
|
41
src/module/fonts.ts
Normal file
41
src/module/fonts.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
type CSSOMString = string;
|
||||
type FontFaceLoadStatus = "unloaded" | "loading" | "loaded" | "error";
|
||||
type FontFaceSetStatus = "loading" | "loaded";
|
||||
|
||||
interface FontFace {
|
||||
family: CSSOMString;
|
||||
style: CSSOMString;
|
||||
weight: CSSOMString;
|
||||
stretch: CSSOMString;
|
||||
unicodeRange: CSSOMString;
|
||||
variant: CSSOMString;
|
||||
featureSettings: CSSOMString;
|
||||
variationSettings: CSSOMString;
|
||||
display: CSSOMString;
|
||||
readonly status: FontFaceLoadStatus;
|
||||
readonly loaded: Promise<FontFace>;
|
||||
load(): Promise<FontFace>;
|
||||
}
|
||||
|
||||
interface FontFaceSet {
|
||||
readonly status: FontFaceSetStatus;
|
||||
readonly ready: Promise<FontFaceSet>;
|
||||
check(font: string, text?: string): boolean;
|
||||
load(font: string, text?: string): Promise<FontFace[]>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Document {
|
||||
fonts: FontFaceSet;
|
||||
}
|
||||
}
|
||||
|
||||
const fonts = ["Lora", "Wood Stamp"];
|
||||
|
||||
export async function preloadFonts(): Promise<FontFace[][]> {
|
||||
return Promise.all(fonts.map((font) => document.fonts.load(`1rem ${font}`)));
|
||||
}
|
20
src/module/global.d.ts
vendored
20
src/module/global.d.ts
vendored
|
@ -2,10 +2,20 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
declare namespace ClientSettings {
|
||||
interface Values {
|
||||
"ds4.systemMigrationVersion": number;
|
||||
"ds4.useSlayingDiceForAutomatedChecks": boolean;
|
||||
"ds4.showSlayerPoints": boolean;
|
||||
declare global {
|
||||
namespace ClientSettings {
|
||||
interface Values {
|
||||
"ds4.systemMigrationVersion": number;
|
||||
"ds4.useSlayingDiceForAutomatedChecks": boolean;
|
||||
"ds4.showSlayerPoints": boolean;
|
||||
}
|
||||
}
|
||||
|
||||
namespace PoolTerm {
|
||||
interface Modifiers {
|
||||
x: (this: PoolTerm, modifier: string) => void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
|
@ -6,29 +6,33 @@
|
|||
|
||||
export default async function registerHandlebarsPartials(): Promise<void> {
|
||||
const templatePaths = [
|
||||
"systems/ds4/templates/sheets/actor/components/character-progression.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/actor-header.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/actor-progression.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/biography.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/character-properties.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/check.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/checks.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/combat-value.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/combat-values.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/core-value.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/core-values.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/creature-properties.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/currency.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/item-list-entry.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/item-list-header.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/items-overview.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/overview-add-button.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/overview-control-buttons.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/profile.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/rollable-image.hbs",
|
||||
"systems/ds4/templates/sheets/actor/components/talent-rank-equation.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/abilities.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/biography.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/character-inventory.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/creature-inventory.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/description.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/profile.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/special-creature-abilities.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/spells.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/talents-abilities.hbs",
|
||||
"systems/ds4/templates/sheets/actor/tabs/values.hbs",
|
||||
"systems/ds4/templates/sheets/item/components/body.hbs",
|
||||
"systems/ds4/templates/sheets/item/components/sheet-header.hbs",
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
|
||||
export function getCanvas(): Canvas {
|
||||
if (!(canvas instanceof Canvas) || !canvas.ready) {
|
||||
throw new Error(game.i18n.localize("DS4.ErrorCanvasIsNotInitialized"));
|
||||
throw new Error(getGame().i18n.localize("DS4.ErrorCanvasIsNotInitialized"));
|
||||
}
|
||||
return canvas;
|
||||
}
|
||||
|
||||
export function getGame(): Game {
|
||||
if (!(game instanceof Game)) {
|
||||
throw new Error("Game is not initialized yet."); // Cannot localize this as we would need to access game to do this.
|
||||
}
|
||||
return game;
|
||||
}
|
||||
|
|
|
@ -2,25 +2,27 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { isCheck } from "../actor/actor-prepared-data";
|
||||
import { isCheck } from "../actor/actor-data-properties";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { DS4ItemData } from "../item/item-data";
|
||||
import { createRollCheckMacro } from "../macros/roll-check";
|
||||
import { createRollItemMacro } from "../macros/roll-item";
|
||||
import notifications from "../ui/notifications";
|
||||
|
||||
export default function registerForHotbarDropHook(): void {
|
||||
Hooks.on("hotbarDrop", async (hotbar: Hotbar, data: { type: string } & Record<string, unknown>, slot: string) => {
|
||||
Hooks.on("hotbarDrop", async (hotbar: Hotbar, data: HotbarDropData, slot: string) => {
|
||||
switch (data.type) {
|
||||
case "Item": {
|
||||
if (!("data" in data)) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems"));
|
||||
if (!isItemDropData(data) || !("data" in data)) {
|
||||
return notifications.warn(
|
||||
getGame().i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems"),
|
||||
);
|
||||
}
|
||||
const itemData = data.data as DS4ItemData;
|
||||
const itemData = data.data;
|
||||
|
||||
if (!DS4Item.rollableItemTypes.includes(itemData.type)) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningItemIsNotRollable", {
|
||||
getGame().i18n.format("DS4.WarningItemIsNotRollable", {
|
||||
name: itemData.name,
|
||||
id: itemData._id,
|
||||
type: itemData.type,
|
||||
|
@ -31,10 +33,16 @@ export default function registerForHotbarDropHook(): void {
|
|||
}
|
||||
case "Check": {
|
||||
if (!("data" in data) || typeof data.data !== "string" || !isCheck(data.data)) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningInvalidCheckDropped"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningInvalidCheckDropped"));
|
||||
}
|
||||
return createRollCheckMacro(data.data, slot);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
type HotbarDropData = ActorSheet.DropData.Item | ({ type: string } & Partial<Record<string, unknown>>);
|
||||
|
||||
function isItemDropData(dropData: HotbarDropData): dropData is ActorSheet.DropData.Item {
|
||||
return dropData.type === "Item";
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4ActiveEffect } from "../active-effect";
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { DS4CharacterActorSheet } from "../actor/sheets/character-sheet";
|
||||
import { DS4CreatureActorSheet } from "../actor/sheets/creature-sheet";
|
||||
import { DS4 } from "../config";
|
||||
import { preloadFonts as preloadFonts } from "../fonts";
|
||||
import registerHandlebarsHelpers from "../handlebars/handlebars-helpers";
|
||||
import registerHandlebarsPartials from "../handlebars/handlebars-partials";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import { DS4ItemSheet } from "../item/item-sheet";
|
||||
import logger from "../logger";
|
||||
|
@ -28,7 +31,7 @@ export default function registerForInitHook(): void {
|
|||
async function init() {
|
||||
logger.info(`Initializing the DS4 Game System\n${DS4.ASCII}`);
|
||||
|
||||
game.ds4 = {
|
||||
getGame().ds4 = {
|
||||
DS4Actor,
|
||||
DS4Item,
|
||||
DS4,
|
||||
|
@ -39,8 +42,9 @@ async function init() {
|
|||
|
||||
CONFIG.DS4 = DS4;
|
||||
|
||||
CONFIG.Actor.entityClass = DS4Actor;
|
||||
CONFIG.Item.entityClass = DS4Item;
|
||||
CONFIG.Actor.documentClass = DS4Actor;
|
||||
CONFIG.Item.documentClass = DS4Item;
|
||||
CONFIG.ActiveEffect.documentClass = DS4ActiveEffect;
|
||||
|
||||
CONFIG.Actor.typeLabels = DS4.i18n.actorTypes;
|
||||
CONFIG.Item.typeLabels = DS4.i18n.itemTypes;
|
||||
|
@ -60,6 +64,24 @@ async function init() {
|
|||
Items.unregisterSheet("core", ItemSheet);
|
||||
Items.registerSheet("ds4", DS4ItemSheet, { makeDefault: true });
|
||||
|
||||
preloadFonts();
|
||||
await registerHandlebarsPartials();
|
||||
registerHandlebarsHelpers();
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Game {
|
||||
ds4: {
|
||||
DS4Actor: typeof DS4Actor;
|
||||
DS4Item: typeof DS4Item;
|
||||
DS4: typeof DS4;
|
||||
createCheckRoll: typeof createCheckRoll;
|
||||
migration: typeof migration;
|
||||
macros: typeof macros;
|
||||
};
|
||||
}
|
||||
|
||||
interface CONFIG {
|
||||
DS4: typeof DS4;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
|
||||
export default function registerForSetupHooks(): void {
|
||||
Hooks.once("setup", () => {
|
||||
|
@ -21,7 +22,7 @@ function localizeAndSortConfigObjects() {
|
|||
|
||||
const localizeObject = <T extends { [s: string]: string }>(obj: T, sort = true): T => {
|
||||
const localized = Object.entries(obj).map(([key, value]) => {
|
||||
return [key, game.i18n.localize(value)];
|
||||
return [key, getGame().i18n.localize(value)];
|
||||
});
|
||||
if (sort) localized.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
return Object.fromEntries(localized);
|
||||
|
|
130
src/module/item/item-data-properties.ts
Normal file
130
src/module/item/item-data-properties.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseTotalMax } from "../common/common-data";
|
||||
import {
|
||||
DS4AlphabetDataSourceData,
|
||||
DS4ArmorDataSourceData,
|
||||
DS4EquipmentDataSourceData,
|
||||
DS4LanguageDataSourceData,
|
||||
DS4LootDataSourceData,
|
||||
DS4RacialAbilityDataSourceData,
|
||||
DS4ShieldDataSourceData,
|
||||
DS4SpecialCreatureAbilityDataSourceData,
|
||||
DS4SpellDataSourceData,
|
||||
DS4TalentDataSourceData,
|
||||
DS4WeaponDataSourceData,
|
||||
} from "./item-data-source";
|
||||
|
||||
declare global {
|
||||
interface DataConfig {
|
||||
Item: DS4ItemDataProperties;
|
||||
}
|
||||
}
|
||||
|
||||
export type DS4ItemDataProperties =
|
||||
| DS4WeaponDataProperties
|
||||
| DS4ArmorDataProperties
|
||||
| DS4ShieldDataProperties
|
||||
| DS4SpellDataProperties
|
||||
| DS4EquipmentDataProperties
|
||||
| DS4LootDataProperties
|
||||
| DS4TalentDataProperties
|
||||
| DS4RacialAbilityDataProperties
|
||||
| DS4LanguageDataProperties
|
||||
| DS4AlphabetDataProperties
|
||||
| DS4SpecialCreatureAbilityDataProperties;
|
||||
|
||||
export interface DS4WeaponDataProperties {
|
||||
type: "weapon";
|
||||
data: DS4WeaponDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4ArmorDataProperties {
|
||||
type: "armor";
|
||||
data: DS4ArmorDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4ShieldDataProperties {
|
||||
type: "shield";
|
||||
data: DS4ShieldDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4SpellDataProperties {
|
||||
type: "spell";
|
||||
data: DS4SpellDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4EquipmentDataProperties {
|
||||
type: "equipment";
|
||||
data: DS4EquipmentDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4LootDataProperties {
|
||||
type: "loot";
|
||||
data: DS4LootDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4TalentDataProperties {
|
||||
type: "talent";
|
||||
data: DS4TalentDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4RacialAbilityDataProperties {
|
||||
type: "racialAbility";
|
||||
data: DS4RacialAbilityDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4LanguageDataProperties {
|
||||
type: "language";
|
||||
data: DS4LanguageDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4AlphabetDataProperties {
|
||||
type: "alphabet";
|
||||
data: DS4AlphabetDataPropertiesData;
|
||||
}
|
||||
|
||||
export interface DS4SpecialCreatureAbilityDataProperties {
|
||||
type: "specialCreatureAbility";
|
||||
data: DS4SpecialCreatureAbilityDataPropertiesData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemDataPropertiesDataRollable {
|
||||
rollable: boolean;
|
||||
}
|
||||
|
||||
//types
|
||||
|
||||
interface DS4WeaponDataPropertiesData extends DS4WeaponDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4ArmorDataPropertiesData extends DS4ArmorDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4ShieldDataPropertiesData extends DS4ShieldDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4SpellDataPropertiesData extends DS4SpellDataSourceData, DS4ItemDataPropertiesDataRollable {
|
||||
price: number | null;
|
||||
}
|
||||
|
||||
interface DS4EquipmentDataPropertiesData extends DS4EquipmentDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4LootDataPropertiesData extends DS4LootDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4TalentDataPropertiesData extends DS4TalentDataSourceData, DS4ItemDataPropertiesDataRollable {
|
||||
rank: ModifiableDataBaseTotalMax<number>;
|
||||
}
|
||||
|
||||
interface DS4RacialAbilityDataPropertiesData
|
||||
extends DS4RacialAbilityDataSourceData,
|
||||
DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4LanguageDataPropertiesData extends DS4LanguageDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4AlphabetDataPropertiesData extends DS4AlphabetDataSourceData, DS4ItemDataPropertiesDataRollable {}
|
||||
|
||||
interface DS4SpecialCreatureAbilityDataPropertiesData
|
||||
extends DS4SpecialCreatureAbilityDataSourceData,
|
||||
DS4ItemDataPropertiesDataRollable {}
|
184
src/module/item/item-data-source.ts
Normal file
184
src/module/item/item-data-source.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBaseMax } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
|
||||
declare global {
|
||||
interface SourceConfig {
|
||||
Item: DS4ItemDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
export type ItemType = keyof typeof DS4.i18n.itemTypes;
|
||||
|
||||
export type DS4ItemDataSource =
|
||||
| DS4WeaponDataSource
|
||||
| DS4ArmorDataSource
|
||||
| DS4ShieldDataSource
|
||||
| DS4SpellDataSource
|
||||
| DS4EquipmentDataSource
|
||||
| DS4LootDataSource
|
||||
| DS4TalentDataSource
|
||||
| DS4RacialAbilityDataSource
|
||||
| DS4LanguageDataSource
|
||||
| DS4AlphabetDataSource
|
||||
| DS4SpecialCreatureAbilityDataSource;
|
||||
|
||||
interface DS4WeaponDataSource {
|
||||
type: "weapon";
|
||||
data: DS4WeaponDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4ArmorDataSource {
|
||||
type: "armor";
|
||||
data: DS4ArmorDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4ShieldDataSource {
|
||||
type: "shield";
|
||||
data: DS4ShieldDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4SpellDataSource {
|
||||
type: "spell";
|
||||
data: DS4SpellDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4EquipmentDataSource {
|
||||
type: "equipment";
|
||||
data: DS4EquipmentDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4LootDataSource {
|
||||
type: "loot";
|
||||
data: DS4LootDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4TalentDataSource {
|
||||
type: "talent";
|
||||
data: DS4TalentDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4RacialAbilityDataSource {
|
||||
type: "racialAbility";
|
||||
data: DS4RacialAbilityDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4LanguageDataSource {
|
||||
type: "language";
|
||||
data: DS4LanguageDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4AlphabetDataSource {
|
||||
type: "alphabet";
|
||||
data: DS4AlphabetDataSourceData;
|
||||
}
|
||||
|
||||
interface DS4SpecialCreatureAbilityDataSource {
|
||||
type: "specialCreatureAbility";
|
||||
data: DS4SpecialCreatureAbilityDataSourceData;
|
||||
}
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemDataSourceDataBase {
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface DS4ItemDataSourceDataPhysical {
|
||||
quantity: number;
|
||||
price: number;
|
||||
availability: keyof typeof DS4.i18n.itemAvailabilities;
|
||||
storageLocation: string;
|
||||
}
|
||||
|
||||
export function isDS4ItemDataTypePhysical(input: foundry.data.ItemData["data"]): boolean {
|
||||
return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input;
|
||||
}
|
||||
|
||||
interface DS4ItemDataSourceDataEquipable {
|
||||
equipped: boolean;
|
||||
}
|
||||
|
||||
interface DS4ItemDataSourceDataProtective {
|
||||
armorValue: number;
|
||||
}
|
||||
|
||||
// types
|
||||
|
||||
export interface DS4WeaponDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable {
|
||||
attackType: AttackType;
|
||||
weaponBonus: number;
|
||||
opponentDefense: number;
|
||||
}
|
||||
|
||||
export type AttackType = keyof typeof DS4.i18n.attackTypes;
|
||||
|
||||
export interface DS4ArmorDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable,
|
||||
DS4ItemDataSourceDataProtective {
|
||||
armorMaterialType: keyof typeof DS4.i18n.armorMaterialTypes;
|
||||
armorType: keyof typeof DS4.i18n.armorTypes;
|
||||
}
|
||||
|
||||
export interface DS4ShieldDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable,
|
||||
DS4ItemDataSourceDataProtective {}
|
||||
|
||||
export interface DS4SpellDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataEquipable {
|
||||
spellType: keyof typeof DS4.i18n.spellTypes;
|
||||
bonus: string;
|
||||
spellCategory: keyof typeof DS4.i18n.spellCategories;
|
||||
maxDistance: UnitData<DistanceUnit>;
|
||||
effectRadius: UnitData<DistanceUnit>;
|
||||
duration: UnitData<CustomTemporalUnit>;
|
||||
cooldownDuration: UnitData<TemporalUnit>;
|
||||
minimumLevels: {
|
||||
healer: number | null;
|
||||
wizard: number | null;
|
||||
sorcerer: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UnitData<UnitType> {
|
||||
value: string;
|
||||
unit: UnitType;
|
||||
}
|
||||
|
||||
type DistanceUnit = keyof typeof DS4.i18n.distanceUnits;
|
||||
|
||||
type CustomTemporalUnit = keyof typeof DS4.i18n.customTemporalUnits;
|
||||
|
||||
export type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
|
||||
|
||||
export interface DS4EquipmentDataSourceData
|
||||
extends DS4ItemDataSourceDataBase,
|
||||
DS4ItemDataSourceDataPhysical,
|
||||
DS4ItemDataSourceDataEquipable {}
|
||||
|
||||
export interface DS4LootDataSourceData extends DS4ItemDataSourceDataBase, DS4ItemDataSourceDataPhysical {}
|
||||
|
||||
export interface DS4TalentDataSourceData extends DS4ItemDataSourceDataBase {
|
||||
rank: ModifiableDataBaseMax<number>;
|
||||
}
|
||||
|
||||
export type DS4RacialAbilityDataSourceData = DS4ItemDataSourceDataBase;
|
||||
|
||||
export type DS4LanguageDataSourceData = DS4ItemDataSourceDataBase;
|
||||
|
||||
export type DS4AlphabetDataSourceData = DS4ItemDataSourceDataBase;
|
||||
|
||||
export interface DS4SpecialCreatureAbilityDataSourceData extends DS4ItemDataSourceDataBase {
|
||||
experiencePoints: number;
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
// SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ModifiableDataBase } from "../common/common-data";
|
||||
import { DS4 } from "../config";
|
||||
|
||||
export type ItemType = keyof typeof DS4.i18n.itemTypes;
|
||||
|
||||
export type DS4ItemData =
|
||||
| DS4WeaponData
|
||||
| DS4ArmorData
|
||||
| DS4ShieldData
|
||||
| DS4SpellData
|
||||
| DS4EquipmentData
|
||||
| DS4LootData
|
||||
| DS4TalentData
|
||||
| DS4RacialAbilityData
|
||||
| DS4LanguageData
|
||||
| DS4AlphabetData
|
||||
| DS4SpecialCreatureAbilityData;
|
||||
|
||||
export interface DS4ItemDataHelper<T, U extends ItemType> extends Item.Data<T> {
|
||||
type: U;
|
||||
}
|
||||
|
||||
type DS4WeaponData = DS4ItemDataHelper<DS4WeaponDataData, "weapon">;
|
||||
type DS4ArmorData = DS4ItemDataHelper<DS4ArmorDataData, "armor">;
|
||||
type DS4ShieldData = DS4ItemDataHelper<DS4ShieldDataData, "shield">;
|
||||
type DS4SpellData = DS4ItemDataHelper<DS4SpellDataData, "spell">;
|
||||
type DS4EquipmentData = DS4ItemDataHelper<DS4EquipmentDataData, "equipment">;
|
||||
type DS4LootData = DS4ItemDataHelper<DS4LootDataData, "loot">;
|
||||
type DS4TalentData = DS4ItemDataHelper<DS4TalentDataData, "talent">;
|
||||
type DS4RacialAbilityData = DS4ItemDataHelper<DS4RacialAbilityDataData, "racialAbility">;
|
||||
type DS4LanguageData = DS4ItemDataHelper<DS4LanguageDataData, "language">;
|
||||
type DS4AlphabetData = DS4ItemDataHelper<DS4AlphabetDataData, "alphabet">;
|
||||
type DS4SpecialCreatureAbilityData = DS4ItemDataHelper<DS4SpecialCreatureAbilityDataData, "specialCreatureAbility">;
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemDataDataBase {
|
||||
description: string;
|
||||
}
|
||||
interface DS4ItemDataDataPhysical {
|
||||
quantity: number;
|
||||
price: number;
|
||||
availability: keyof typeof DS4.i18n.itemAvailabilities;
|
||||
storageLocation: string;
|
||||
}
|
||||
|
||||
export function isDS4ItemDataTypePhysical(input: DS4ItemData["data"]): boolean {
|
||||
return "quantity" in input && "price" in input && "availability" in input && "storageLocation" in input;
|
||||
}
|
||||
|
||||
interface DS4ItemDataDataEquipable {
|
||||
equipped: boolean;
|
||||
}
|
||||
|
||||
interface DS4ItemDataDataProtective {
|
||||
armorValue: number;
|
||||
}
|
||||
|
||||
export interface UnitData<UnitType> {
|
||||
value: string;
|
||||
unit: UnitType;
|
||||
}
|
||||
export type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
|
||||
type CustomTemporalUnit = keyof typeof DS4.i18n.customTemporalUnits;
|
||||
type DistanceUnit = keyof typeof DS4.i18n.distanceUnits;
|
||||
|
||||
// types
|
||||
|
||||
export interface DS4WeaponDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {
|
||||
attackType: AttackType;
|
||||
weaponBonus: number;
|
||||
opponentDefense: number;
|
||||
}
|
||||
|
||||
export type AttackType = keyof typeof DS4.i18n.attackTypes;
|
||||
|
||||
export interface DS4ArmorDataData
|
||||
extends DS4ItemDataDataBase,
|
||||
DS4ItemDataDataPhysical,
|
||||
DS4ItemDataDataEquipable,
|
||||
DS4ItemDataDataProtective {
|
||||
armorMaterialType: keyof typeof DS4.i18n.armorMaterialTypes;
|
||||
armorType: keyof typeof DS4.i18n.armorTypes;
|
||||
}
|
||||
|
||||
export interface DS4TalentDataData extends DS4ItemDataDataBase {
|
||||
rank: DS4TalentRank;
|
||||
}
|
||||
|
||||
export interface DS4TalentRank extends ModifiableDataBase<number> {
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable {
|
||||
spellType: keyof typeof DS4.i18n.spellTypes;
|
||||
bonus: string;
|
||||
spellCategory: keyof typeof DS4.i18n.spellCategories;
|
||||
maxDistance: UnitData<DistanceUnit>;
|
||||
effectRadius: UnitData<DistanceUnit>;
|
||||
duration: UnitData<CustomTemporalUnit>;
|
||||
cooldownDuration: UnitData<TemporalUnit>;
|
||||
minimumLevels: {
|
||||
healer: number | null;
|
||||
wizard: number | null;
|
||||
sorcerer: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DS4ShieldDataData
|
||||
extends DS4ItemDataDataBase,
|
||||
DS4ItemDataDataPhysical,
|
||||
DS4ItemDataDataEquipable,
|
||||
DS4ItemDataDataProtective {}
|
||||
|
||||
export interface DS4EquipmentDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical, DS4ItemDataDataEquipable {}
|
||||
|
||||
export interface DS4LootDataData extends DS4ItemDataDataBase, DS4ItemDataDataPhysical {}
|
||||
|
||||
export type DS4RacialAbilityDataData = DS4ItemDataDataBase;
|
||||
|
||||
export type DS4LanguageDataData = DS4ItemDataDataBase;
|
||||
|
||||
export type DS4AlphabetDataData = DS4ItemDataDataBase;
|
||||
|
||||
export interface DS4SpecialCreatureAbilityDataData extends DS4ItemDataDataBase {
|
||||
experiencePoints: number;
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { HasTotal } from "../common/common-data";
|
||||
import {
|
||||
DS4AlphabetDataData,
|
||||
DS4ArmorDataData,
|
||||
DS4EquipmentDataData,
|
||||
DS4ItemDataHelper,
|
||||
DS4LanguageDataData,
|
||||
DS4LootDataData,
|
||||
DS4RacialAbilityDataData,
|
||||
DS4ShieldDataData,
|
||||
DS4SpecialCreatureAbilityDataData,
|
||||
DS4SpellDataData,
|
||||
DS4TalentDataData,
|
||||
DS4TalentRank,
|
||||
DS4WeaponDataData,
|
||||
} from "./item-data";
|
||||
|
||||
export type DS4ItemPreparedData =
|
||||
| DS4WeaponPreparedData
|
||||
| DS4ArmorPreparedData
|
||||
| DS4ShieldPreparedData
|
||||
| DS4SpellPreparedData
|
||||
| DS4EquipmentPreparedData
|
||||
| DS4LootPreparedData
|
||||
| DS4TalentPreparedData
|
||||
| DS4RacialAbilityPreparedData
|
||||
| DS4LanguagePreparedData
|
||||
| DS4AlphabetPreparedData
|
||||
| DS4SpecialCreatureAbilityPreparedData;
|
||||
|
||||
export type DS4WeaponPreparedData = DS4ItemDataHelper<DS4WeaponPreparedDataData, "weapon">;
|
||||
export type DS4ArmorPreparedData = DS4ItemDataHelper<DS4ArmorPreparedDataData, "armor">;
|
||||
export type DS4ShieldPreparedData = DS4ItemDataHelper<DS4ShieldPreparedDataData, "shield">;
|
||||
export type DS4SpellPreparedData = DS4ItemDataHelper<DS4SpellPreparedDataData, "spell">;
|
||||
export type DS4EquipmentPreparedData = DS4ItemDataHelper<DS4EquipmentPreparedDataData, "equipment">;
|
||||
export type DS4LootPreparedData = DS4ItemDataHelper<DS4LootPreparedDataData, "loot">;
|
||||
export type DS4TalentPreparedData = DS4ItemDataHelper<DS4TalentPreparedDataData, "talent">;
|
||||
export type DS4RacialAbilityPreparedData = DS4ItemDataHelper<DS4RacialAbilityPreparedDataData, "racialAbility">;
|
||||
export type DS4LanguagePreparedData = DS4ItemDataHelper<DS4LanguagePreparedDataData, "language">;
|
||||
export type DS4AlphabetPreparedData = DS4ItemDataHelper<DS4AlphabetPreparedDataData, "alphabet">;
|
||||
export type DS4SpecialCreatureAbilityPreparedData = DS4ItemDataHelper<
|
||||
DS4SpecialCreatureAbilityPreparedDataData,
|
||||
"specialCreatureAbility"
|
||||
>;
|
||||
|
||||
// templates
|
||||
|
||||
interface DS4ItemPreparedDataDataRollable {
|
||||
rollable: boolean;
|
||||
}
|
||||
|
||||
//types
|
||||
|
||||
interface DS4WeaponPreparedDataData extends DS4WeaponDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4ArmorPreparedDataData extends DS4ArmorDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4ShieldPreparedDataData extends DS4ShieldDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4SpellPreparedDataData extends DS4SpellDataData, DS4ItemPreparedDataDataRollable {
|
||||
price: number | null;
|
||||
}
|
||||
|
||||
interface DS4EquipmentPreparedDataData extends DS4EquipmentDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4LootPreparedDataData extends DS4LootDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4TalentPreparedDataData extends DS4TalentDataData, DS4ItemPreparedDataDataRollable {
|
||||
rank: DS4TalentPreparedRank;
|
||||
}
|
||||
|
||||
interface DS4TalentPreparedRank extends DS4TalentRank, HasTotal<number> {}
|
||||
|
||||
interface DS4RacialAbilityPreparedDataData extends DS4RacialAbilityDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4LanguagePreparedDataData extends DS4LanguageDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4AlphabetPreparedDataData extends DS4AlphabetDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4SpecialCreatureAbilityPreparedDataData
|
||||
extends DS4SpecialCreatureAbilityDataData,
|
||||
DS4ItemPreparedDataDataRollable {}
|
|
@ -5,19 +5,17 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { DS4Item } from "./item";
|
||||
import { isDS4ItemDataTypePhysical } from "./item-data";
|
||||
import { isDS4ItemDataTypePhysical } from "./item-data-source";
|
||||
|
||||
/**
|
||||
* The Sheet class for DS4 Items
|
||||
*/
|
||||
export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
||||
export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData> {
|
||||
/** @override */
|
||||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
static get defaultOptions(): ItemSheet.Options {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
width: 540,
|
||||
height: 400,
|
||||
classes: ["ds4", "sheet", "item"],
|
||||
|
@ -33,7 +31,7 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
async getData(): Promise<ItemSheet.Data<DS4Item>> {
|
||||
async getData(): Promise<DS4ItemSheetData> {
|
||||
const data = {
|
||||
...(await super.getData()),
|
||||
config: DS4,
|
||||
|
@ -45,11 +43,14 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
setPosition(options: Partial<Application.Position> = {}): Application.Position & { height: number } {
|
||||
setPosition(options: Partial<Application.Position> = {}): (Application.Position & { height: number }) | undefined {
|
||||
const position = super.setPosition(options);
|
||||
const sheetBody = this.element.find(".sheet-body");
|
||||
const bodyHeight = position.height - 192;
|
||||
sheetBody.css("height", bodyHeight);
|
||||
if (position) {
|
||||
const sheetBody = this.element.find(".sheet-body");
|
||||
const bodyHeight = position.height - 192;
|
||||
sheetBody.css("height", bodyHeight);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
@ -70,23 +71,25 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
event.preventDefault();
|
||||
|
||||
if (this.item.isOwned) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningManageActiveEffectOnOwnedItem"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningManageActiveEffectOnOwnedItem"));
|
||||
}
|
||||
const a = event.currentTarget;
|
||||
const li = $(a).parents(".effect");
|
||||
|
||||
switch (a.dataset["action"]) {
|
||||
case "create":
|
||||
return this._createActiveEffect();
|
||||
return this.createActiveEffect();
|
||||
case "edit":
|
||||
const id = li.data("effectId");
|
||||
const effect = this.item.effects.get(id);
|
||||
if (!effect) {
|
||||
throw new Error(game.i18n.format("DS4.ErrorItemDoesNotHaveEffect", { id, item: this.item.name }));
|
||||
throw new Error(
|
||||
getGame().i18n.format("DS4.ErrorItemDoesNotHaveEffect", { id, item: this.item.name }),
|
||||
);
|
||||
}
|
||||
return effect.sheet.render(true);
|
||||
case "delete": {
|
||||
return this.item.deleteEmbeddedEntity("ActiveEffect", li.data("effectId"));
|
||||
return this.item.deleteEmbeddedDocuments("ActiveEffect", [li.data("effectId")]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,17 +97,19 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
/**
|
||||
* Create a new ActiveEffect for the item using default data.
|
||||
*/
|
||||
protected async _createActiveEffect(): Promise<ActiveEffect.Data> {
|
||||
const label = `New Effect`;
|
||||
|
||||
protected async createActiveEffect(): Promise<ActiveEffect | undefined> {
|
||||
const createData = {
|
||||
label: label,
|
||||
changes: [],
|
||||
duration: {},
|
||||
transfer: true,
|
||||
label: "New Effect",
|
||||
icon: "icons/svg/aura.svg",
|
||||
};
|
||||
|
||||
const effect = ActiveEffect.create(createData, this.item);
|
||||
return effect.create({});
|
||||
return ActiveEffect.create(createData, { parent: this.item });
|
||||
}
|
||||
}
|
||||
|
||||
interface DS4ItemSheetData extends ItemSheet.Data<ItemSheet.Options> {
|
||||
config: typeof DS4;
|
||||
isOwned: boolean;
|
||||
actor: DS4ItemSheet["item"]["actor"];
|
||||
isPhysical: boolean;
|
||||
}
|
||||
|
|
|
@ -3,26 +3,29 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import { createCheckRoll } from "../rolls/check-factory";
|
||||
import notifications from "../ui/notifications";
|
||||
import { AttackType, DS4ItemData, ItemType } from "./item-data";
|
||||
import { DS4ItemPreparedData } from "./item-prepared-data";
|
||||
import { AttackType, ItemType } from "./item-data-source";
|
||||
import { calculateSpellPrice } from "./type-specific-helpers/spell";
|
||||
|
||||
declare global {
|
||||
interface DocumentClassConfig {
|
||||
Item: typeof DS4Item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Item class for DS4
|
||||
*/
|
||||
export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
export class DS4Item extends Item {
|
||||
/** @override */
|
||||
prepareData(): void {
|
||||
super.prepareData();
|
||||
this.prepareDerivedData();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
prepareDerivedData(): void {
|
||||
if (this.data.type === "talent") {
|
||||
const data = this.data.data;
|
||||
|
@ -63,24 +66,22 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
* Roll a check for an action with this item.
|
||||
*/
|
||||
async roll(): Promise<void> {
|
||||
if (!this.isOwnedItem()) {
|
||||
throw new Error(game.i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
|
||||
}
|
||||
|
||||
switch (this.data.type) {
|
||||
case "weapon":
|
||||
return this.rollWeapon();
|
||||
case "spell":
|
||||
return this.rollSpell();
|
||||
default:
|
||||
throw new Error(game.i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type }));
|
||||
throw new Error(
|
||||
getGame().i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected async rollWeapon(this: this & { readonly isOwned: true }): Promise<void> {
|
||||
protected async rollWeapon(): Promise<void> {
|
||||
if (!(this.data.type === "weapon")) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorWrongItemType", {
|
||||
getGame().i18n.format("DS4.ErrorWrongItemType", {
|
||||
actualType: this.data.type,
|
||||
expectedType: "weapon",
|
||||
id: this.id,
|
||||
|
@ -91,7 +92,7 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
|
||||
if (!this.data.data.equipped) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
getGame().i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
type: this.data.type,
|
||||
|
@ -99,24 +100,27 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
);
|
||||
}
|
||||
|
||||
const actor = this.actor as unknown as DS4Actor; // TODO(types): Improve so that the concrete Actor type is known here
|
||||
const ownerDataData = actor.data.data;
|
||||
if (!this.actor) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
|
||||
}
|
||||
|
||||
const ownerDataData = this.actor.data.data;
|
||||
const weaponBonus = this.data.data.weaponBonus;
|
||||
const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType);
|
||||
const checkTargetNumber = ownerDataData.combatValues[combatValue].total + weaponBonus;
|
||||
|
||||
await createCheckRoll(checkTargetNumber, {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ItemWeaponCheckFlavor", { actor: actor.name, weapon: this.name }),
|
||||
flavor: getGame().i18n.format("DS4.ItemWeaponCheckFlavor", { actor: this.actor.name, weapon: this.name }),
|
||||
});
|
||||
}
|
||||
|
||||
protected async rollSpell(): Promise<void> {
|
||||
if (!(this.data.type === "spell")) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorWrongItemType", {
|
||||
getGame().i18n.format("DS4.ErrorWrongItemType", {
|
||||
actualType: this.data.type,
|
||||
expectedType: "spell",
|
||||
id: this.id,
|
||||
|
@ -127,7 +131,7 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
|
||||
if (!this.data.data.equipped) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
getGame().i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
type: this.data.type,
|
||||
|
@ -135,12 +139,15 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
);
|
||||
}
|
||||
|
||||
const actor = this.actor as unknown as DS4Actor; // TODO(types): Improve so that the concrete Actor type is known here
|
||||
const ownerDataData = actor.data.data;
|
||||
if (!this.actor) {
|
||||
throw new Error(getGame().i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
|
||||
}
|
||||
|
||||
const ownerDataData = this.actor.data.data;
|
||||
const spellBonus = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined;
|
||||
if (spellBonus === undefined) {
|
||||
notifications.info(
|
||||
game.i18n.format("DS4.InfoManuallyEnterSpellBonus", {
|
||||
getGame().i18n.format("DS4.InfoManuallyEnterSpellBonus", {
|
||||
name: this.name,
|
||||
spellBonus: this.data.data.bonus,
|
||||
}),
|
||||
|
@ -150,10 +157,10 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
const checkTargetNumber = ownerDataData.combatValues[spellType].total + (spellBonus ?? 0);
|
||||
|
||||
await createCheckRoll(checkTargetNumber, {
|
||||
rollMode: game.settings.get("core", "rollMode") as Const.DiceRollMode, // TODO(types): Type this setting in upstream
|
||||
rollMode: getGame().settings.get("core", "rollMode"),
|
||||
maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
|
||||
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult,
|
||||
flavor: game.i18n.format("DS4.ItemSpellCheckFlavor", { actor: actor.name, spell: this.name }),
|
||||
flavor: getGame().i18n.format("DS4.ItemSpellCheckFlavor", { actor: this.actor.name, spell: this.name }),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -162,22 +169,22 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
const { melee, ranged } = { ...DS4.i18n.attackTypes };
|
||||
const identifier = "attack-type-selection";
|
||||
return Dialog.prompt({
|
||||
title: game.i18n.localize("DS4.DialogAttackTypeSelection"),
|
||||
title: getGame().i18n.localize("DS4.DialogAttackTypeSelection"),
|
||||
content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
|
||||
selects: [
|
||||
{
|
||||
label: game.i18n.localize("DS4.AttackType"),
|
||||
label: getGame().i18n.localize("DS4.AttackType"),
|
||||
identifier,
|
||||
options: { melee, ranged },
|
||||
},
|
||||
],
|
||||
}),
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
const selectedAttackType = html.find(`#${identifier}`).val();
|
||||
if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedAttackType", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedAttackType", {
|
||||
actualType: selectedAttackType,
|
||||
expectedTypes: "'melee', 'ranged'",
|
||||
}),
|
||||
|
@ -190,11 +197,4 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
return `${attackType}Attack` as const;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-guarding variant to check if the item is owned.
|
||||
*/
|
||||
isOwnedItem(): this is this & { readonly isOwned: true } {
|
||||
return this.isOwned;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { hoursPerDay, minutesPerHour, secondsPerMinute, secondsPerRound } from "../../common/time-helpers";
|
||||
import { DS4SpellDataData, TemporalUnit, UnitData } from "../item-data";
|
||||
import { DS4SpellDataSourceData, TemporalUnit, UnitData } from "../item-data-source";
|
||||
|
||||
export function calculateSpellPrice(data: DS4SpellDataData): number | null {
|
||||
export function calculateSpellPrice(data: DS4SpellDataSourceData): number | null {
|
||||
const spellPriceFactor = calculateSpellPriceFactor(data.cooldownDuration);
|
||||
const baseSpellPrices = [
|
||||
data.minimumLevels.healer !== null ? 10 + (data.minimumLevels.healer - 1) * 35 : null,
|
||||
|
|
|
@ -8,24 +8,17 @@ const loggingSeparator = "|";
|
|||
type LogLevel = "debug" | "info" | "warning" | "error";
|
||||
type LoggingFunction = (...data: unknown[]) => void;
|
||||
|
||||
class Logger {
|
||||
readonly debug: LoggingFunction;
|
||||
readonly info: LoggingFunction;
|
||||
readonly warn: LoggingFunction;
|
||||
readonly error: LoggingFunction;
|
||||
const getLoggingFunction = (type: LogLevel = "info"): LoggingFunction => {
|
||||
const log = { debug: console.debug, info: console.info, warning: console.warn, error: console.error }[type];
|
||||
return (...data: unknown[]) => log(loggingContext, loggingSeparator, ...data);
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.debug = this.getLoggingFunction("debug");
|
||||
this.info = this.getLoggingFunction("info");
|
||||
this.warn = this.getLoggingFunction("warning");
|
||||
this.error = this.getLoggingFunction("error");
|
||||
}
|
||||
const logger = Object.freeze({
|
||||
debug: getLoggingFunction("debug"),
|
||||
info: getLoggingFunction("info"),
|
||||
warn: getLoggingFunction("warning"),
|
||||
error: getLoggingFunction("error"),
|
||||
getLoggingFunction,
|
||||
});
|
||||
|
||||
getLoggingFunction(type: LogLevel = "info") {
|
||||
const log = { debug: console.debug, info: console.info, warning: console.warn, error: console.error }[type];
|
||||
return (...data: unknown[]) => log(loggingContext, loggingSeparator, ...data);
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger();
|
||||
export default logger;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { getCanvas } from "../helpers";
|
||||
import { getCanvas, getGame } from "../helpers";
|
||||
|
||||
/**
|
||||
* Gets the currently active actor based on how {@link ChatMessage} determines
|
||||
|
@ -13,13 +13,13 @@ import { getCanvas } from "../helpers";
|
|||
export function getActiveActor(): DS4Actor | undefined {
|
||||
const speaker = ChatMessage.getSpeaker();
|
||||
|
||||
const speakerToken = speaker.token ? getCanvas().tokens.get(speaker.token) : undefined;
|
||||
const speakerToken = speaker.token ? getCanvas().tokens?.get(speaker.token) : undefined;
|
||||
if (speakerToken) {
|
||||
return speakerToken.actor as DS4Actor;
|
||||
return speakerToken.actor ?? undefined;
|
||||
}
|
||||
|
||||
const speakerActor = speaker.actor ? game.actors?.get(speaker.actor) : undefined;
|
||||
const speakerActor = speaker.actor ? getGame().actors?.get(speaker.actor) : undefined;
|
||||
if (speakerActor) {
|
||||
return speakerActor as DS4Actor;
|
||||
return speakerActor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Check } from "../actor/actor-prepared-data";
|
||||
import { Check } from "../actor/actor-data-properties";
|
||||
import { DS4 } from "../config";
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { getActiveActor } from "./helpers";
|
||||
|
||||
|
@ -15,13 +16,13 @@ import { getActiveActor } from "./helpers";
|
|||
*/
|
||||
export async function createRollCheckMacro(check: Check, slot: string): Promise<void> {
|
||||
const macro = await getOrCreateRollCheckMacro(check);
|
||||
game.user?.assignHotbarMacro(macro, slot);
|
||||
getGame().user?.assignHotbarMacro(macro ?? null, slot);
|
||||
}
|
||||
|
||||
async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | null> {
|
||||
async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | undefined> {
|
||||
const command = `game.ds4.macros.rollCheck("${check}");`;
|
||||
|
||||
const existingMacro = game.macros?.entities.find(
|
||||
const existingMacro = getGame().macros?.find(
|
||||
(m) => m.name === DS4.i18n.checks[check] && m.data.command === command,
|
||||
);
|
||||
if (existingMacro) {
|
||||
|
@ -36,7 +37,7 @@ async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | null> {
|
|||
img: DS4.icons.checks[check],
|
||||
flags: { "ds4.checkMacro": true },
|
||||
},
|
||||
{ displaySheet: false },
|
||||
{ renderSheet: false },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -46,7 +47,7 @@ async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | null> {
|
|||
export async function rollCheck(check: Check): Promise<void> {
|
||||
const actor = getActiveActor();
|
||||
if (!actor) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
}
|
||||
|
||||
return actor.rollCheck(check).catch((e) => notifications.error(e, { log: true }));
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { getActiveActor } from "./helpers";
|
||||
|
||||
/**
|
||||
* Executes the roll generic check macro.
|
||||
*/
|
||||
export async function rollGenericCheck(): Promise<void> {
|
||||
const actor = getActiveActor();
|
||||
if (!actor) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningMustControlActorToUseRollCheckMacro"));
|
||||
}
|
||||
|
||||
return actor.rollGenericCheck().catch((e) => notifications.error(e, { log: true }));
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4ItemData } from "../item/item-data";
|
||||
import { getGame } from "../helpers";
|
||||
import notifications from "../ui/notifications";
|
||||
import { getActiveActor } from "./helpers";
|
||||
|
||||
|
@ -12,15 +12,15 @@ import { getActiveActor } from "./helpers";
|
|||
* @param itemData - The item data
|
||||
* @param slot - The hotbar slot to use
|
||||
*/
|
||||
export async function createRollItemMacro(itemData: DS4ItemData, slot: string): Promise<void> {
|
||||
export async function createRollItemMacro(itemData: foundry.data.ItemData["_source"], slot: string): Promise<void> {
|
||||
const macro = await getOrCreateRollItemMacro(itemData);
|
||||
game.user?.assignHotbarMacro(macro, slot);
|
||||
getGame().user?.assignHotbarMacro(macro ?? null, slot);
|
||||
}
|
||||
|
||||
async function getOrCreateRollItemMacro(itemData: DS4ItemData): Promise<Macro | null> {
|
||||
async function getOrCreateRollItemMacro(itemData: foundry.data.ItemData["_source"]): Promise<Macro | undefined> {
|
||||
const command = `game.ds4.macros.rollItem("${itemData._id}");`;
|
||||
|
||||
const existingMacro = game.macros?.entities.find((m) => m.name === itemData.name && m.data.command === command);
|
||||
const existingMacro = getGame().macros?.find((m) => m.name === itemData.name && m.data.command === command);
|
||||
if (existingMacro) {
|
||||
return existingMacro;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ async function getOrCreateRollItemMacro(itemData: DS4ItemData): Promise<Macro |
|
|||
img: itemData.img,
|
||||
flags: { "ds4.itemMacro": true },
|
||||
},
|
||||
{ displaySheet: false },
|
||||
{ renderSheet: false },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -43,13 +43,13 @@ async function getOrCreateRollItemMacro(itemData: DS4ItemData): Promise<Macro |
|
|||
export async function rollItem(itemId: string): Promise<void> {
|
||||
const actor = getActiveActor();
|
||||
if (!actor) {
|
||||
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollItemMacro"));
|
||||
return notifications.warn(getGame().i18n.localize("DS4.WarningMustControlActorToUseRollItemMacro"));
|
||||
}
|
||||
|
||||
const item = actor.items?.get(itemId);
|
||||
if (!item) {
|
||||
return notifications.warn(
|
||||
game.i18n.format("DS4.WarningControlledActorDoesNotHaveItem", {
|
||||
getGame().i18n.format("DS4.WarningControlledActorDoesNotHaveItem", {
|
||||
actorName: actor.name,
|
||||
actorId: actor.id,
|
||||
itemId,
|
||||
|
|
|
@ -2,25 +2,25 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "./helpers";
|
||||
import logger from "./logger";
|
||||
import { migrate as migrate001 } from "./migrations/001";
|
||||
import { migrate as migrate002 } from "./migrations/002";
|
||||
import { migrate as migrate003 } from "./migrations/003";
|
||||
import { migrate as migrate004 } from "./migrations/004";
|
||||
|
||||
import notifications from "./ui/notifications";
|
||||
|
||||
async function migrate(): Promise<void> {
|
||||
if (!game.user?.isGM) {
|
||||
if (!getGame().user?.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldMigrationVersion = game.settings.get("ds4", "systemMigrationVersion");
|
||||
const oldMigrationVersion = getGame().settings.get("ds4", "systemMigrationVersion");
|
||||
|
||||
const targetMigrationVersion = migrations.length;
|
||||
|
||||
if (isFirstWorldStart(oldMigrationVersion)) {
|
||||
game.settings.set("ds4", "systemMigrationVersion", targetMigrationVersion);
|
||||
getGame().settings.set("ds4", "systemMigrationVersion", targetMigrationVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ async function migrate(): Promise<void> {
|
|||
}
|
||||
|
||||
async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> {
|
||||
if (!game.user?.isGM) {
|
||||
if (!getGame().user?.isGM) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
|
||||
if (migrationsToExecute.length > 0) {
|
||||
notifications.info(
|
||||
game.i18n.format("DS4.InfoSystemUpdateStart", {
|
||||
getGame().i18n.format("DS4.InfoSystemUpdateStart", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
}),
|
||||
|
@ -48,10 +48,10 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
logger.info("executing migration script ", currentMigrationVersion);
|
||||
try {
|
||||
await migration();
|
||||
game.settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
|
||||
getGame().settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
|
||||
} catch (err) {
|
||||
notifications.error(
|
||||
game.i18n.format("DS4.ErrorDuringMigration", {
|
||||
getGame().i18n.format("DS4.ErrorDuringMigration", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
migrationVersion: currentMigrationVersion,
|
||||
|
@ -65,7 +65,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
|
|||
}
|
||||
|
||||
notifications.info(
|
||||
game.i18n.format("DS4.InfoSystemUpdateCompleted", {
|
||||
getGame().i18n.format("DS4.InfoSystemUpdateCompleted", {
|
||||
currentVersion: oldMigrationVersion,
|
||||
targetVersion: targetMigrationVersion,
|
||||
}),
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
for (const a of game.actors?.entities ?? []) {
|
||||
const updateData = getActorUpdateData();
|
||||
logger.info(`Migrating actor ${a.name}`);
|
||||
await a.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
function getActorUpdateData(): Record<string, unknown> {
|
||||
|
@ -32,3 +36,6 @@ function getActorUpdateData(): Record<string, unknown> {
|
|||
};
|
||||
return updateData;
|
||||
}
|
||||
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator({ getActorUpdateData, getSceneUpdateData });
|
||||
|
|
|
@ -2,142 +2,33 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getActorUpdateDataGetter,
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateItems,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
await migrateItems();
|
||||
await migrateActors();
|
||||
await migrateScenes();
|
||||
await migrateCompendiums();
|
||||
await migrateItems(getItemUpdateData);
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
async function migrateItems() {
|
||||
for (const item of game.items?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item entity ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item entity ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
||||
function getItemUpdateData(
|
||||
itemData: Partial<foundry.data.ItemData["_source"]>,
|
||||
): DeepPartial<foundry.data.ItemData["_source"]> | undefined {
|
||||
if (!["equipment", "trinket"].includes(itemData.type ?? "")) return undefined;
|
||||
return { type: itemData.type === "equipment" ? "loot" : "equipment" };
|
||||
return { type: itemData.type === "equipment" ? ("loot" as const) : ("equipment" as const) };
|
||||
}
|
||||
|
||||
async function migrateActors() {
|
||||
for (const actor of game.actors?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor entity ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor entity ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActorUpdateData(actorData: DeepPartial<Actor.Data>) {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = itemData ? getItemUpdateData(itemData) : undefined;
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return { ...itemData, ...update };
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
return hasItemUpdates ? { items } : undefined;
|
||||
}
|
||||
|
||||
async function migrateScenes() {
|
||||
for (const scene of game.scenes?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene entity ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene entity ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSceneUpdateData(sceneData: Scene.Data) {
|
||||
let hasTokenUpdates = false;
|
||||
const tokens = sceneData.tokens.map((tokenData) => {
|
||||
if (!tokenData.actorId || tokenData.actorLink || tokenData.actorData.data) {
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
return tokenData;
|
||||
}
|
||||
const token = new Token(tokenData);
|
||||
if (!token.actor) {
|
||||
tokenData.actorId = null as unknown as string;
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
} else if (!tokenData.actorLink) {
|
||||
const actorUpdateData = getActorUpdateData(token.data.actorData);
|
||||
tokenData.actorData = mergeObject(token.data.actorData, actorUpdateData);
|
||||
hasTokenUpdates = true;
|
||||
}
|
||||
return tokenData;
|
||||
});
|
||||
if (!hasTokenUpdates) return undefined;
|
||||
return hasTokenUpdates ? { tokens } : undefined;
|
||||
}
|
||||
|
||||
async function migrateCompendiums() {
|
||||
for (const compendium of game.packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCompendium(compendium: Compendium) {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
|
||||
const content = await compendium.getContent();
|
||||
|
||||
for (const entity of content) {
|
||||
try {
|
||||
const getUpdateData = (entity: Entity) => {
|
||||
switch (entityName) {
|
||||
case "Item":
|
||||
return getItemUpdateData(entity._data);
|
||||
case "Actor":
|
||||
return getActorUpdateData(entity._data);
|
||||
case "Scene":
|
||||
return getSceneUpdateData(entity._data as Scene.Data);
|
||||
}
|
||||
};
|
||||
const updateData = getUpdateData(entity);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}`);
|
||||
await compendium.updateEntity({ ...updateData, _id: entity._id });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
await compendium.migrate({});
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
}
|
||||
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator(
|
||||
{ getItemUpdateData, getActorUpdateData, getSceneUpdateData },
|
||||
{ migrateToTemplateEarly: false },
|
||||
);
|
||||
|
|
|
@ -2,31 +2,24 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getActorUpdateDataGetter,
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateItems,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
await migrateItems();
|
||||
await migrateActors();
|
||||
await migrateScenes();
|
||||
await migrateCompendiums();
|
||||
await migrateItems(getItemUpdateData);
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
async function migrateItems() {
|
||||
for (const item of game.items?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item entity ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item entity ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
||||
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
|
||||
if (!["loot"].includes(itemData.type ?? "")) return undefined;
|
||||
return {
|
||||
data: {
|
||||
|
@ -35,113 +28,9 @@ function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
|||
};
|
||||
}
|
||||
|
||||
async function migrateActors() {
|
||||
for (const actor of game.actors?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor entity ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor entity ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActorUpdateData(actorData: DeepPartial<Actor.Data>) {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = itemData ? getItemUpdateData(itemData) : undefined;
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return mergeObject(itemData, update, { enforceTypes: false, inplace: false });
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
return hasItemUpdates ? { items } : undefined;
|
||||
}
|
||||
|
||||
async function migrateScenes() {
|
||||
for (const scene of game.scenes?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene entity ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene entity ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSceneUpdateData(sceneData: Scene.Data) {
|
||||
let hasTokenUpdates = false;
|
||||
const tokens = sceneData.tokens.map((tokenData) => {
|
||||
if (!tokenData.actorId || tokenData.actorLink || tokenData.actorData.data) {
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
return tokenData;
|
||||
}
|
||||
const token = new Token(tokenData);
|
||||
if (!token.actor) {
|
||||
tokenData.actorId = null as unknown as string;
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
} else if (!tokenData.actorLink) {
|
||||
const actorUpdateData = getActorUpdateData(token.data.actorData);
|
||||
tokenData.actorData = mergeObject(token.data.actorData, actorUpdateData);
|
||||
hasTokenUpdates = true;
|
||||
}
|
||||
return tokenData;
|
||||
});
|
||||
if (!hasTokenUpdates) return undefined;
|
||||
return hasTokenUpdates ? { tokens } : undefined;
|
||||
}
|
||||
|
||||
async function migrateCompendiums() {
|
||||
for (const compendium of game.packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCompendium(compendium: Compendium) {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
|
||||
const content = await compendium.getContent();
|
||||
|
||||
for (const entity of content) {
|
||||
try {
|
||||
const getUpdateData = (entity: Entity) => {
|
||||
switch (entityName) {
|
||||
case "Item":
|
||||
return getItemUpdateData(entity._data);
|
||||
case "Actor":
|
||||
return getActorUpdateData(entity._data);
|
||||
case "Scene":
|
||||
return getSceneUpdateData(entity._data as Scene.Data);
|
||||
}
|
||||
};
|
||||
const updateData = getUpdateData(entity);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}`);
|
||||
await compendium.updateEntity({ ...updateData, _id: entity._id });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
await compendium.migrate({});
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
}
|
||||
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator(
|
||||
{ getItemUpdateData, getActorUpdateData },
|
||||
{ migrateToTemplateEarly: false },
|
||||
);
|
||||
|
|
|
@ -2,157 +2,39 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4SpellDataData } from "../item/item-data";
|
||||
import logger from "../logger";
|
||||
import {
|
||||
getActorUpdateDataGetter,
|
||||
getCompendiumMigrator,
|
||||
getSceneUpdateDataGetter,
|
||||
migrateActors,
|
||||
migrateCompendiums,
|
||||
migrateItems,
|
||||
migrateScenes,
|
||||
} from "./migrationHelpers";
|
||||
|
||||
export async function migrate(): Promise<void> {
|
||||
await migrateItems();
|
||||
await migrateActors();
|
||||
await migrateScenes();
|
||||
await migrateCompendiums();
|
||||
await migrateItems(getItemUpdateData);
|
||||
await migrateActors(getActorUpdateData);
|
||||
await migrateScenes(getSceneUpdateData);
|
||||
await migrateCompendiums(migrateCompendium);
|
||||
}
|
||||
|
||||
async function migrateItems() {
|
||||
for (const item of game.items?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item entity ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item entity ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
|
||||
if (itemData.type !== "spell") return;
|
||||
const cooldownDurationUnit: string | undefined = itemData.data?.cooldownDuration.unit;
|
||||
|
||||
function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
||||
if (!["spell"].includes(itemData.type ?? "")) return undefined;
|
||||
const updateData: Record<string, unknown> = {
|
||||
"-=data.scrollPrice": null,
|
||||
"data.minimumLevels": { healer: null, wizard: null, sorcerer: null },
|
||||
data: {
|
||||
"-=scrollPrice": null,
|
||||
minimumLevels: { healer: null, wizard: null, sorcerer: null },
|
||||
cooldownDuration: {
|
||||
unit: cooldownDurationUnit === "custom" ? "rounds" : cooldownDurationUnit,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (((itemData.data as DS4SpellDataData).cooldownDuration.unit as string) === "custom") {
|
||||
updateData["data.cooldownDuration.unit"] = "rounds";
|
||||
}
|
||||
return updateData;
|
||||
}
|
||||
|
||||
async function migrateActors() {
|
||||
for (const actor of game.actors?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor entity ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor entity ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActorUpdateData(actorData: DeepPartial<Actor.Data>) {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = itemData ? getItemUpdateData(itemData) : undefined;
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return mergeObject(itemData, update, { enforceTypes: false, inplace: false });
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (actorData.type === "character") {
|
||||
updateData["data.slayerPoints"] = { value: 0 };
|
||||
}
|
||||
if (hasItemUpdates) {
|
||||
updateData["items"] = items;
|
||||
}
|
||||
return updateData;
|
||||
}
|
||||
|
||||
async function migrateScenes() {
|
||||
for (const scene of game.scenes?.entities ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene._data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene entity ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData, { enforceTypes: false });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene entity ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSceneUpdateData(sceneData: Scene.Data) {
|
||||
let hasTokenUpdates = false;
|
||||
const tokens = sceneData.tokens.map((tokenData) => {
|
||||
if (!tokenData.actorId || tokenData.actorLink || tokenData.actorData.data) {
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
return tokenData;
|
||||
}
|
||||
const token = new Token(tokenData);
|
||||
if (!token.actor) {
|
||||
tokenData.actorId = null as unknown as string;
|
||||
tokenData.actorData = {};
|
||||
hasTokenUpdates = true;
|
||||
} else if (!tokenData.actorLink) {
|
||||
const actorUpdateData = getActorUpdateData(token.data.actorData);
|
||||
tokenData.actorData = mergeObject(token.data.actorData, actorUpdateData);
|
||||
hasTokenUpdates = true;
|
||||
}
|
||||
return tokenData;
|
||||
});
|
||||
if (!hasTokenUpdates) return undefined;
|
||||
return hasTokenUpdates ? { tokens } : undefined;
|
||||
}
|
||||
|
||||
async function migrateCompendiums() {
|
||||
for (const compendium of game.packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateCompendium(compendium: Compendium) {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
await compendium.migrate({});
|
||||
|
||||
const content = await compendium.getContent();
|
||||
|
||||
for (const entity of content) {
|
||||
try {
|
||||
const getUpdateData = (entity: Entity) => {
|
||||
switch (entityName) {
|
||||
case "Item":
|
||||
return getItemUpdateData(entity._data);
|
||||
case "Actor":
|
||||
return getActorUpdateData(entity._data);
|
||||
case "Scene":
|
||||
return getSceneUpdateData(entity._data as Scene.Data);
|
||||
}
|
||||
};
|
||||
const updateData = getUpdateData(entity);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}`);
|
||||
await compendium.updateEntity({ ...updateData, _id: entity._id });
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of entity ${entity.name} (${entity.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
}
|
||||
const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
|
||||
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
|
||||
const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
|
||||
|
|
178
src/module/migrations/migrationHelpers.ts
Normal file
178
src/module/migrations/migrationHelpers.ts
Normal file
|
@ -0,0 +1,178 @@
|
|||
// SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4Actor } from "../actor/actor";
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Item } from "../item/item";
|
||||
import logger from "../logger";
|
||||
|
||||
type ItemUpdateDataGetter = (
|
||||
itemData: Partial<foundry.data.ItemData["_source"]>,
|
||||
) => DeepPartial<foundry.data.ItemData["_source"]> | Record<string, unknown> | undefined;
|
||||
|
||||
export async function migrateItems(getItemUpdateData: ItemUpdateDataGetter): Promise<void> {
|
||||
for (const item of getGame().items ?? []) {
|
||||
try {
|
||||
const updateData = getItemUpdateData(item.toObject());
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Item document ${item.name} (${item.id})`);
|
||||
await item.update(updateData), { enforceTypes: false };
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Item document ${item.name} (${item.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ActorUpdateDataGetter = (
|
||||
itemData: Partial<foundry.data.ActorData["_source"]>,
|
||||
) => DeepPartial<foundry.data.ActorData["_source"]> | undefined;
|
||||
|
||||
export async function migrateActors(getActorUpdateData: ActorUpdateDataGetter): Promise<void> {
|
||||
for (const actor of getGame().actors ?? []) {
|
||||
try {
|
||||
const updateData = getActorUpdateData(actor.toObject());
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Actor document ${actor.name} (${actor.id})`);
|
||||
await actor.update(updateData);
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Actor document ${actor.name} (${actor.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SceneUpdateDataGetter = (
|
||||
sceneData: foundry.documents.BaseScene["data"],
|
||||
) => DeepPartial<foundry.documents.BaseScene["data"]["_source"]>;
|
||||
|
||||
export async function migrateScenes(getSceneUpdateData: SceneUpdateDataGetter): Promise<void> {
|
||||
for (const scene of getGame().scenes ?? []) {
|
||||
try {
|
||||
const updateData = getSceneUpdateData(scene.data);
|
||||
if (updateData) {
|
||||
logger.info(`Migrating Scene document ${scene.name} (${scene.id})`);
|
||||
await scene.update(updateData);
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of Scene document ${scene.name} (${scene.id}), continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CompendiumMigrator = (compendium: CompendiumCollection<CompendiumCollection.Metadata>) => Promise<void>;
|
||||
|
||||
export async function migrateCompendiums(migrateCompendium: CompendiumMigrator): Promise<void> {
|
||||
for (const compendium of getGame().packs ?? []) {
|
||||
if (compendium.metadata.package !== "world") continue;
|
||||
if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue;
|
||||
await migrateCompendium(compendium);
|
||||
}
|
||||
}
|
||||
|
||||
export function getActorUpdateDataGetter(getItemUpdateData: ItemUpdateDataGetter): ActorUpdateDataGetter {
|
||||
return (
|
||||
actorData: Partial<foundry.data.ActorData["_source"]>,
|
||||
): DeepPartial<foundry.data.ActorData["_source"]> | undefined => {
|
||||
let hasItemUpdates = false;
|
||||
const items = actorData.items?.map((itemData) => {
|
||||
const update = getItemUpdateData(itemData);
|
||||
if (update) {
|
||||
hasItemUpdates = true;
|
||||
return { ...itemData, ...update };
|
||||
} else {
|
||||
return itemData;
|
||||
}
|
||||
});
|
||||
return hasItemUpdates ? { items } : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export function getSceneUpdateDataGetter(getActorUpdateData: ActorUpdateDataGetter): SceneUpdateDataGetter {
|
||||
return (sceneData: foundry.documents.BaseScene["data"]) => {
|
||||
const tokens = (sceneData.tokens as Collection<TokenDocument>).map((token: TokenDocument) => {
|
||||
const t = token.toObject();
|
||||
if (!t.actorId || t.actorLink) {
|
||||
t.actorData = {};
|
||||
} else if (!getGame().actors?.has(t.actorId)) {
|
||||
t.actorId = null;
|
||||
t.actorData = {};
|
||||
} else if (!t.actorLink) {
|
||||
const actorData = foundry.utils.deepClone(t.actorData);
|
||||
actorData.type = token.actor?.type;
|
||||
const update = getActorUpdateData(actorData);
|
||||
if (update !== undefined) {
|
||||
["items" as const, "effects" as const].forEach((embeddedName) => {
|
||||
const embeddedUpdates = update[embeddedName];
|
||||
if (embeddedUpdates === undefined || !embeddedUpdates.length) return;
|
||||
const updates = new Map(embeddedUpdates.flatMap((u) => (u && u._id ? [[u._id, u]] : [])));
|
||||
const originals = t.actorData[embeddedName];
|
||||
if (!originals) return;
|
||||
originals.forEach((original) => {
|
||||
if (!original._id) return;
|
||||
const update = updates.get(original._id);
|
||||
if (update) foundry.utils.mergeObject(original, update);
|
||||
});
|
||||
delete update[embeddedName];
|
||||
});
|
||||
foundry.utils.mergeObject(t.actorData, update);
|
||||
}
|
||||
}
|
||||
return t;
|
||||
});
|
||||
return { tokens };
|
||||
};
|
||||
}
|
||||
|
||||
export function getCompendiumMigrator(
|
||||
{
|
||||
getItemUpdateData,
|
||||
getActorUpdateData,
|
||||
getSceneUpdateData,
|
||||
}: {
|
||||
getItemUpdateData?: ItemUpdateDataGetter;
|
||||
getActorUpdateData?: ActorUpdateDataGetter;
|
||||
getSceneUpdateData?: SceneUpdateDataGetter;
|
||||
} = {},
|
||||
{ migrateToTemplateEarly = true } = {},
|
||||
) {
|
||||
return async (compendium: CompendiumCollection<CompendiumCollection.Metadata>): Promise<void> => {
|
||||
const entityName = compendium.metadata.entity;
|
||||
if (!["Actor", "Item", "Scene"].includes(entityName)) return;
|
||||
const wasLocked = compendium.locked;
|
||||
await compendium.configure({ locked: false });
|
||||
if (migrateToTemplateEarly) {
|
||||
await compendium.migrate();
|
||||
}
|
||||
|
||||
const documents = await compendium.getDocuments();
|
||||
|
||||
for (const doc of documents) {
|
||||
try {
|
||||
logger.info(`Migrating document ${doc.name} (${doc.id}) in compendium ${compendium.collection}`);
|
||||
if (doc instanceof DS4Item && getItemUpdateData) {
|
||||
const updateData = getItemUpdateData(doc.toObject());
|
||||
updateData && (await doc.update(updateData));
|
||||
} else if (doc instanceof DS4Actor && getActorUpdateData) {
|
||||
const updateData = getActorUpdateData(doc.toObject());
|
||||
updateData && (await doc.update(updateData));
|
||||
} else if (doc instanceof Scene && getSceneUpdateData) {
|
||||
const updateData = getSceneUpdateData(doc.data);
|
||||
updateData && (await doc.update(updateData));
|
||||
}
|
||||
} catch (err) {
|
||||
err.message = `Error during migration of document ${doc.name} (${doc.id}) in compendium ${compendium.collection}, continuing anyways.`;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (!migrateToTemplateEarly) {
|
||||
await compendium.migrate();
|
||||
}
|
||||
await compendium.configure({ locked: wasLocked });
|
||||
};
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
|
||||
export default function evaluateCheck(
|
||||
dice: number[],
|
||||
checkTargetNumber: number,
|
||||
|
@ -39,7 +41,7 @@ function assignSubChecksToDice(
|
|||
const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);
|
||||
|
||||
if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) {
|
||||
throw new Error(game.i18n.localize("DS4.ErrorInvalidNumberOfDice"));
|
||||
throw new Error(getGame().i18n.localize("DS4.ErrorInvalidNumberOfDice"));
|
||||
}
|
||||
|
||||
const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);
|
||||
|
@ -86,11 +88,7 @@ function shouldUseCoupForLastSubCheck(
|
|||
);
|
||||
}
|
||||
|
||||
interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {
|
||||
success?: boolean;
|
||||
failure?: boolean;
|
||||
count?: number;
|
||||
}
|
||||
interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {}
|
||||
|
||||
function evaluateDiceWithSubChecks(
|
||||
results: DieWithSubCheck[],
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
|
||||
/**
|
||||
* Provides default values for all arguments the `CheckFactory` expects.
|
||||
*/
|
||||
|
@ -10,7 +12,7 @@ class DefaultCheckOptions implements DS4CheckFactoryOptions {
|
|||
readonly maximumCoupResult = 1;
|
||||
readonly minimumFumbleResult = 20;
|
||||
readonly useSlayingDice = false;
|
||||
readonly rollMode: Const.DiceRollMode = "roll";
|
||||
readonly rollMode: foundry.CONST.DiceRollMode = "roll";
|
||||
readonly flavor: undefined;
|
||||
|
||||
mergeWith(other: Partial<DS4CheckFactoryOptions>): DS4CheckFactoryOptions {
|
||||
|
@ -37,15 +39,16 @@ class CheckFactory {
|
|||
|
||||
private options: DS4CheckFactoryOptions;
|
||||
|
||||
async execute(): Promise<ChatMessage> {
|
||||
async execute(): Promise<ChatMessage | undefined> {
|
||||
const innerFormula = ["ds", this.createCheckTargetNumberModifier(), this.createCoupFumbleModifier()].filterJoin(
|
||||
"",
|
||||
);
|
||||
const formula = this.options.useSlayingDice ? `{${innerFormula}}x` : innerFormula;
|
||||
const roll = Roll.create(formula);
|
||||
const speaker = ChatMessage.getSpeaker();
|
||||
|
||||
return roll.toMessage(
|
||||
{ speaker: ChatMessage.getSpeaker(), flavor: this.options.flavor },
|
||||
{ speaker, flavor: this.options.flavor },
|
||||
{ rollMode: this.options.rollMode, create: true },
|
||||
);
|
||||
}
|
||||
|
@ -85,7 +88,7 @@ export async function createCheckRoll(
|
|||
const newOptions: Partial<DS4CheckFactoryOptions> = {
|
||||
maximumCoupResult: gmModifierData.maximumCoupResult ?? options.maximumCoupResult,
|
||||
minimumFumbleResult: gmModifierData.minimumFumbleResult ?? options.minimumFumbleResult,
|
||||
useSlayingDice: game.settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
useSlayingDice: getGame().settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
rollMode: gmModifierData.rollMode ?? options.rollMode,
|
||||
flavor: options.flavor,
|
||||
};
|
||||
|
@ -113,13 +116,13 @@ async function askGmModifier(
|
|||
{ template, title }: { template?: string; title?: string } = {},
|
||||
): Promise<Partial<IntermediateGmModifierData>> {
|
||||
const usedTemplate = template ?? "systems/ds4/templates/dialogs/roll-options.hbs";
|
||||
const usedTitle = title ?? game.i18n.localize("DS4.DialogRollOptionsDefaultTitle");
|
||||
const usedTitle = title ?? getGame().i18n.localize("DS4.DialogRollOptionsDefaultTitle");
|
||||
const templateData = {
|
||||
title: usedTitle,
|
||||
checkTargetNumber: checkTargetNumber,
|
||||
maximumCoupResult: options.maximumCoupResult ?? defaultCheckOptions.maximumCoupResult,
|
||||
minimumFumbleResult: options.minimumFumbleResult ?? defaultCheckOptions.minimumFumbleResult,
|
||||
rollMode: options.rollMode ?? game.settings.get("core", "rollMode"),
|
||||
rollMode: options.rollMode ?? getGame().settings.get("core", "rollMode"),
|
||||
rollModes: CONFIG.Dice.rollModes,
|
||||
};
|
||||
const renderedHtml = await renderTemplate(usedTemplate, templateData);
|
||||
|
@ -131,11 +134,11 @@ async function askGmModifier(
|
|||
buttons: {
|
||||
ok: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize("DS4.GenericOkButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericOkButton"),
|
||||
callback: (html) => {
|
||||
if (!("jquery" in html)) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorUnexpectedHtmlType", {
|
||||
getGame().i18n.format("DS4.ErrorUnexpectedHtmlType", {
|
||||
exType: "JQuery",
|
||||
realType: "HTMLElement",
|
||||
}),
|
||||
|
@ -144,7 +147,7 @@ async function askGmModifier(
|
|||
const innerForm = html[0].querySelector("form");
|
||||
if (!innerForm) {
|
||||
throw new Error(
|
||||
game.i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
|
||||
getGame().i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
|
||||
);
|
||||
}
|
||||
resolve(innerForm);
|
||||
|
@ -153,7 +156,7 @@ async function askGmModifier(
|
|||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize("DS4.GenericCancelButton"),
|
||||
label: getGame().i18n.localize("DS4.GenericCancelButton"),
|
||||
},
|
||||
},
|
||||
default: "ok",
|
||||
|
@ -174,13 +177,11 @@ function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmM
|
|||
const chosenMinimumFumbleResult = parseInt(formData["minimum-fumble-result"]?.value);
|
||||
const chosenRollMode = formData["roll-mode"]?.value;
|
||||
|
||||
const invalidNumbers = [NaN, Infinity, -Infinity];
|
||||
|
||||
return {
|
||||
checkTargetNumber: invalidNumbers.includes(chosenCheckTargetNumber) ? undefined : chosenCheckTargetNumber,
|
||||
gmModifier: invalidNumbers.includes(chosenGMModifier) ? undefined : chosenGMModifier,
|
||||
maximumCoupResult: invalidNumbers.includes(chosenMaximumCoupResult) ? undefined : chosenMaximumCoupResult,
|
||||
minimumFumbleResult: invalidNumbers.includes(chosenMinimumFumbleResult) ? undefined : chosenMinimumFumbleResult,
|
||||
checkTargetNumber: Number.isSafeInteger(chosenCheckTargetNumber) ? chosenCheckTargetNumber : undefined,
|
||||
gmModifier: Number.isSafeInteger(chosenGMModifier) ? chosenGMModifier : undefined,
|
||||
maximumCoupResult: Number.isSafeInteger(chosenMaximumCoupResult) ? chosenMaximumCoupResult : undefined,
|
||||
minimumFumbleResult: Number.isSafeInteger(chosenMinimumFumbleResult) ? chosenMinimumFumbleResult : undefined,
|
||||
rollMode: Object.values(CONST.DICE_ROLL_MODES).includes(chosenRollMode) ? chosenRollMode : undefined,
|
||||
};
|
||||
}
|
||||
|
@ -190,7 +191,7 @@ function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmM
|
|||
*/
|
||||
interface GmModifierData {
|
||||
gmModifier: number;
|
||||
rollMode: Const.DiceRollMode;
|
||||
rollMode: foundry.CONST.DiceRollMode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,6 +222,6 @@ export interface DS4CheckFactoryOptions {
|
|||
maximumCoupResult: number;
|
||||
minimumFumbleResult: number;
|
||||
useSlayingDice: boolean;
|
||||
rollMode: Const.DiceRollMode;
|
||||
rollMode: foundry.CONST.DiceRollMode;
|
||||
flavor?: string;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation";
|
||||
|
||||
/**
|
||||
|
@ -15,11 +16,12 @@ import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation";
|
|||
* - Roll a check with a racial ability that makes `5` a coup and default fumble: `/r dsv19c5`
|
||||
*/
|
||||
export class DS4Check extends DiceTerm {
|
||||
constructor({ modifiers = [], options }: Partial<DiceTerm.TermData> = {}) {
|
||||
constructor({ modifiers = [], results = [], options }: Partial<DiceTerm.TermData> = {}) {
|
||||
super({
|
||||
faces: 20,
|
||||
modifiers: modifiers,
|
||||
options: options,
|
||||
results,
|
||||
modifiers,
|
||||
options,
|
||||
});
|
||||
|
||||
// Parse and store check target number
|
||||
|
@ -49,7 +51,7 @@ export class DS4Check extends DiceTerm {
|
|||
? parseInt(parseMinimumFumbleResult)
|
||||
: DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
|
||||
if (this.minimumFumbleResult <= this.maximumCoupResult)
|
||||
throw new SyntaxError(game.i18n.localize("DS4.ErrorDiceCoupFumbleOverlap"));
|
||||
throw new SyntaxError(getGame().i18n.localize("DS4.ErrorDiceCoupFumbleOverlap"));
|
||||
}
|
||||
|
||||
// Parse and store no fumble
|
||||
|
@ -57,6 +59,10 @@ export class DS4Check extends DiceTerm {
|
|||
if (noFumbleModifier) {
|
||||
this.canFumble = false;
|
||||
}
|
||||
|
||||
if (this.results.length > 0) {
|
||||
this.evaluateResults();
|
||||
}
|
||||
}
|
||||
|
||||
coup: boolean | null = null;
|
||||
|
@ -72,14 +78,14 @@ export class DS4Check extends DiceTerm {
|
|||
}
|
||||
|
||||
/** @override */
|
||||
get total(): number | null {
|
||||
get total(): string | number | null | undefined {
|
||||
if (this.fumble) return 0;
|
||||
return super.total;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
evaluate({ minimize = false, maximize = false } = {}): this {
|
||||
super.evaluate({ minimize, maximize });
|
||||
_evaluateSync({ minimize = false, maximize = false } = {}): this {
|
||||
super._evaluateSync({ minimize, maximize });
|
||||
this.evaluateResults();
|
||||
return this;
|
||||
}
|
||||
|
@ -102,17 +108,14 @@ export class DS4Check extends DiceTerm {
|
|||
this.fumble = results[0].failure ?? false;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static fromResults<T extends DS4Check>(
|
||||
this: ConstructorOf<T>,
|
||||
options: Partial<DiceTerm.TermData>,
|
||||
results: DiceTerm.Result[],
|
||||
): T {
|
||||
const term = new this(options);
|
||||
term.results = results;
|
||||
term.evaluateResults();
|
||||
term._evaluated = true;
|
||||
return term;
|
||||
/**
|
||||
* @override
|
||||
* @remarks "min" and "max" are filtered out because they are irrelevant for
|
||||
* {@link DS4Check}s and only result in some dice rolls being highlighted
|
||||
* incorrectly.
|
||||
*/
|
||||
getResultCSS(result: DiceTerm.Result): (string | null)[] {
|
||||
return super.getResultCSS(result).filter((cssClass) => cssClass !== "min" && cssClass !== "max");
|
||||
}
|
||||
|
||||
static readonly DEFAULT_CHECK_TARGET_NUMBER = 10;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Check } from "./check";
|
||||
|
||||
export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>> extends Roll<D> {
|
||||
|
@ -12,10 +13,10 @@ export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>
|
|||
* template if the first dice term is a ds4 check.
|
||||
* @override
|
||||
*/
|
||||
async render(chatOptions: Roll.ChatOptions = {}): Promise<string> {
|
||||
chatOptions = mergeObject(
|
||||
async render(chatOptions: Parameters<Roll["render"]>[0] = {}): Promise<string> {
|
||||
chatOptions = foundry.utils.mergeObject(
|
||||
{
|
||||
user: game.user?._id,
|
||||
user: getGame().user?.id,
|
||||
flavor: null,
|
||||
template: DS4Roll.CHAT_TEMPLATE,
|
||||
blind: false,
|
||||
|
@ -25,7 +26,7 @@ export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>
|
|||
const isPrivate = chatOptions.isPrivate;
|
||||
|
||||
// Execute the roll, if needed
|
||||
if (!this._rolled) this.roll();
|
||||
if (!this._evaluated) this.evaluate();
|
||||
|
||||
// Define chat data
|
||||
const firstDiceTerm = this.dice[0];
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "../helpers";
|
||||
import { DS4Check } from "./check";
|
||||
|
||||
export default function registerSlayingDiceModifier(): void {
|
||||
DicePool.MODIFIERS.x = slay;
|
||||
DicePool.POOL_REGEX = /^{([^}]+)}([A-z]([A-z0-9<=>]+)?)?$/;
|
||||
PoolTerm.MODIFIERS.x = slay;
|
||||
}
|
||||
|
||||
function slay(this: DicePool, modifier: string): void {
|
||||
function slay(this: PoolTerm, modifier: string): void {
|
||||
const rgx = /[xX]/;
|
||||
const match = modifier.match(rgx);
|
||||
if (!match || !this.rolls) return;
|
||||
|
@ -21,11 +21,12 @@ function slay(this: DicePool, modifier: string): void {
|
|||
checked++;
|
||||
if (diceTerm instanceof DS4Check && diceTerm.coup) {
|
||||
const formula = `dsv${diceTerm.checkTargetNumber}c${diceTerm.maximumCoupResult}:${diceTerm.minimumFumbleResult}n`;
|
||||
const additionalRoll = Roll.create(formula).evaluate();
|
||||
const additionalRoll = Roll.create(formula).evaluate({ async: false });
|
||||
|
||||
this.rolls.push(additionalRoll);
|
||||
this.results.push({ result: additionalRoll.total ?? 0, active: true });
|
||||
this.terms.push(formula);
|
||||
}
|
||||
if (checked > 1000) throw new Error(game.i18n.localize("DS4.ErrorSlayingDiceRecursionLimitExceeded"));
|
||||
if (checked > 1000) throw new Error(getGame().i18n.localize("DS4.ErrorSlayingDiceRecursionLimitExceeded"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { getGame } from "./helpers";
|
||||
|
||||
export function registerSystemSettings(): void {
|
||||
/**
|
||||
* Track the migrations version of the latest migration that has been applied
|
||||
*/
|
||||
game.settings.register("ds4", "systemMigrationVersion", {
|
||||
getGame().settings.register("ds4", "systemMigrationVersion", {
|
||||
name: "System Migration Version",
|
||||
scope: "world",
|
||||
config: false,
|
||||
|
@ -14,7 +16,7 @@ export function registerSystemSettings(): void {
|
|||
default: -1,
|
||||
});
|
||||
|
||||
game.settings.register("ds4", "useSlayingDiceForAutomatedChecks", {
|
||||
getGame().settings.register("ds4", "useSlayingDiceForAutomatedChecks", {
|
||||
name: "DS4.SettingUseSlayingDiceForAutomatedChecksName",
|
||||
hint: "DS4.SettingUseSlayingDiceForAutomatedChecksHint",
|
||||
scope: "world",
|
||||
|
@ -23,7 +25,7 @@ export function registerSystemSettings(): void {
|
|||
default: false,
|
||||
});
|
||||
|
||||
game.settings.register("ds4", "showSlayerPoints", {
|
||||
getGame().settings.register("ds4", "showSlayerPoints", {
|
||||
name: "DS4.SettingShowSlayerPointsName",
|
||||
hint: "DS4.SettingShowSlayerPointsHint",
|
||||
scope: "world",
|
||||
|
@ -33,7 +35,7 @@ export function registerSystemSettings(): void {
|
|||
});
|
||||
}
|
||||
|
||||
interface DS4Settings {
|
||||
export interface DS4Settings {
|
||||
systemMigrationVersion: number;
|
||||
useSlayingDiceForAutomatedChecks: boolean;
|
||||
showSlayerPoints: boolean;
|
||||
|
@ -41,8 +43,8 @@ interface DS4Settings {
|
|||
|
||||
export function getDS4Settings(): DS4Settings {
|
||||
return {
|
||||
systemMigrationVersion: game.settings.get("ds4", "systemMigrationVersion"),
|
||||
useSlayingDiceForAutomatedChecks: game.settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"),
|
||||
systemMigrationVersion: getGame().settings.get("ds4", "systemMigrationVersion"),
|
||||
useSlayingDiceForAutomatedChecks: getGame().settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
|
||||
showSlayerPoints: getGame().settings.get("ds4", "showSlayerPoints"),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,18 +12,18 @@
|
|||
{"_id":"0vIgZkHBeEPut73w","name":"Elfenbogen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":3325,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":6,"opponentDefense":-3},"flags":{},"img":"icons/weapons/bows/longbow-recurve.webp","effects":[{"_id":"QScLkDv6gysh119m","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"azjxgNJkYNvxg622","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"0wgXMtaVpVJabEun","name":"Dolch +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative +1</p>","quantity":1,"price":1752,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-3},"flags":{},"img":"icons/weapons/daggers/dagger-double-engraved-black.webp","effects":[{"_id":"9jtH6ER0s0I8SPyi","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"JLc9UYdHy4aAeqA2","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"12WbnUt5h84JQxMp","name":"Kettenpanzer +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":4260,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":2,"armorMaterialType":"chain","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-scale-grey.webp","effects":[{"_id":"gPN9UcowmdjdyGyn","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}]}
|
||||
{"name":"Heiltrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieses oftmals rote Getränk heilt W20 Lebenskraft.</p>","quantity":1,"price":10,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"19bmt5UJrT3T36wE"}
|
||||
{"name":"Unverwundbartrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Der Charakter erhält für W20 Runden +20 auf seine Abwehr durch diesen meist roten, flockigen Trank. Dieser Bonus gilt auch bei Schaden, gegen den normalerweise keine Abwehr zulässig ist.</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"1HjmUAR5mf59yXlw"}
|
||||
{"_id":"19bmt5UJrT3T36wE","name":"Heiltrank","type":"loot","img":"icons/consumables/potions/bottle-round-corked-red.webp","data":{"description":"<p>Dieses oftmals rote Getränk heilt W20 Lebenskraft.</p>","quantity":1,"price":10,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"1HjmUAR5mf59yXlw","name":"Unverwundbartrank","type":"loot","img":"icons/consumables/potions/potion-flask-corked-shiny-red.webp","data":{"description":"<p>Der Charakter erhält für W20 Runden +20 auf seine Abwehr durch diesen meist roten, flockigen Trank. Dieser Bonus gilt auch bei Schaden, gegen den normalerweise keine Abwehr zulässig ist.</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"1IWsAaMSnz1Q9ZWd","name":"Schleuder +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Distanzmalus -1 pro 2m</p>","quantity":1,"price":1250.1,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":2,"opponentDefense":-2},"flags":{},"img":"icons/weapons/slings/slingshot-wood.webp","effects":[{"_id":"aKfE4S2oocgJMro8","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"1hmprC7XVhIPemy5","name":"Kurzbogen +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1</p>","quantity":1,"price":1756,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":3,"opponentDefense":-2},"flags":{},"img":"icons/weapons/bows/shortbow-recurve.webp","effects":[{"_id":"zgiIGlRMVCgAzrn7","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"VkuGm7hES83WX4HD","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"name":"Teleporttrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieser rauchige, wirbelnde Trank wirkt den Zauber <em>Teleport</em> auf den Trinker (keine Probe notwendig), nicht jedoch auf weitere Charaktere.</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"1uHuQJcCjjxzvP4C"}
|
||||
{"_id":"1uHuQJcCjjxzvP4C","name":"Teleporttrank","type":"loot","img":"icons/consumables/potions/potion-bottle-fumes-blue.webp","data":{"description":"<p>Dieser rauchige, wirbelnde Trank wirkt den Zauber <em>Teleport</em> auf den Trinker (keine Probe notwendig), nicht jedoch auf weitere Charaktere.</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Rüstung des Löwen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Eine Plattenrüstung +2 mit verzierten Löwenköpfen, die <em>Laufen</em> +1,5m gewährt.</p>","quantity":1,"price":6800,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":3,"armorMaterialType":"plate","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-layered-gold.webp","effects":[{"_id":"uOEN4xXL3IcebbJO","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true},{"_id":"iIT1kOsyMJn0mIte","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":1.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen +1,5 (magisch)","tint":"","transfer":true}],"_id":"1uYooTtDWgzB9FI9"}
|
||||
{"name":"Wechselring","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Mittels <strong>Wechsler +V</strong> verleiht dieser Ring +10 auf Proben, um die eigenen Zauber zu wechseln.</p>","quantity":1,"price":1502,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/finger/ring-cabochon-white-blue.webp","effects":[],"_id":"1vrVO2sqFqC4AA1k"}
|
||||
{"_id":"2BNzuiU6wc3r9ByF","name":"Plattenarmschienen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":4257,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"plate","armorType":"vambrace"},"flags":{},"img":"icons/equipment/wrist/bracer-armored-steel-blue.webp","effects":[{"_id":"PIRBFfHOrmdREhnH","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true}]}
|
||||
{"_id":"2C0GH1sYXj8QtRTK","name":"Krummsäbel +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1756,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-2},"flags":{},"img":"icons/weapons/swords/scimitar-guard-brown.webp","effects":[{"_id":"xjUL1B0P5jhze3vQ","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"2JQowFF6ZjF90OFI","name":"Hellebarde","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2, Zerbricht bei Schlagen-Patzer</p>","quantity":1,"price":1754,"availability":"village","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":0},"flags":{},"img":"icons/weapons/polearms/halberd-engraved-black.webp","effects":[{"_id":"APXje5Ppu0d75HNw","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"duration":{},"label":"Initiative -2","transfer":true}]}
|
||||
{"name":"Abklingring","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Dieser einfache Abklingring senkt die Abklingzeit sämtlicher Zauber seines Trägers um 1.</p>","quantity":1,"price":5252,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/finger/ring-band-engraved-scrolls-silver.webp","effects":[],"_id":"2XfoxOYNOTar9OAt"}
|
||||
{"name":"Atemfreitrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Der Trinker dieses sprudelnden Trankes braucht für KÖR in Stunden nicht zu atmen.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"2jgIyVHZYJroSUFY"}
|
||||
{"_id":"2jgIyVHZYJroSUFY","name":"Atemfreitrank","type":"loot","img":"icons/consumables/potions/bottle-conical-bubbling-blue.webp","data":{"description":"<p>Der Trinker dieses sprudelnden Trankes braucht für KÖR in Stunden nicht zu atmen.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"2le5COwoh45Pc4oD","name":"Flegel +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative -2</p>","quantity":1,"price":1758,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-1},"flags":{},"img":"icons/weapons/maces/flail-cube-grey.webp","effects":[{"_id":"yXvt3CT4FbXYjIfc","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"duration":{},"label":"Initiative -2","transfer":true},{"_id":"rUye8ORwMGGtWPrr","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"2ydkhz5gDjxAiaYy","name":"Bihänder +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":2260,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-5},"flags":{},"img":"icons/weapons/swords/greatsword-crossguard-engraved-green.webp","effects":[{"_id":"DaKTtdhRO45QZuDJ","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative -2","tint":"","transfer":true},{"_id":"qdSiJ4l0AuTd68CB","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"34fD45Yzi3s2cSgy","name":"Lederpanzer (Für Reittiere) +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"","quantity":1,"price":4262,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"leather","armorType":"body"},"flags":{},"img":"icons/commodities/leather/leather-leaf-tan.webp","effects":[{"_id":"DY0fXwaK8RHbyo75","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true}]}
|
||||
|
@ -31,8 +31,8 @@
|
|||
{"_id":"3pdw4CN8Wc9oCKX5","name":"Schloss: Einfach (SW: 0)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":1,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/containers/chest/chest-reinforced-steel-oak-tan.webp","effects":[]}
|
||||
{"_id":"3zqSBuiQWIsIov4h","name":"Kletterausrüstung","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":1,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/survival/rope-wrapped-loops-grey.webp","effects":[]}
|
||||
{"_id":"4E9WdEs1JaWrCYim","name":"Wagen (4 Räder)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":35,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/commodities/wood/wood-wheel-brown.webp","effects":[]}
|
||||
{"name":"Rüstung des Kriegers","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese aufwendig verzierte Plattenrüstung +2 gewährt ihrem Träger +1 auf <em>Körper</em>.</p>","quantity":1,"price":7300,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":3,"armorMaterialType":"plate","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-metal-white-02.webp","effects":[{"_id":"uOEN4xXL3IcebbJO","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true},{"_id":"TZoEpatdi8z1nreX","flags":{},"changes":[{"key":"data.attributes.body.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Körper +1 (magisch)","tint":"","transfer":true}],"_id":"55AkLjiaIn0SWO9k"}
|
||||
{"_id":"5DY52CR03xXJHG6m","name":"Plattenpanzer +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":6300,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":3,"armorMaterialType":"plate","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-metal-pieced-grey-02.webp","effects":[{"_id":"wz2krJzwVba18XvV","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true}]}
|
||||
{"_id":"55AkLjiaIn0SWO9k","name":"Rüstung des Kriegers","type":"armor","img":"icons/equipment/chest/breastplate-collared-steel.webp","data":{"description":"<p>Diese aufwendig verzierte Plattenrüstung +2 gewährt ihrem Träger +1 auf <em>Körper</em>.</p>","quantity":1,"price":7300,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":3,"armorMaterialType":"plate","armorType":"body"},"effects":[{"_id":"uOEN4xXL3IcebbJO","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":"2","mode":2}],"duration":{"startTime":null},"label":"Panzerung +2 (magisch)","transfer":true,"disabled":false},{"_id":"TZoEpatdi8z1nreX","flags":{},"changes":[{"key":"data.attributes.body.total","value":"1","mode":2}],"disabled":false,"duration":{"startTime":null},"icon":null,"label":"Körper +1 (magisch)","tint":null,"transfer":true}],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"5DY52CR03xXJHG6m","name":"Plattenpanzer +3","type":"armor","img":"icons/equipment/chest/breastplate-gorget-steel.webp","data":{"description":null,"quantity":1,"price":6300,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":3,"armorMaterialType":"plate","armorType":"body"},"effects":[{"_id":"wz2krJzwVba18XvV","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":"3","mode":2}],"duration":{"startTime":null},"label":"Panzerung +3 (magisch)","transfer":true,"disabled":false}],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"5KxdKllRXuau0Uhm","name":"Breitschwert","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":8,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":-2},"flags":{},"img":"icons/weapons/swords/sword-broad-worn.webp","effects":[]}
|
||||
{"_id":"5MrsKOS4sAxpMv2U","name":"Lanze +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Nur im Trab (WB +1) oder Galopp (WB +4)</p>","quantity":1,"price":1752,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-2},"flags":{},"img":"icons/weapons/polearms/spear-flared-blue.webp","effects":[{"_id":"iLA1AQrTlmA0BFnC","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"name":"Immertreff","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Ein tödlicher Langbogen +2 mit <strong>Fieser Schuss +II</strong> und <strong>Scharfschütze +II</strong>.</p>\n<p>Zweihändig, Initiative +1, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":10660,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":4,"opponentDefense":-2},"flags":{},"img":"icons/weapons/bows/longbow-gold-pink.webp","effects":[{"_id":"XsqzwEX1AvYJyczG","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"iUXn0CQKZw6Rr1yH","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}],"_id":"5cqP2SvMe5j0BD3t"}
|
||||
|
@ -40,7 +40,7 @@
|
|||
{"_id":"6QehiJpVqqA9bW3P","name":"Hammer +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1757,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-3},"flags":{},"img":"icons/weapons/hammers/shorthammer-double-steel-embossed.webp","effects":[{"_id":"xQlGUPOpB6Me9OgF","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"name":"Gewänder des Adlers","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese hellbeige, mit Adlerfedern verzierte Lederrüstung +1 gewährt +1 auf <em>Geist</em>.</p>","quantity":1,"price":4254,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"leather","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-banded-simple-leather-brown.webp","effects":[{"_id":"CUa4rA1A1cwWac46","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true},{"_id":"cl2PqWeAtDsBjz8k","flags":{},"changes":[{"key":"data.attributes.mind.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Geist +1 (magisch)","tint":"","transfer":true}],"_id":"6UBvjMJd6n5P5YWv"}
|
||||
{"_id":"6WqPqjMQMMPjV99U","name":"Kurzbogen +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1</p>","quantity":1,"price":1256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/bows/shortbow-recurve-leather.webp","effects":[{"_id":"zgiIGlRMVCgAzrn7","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"8iny3Tt6i6g5EcYh","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Trank der Lebenskraft","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Diese meist blutroten Tränke erhöhen die Lebenskraft um W20 für W20 Stunden.</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"74iFRkzvOwLxOxOq"}
|
||||
{"_id":"74iFRkzvOwLxOxOq","name":"Trank der Lebenskraft","type":"loot","img":"icons/consumables/potions/potion-bottle-corked-labeled-red.webp","data":{"description":"<p>Diese meist blutroten Tränke erhöhen die Lebenskraft um W20 für W20 Stunden.</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"7CCoTap4GzN1uSNw","name":"Krummschwert +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1757,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-1},"flags":{},"img":"icons/weapons/swords/scimitar-guard-wood.webp","effects":[{"_id":"Vg3Q9A7QIXHrABFk","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"7JCc96rbTbTSASbT","name":"Metallbesteck","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":4,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/tools/cooking/fork-steel-grey.webp","effects":[]}
|
||||
{"_id":"7g4vNrJOoX0v4Byu","name":"Keule +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":null,"quantity":1,"price":2250.2,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-3},"flags":{},"img":"icons/weapons/clubs/club-banded-brown.webp","effects":[{"_id":"8y8e8BsFQZZkwUXk","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
|
@ -54,7 +54,7 @@
|
|||
{"_id":"99xGE9jkmXEXyf4U","name":"Plattenpanzer (Für Reittiere) +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Laufen -0,5m</p>","quantity":1,"price":4400,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":3,"armorMaterialType":"plate","armorType":"body"},"flags":{},"img":"icons/commodities/metal/mail-plate-steel.webp","effects":[{"_id":"pAxAXam5JBN6Acz9","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":-0.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen -0,5m","tint":"","transfer":true},{"_id":"Jkhqmke6gzWU8vPZ","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}]}
|
||||
{"_id":"9H3CBGOLUJ5JCHGJ","name":"Plattenbeinschienen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Laufen -0,5m</p>","quantity":1,"price":8,"availability":"village","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"plate","armorType":"greaves"},"flags":{},"img":"icons/equipment/leg/pants-armored-tasset-steel.webp","effects":[{"_id":"sIbiQW6tSjZyfCzk","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":-0.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen -0,5m","tint":"","transfer":true}]}
|
||||
{"_id":"9MsBlnMhvAMe4zTk","name":"Leichte Armbrust +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2</p>","quantity":1,"price":1758,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":3,"opponentDefense":-1},"flags":{},"img":"icons/weapons/crossbows/crossbow-simple-brown.webp","effects":[{"_id":"gMm2PnBADqXBrCi1","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative -2","tint":"","transfer":true},{"_id":"sACwF2e5qLRs6c1a","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Andauernder Heiltrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieses oft purpurne Getränk heilt 2W20 Runden lang 1 Lebenskraft/Runde.</p>","quantity":1,"price":20,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"9NUM3H7oTfrHhFpD"}
|
||||
{"_id":"9NUM3H7oTfrHhFpD","name":"Andauernder Heiltrank","type":"loot","img":"icons/consumables/potions/bottle-round-corked-pink.webp","data":{"description":"<p>Dieses oft purpurne Getränk heilt 2W20 Runden lang 1 Lebenskraft/Runde.</p>","quantity":1,"price":20,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"9PdQv9CRy4ckaF0j","name":"Metallhelm +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":3256,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"plate","armorType":"helmet"},"flags":{},"img":"icons/equipment/head/helm-barbute-engraved.webp","effects":[{"_id":"9IOcWr1CbRpF3lya","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}]}
|
||||
{"_id":"9aY1zWfD5RwZlAUH","name":"Tee (10 Tassen)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.05,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/commodities/flowers/buds-red-green.webp","effects":[]}
|
||||
{"name":"Smaragd-Schlüssel","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"loot","data":{"description":"<p>Einmal alle 24h kann man damit den Zauber <em>Öffnen</em> auf ein Schloss wirken.</p>","quantity":1,"price":null,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/sundries/misc/key-jeweled-gold-purple.webp","effects":[],"_id":"9xQRXWUYj3giHVRC"}
|
||||
|
@ -68,7 +68,7 @@
|
|||
{"_id":"Aw9aoumlI69gYv75","name":"Lederschienen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>An Arm & Bein</p>","quantity":1,"price":4254,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"leather","armorType":"vambraceGreaves"},"flags":{},"img":"icons/equipment/wrist/bracer-banded-leather-black.webp","effects":[{"_id":"LE8TRjZF13O6kDB8","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true}]}
|
||||
{"_id":"BeXHrv1TQzgfV6Mc","name":"Umhängetasche","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/containers/bags/pack-leather-embossed-brown.webp","effects":[]}
|
||||
{"_id":"BrsnuGuOEfolt9VV","name":"Flegel +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative -2</p>","quantity":1,"price":2758,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":5,"opponentDefense":-3},"flags":{},"img":"icons/weapons/maces/flail-studded-grey.webp","effects":[{"_id":"yXvt3CT4FbXYjIfc","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"duration":{},"label":"Initiative -2","transfer":true},{"_id":"98YoU1PGqcSm47Zw","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"C2ggZE3ifOXlonkj","name":"Laternenöl (brennt 4h)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.05,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/containers/kitchenware/vase-009.webp","effects":[]}
|
||||
{"_id":"C2ggZE3ifOXlonkj","name":"Laternenöl (brennt 4h)","type":"loot","img":"icons/containers/kitchenware/vase-clay-cracked-white.webp","data":{"description":"","quantity":1,"price":0.05,"availability":"hamlet","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"CHRqMQxkgz3jad9J","name":"Schlagring +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Wie waffenlos, Gegner aber kein Abwehr-Bonus</p>","quantity":1,"price":1751,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-3},"flags":{},"img":"icons/weapons/fist/fist-knuckles-brass.webp","effects":[{"_id":"K3m0tLhoW9vT6e19","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"name":"Blutrüstung","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>In diese rotgefärbte Plattenrüstung +1 ist das Talent <strong>Verletzen +I</strong> eingebettet.</p>\n<p>Laufen -0,5m</p>","quantity":1,"price":5300,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":3,"armorMaterialType":"plate","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-rivited-red.webp","effects":[{"_id":"pAxAXam5JBN6Acz9","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":-0.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen -0,5m","tint":"","transfer":true},{"_id":"CXouU4P0kZYU6oXR","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}],"_id":"CQZtlL8zUdOVUVrU"}
|
||||
{"_id":"CsUnbnytOapKsjuW","name":"Kartenspiel","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":1,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/sundries/gaming/playing-cards-black.webp","effects":[]}
|
||||
|
@ -109,7 +109,7 @@
|
|||
{"_id":"JZDsSUkQGVf0aOjH","name":"Flegel +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative -2</p>","quantity":1,"price":2258,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-2},"flags":{},"img":"icons/weapons/maces/flail-spiked-grey.webp","effects":[{"_id":"yXvt3CT4FbXYjIfc","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"duration":{},"label":"Initiative -2","transfer":true},{"_id":"PD6vgViNBP4QaxMK","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"JZkzRagRS8TKZplw","name":"Zwergenaxt +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -1</p>","quantity":1,"price":2310,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-3},"flags":{},"img":"icons/weapons/axes/axe-double-engraved-runes.webp","effects":[{"_id":"Ytio5tOcCUO91MQ0","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-1,"mode":2}],"duration":{},"label":"Initiative -1","transfer":true},{"_id":"armx56Psy1qosJib","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"JjM6nTZzV28ioIFq","name":"Feuerstein & Zunder","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.05,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/commodities/stone/geode-raw-white.webp","effects":[]}
|
||||
{"name":"Stärketrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieser nach Schweiß riechende Trank verdoppelt ST für ST in Runden.</p>","quantity":1,"price":150,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"JlcYB53S1wQRfmUG"}
|
||||
{"_id":"JlcYB53S1wQRfmUG","name":"Stärketrank","type":"loot","img":"icons/consumables/potions/potion-bottle-corked-stopper-yellow.webp","data":{"description":"<p>Dieser nach Schweiß riechende Trank verdoppelt ST für ST in Runden.</p>","quantity":1,"price":150,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Skrupelloser Bogen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Ein Kurzbogen +1 mit <em>Kleiner Terror</em>, der Feinde angeblich immer in den Rücken trifft.</p>\n<p>Zweihändig, Initiative +1</p>","quantity":1,"price":2006,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/bows/shortbow-recurve-red.webp","effects":[{"_id":"zgiIGlRMVCgAzrn7","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"8iny3Tt6i6g5EcYh","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}],"_id":"K4fd3AlpMoK1EZeg"}
|
||||
{"name":"Robe der Macht","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese violette Robe +3 verleiht ihrem Träger +1 auf <em>Geist</em>.</p>","quantity":1,"price":5251,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/robe-collared-pink.webp","effects":[{"_id":"Qpy3XcHOokbU7ZaS","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true},{"_id":"tGAxxMZu2cj0Pzs2","flags":{},"changes":[{"key":"data.attributes.mind.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Geist +1 (magisch)","tint":"","transfer":true}],"_id":"KGk7UFwLwrsdPuQe"}
|
||||
{"_id":"KJsCiqvtPqjyu65Y","name":"Plattenbeinschienen +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":2258,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"plate","armorType":"greaves"},"flags":{},"img":"icons/equipment/leg/cuisses-reticulated-steel.webp","effects":[{"_id":"I32OnPe7rgPEHtEk","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}]}
|
||||
|
@ -117,18 +117,18 @@
|
|||
{"_id":"KefO4lxHxwddpFFu","name":"Holzschild +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"shield","data":{"description":null,"quantity":1,"price":4251,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1},"flags":{},"img":"icons/equipment/shield/round-wooden-boss-gold-brown.webp","effects":[{"_id":"AkDi2TNzgpT2ZfrH","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true}]}
|
||||
{"_id":"KyhMB9Jn8Vaxs4eF","name":"Axt +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":2256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-3},"flags":{},"img":"icons/weapons/axes/shortaxe-yellow.webp","effects":[{"_id":"yaR4SKssj8gYJB91","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"L2ZE2l98snBZw5DZ","name":"Federkiel","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.1,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/tools/scribal/ink-quill-pink.webp","effects":[]}
|
||||
{"name":"Verkleinerungstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Verkleinert den Trinkenden auf ein Zehntel seiner normalen Größe für W20 Minuten. KÖR, ST und HÄ werden solange halbiert und die Kampfwerte entsprechend angepasst.</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"LAI81qZlbkr7MlGY"}
|
||||
{"_id":"LAI81qZlbkr7MlGY","name":"Verkleinerungstrank","type":"loot","img":"icons/consumables/potions/potion-flask-corked-yellow.webp","data":{"description":"<p>Verkleinert den Trinkenden auf ein Zehntel seiner normalen Größe für W20 Minuten. KÖR, ST und HÄ werden solange halbiert und die Kampfwerte entsprechend angepasst.</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"Li5rC0lvytKRAc31","name":"Turmschild +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"shield","data":{"description":null,"quantity":1,"price":4265,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":2},"flags":{},"img":"icons/equipment/shield/heater-steel-gold.webp","effects":[{"_id":"yfHGGfxxGvmHG6b9","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}]}
|
||||
{"name":"Zaubertrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Erhöht die Werte von <em>Zaubern</em> und <em>Zielzauber</em> für die Dauer eines Kampfes um +1.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"LoY2CnEEWfxbvjEt"}
|
||||
{"_id":"LoY2CnEEWfxbvjEt","name":"Zaubertrank","type":"loot","img":"icons/consumables/potions/bottle-pear-corked-pink.webp","data":{"description":"<p>Erhöht die Werte von <em>Zaubern</em> und <em>Zielzauber</em> für die Dauer eines Kampfes um +1.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"MO1ga2aLjjkBZRzt","name":"Kurzschwert +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":null,"quantity":1,"price":1756,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-2},"flags":{},"img":"icons/weapons/swords/shortsword-guard-silver.webp","effects":[{"_id":"qkDy1MWD6Fkk97e6","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"N3RcggWJuKGtKZyP","name":"Schlachtbeil +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -6, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":2770,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":5,"opponentDefense":-5},"flags":{},"img":"icons/weapons/axes/axe-battle-blackened.webp","effects":[{"_id":"atmWPMxgNoeole7n","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-6,"mode":2}],"duration":{},"label":"Initiative -6","transfer":true},{"_id":"aHr33jsfG78BJ7m2","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"NHV9ho8tGutv0mrS","name":"Zwergenaxt +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -1</p>","quantity":1,"price":2810,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":5,"opponentDefense":-4},"flags":{},"img":"icons/weapons/axes/axe-double-engraved-black.webp","effects":[{"_id":"Ytio5tOcCUO91MQ0","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-1,"mode":2}],"duration":{},"label":"Initiative -1","transfer":true},{"_id":"WsibIQWwcjabH8QS","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"NMmZ3Uu9uwAHc5IP","name":"Schwere Armbrust +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -4</p>","quantity":1,"price":2765,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":5,"opponentDefense":-4},"flags":{},"img":"icons/weapons/crossbows/crossbow-blue.webp","effects":[{"_id":"N4vxVFNLW9Q9QsIp","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative -4","tint":"","transfer":true},{"_id":"sOBnfFviucXKykt1","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"NSg4SdEHDuCfwXji","name":"Robe +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"","quantity":1,"price":2251,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/robe-layered-red.webp","effects":[{"_id":"0GJ943ZfeGKcsb2l","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}]}
|
||||
{"name":"Abklingtrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Diese meist hellblauen Tränke halbieren (abrunden) die Abklingzeit aller Zauber für die Dauer eines Kampfes.</p>","quantity":1,"price":50,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"NZPp8EGEg1JmXdNd"}
|
||||
{"_id":"NZPp8EGEg1JmXdNd","name":"Abklingtrank","type":"loot","img":"icons/consumables/potions/bottle-round-corked-blue.webp","data":{"description":"<p>Diese meist hellblauen Tränke halbieren (abrunden) die Abklingzeit aller Zauber für die Dauer eines Kampfes.</p>","quantity":1,"price":50,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"Nb65CmtFiiWPpnNZ","name":"Schlachtbeil +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -6, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":3770,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":7,"opponentDefense":-7},"flags":{},"img":"icons/weapons/axes/axe-battle-heavy-black.webp","effects":[{"_id":"atmWPMxgNoeole7n","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-6,"mode":2}],"duration":{},"label":"Initiative -6","transfer":true},{"_id":"DJQ1hHJRJuraxIym","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"Nz6gFGSHzHVO3QxA","name":"Breitschwert +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1258,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-3},"flags":{},"img":"icons/weapons/swords/sword-broad-red.webp","effects":[{"_id":"M7zvcLqDr9HuzqTV","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Schwebentrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieses meist grünliche Getränk wirkt den Zauber <em>Schweben</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"OZU7ietSpnivKPVt"}
|
||||
{"_id":"OZU7ietSpnivKPVt","name":"Schwebentrank","type":"loot","img":"icons/consumables/potions/bottle-bulb-corked-green.webp","data":{"description":"<p>Dieses meist grünliche Getränk wirkt den Zauber <em>Schweben</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Elfenstiefel","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Diese bequemen Stiefel erhöhen den <em>Laufen</em>-Wert um 1.</p>","quantity":1,"price":1251.5,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/feet/boots-leather-green.webp","effects":[{"_id":"mdjigDqsRTZyFNmC","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen +1 (magisch)","tint":"","transfer":true}],"_id":"OaG6IhVfS6EmHRux"}
|
||||
{"_id":"OvxWaEJrElas3EUL","name":"Waffenlos","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":0,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":0,"opponentDefense":5},"flags":{},"img":"icons/equipment/hand/gauntlet-tooled-leather-brown.webp","effects":[]}
|
||||
{"name":"Elfischer Tarnumhang","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Dieser Umhang, in den Feengarn eingewebt wurde, verleiht zusätzlich zu seiner eingebetteten <strong>Heimlichkeit +III</strong> auf Verbergen-Proben +3.</p>","quantity":1,"price":875.5,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/back/cloak-collared-leaves-green.webp","effects":[],"_id":"PE3eWmsGcd5dVVh4"}
|
||||
|
@ -150,10 +150,10 @@
|
|||
{"name":"Orkspalter","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Eine alte, legendäre Zwergenaxt +1 mit <strong>Brutaler Hieb +II</strong>.</p>\n<p>Zweihändig, Initiative -1</p>","quantity":1,"price":4310,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-3},"flags":{},"img":"icons/weapons/axes/axe-double-engraved-runes.webp","effects":[{"_id":"Ytio5tOcCUO91MQ0","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-1,"mode":2}],"duration":{},"label":"Initiative -1","transfer":true},{"_id":"armx56Psy1qosJib","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}],"_id":"RGyfv3y0LxHORAzA"}
|
||||
{"_id":"RKKhoedvylYWFBN3","name":"Kurzbogen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1</p>","quantity":1,"price":2256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":4,"opponentDefense":-3},"flags":{},"img":"icons/weapons/bows/shortbow-recurve-blue.webp","effects":[{"_id":"zgiIGlRMVCgAzrn7","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"OQr5POyJ0Azklftu","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"RWSRxi9np1UrEi7N","name":"Schwere Armbrust +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -4</p>","quantity":1,"price":3265,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":6,"opponentDefense":-5},"flags":{},"img":"icons/weapons/crossbows/crossbow-ornamental-black.webp","effects":[{"_id":"N4vxVFNLW9Q9QsIp","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative -4","tint":"","transfer":true},{"_id":"SlJV88ZHTV0VORzk","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"name":"Verjüngungstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Der Trinkende wird augenblicklich W20 Jahre jünger.</p>","quantity":1,"price":5000,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"RlA4PIa9hnsqoqFi"}
|
||||
{"_id":"RoXGTPdisjn6AdYK","name":"Weihwasser (1/2 Liter)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":null,"quantity":1,"price":0.1,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/containers/kitchenware/vase-005.webp","effects":[]}
|
||||
{"_id":"RlA4PIa9hnsqoqFi","name":"Verjüngungstrank","type":"loot","img":"icons/consumables/potions/potion-tube-corked-orange.webp","data":{"description":"<p>Der Trinkende wird augenblicklich W20 Jahre jünger.</p>","quantity":1,"price":5000,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"RoXGTPdisjn6AdYK","name":"Weihwasser (1/2 Liter)","type":"loot","img":"icons/consumables/potions/bottle-conical-corked-labeled-shell-cyan.webp","data":{"description":null,"quantity":1,"price":0.1,"availability":"hamlet","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"RsKimH7snoPFlg4L","name":"Plattenarmschienen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Laufen -0,5m</p>","quantity":1,"price":7,"availability":"village","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"plate","armorType":"vambrace"},"flags":{},"img":"icons/equipment/wrist/bracer-armored-steel.webp","effects":[{"_id":"DxcTuYT1mUpj9RdQ","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":-0.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen -0,5m","tint":"","transfer":true}]}
|
||||
{"name":"Großer Schutztrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Erhöht für W20 Runden die Abwehr um +3.</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"Ryv745YriIZNKXG5"}
|
||||
{"_id":"Ryv745YriIZNKXG5","name":"Großer Schutztrank","type":"loot","img":"icons/consumables/potions/bottle-conical-corked-tied-blue.webp","data":{"description":"<p>Erhöht für W20 Runden die Abwehr um +3.</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"S0EV25lf5TbN6COJ","name":"Schwere Armbrust +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -4</p>","quantity":1,"price":2265,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":4,"opponentDefense":-3},"flags":{},"img":"icons/weapons/crossbows/crossbow-loaded-black.webp","effects":[{"_id":"N4vxVFNLW9Q9QsIp","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative -4","tint":"","transfer":true},{"_id":"ZBJldrSO6rRHmgnI","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"S204KfhmoRdMDVpZ","name":"Wachskerze (brennt 10h)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.02,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/lights/candle-unlit-tan.webp","effects":[]}
|
||||
{"_id":"S4pc70BxmRU0DCsW","name":"Schleuder +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Distanzmalus -1 pro 2m</p>","quantity":1,"price":1750.1,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":3,"opponentDefense":-3},"flags":{},"img":"icons/weapons/slings/slingshot-wood.webp","effects":[{"_id":"kM7ZknhuQS8uIJbu","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
|
@ -166,7 +166,7 @@
|
|||
{"_id":"TSoSy8ck9XCUa1SM","name":"Morgenstern +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1757,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-3},"flags":{},"img":"icons/weapons/maces/mace-round-spiked-grey.webp","effects":[{"_id":"fZ3VByauGosUUN3D","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"TcyE0faEebPLoLOD","name":"Kettenpanzer +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":3260,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":2,"armorMaterialType":"chain","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-scale-grey.webp","effects":[{"_id":"C7jqj8julpambLpm","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}]}
|
||||
{"_id":"TjB6AWIwGY5Q8xln","name":"Dietrich","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":1,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/tools/hand/lockpicks-steel-grey.webp","effects":[]}
|
||||
{"name":"Schnelligkeitstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Für W20 Runden erhöht sich der Laufen- Wert des Trinkenden um 100%.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"Tlfjrxlm9NpmVR0L"}
|
||||
{"_id":"Tlfjrxlm9NpmVR0L","name":"Schnelligkeitstrank","type":"loot","img":"icons/consumables/potions/potion-bottle-corked-fancy-orange.webp","data":{"description":"<p>Für W20 Runden erhöht sich der Laufen- Wert des Trinkenden um 100%.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"Trl2ljtHi1kRYHTX","name":"Streitkolben +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1757,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-3},"flags":{},"img":"icons/weapons/maces/mace-studded-steel.webp","effects":[{"_id":"PeyncDD5OoPJkkTO","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"Tu0Mf3Qib68wxpRs","name":"Zwergenaxt +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -1</p>","quantity":1,"price":3310,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":6,"opponentDefense":-5},"flags":{},"img":"icons/weapons/axes/axe-double-engraved-blue.webp","effects":[{"_id":"Ytio5tOcCUO91MQ0","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-1,"mode":2}],"duration":{},"label":"Initiative -1","transfer":true},{"_id":"ulhp9EsUM5Tf0iCd","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"U89qHZqIfVM8xvaJ","name":"Tagesration (3 Mahlzeiten)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/tools/cooking/can.webp","effects":[]}
|
||||
|
@ -175,23 +175,23 @@
|
|||
{"_id":"UNd96UN0NQo8I0SI","name":"Turmschild +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"shield","data":{"description":null,"quantity":1,"price":5265,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":2},"flags":{},"img":"icons/equipment/shield/heater-embossed-gold.webp","effects":[{"_id":"ZXSBIh1UotdZuHbj","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true}]}
|
||||
{"name":"Satteltasche","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"loot","data":{"description":"","quantity":1,"price":4,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/containers/bags/pouch-simple-leather-tan.webp","effects":[],"_id":"UpkQTtuxNS1eOIRu"}
|
||||
{"name":"Elfischer Sattel","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>In diesen äußerst fein gearbeiteten Sattel ist <strong>Reiten +I</strong> eingebettet.</p>","quantity":1,"price":505,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/commodities/leather/leather-studded-tan.webp","effects":[],"_id":"V3D7u9LDumvBwJQI"}
|
||||
{"name":"Fliegentrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieser oft gelbe Trank wirkt auf den Trinker den Zauber <em>Fliegen</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"V6ywiDBc1Son1XrQ"}
|
||||
{"_id":"V6ywiDBc1Son1XrQ","name":"Fliegentrank","type":"loot","img":"icons/consumables/potions/bottle-conical-corked-yellow.webp","data":{"description":"<p>Dieser oft gelbe Trank wirkt auf den Trinker den Zauber <em>Fliegen</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Armreif des Bogners","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Verleiht auf <em>Schießen</em>-Proben mit Bögen einen Bonus von +2.</p>","quantity":1,"price":1260,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/wrist/bracer-belted-leather-brown.webp","effects":[],"_id":"VJftG703v7db0Cqf"}
|
||||
{"_id":"VQtcpPYhzifN9Lqr","name":"Plattenarmschienen +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":3257,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"plate","armorType":"vambrace"},"flags":{},"img":"icons/equipment/wrist/bracer-armored-steel-worn.webp","effects":[{"_id":"pPtRJMUFA3gbNZXx","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}]}
|
||||
{"_id":"Van6Sze8TZl8Y88o","name":"Zelt (2 Mann)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":4,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/environment/settlement/tent.webp","effects":[]}
|
||||
{"name":"Kette der Regeneration","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Diese schlichte Silberkette heilt in jeder Kamfprunde 1 LK.</p>","quantity":1,"price":null,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/neck/choker-chain-thick-silver.webp","effects":[],"_id":"VfjAtfQv5Ga8hoHu"}
|
||||
{"_id":"VxyrCBfVdbRD5nj8","name":"Wurfmesser +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Distanzmalus -1 pro 2m</p>","quantity":1,"price":1752,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"meleeRanged","weaponBonus":3,"opponentDefense":-3},"flags":{},"img":"icons/weapons/thrown/dagger-ringed-blue.webp","effects":[{"_id":"hQfQjfXJhE4yDXQU","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"name":"Konzentrationstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieser oft graue Trank verdoppelt GEI für GEI in Runden.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"W2dHT0OENc5kNwnZ"}
|
||||
{"_id":"W2dHT0OENc5kNwnZ","name":"Konzentrationstrank","type":"loot","img":"icons/consumables/potions/potion-bottle-corked-white.webp","data":{"description":"<p>Dieser oft graue Trank verdoppelt GEI für GEI in Runden.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"WP62N2sjGz3eIN18","name":"Robe","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"","quantity":1,"price":1,"availability":"hamlet","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/robe-layered-teal.webp","effects":[]}
|
||||
{"_id":"WiW9Sad1D2HjkNMz","name":"Langbogen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":2760,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":5,"opponentDefense":-3},"flags":{},"img":"icons/weapons/bows/bow-recurve-black.webp","effects":[{"_id":"XsqzwEX1AvYJyczG","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"B6tNz4B208qZf3JU","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"name":"Vergrößerungstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Vergrößert den Trinkenden auf das Doppelte seiner normalen Größe für W20/2 Minuten. KÖR, ST und HÄ werden verdoppelt und die Kampfwerte entsprechend angepasst.</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"Wjcv3WZW3j4jg9XY"}
|
||||
{"_id":"Wjcv3WZW3j4jg9XY","name":"Vergrößerungstrank","type":"loot","img":"icons/consumables/potions/bottle-bulb-corked-purple.webp","data":{"description":"<p>Vergrößert den Trinkenden auf das Doppelte seiner normalen Größe für W20/2 Minuten. KÖR, ST und HÄ werden verdoppelt und die Kampfwerte entsprechend angepasst.</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"XBCdDIpdR1weOhiy","name":"Plattenarmschienen +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":null,"quantity":1,"price":2257,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"plate","armorType":"vambrace"},"flags":{},"img":"icons/equipment/wrist/bracer-armored-steel-white.webp","effects":[{"_id":"IwrLITAgM3vLzkwA","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}]}
|
||||
{"_id":"XSXEDiMN27cuEoOx","name":"Leichte Armbrust +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2</p>","quantity":1,"price":2758,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":5,"opponentDefense":-3},"flags":{},"img":"icons/weapons/crossbows/crossbow-purple.webp","effects":[{"_id":"gMm2PnBADqXBrCi1","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative -2","tint":"","transfer":true},{"_id":"z2uPOzDQ47VbWmt5","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"name":"Schutztrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Erhöht für W20 Runden die Abwehr um +2.</p>","quantity":1,"price":50,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"XiW3k793840i0rdo"}
|
||||
{"_id":"XiW3k793840i0rdo","name":"Schutztrank","type":"loot","img":"icons/consumables/potions/bottle-pear-corked-blue.webp","data":{"description":"<p>Erhöht für W20 Runden die Abwehr um +2.</p>","quantity":1,"price":50,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"XyDt7MThnLkDCldV","name":"Metallschild +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"shield","data":{"description":null,"quantity":1,"price":3258,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1},"flags":{},"img":"icons/equipment/shield/heater-steel-sword-yellow-black.webp","effects":[{"_id":"MidC9f4kBbIz7Z7S","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}]}
|
||||
{"_id":"YSl6qdVC6fDYVkFQ","name":"Schlachtgeißel +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative -4, Bei Schlagen-Patzer trifft Angreifer sich selbst (Patzer ausgeschlossen)</p>","quantity":1,"price":2766,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":5,"opponentDefense":-6},"flags":{},"img":"icons/weapons/maces/flail-triple-grey.webp","effects":[{"_id":"BdejYg7Bq6tM2ROu","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"duration":{},"label":"Initiative -4","transfer":true},{"_id":"OF7IAa9tUBoz23TD","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"name":"Kundschafterpanzer","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese mit braunen Fellschulterpolstern verzierte Kettenrüstung gewährt ihrem Träger +1 auf <em>Bewegung</em>.</p>\n<p>Laufen -0,5m</p>","quantity":1,"price":1260,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":2,"armorMaterialType":"chain","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-scale-leather.webp","effects":[{"_id":"EkJB0kpYFHRMYSgl","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":-0.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen -0,5m","tint":"","transfer":true},{"_id":"mq2ViimrfXr7sGC7","flags":{},"changes":[{"key":"data.traits.agility.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Bewegung +1 (magisch)","tint":"","transfer":true}],"_id":"ZLDJNdtwxbdiJOP2"}
|
||||
{"_id":"Zl8ZkPrwHibRYWPh","name":"Allsichttrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Für W20 Minuten kann der Trinker Magie, Unsichtbares und Verborgenes (Fallen, Geheimtüren usw.) automatisch erkennen.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[]}
|
||||
{"_id":"Zl8ZkPrwHibRYWPh","name":"Allsichttrank","type":"loot","img":"icons/consumables/potions/potion-vial-corked-labeled-purple.webp","data":{"description":"<p>Für W20 Minuten kann der Trinker Magie, Unsichtbares und Verborgenes (Fallen, Geheimtüren usw.) automatisch erkennen.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"ZyML0QPctIXK8A4c","name":"Laterne","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/lights/lantern-iron-yellow.webp","effects":[]}
|
||||
{"_id":"aJ9xkECmqeBUhING","name":"Schlagring","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Wie waffenlos, Gegner aber kein Abwehr-Bonus</p>","quantity":1,"price":1,"availability":"village","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":0,"opponentDefense":0},"flags":{},"img":"icons/weapons/fist/fist-knuckles-spiked-grey.webp","effects":[]}
|
||||
{"_id":"aNGn2tplXGSikyV7","name":"Lederpanzer (Für Reittiere)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"","quantity":1,"price":12,"availability":"hamlet","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"leather","armorType":"body"},"flags":{},"img":"icons/commodities/leather/leather-leaf-tan.webp","effects":[]}
|
||||
|
@ -212,7 +212,7 @@
|
|||
{"_id":"dDhfIuRomMfy2kkP","name":"Anhänger mit heiligem Symbol","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"equipment","data":{"description":"","quantity":1,"price":1,"availability":"village","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/neck/amulet-carved-stone-spiral.webp","effects":[]}
|
||||
{"_id":"dIA53f3kvU9JgGvg","name":"Bihänder +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":3260,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":6,"opponentDefense":-7},"flags":{},"img":"icons/weapons/swords/greatsword-guard-gem-blue.webp","effects":[{"_id":"DaKTtdhRO45QZuDJ","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative -2","tint":"","transfer":true},{"_id":"n4mZBHWKSQLQcHHD","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"dMbjx675yI2F4rUg","name":"Langschwert +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1757,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-1},"flags":{},"img":"icons/weapons/swords/sword-guard-embossed-green.webp","effects":[{"_id":"l7qeGLLyuz7VXAAd","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Trank der Gasgestalt","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieser meist rauchige Trank wirkt auf den Trinker den Zauber <em>Gasgestalt</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"dUQPe5X6ka3HJLuF"}
|
||||
{"_id":"dUQPe5X6ka3HJLuF","name":"Trank der Gasgestalt","type":"loot","img":"icons/consumables/potions/potion-bottle-corked-fancy-blue.webp","data":{"description":"<p>Dieser meist rauchige Trank wirkt auf den Trinker den Zauber <em>Gasgestalt</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"dW8OETKY21O3GNHf","name":"Schloss: Solide (SW: 4)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":10,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/containers/chest/chest-reinforced-steel-oak-tan.webp","effects":[]}
|
||||
{"_id":"dq4wK5bmMvOfwoWH","name":"Kampfstab +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Zielzaubern +1</p>","quantity":1,"price":1250.5,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/staves/staff-simple-gold.webp","effects":[{"_id":"1aNTAQBai6qAcWGM","flags":{},"changes":[{"key":"data.combatValues.targetedSpellcasting.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Zielzaubern +1","tint":"","transfer":true},{"_id":"ve7iQmkxC6l5WmTE","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Fäustlinge der Verstümmelung","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Diese blutverschmutzten Wildlederhandschuhe gewähren ihrem Träger <strong>Brutaler Hieb +I</strong> und <strong>Verletzen +I</strong>.</p>","quantity":1,"price":3050.1,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/hand/glove-tooled-leather-brown.webp","effects":[],"_id":"dvVhgqCv9VDpFW0l"}
|
||||
|
@ -223,7 +223,7 @@
|
|||
{"_id":"eLTOIKuFGf3M9HYt","name":"Decke","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.1,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/survival/bedroll-worn-beige.webp","effects":[]}
|
||||
{"_id":"eMeS2JSyiRJ5xO48","name":"Streithammer +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -4</p>","quantity":1,"price":2756,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":5,"opponentDefense":-2},"flags":{},"img":"icons/weapons/hammers/hammer-war-rounding.webp","effects":[{"_id":"dcGiLPK2AIDA7NHH","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"duration":{},"label":"Initiative -4","transfer":true},{"_id":"1I5R9xQlug0aSVTZ","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"eMyZ5EJ9SsiXaeKK","name":"Streithammer +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -4</p>","quantity":1,"price":2256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-1},"flags":{},"img":"icons/weapons/hammers/hammer-war-spiked-simple.webp","effects":[{"_id":"dcGiLPK2AIDA7NHH","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"duration":{},"label":"Initiative -4","transfer":true},{"_id":"bPdVsWIS1AC54ZA8","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Allheilungstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Von milchiger Färbung, wirkt dieser Trank den Zauber <em>Allheilung</em> auf den Trinkenden (keine Probe nötig).</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"eRQ9LUNWbnAuuVX6"}
|
||||
{"_id":"eRQ9LUNWbnAuuVX6","name":"Allheilungstrank","type":"loot","img":"icons/consumables/potions/potion-bottle-corked-white.webp","data":{"description":"<p>Von milchiger Färbung, wirkt dieser Trank den Zauber <em>Allheilung</em> auf den Trinkenden (keine Probe nötig).</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"ec2Aft0epeFhdg9B","name":"Bärenfalle","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Schlagen 30</p>","quantity":1,"price":10,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/environment/traps/trap-jaw-green.webp","effects":[]}
|
||||
{"_id":"egleYygLWn8IfeuF","name":"Metallkrug","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":1,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/containers/kitchenware/mug-steel-wood-brown.webp","effects":[]}
|
||||
{"_id":"eqGo6VKETI1UTxP1","name":"Schlachtgeißel +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative -4, Bei Schlagen-Patzer trifft Angreifer sich selbst (Patzer ausgeschlossen)</p>","quantity":1,"price":3266,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":6,"opponentDefense":-7},"flags":{},"img":"icons/weapons/maces/flail-triple-grey.webp","effects":[{"_id":"BdejYg7Bq6tM2ROu","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"duration":{},"label":"Initiative -4","transfer":true},{"_id":"uhpIAR3T0P3bJw1C","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
|
@ -236,45 +236,45 @@
|
|||
{"_id":"fziU7tEIQPowqBJj","name":"Schlagring +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Wie waffenlos, Gegner aber kein Abwehr-Bonus</p>","quantity":1,"price":751,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":-1},"flags":{},"img":"icons/weapons/fist/fist-knuckles-spiked-blue.webp","effects":[{"_id":"ph5sqT08zd8lSi7s","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Runenbestickte Feurrobe","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Eine feuerrote Robe +3 mit aufgestickten Flammenrunen und <strong>Feuermagier +V</strong>.</p>","quantity":1,"price":4501,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/robe-layered-red.webp","effects":[{"_id":"Qpy3XcHOokbU7ZaS","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"duration":{},"label":"Panzerung +3 (magisch)","transfer":true}],"_id":"gK76RPRhQF7T4AQC"}
|
||||
{"_id":"gVwoOpuHkmwvcUow","name":"Langbogen +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":2260,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":4,"opponentDefense":-2},"flags":{},"img":"icons/weapons/bows/bow-ornamental-gold-blue.webp","effects":[{"_id":"XsqzwEX1AvYJyczG","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"iUXn0CQKZw6Rr1yH","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"name":"Wachsamkeitstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieses meist klare Getränk gewährt für W20 Stunden auf alle Bemerken-Proben einen Bonus von +5.</p>","quantity":1,"price":15,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"gXr3lLQmlHeDMuv5"}
|
||||
{"_id":"gXr3lLQmlHeDMuv5","name":"Wachsamkeitstrank","type":"loot","img":"icons/consumables/potions/potion-tube-corked-labeled-cyan.webp","data":{"description":"<p>Dieses meist klare Getränk gewährt für W20 Stunden auf alle Bemerken-Proben einen Bonus von +5.</p>","quantity":1,"price":15,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"grAnIWqeXTAIGXmN","name":"Wurfmesser","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Distanzmalus -1 pro 2m</p>","quantity":1,"price":2,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"meleeRanged","weaponBonus":0,"opponentDefense":0},"flags":{},"img":"icons/weapons/thrown/dagger-simple.webp","effects":[]}
|
||||
{"_id":"gyfU78OLQj8qH3Zh","name":"Metallschild","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"shield","data":{"description":"<p>Laufen -0,5m</p>","quantity":1,"price":8,"availability":"hamlet","storageLocation":"-","equipped":false,"armorValue":1},"flags":{},"img":"icons/equipment/shield/heater-steel-worn.webp","effects":[{"_id":"kDcyXkLx75K2YRCl","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":-0.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen -0,5m","tint":"","transfer":true}]}
|
||||
{"_id":"hBemmfRcQLFU7PSt","name":"Schlachtgeißel +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative -4, Bei Schlagen-Patzer trifft Angreifer sich selbst (Patzer ausgeschlossen)</p>","quantity":1,"price":2266,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-5},"flags":{},"img":"icons/weapons/maces/flail-triple-grey.webp","effects":[{"_id":"BdejYg7Bq6tM2ROu","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"duration":{},"label":"Initiative -4","transfer":true},{"_id":"oOQJt7Ub0PGGjdzW","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"hGiaAJyEUHbPl5pf","name":"Holzbesteck","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.2,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/tools/cooking/fork-steel-tan.webp","effects":[]}
|
||||
{"_id":"hfxblADLXdGaRMAA","name":"Wurfmesser +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Distanzmalus -1 pro 2m</p>","quantity":1,"price":752,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"meleeRanged","weaponBonus":1,"opponentDefense":-1},"flags":{},"img":"icons/weapons/thrown/dagger-ringed-steel.webp","effects":[{"_id":"c2P4Qt8b6GonPFv7","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Glückstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Der Trinkende kann für W20 Stunden alle Patzer ignorieren.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"hrAyguSjO6JhTE5m"}
|
||||
{"_id":"hrAyguSjO6JhTE5m","name":"Glückstrank","type":"loot","img":"icons/consumables/potions/bottle-round-corked-green.webp","data":{"description":"<p>Der Trinkende kann für W20 Stunden alle Patzer ignorieren.</p>","quantity":1,"price":200,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"htmQWmMCQN620KrE","name":"Langschwert","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":7,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":0},"flags":{},"img":"icons/weapons/swords/sword-guard-blue.webp","effects":[]}
|
||||
{"_id":"i1ZcbKzviqz0nxjV","name":"Langschwert +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":2257,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-2},"flags":{},"img":"icons/weapons/swords/sword-guard-purple.webp","effects":[{"_id":"5JZ001rLJuZMJSrD","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"iGiZU77IGQQqlYFr","name":"Metallschild +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"shield","data":{"description":null,"quantity":1,"price":2258,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1},"flags":{},"img":"icons/equipment/shield/heater-steel-grey.webp","effects":[{"_id":"9xMdH6XQxlulL7wv","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}]}
|
||||
{"_id":"iMX7YuWPHnCnbeh8","name":"Talgkerze (brennt 6h)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.01,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/lights/candle-unlit-grey.webp","effects":[]}
|
||||
{"_id":"iOAmrK7t7ZyrtGy1","name":"Pfeife","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/misc/pipe-wooden-02.webp","effects":[]}
|
||||
{"_id":"iOAmrK7t7ZyrtGy1","name":"Pfeife","type":"loot","img":"icons/sundries/misc/pipe-wooden-straight-brown.webp","data":{"description":"","quantity":1,"price":0.5,"availability":"hamlet","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"iORLpplub64kuxb4","name":"Streithammer +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -4</p>","quantity":1,"price":3256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":6,"opponentDefense":-3},"flags":{},"img":"icons/weapons/hammers/hammer-war-spiked.webp","effects":[{"_id":"dcGiLPK2AIDA7NHH","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"duration":{},"label":"Initiative -4","transfer":true},{"_id":"RWRk7pwQf2vs1Aqv","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"iWOrlMcGVAXTvFEg","name":"Holzwürfel (sechsseitig)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.02,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/gaming/dice-runed-tan.webp","effects":[]}
|
||||
{"_id":"iWOrlMcGVAXTvFEg","name":"Holzwürfel (sechsseitig)","type":"loot","img":"icons/sundries/gaming/dice-runed-brown.webp","data":{"description":"","quantity":1,"price":0.02,"availability":"hamlet","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"ibiHqm4rH8meeu9m","name":"Turmschild +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"shield","data":{"description":"<p>Laufen -0,5m</p>","quantity":1,"price":3265,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":2},"flags":{},"img":"icons/equipment/shield/heater-steel-engraved-lance-rest.webp","effects":[{"_id":"ScAKi1eiWow9Y1ZZ","flags":{},"changes":[{"key":"data.combatValues.movement.total","value":-0.5,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Laufen -0,5m","tint":"","transfer":true},{"_id":"TeyRcwja5lQWHICW","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}]}
|
||||
{"_id":"j3wcDmBNiDx7sK0n","name":"Runenbestickte Robe +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Aura +1</p>","quantity":1,"price":2258,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/robe-layered-blue.webp","effects":[{"_id":"vgJWV95OeyzrjyFw","flags":{},"changes":[{"key":"data.traits.aura.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Aura +1","tint":"","transfer":true},{"_id":"a2GXKNKHJWdyKb7h","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}]}
|
||||
{"_id":"jRzgvvygxk5IjXYB","name":"Speer +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":null,"quantity":1,"price":1251,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"meleeRanged","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/polearms/spear-hooked-red.webp","effects":[{"_id":"r0XGSAypTjvBmHHC","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"jsANSjzxRPKO3AyG","name":"Axt +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/axes/shortaxe-black.webp","effects":[{"_id":"wh12XwprBMtY3BTB","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Schutzring +1","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Erhöht die Abwehr um 1, ohne dabei Panzerungsmalus zu verursachen.</p>","quantity":1,"price":752,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/finger/ring-band-engraved-lines-bronze.webp","effects":[{"_id":"yH3Ujo3jKNCYI4n9","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Abwehr +1 (magisch)","tint":"","transfer":true}],"_id":"kGTB9f2zPrmedHq4"}
|
||||
{"name":"Kampftrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Meist von oranger Farbe erhöht solch ein Trank <em>Schlagen</em> und <em>Abwehr</em> für die Dauer eines Kampfes um +1.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"kIiDbrtAPno14O85"}
|
||||
{"_id":"kIiDbrtAPno14O85","name":"Kampftrank","type":"loot","img":"icons/consumables/potions/bottle-round-corked-orange.webp","data":{"description":"<p>Meist von oranger Farbe erhöht solch ein Trank <em>Schlagen</em> und <em>Abwehr</em> für die Dauer eines Kampfes um +1.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Robe des Heilers","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Eine weiße Robe, die ihrem Träger +1 auf Heilzauber gewährt.</p>","quantity":1,"price":751,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/robe-layered-white.webp","effects":[],"_id":"kVRybb0knZkoJLlj"}
|
||||
{"name":"Unsichtbarkeitsring","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Böse Zungen behaupten, dass dieser Ring, in den <em>Unsichtbarkeit</em> eingebettet ist, seinen Träger zu seinem abhängigen Sklaven macht.</p>","quantity":1,"price":6972,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/finger/ring-band-rounded-gold.webp","effects":[],"_id":"kurEYTP9rqk8YnND"}
|
||||
{"name":"Grausame Axt","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Antike Axt +1 mit <strong>Verletzen +III</strong></p>","quantity":1,"price":4256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/axes/shortaxe-simple-black.webp","effects":[{"_id":"wh12XwprBMtY3BTB","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}],"_id":"lHlwYWlgC2iKjDFM"}
|
||||
{"name":"Waffenweih","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Über eine Waffe geschüttet, verleiht dieser meist silberne Trank dieser für die Dauer eines Kampfes den Effekt des Zaubers <em>Magische Waffe</em>.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"luYRwVP5oR6cdDMQ"}
|
||||
{"_id":"luYRwVP5oR6cdDMQ","name":"Waffenweih","type":"loot","img":"icons/consumables/potions/potion-jar-capped-teal.webp","data":{"description":"<p>Über eine Waffe geschüttet, verleiht dieser meist silberne Trank dieser für die Dauer eines Kampfes den Effekt des Zaubers <em>Magische Waffe</em>.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"mjBwhnK5gf9MIdlm","name":"Streitkolben","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":7,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":-1},"flags":{},"img":"icons/weapons/maces/mace-spiked-steel-wood.webp","effects":[]}
|
||||
{"name":"Berserkertrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieses von verrückten Orkschamanen entwickelte, dampfende Getränk heilt drei Kampfrunden jeweils W20 Lebenskraft. In der vierten Runde explodiert der Trinker und verursacht in 2m Radius Schaden in Höhe der erwürfelten Heilung (Abwehr gilt).</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"nH1Vfhhx2jlGoQ6i"}
|
||||
{"_id":"nH1Vfhhx2jlGoQ6i","name":"Berserkertrank","type":"loot","img":"icons/consumables/potions/bottle-round-flask-fumes-purple.webp","data":{"description":"<p>Dieses von verrückten Orkschamanen entwickelte, dampfende Getränk heilt drei Kampfrunden jeweils W20 Lebenskraft. In der vierten Runde explodiert der Trinker und verursacht in 2m Radius Schaden in Höhe der erwürfelten Heilung (Abwehr gilt).</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Karten des Schummlers","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"loot","data":{"description":"<p>In dieses wunderschöne Spielkarten-Set ist der Zauber <em>Zeitstop</em> eingebettet.</p>","quantity":1,"price":13031,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/sundries/gaming/playing-cards-grey.webp","effects":[],"_id":"nff3XieL5dvOZga9"}
|
||||
{"name":"Trank der Zwergensicht","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieses meist schwarze Getränk gewährt für W20 Stunden dem Trinker die zwergische Volksfähigkeit Dunkelsicht (siehe GRW Seite 83).</p>","quantity":1,"price":15,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"nixhgFSQ7jaZrvxD"}
|
||||
{"_id":"nixhgFSQ7jaZrvxD","name":"Trank der Zwergensicht","type":"loot","img":"icons/consumables/potions/potion-bottle-labeled-medicine-capped-red-black.webp","data":{"description":"<p>Dieses meist schwarze Getränk gewährt für W20 Stunden dem Trinker die zwergische Volksfähigkeit Dunkelsicht (siehe GRW Seite 83).</p>","quantity":1,"price":15,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Sattel","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"","quantity":1,"price":5,"availability":"hamlet","storageLocation":"-","equipped":false},"flags":{},"img":"icons/commodities/leather/leather-scrap-brown.webp","effects":[],"_id":"nslQfc441x1GG0SL"}
|
||||
{"name":"Spruchspeicherring","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Einmal pro Tag kann der Träger des Ringes zu einem vorher festgelegten Zauber aktionsfrei wechseln, ohne dafür würfeln zu müssen, da in den Ring <em>Wechselzauber</em> eingebettet wurde.</p>","quantity":1,"price":4992,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/finger/ring-cabochon-engraved-gold-pink.webp","effects":[],"_id":"oJbpYZlvfJ6kpudj"}
|
||||
{"name":"Giftbanntrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Wirkt den Zauber <em>Giftbann</em> auf den Trinkenden (keine Probe nötig).</p>","quantity":1,"price":150,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"oPUlB9rz5rvRKrq8"}
|
||||
{"_id":"oPUlB9rz5rvRKrq8","name":"Giftbanntrank","type":"loot","img":"icons/consumables/potions/bottle-bulb-corked-glowing-red.webp","data":{"description":"<p>Wirkt den Zauber <em>Giftbann</em> auf den Trinkenden (keine Probe nötig).</p>","quantity":1,"price":150,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"oWvJfxEBr83QxO9Q","name":"Speer","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zerbricht bei Schießen-Patzer</p>","quantity":1,"price":1,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"meleeRanged","weaponBonus":1,"opponentDefense":0},"flags":{},"img":"icons/weapons/polearms/spear-hooked-simple.webp","effects":[]}
|
||||
{"name":"Alterungstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Der Trinkende wird augenblicklich W20 Jahre älter, Haare und Nägel wachsen entsprechend.</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"oeyhSfAQQPUbm10p"}
|
||||
{"_id":"oeyhSfAQQPUbm10p","name":"Alterungstrank","type":"loot","img":"icons/consumables/potions/potion-vial-corked-purple.webp","data":{"description":"<p>Der Trinkende wird augenblicklich W20 Jahre älter, Haare und Nägel wachsen entsprechend.</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"opq2AakrpM9gLSa0","name":"Krummschwert +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":2757,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":5,"opponentDefense":-3},"flags":{},"img":"icons/weapons/swords/scimitar-blue.webp","effects":[{"_id":"RGYzuIow1nDLd681","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"oqnI982dhCya94Tu","name":"Elfenbogen +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":2825,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":5,"opponentDefense":-2},"flags":{},"img":"icons/weapons/bows/longbow-recurve-leather-red.webp","effects":[{"_id":"QScLkDv6gysh119m","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"l9ZRRSrd6yBhQUJL","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":2,"mode":2}],"duration":{},"label":"Initiative +2 (magisch)","transfer":true}]}
|
||||
{"_id":"ozPRhUPx9Y9u3GNE","name":"Seife (1 Stück)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/survival/soap.webp","effects":[]}
|
||||
{"name":"Zornhammer","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Ein schwerer, schlichter Streithammer +3, der einst einem Zwergenhelden gehörte.</p>\n<p>Zweihändig, Initiative -4</p>","quantity":1,"price":3256,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":6,"opponentDefense":-3},"flags":{},"img":"icons/weapons/hammers/hammer-double-steel-embossed.webp","effects":[{"_id":"dcGiLPK2AIDA7NHH","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-4,"mode":2}],"duration":{},"label":"Initiative -4","transfer":true},{"_id":"RWRk7pwQf2vs1Aqv","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}],"_id":"pQqbXD5ELmjcu4Dd"}
|
||||
{"_id":"pYP26CskxKade3GF","name":"Pfanne","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":1,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/tools/cooking/pot-camping-iron-black.webp","effects":[]}
|
||||
{"name":"Talenttrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Metallisch riechend, erhöht dieser Trank für W20 Runden ein vom Trinkenden bereits beherrschtes Talent um +I.</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"pljOii88ltzuQNsu"}
|
||||
{"_id":"pljOii88ltzuQNsu","name":"Talenttrank","type":"loot","img":"icons/consumables/potions/potion-flask-corked-labeled-pink.webp","data":{"description":"<p>Metallisch riechend, erhöht dieser Trank für W20 Runden ein vom Trinkenden bereits beherrschtes Talent um +I.</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"pzD6fcJa1Hk4Duwu","name":"Keule +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":null,"quantity":1,"price":1250.2,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/clubs/club-simple-barbed.webp","effects":[{"_id":"9ZyXiW0n9WJ5WQOv","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"pzjZv0HhCA15wy1i","name":"Holzbecher","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":0.2,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/containers/kitchenware/mug-simple-wooden-brown.webp","effects":[]}
|
||||
{"name":"Schutzring +3","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Erhöht die Abwehr um 3, ohne dabei Panzerungsmalus zu verursachen.</p>","quantity":1,"price":1752,"availability":"unset","storageLocation":"-","equipped":false},"flags":{},"img":"icons/equipment/finger/ring-band-engraved-scrolls-gold.webp","effects":[{"_id":"yH3Ujo3jKNCYI4n9","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":3,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Abwehr +3 (magisch)","tint":"","transfer":true}],"_id":"qlBIUI00RTYZzclX"}
|
||||
|
@ -282,7 +282,7 @@
|
|||
{"_id":"rdOU1FmBq1nqQKW5","name":"Streitkolben +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":2257,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-4},"flags":{},"img":"icons/weapons/maces/mace-spiked-steel-grey.webp","effects":[{"_id":"dgH1Fyz2pVFcmhFL","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"name":"Fellmantel des Heilers","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese aus weißem Fell geschneiderte Lederrüstung gewährt +2 auf alle Heilzauber.</p>","quantity":1,"price":4254,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":1,"armorMaterialType":"leather","armorType":"body"},"flags":{},"img":"icons/equipment/chest/vest-leather-tattered-white.webp","effects":[{"_id":"5vJF9ck6rDsBPnKl","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":2,"mode":2}],"duration":{},"label":"Panzerung +2 (magisch)","transfer":true}],"_id":"rvDTHq5UoRK6acm6"}
|
||||
{"_id":"s1xaEPPKJGMImZ9m","name":"Streitaxt +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2</p>","quantity":1,"price":2257,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-1},"flags":{},"img":"icons/weapons/axes/axe-double-chopping-black.webp","effects":[{"_id":"DPS3CaNXapsBzzsj","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"duration":{},"label":"Initiative -2","transfer":true},{"_id":"fQgMSANHWdQrzmlJ","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Wasserwandeltrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieser oft braune Trank wirkt auf den Trinker den Zauber <em>Wasserwandeln</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"s47J9CEdTELiRlPT"}
|
||||
{"_id":"s47J9CEdTELiRlPT","name":"Wasserwandeltrank","type":"loot","img":"icons/consumables/potions/potion-jar-corked-orange.webp","data":{"description":"<p>Dieser oft braune Trank wirkt auf den Trinker den Zauber <em>Wasserwandeln</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":100,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"sMJw9EQtd2pYsgYE","name":"Hammer","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":7,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":-1},"flags":{},"img":"icons/weapons/hammers/shorthammer-simple-iron-black.webp","effects":[]}
|
||||
{"_id":"sUHJSIG8aTs0do67","name":"Runenbestickte Robe +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Aura +1</p>","quantity":1,"price":1258,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/robe-collared-blue.webp","effects":[{"_id":"vgJWV95OeyzrjyFw","flags":{},"changes":[{"key":"data.traits.aura.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Aura +1","tint":"","transfer":true},{"_id":"6NLQSkPxsA3H8Gc8","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true}]}
|
||||
{"_id":"siJAzGmpHVegUKBF","name":"Keule","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zerbricht bei Schlagen-Patzer</p>","quantity":1,"price":0.2,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":0},"flags":{},"img":"icons/weapons/clubs/club-simple-black.webp","effects":[]}
|
||||
|
@ -290,17 +290,17 @@
|
|||
{"_id":"tAdNTxSRq9hARm1p","name":"Hammer +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":2257,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":4,"opponentDefense":-4},"flags":{},"img":"icons/weapons/hammers/shorthammer-embossed-white.webp","effects":[{"_id":"aLeVPvqVvSHnDTsp","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":3,"mode":2}],"duration":{},"label":"Initiative +3 (magisch)","transfer":true}]}
|
||||
{"_id":"tPlXSWQ4mP0dqnOa","name":"Schloss: Zwergenarbeit (SW: 12)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"","quantity":1,"price":250,"availability":"city","storageLocation":"-"},"flags":{},"img":"icons/containers/chest/chest-reinforced-steel-oak-tan.webp","effects":[]}
|
||||
{"name":"Rüstung des Schützen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese mit dunkelgrünen Stoffrändern gesäumte Kettenrüstung +1 gewährt ihrem Träger +3 auf <em>Schießen</em>.</p>","quantity":1,"price":4760,"availability":"unset","storageLocation":"-","equipped":false,"armorValue":2,"armorMaterialType":"chain","armorType":"body"},"flags":{},"img":"icons/equipment/chest/breastplate-scale-grey.webp","effects":[{"_id":"C7jqj8julpambLpm","flags":{},"changes":[{"key":"data.combatValues.defense.total","value":1,"mode":2}],"duration":{},"label":"Panzerung +1 (magisch)","transfer":true},{"_id":"fwNP4w1u7JP3OFEb","flags":{},"changes":[{"key":"data.combatValues.rangedAttack.total","value":3,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Schießen +3 (magisch)","tint":"","transfer":true}],"_id":"tQ1LUX7in4xuuR12"}
|
||||
{"name":"Großer Heiltrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Diese meist weinroten Tränke heilen 2W20 Lebenskraft.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"tW53rAmCXx6rqKMP"}
|
||||
{"_id":"tW53rAmCXx6rqKMP","name":"Großer Heiltrank","type":"loot","img":"icons/consumables/potions/potion-flask-stopped-red.webp","data":{"description":"<p>Diese meist weinroten Tränke heilen 2W20 Lebenskraft.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"trop6WmDZEhv3gRA","name":"Dolch +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Initiative +1</p>","quantity":1,"price":752,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":-1},"flags":{},"img":"icons/weapons/daggers/dagger-curved-blue.webp","effects":[{"_id":"9jtH6ER0s0I8SPyi","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true},{"_id":"aDUESIHIetT9xLbD","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"u1ycDWee8nHPfZHA","name":"Morgenstern +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1257,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-2},"flags":{},"img":"icons/weapons/maces/mace-round-studded.webp","effects":[{"_id":"nTOl8E9qA6stLcEJ","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"_id":"uDdLTyyNqeXzCssp","name":"Elfenbogen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative +1, Für Zwerge auf Grund der Größe zu unhandlich</p>","quantity":1,"price":75,"availability":"elves","storageLocation":"-","equipped":false,"attackType":"ranged","weaponBonus":3,"opponentDefense":0},"flags":{},"img":"icons/weapons/bows/longbow-recurve-brown.webp","effects":[{"_id":"QScLkDv6gysh119m","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Initiative +1","tint":"","transfer":true}]}
|
||||
{"_id":"uVYJY3A8kLne2ZkQ","name":"Parfüm (50 x benutzbar)","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Gibt 4 Stunden lang +1 auf Proben sozialer Interaktion mit anderem Geschlecht</p>","quantity":1,"price":5,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/tools/laboratory/vial-orange.webp","effects":[]}
|
||||
{"name":"Unsichtbarkeitstrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Dieser oft klare, farblose Trank wirkt auf den Trinker den Zauber <em>Unsichtbarkeit</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"uYcNgFz5PkaOmK6b"}
|
||||
{"_id":"uYcNgFz5PkaOmK6b","name":"Unsichtbarkeitstrank","type":"loot","img":"icons/consumables/potions/potion-flask-corked-tied-necklace-teal.webp","data":{"description":"<p>Dieser oft klare, farblose Trank wirkt auf den Trinker den Zauber <em>Unsichtbarkeit</em> (Probenwert 20; Patzer ausgeschlossen).</p>","quantity":1,"price":500,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Stab des Magus","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Ein Kampfstab +1 mit Zielzaubern +3 (insgesamt also mit dem normalen Bonus Zielzaubern +4).</p>\n<p>Zweihändig, Zielzaubern +1</p>","quantity":1,"price":2750.5,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":2,"opponentDefense":-1},"flags":{},"img":"icons/weapons/staves/staff-ornate-purple.webp","effects":[{"_id":"1aNTAQBai6qAcWGM","flags":{},"changes":[{"key":"data.combatValues.targetedSpellcasting.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Zielzaubern +1","tint":"","transfer":true},{"_id":"ve7iQmkxC6l5WmTE","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true},{"_id":"XD3tGbvi1S03diuz","flags":{},"changes":[{"key":"data.combatValues.targetedSpellcasting.total","value":3,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Zielzaubern +3 (magisch)","tint":"","transfer":true}],"_id":"uafOWinH9nnRO3lr"}
|
||||
{"name":"Klettertrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Der Charakter kann für W20 Runden mit seinem normalen Laufen-Wert wie eine Spinne klettern, selbst kopfüber an der Decke.</p>","quantity":1,"price":50,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"udsNOh5h0TQmOYBl"}
|
||||
{"_id":"udsNOh5h0TQmOYBl","name":"Klettertrank","type":"loot","img":"icons/consumables/potions/bottle-conical-corked-cyan.webp","data":{"description":"<p>Der Charakter kann für W20 Runden mit seinem normalen Laufen-Wert wie eine Spinne klettern, selbst kopfüber an der Decke.</p>","quantity":1,"price":50,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"upLe2iticb6YKsIR","name":"Brechstange","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"","quantity":1,"price":1.5,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":0},"flags":{},"img":"icons/tools/hand/wrench-iron-grey.webp","effects":[]}
|
||||
{"_id":"upkDR02zMILTPib6","name":"Lanze +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Nur im Trab (WB +1) oder Galopp (WB +4)</p>","quantity":1,"price":1252,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":1,"opponentDefense":-1},"flags":{},"img":"icons/weapons/polearms/spear-flared-green.webp","effects":[{"_id":"d8N240qBKaRGqkcf","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Zauberwechseltrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Diese meist blauen Tränke gewähren für die Dauer eines Kampfes +10 auf Zauber wechseln.</p>","quantity":1,"price":10,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"uqIQjhDGgbEuHYmd"}
|
||||
{"_id":"uqIQjhDGgbEuHYmd","name":"Zauberwechseltrank","type":"loot","img":"icons/consumables/potions/potion-bottle-corked-blue.webp","data":{"description":"<p>Diese meist blauen Tränke gewähren für die Dauer eines Kampfes +10 auf Zauber wechseln.</p>","quantity":1,"price":10,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"name":"Geisterbote","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"loot","data":{"description":"<p>Jeder dieser mit Rauch gefüllten Behälter enthält eine Ladung des Zaubers <em>Botschaft</em>.</p>","quantity":1,"price":454,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/commodities/materials/glass-orb-blue.webp","effects":[],"_id":"v7WiMqH1XylGqNOy"}
|
||||
{"_id":"vqKLn65gjoced8jV","name":"Streitaxt","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2</p>","quantity":1,"price":7,"availability":"hamlet","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":0},"flags":{},"img":"icons/weapons/axes/axe-double-brown.webp","effects":[{"_id":"DPS3CaNXapsBzzsj","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"duration":{},"label":"Initiative -2","transfer":true}]}
|
||||
{"_id":"w1bZ2431gdOQumWK","name":"Runenbestickte Robe","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Aura +1</p>","quantity":1,"price":8,"availability":"village","storageLocation":"-","equipped":false,"armorValue":0,"armorMaterialType":"cloth","armorType":"body"},"flags":{},"img":"icons/equipment/chest/coat-leather-blue.webp","effects":[{"_id":"vgJWV95OeyzrjyFw","flags":{},"changes":[{"key":"data.traits.aura.total","value":1,"mode":2}],"disabled":false,"duration":{"startTime":null,"seconds":null,"rounds":null,"turns":null,"startRound":null,"startTurn":null},"icon":"","label":"Aura +1","tint":"","transfer":true}]}
|
||||
|
@ -320,6 +320,6 @@
|
|||
{"_id":"zXgxu2gCkaTSSSMU","name":"Waffenpaste","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Macht WB+1; hält W20 Nahkampfangriffe bzw. reicht für W20 Fernkampfgeschosse</p>","quantity":1,"price":0.5,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/tools/laboratory/bowl-liquid-black.webp","effects":[]}
|
||||
{"_id":"zjefX9KYvMphtVwX","name":"Handschellen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Preis für beide Schlösser extra ermitteln</p>","quantity":1,"price":8,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/sundries/survival/cuffs-shackles-steel.webp","effects":[]}
|
||||
{"_id":"zoPPqqDyRTvmV2do","name":"Hellebarde +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweihändig, Initiative -2</p>","quantity":1,"price":4,"availability":"unset","storageLocation":"-","equipped":false,"attackType":"melee","weaponBonus":3,"opponentDefense":-1},"flags":{},"img":"icons/weapons/polearms/halberd-crescent-engraved-steel.webp","effects":[{"_id":"APXje5Ppu0d75HNw","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":-2,"mode":2}],"duration":{},"label":"Initiative -2","transfer":true},{"_id":"FuPTO8plsKR3lq8Z","flags":{},"changes":[{"key":"data.combatValues.initiative.total","value":1,"mode":2}],"duration":{},"label":"Initiative +1 (magisch)","transfer":true}]}
|
||||
{"name":"Zieltrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Erhöht die Werte von <em>Schießen</em> und <em>Zielzauber</em> für die Dauer eines Kampfes um +1.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"zqlc3bq1ZfneLeYx"}
|
||||
{"_id":"zqlc3bq1ZfneLeYx","name":"Zieltrank","type":"loot","img":"icons/consumables/potions/potion-flask-capped-yellow-green.webp","data":{"description":"<p>Erhöht die Werte von <em>Schießen</em> und <em>Zielzauber</em> für die Dauer eines Kampfes um +1.</p>","quantity":1,"price":25,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
|
||||
{"_id":"zquQpOOVr5lIbnmL","name":"Heilkraut","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Probenwert 10: 1-10 heilt LK in Ergebnishöhe, 11+ kein Heileffekt</p>","quantity":1,"price":2.5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/tools/laboratory/bowl-herbs-green.webp","effects":[]}
|
||||
{"_id":"zx3FKwvrLw147ARX","name":"Pergamentblatt","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":null,"quantity":1,"price":0.5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/sundries/documents/paper-plain-white.webp","effects":[]}
|
||||
|
|
53
src/scss/components/_actor_header.scss
Normal file
53
src/scss/components/_actor_header.scss
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use "../utils/typography";
|
||||
|
||||
.ds4-actor-header {
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
gap: 1em;
|
||||
|
||||
&__img {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
&__data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__data-row {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__name-input {
|
||||
@include typography.font-heading-upper;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
flex: 1;
|
||||
font-size: 1.25em;
|
||||
height: auto;
|
||||
}
|
||||
}
|
49
src/scss/components/_actor_progression.scss
Normal file
49
src/scss/components/_actor_progression.scss
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
* SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use "../utils/colors";
|
||||
@use "../utils/typography";
|
||||
@use "../utils/variables";
|
||||
@use "../utils/mixins";
|
||||
|
||||
.ds4-actor-progression {
|
||||
@include mixins.mark-invalid-or-disabled-input;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
&__entry {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 0.25em;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__label {
|
||||
@include typography.font-heading-upper;
|
||||
border: none;
|
||||
color: colors.$c-light-grey;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__input {
|
||||
flex: 0 0 5ch;
|
||||
|
||||
&--slayer-points {
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: auto;
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
-moz-appearance: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
src/scss/components/_actor_properties.scss
Normal file
36
src/scss/components/_actor_properties.scss
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
* SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
* SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use "../utils/mixins";
|
||||
@use "../utils/variables";
|
||||
|
||||
.ds4-actor-properties {
|
||||
@include mixins.mark-invalid-or-disabled-input;
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
|
||||
&__property {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__property-label {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__property-select {
|
||||
width: 100%;
|
||||
height: variables.$default-input-height;
|
||||
}
|
||||
|
||||
&__property-multi-input {
|
||||
display: flex;
|
||||
gap: 0.125em;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
padding-right: 1px;
|
||||
|
||||
& > label {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
* SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use "../utils/colors";
|
||||
@use "../utils/typography";
|
||||
@use "../utils/variables";
|
||||
@use "../utils/mixins";
|
||||
|
||||
.progression {
|
||||
.progression-entry {
|
||||
@include mixins.mark-invalid-or-disabled-input;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
padding-right: 3px;
|
||||
h2.progression-label {
|
||||
@include typography.font-heading-upper;
|
||||
display: block;
|
||||
height: 50px;
|
||||
padding: 0;
|
||||
color: colors.$c-light-grey;
|
||||
border: none;
|
||||
line-height: 50px;
|
||||
margin: variables.$margin-sm 0;
|
||||
text-align: right;
|
||||
}
|
||||
input.progression-value {
|
||||
margin-left: 5px;
|
||||
flex: 0 0 40px;
|
||||
text-align: left;
|
||||
|
||||
&--slayer-points {
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: auto;
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
-moz-appearance: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,13 +6,14 @@
|
|||
|
||||
@use "../utils/mixins";
|
||||
@use "../utils/variables";
|
||||
@use "../utils/typography";
|
||||
|
||||
.ds4-combat-value {
|
||||
$size: 3.75rem;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
row-gap: 0.5em;
|
||||
row-gap: 0.125em;
|
||||
|
||||
&__value {
|
||||
$combat-values-icons-path: "#{variables.$official-icons-path}/combat-values";
|
||||
|
@ -51,6 +52,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
@include typography.font-heading-upper;
|
||||
font-size: 1.2em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__formula {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -75,4 +75,10 @@
|
|||
.tox-edit-area {
|
||||
padding: 0 8px;
|
||||
}
|
||||
.tox-toolbar-overlord {
|
||||
background-color: transparent;
|
||||
.tox-toolbar__primary {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ header.sheet-header {
|
|||
flex: 0 0 100px;
|
||||
height: 100px;
|
||||
margin: variables.$margin-sm 10px variables.$margin-sm 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.header-fields {
|
||||
|
|
28
src/scss/components/_profile.scss
Normal file
28
src/scss/components/_profile.scss
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
.ds4-profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
&__entry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__entry-input {
|
||||
&--multiline {
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__entry-label {
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
header.sheet-header {
|
||||
.character-values {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
.ds4-sheet {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
}
|
|
@ -6,16 +6,13 @@
|
|||
|
||||
@use "../utils/variables";
|
||||
|
||||
nav.tabs {
|
||||
height: auto;
|
||||
border-top: variables.$border-groove;
|
||||
.ds4-sheet-tab-nav {
|
||||
border-bottom: variables.$border-groove;
|
||||
.item {
|
||||
border-top: variables.$border-groove;
|
||||
height: auto;
|
||||
|
||||
&__item {
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item.active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
|
@ -20,10 +20,11 @@
|
|||
|
||||
/* Styles limited to ds4 sheets */
|
||||
.ds4 {
|
||||
@include meta.load-css("components/actor_header");
|
||||
@include meta.load-css("components/actor_progression");
|
||||
@include meta.load-css("components/actor_properties");
|
||||
@include meta.load-css("components/apps");
|
||||
@include meta.load-css("components/basic_property");
|
||||
@include meta.load-css("components/character_progression");
|
||||
@include meta.load-css("components/character_values");
|
||||
@include meta.load-css("components/check");
|
||||
@include meta.load-css("components/checks");
|
||||
@include meta.load-css("components/combat_value");
|
||||
|
@ -34,7 +35,10 @@
|
|||
@include meta.load-css("components/description");
|
||||
@include meta.load-css("components/forms");
|
||||
@include meta.load-css("components/item_list");
|
||||
@include meta.load-css("components/profile");
|
||||
@include meta.load-css("components/rollable_image");
|
||||
@include meta.load-css("components/tabs");
|
||||
@include meta.load-css("components/sheet_tab_nav");
|
||||
@include meta.load-css("components/sheet");
|
||||
@include meta.load-css("components/talent_rank_equation");
|
||||
@include meta.load-css("tabs/biography");
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Lora";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
|
@ -12,6 +13,7 @@
|
|||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Lora";
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
|
@ -19,6 +21,7 @@
|
|||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Lora";
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
|
@ -26,6 +29,7 @@
|
|||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Lora";
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
|
@ -33,6 +37,7 @@
|
|||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "Wood Stamp";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
|
|
11
src/scss/tabs/_biography.scss
Normal file
11
src/scss/tabs/_biography.scss
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
.ds4-biography-tab-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
column-gap: 1em;
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
"name": "ds4",
|
||||
"title": "Dungeonslayers 4",
|
||||
"description": "The Dungeonslayers 4 system for FoundryVTT. Dungeonslayers by Christian Kennig is licensed under CC BY-NC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/). The icons by the authors of Game-icons.net are licensed under CC BY 3.0 (https://creativecommons.org/licenses/by/3.0/). The creature images by Devin Night (https://immortalnights.com/) and are licensed under the terms described at https://immortalnights.com/tokens/token-usage-rights/.",
|
||||
"version": "0.8.0",
|
||||
"minimumCoreVersion": "0.7.9",
|
||||
"compatibleCoreVersion": "0.7.10",
|
||||
"version": "1.1.3",
|
||||
"minimumCoreVersion": "0.8.8",
|
||||
"compatibleCoreVersion": "0.8.8",
|
||||
"templateVersion": 6,
|
||||
"author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug, Max Tharr, Sascha Martens",
|
||||
"authors": [
|
||||
|
@ -93,7 +93,7 @@
|
|||
"primaryTokenAttribute": "combatValues.hitPoints",
|
||||
"url": "https://git.f3l.de/dungeonslayers/ds4",
|
||||
"manifest": "https://git.f3l.de/dungeonslayers/ds4/-/raw/latest/src/system.json?inline=false",
|
||||
"download": "https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/0.8.0/download?job=build",
|
||||
"download": "https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/1.1.3/download?job=build",
|
||||
"license": "https://git.f3l.de/dungeonslayers/ds4#licensing",
|
||||
"initiative": "@combatValues.initiative.total",
|
||||
"manifestPlusVersion": "1.0.0",
|
||||
|
|
|
@ -170,29 +170,6 @@
|
|||
"shield": {
|
||||
"templates": ["base", "physical", "equipable", "protective"]
|
||||
},
|
||||
"equipment": {
|
||||
"templates": ["base", "physical", "equipable"]
|
||||
},
|
||||
"loot": {
|
||||
"templates": ["base", "physical"]
|
||||
},
|
||||
"talent": {
|
||||
"templates": ["base"],
|
||||
"rank": {
|
||||
"base": 0,
|
||||
"max": 0,
|
||||
"mod": 0
|
||||
}
|
||||
},
|
||||
"racialAbility": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"language": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"alphabet": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"spell": {
|
||||
"templates": ["base", "equipable"],
|
||||
"spellType": "spellcasting",
|
||||
|
@ -220,6 +197,29 @@
|
|||
"sorcerer": null
|
||||
}
|
||||
},
|
||||
"equipment": {
|
||||
"templates": ["base", "physical", "equipable"]
|
||||
},
|
||||
"loot": {
|
||||
"templates": ["base", "physical"]
|
||||
},
|
||||
"talent": {
|
||||
"templates": ["base"],
|
||||
"rank": {
|
||||
"base": 0,
|
||||
"max": 0,
|
||||
"mod": 0
|
||||
}
|
||||
},
|
||||
"racialAbility": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"language": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"alphabet": {
|
||||
"templates": ["base"]
|
||||
},
|
||||
"specialCreatureAbility": {
|
||||
"templates": ["base"],
|
||||
"experiencePoints": 0
|
||||
|
|
|
@ -6,84 +6,19 @@ SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
|||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<form class="{{cssClass}} flexcol" autocomplete="off">
|
||||
{{!-- Sheet Header --}}
|
||||
<header class="sheet-header">
|
||||
<img class="profile-img" src="{{actor.img}}" data-edit="img" alt="Actor Icon" title="{{actor.name}}"
|
||||
height="100" width="100" />
|
||||
<div class="header-fields flexrow">
|
||||
<h1 class="charname">
|
||||
<label for="actor.name" class="hidden">Name</label>
|
||||
<input name="name" type="text" id="actor.name" value="{{actor.name}}" placeholder="Name" />
|
||||
</h1>
|
||||
{{> systems/ds4/templates/sheets/actor/components/character-progression.hbs}}
|
||||
|
||||
<div class="flexrow basic-properties">
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.race">{{config.i18n.characterBaseInfo.race}}</label>
|
||||
<input type="text" name="data.baseInfo.race" id="data.baseInfo.race" value="{{data.baseInfo.race}}"
|
||||
data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.culture">{{config.i18n.characterBaseInfo.culture}}</label>
|
||||
<input id="data.baseInfo.culture" type="text" name="data.baseInfo.culture"
|
||||
value="{{data.baseInfo.culture}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property flex125">
|
||||
<label class="basic-property-label"
|
||||
for="data.progression.progressPoints.used">{{config.i18n.characterProgression.progressPoints}}</label>
|
||||
<div class="flexrow">
|
||||
<input id="data.progression.progressPoints.used" type="number"
|
||||
name="data.progression.progressPoints.used" value="{{data.progression.progressPoints.used}}"
|
||||
data-dtype="Number" />
|
||||
<span class="input-divider"> / </span>
|
||||
<label class="hidden" for="data.progression.progressPoints.total">Total
|
||||
Progression Points</label>
|
||||
<input type="number" id="data.progression.progressPoints.total"
|
||||
name="data.progression.progressPoints.total"
|
||||
value="{{data.progression.progressPoints.total}}" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="basic-property flex125">
|
||||
<label class="basic-property-label"
|
||||
for="data.progression.talentPoints.used">{{config.i18n.characterProgression.talentPoints}}</label>
|
||||
<div class="flexrow">
|
||||
<input type="number" name="data.progression.talentPoints.used"
|
||||
id="data.progression.talentPoints.used" value="{{data.progression.talentPoints.used}}"
|
||||
data-dtype="Number" />
|
||||
<span class="input-divider"> / </span>
|
||||
<label for="data.progression.talentPoints.total" class="hidden">Total Talent Points</label>
|
||||
<input type="number" name="data.progression.talentPoints.total"
|
||||
id="data.progression.talentPoints.total" value="{{data.progression.talentPoints.total}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.class">{{config.i18n.characterBaseInfo.class}}</label>
|
||||
<input type="text" id="data.baseInfo.class" name="data.baseInfo.class"
|
||||
value="{{data.baseInfo.class}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.heroClass">{{config.i18n.characterBaseInfo.heroClass}}</label>
|
||||
<input type="text" id="data.baseInfo.heroClass" name="data.baseInfo.heroClass"
|
||||
value="{{data.baseInfo.heroClass}}" data-dtype="String" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<form class="{{cssClass}} ds4-sheet" autocomplete="off">
|
||||
{{!-- Header --}}
|
||||
{{#> systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/character-properties.hbs}}
|
||||
{{/systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
|
||||
|
||||
{{!-- Sheet Tab Navigation --}}
|
||||
<nav class="sheet-tabs tabs" data-group="primary">
|
||||
<a class="item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a>
|
||||
<a class="item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
|
||||
<a class="item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a>
|
||||
<a class="item" data-tab="talents-abilities">{{localize 'DS4.HeadingTalentsAbilities'}}</a>
|
||||
<a class="item" data-tab="profile">{{localize "DS4.HeadingProfile"}}</a>
|
||||
<a class="item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a>
|
||||
<nav class="ds4-sheet-tab-nav sheet-tabs tabs" data-group="primary">
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="abilities">{{localize 'DS4.HeadingAbilities'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a>
|
||||
</nav>
|
||||
|
||||
<!-- beautify ignore:start -->
|
||||
|
@ -99,11 +34,8 @@ SPDX-License-Identifier: MIT
|
|||
{{!-- Spells Tab --}}
|
||||
{{> systems/ds4/templates/sheets/actor/tabs/spells.hbs}}
|
||||
|
||||
{{!-- Talents Tab --}}
|
||||
{{> systems/ds4/templates/sheets/actor/tabs/talents-abilities.hbs}}
|
||||
|
||||
{{! Profile Tab --}}
|
||||
{{> systems/ds4/templates/sheets/actor/tabs/profile.hbs}}
|
||||
{{!-- Abilities Tab --}}
|
||||
{{> systems/ds4/templates/sheets/actor/tabs/abilities.hbs}}
|
||||
|
||||
{{!-- Biography Tab --}}
|
||||
{{> systems/ds4/templates/sheets/actor/tabs/biography.hbs}}
|
||||
|
|
29
src/templates/sheets/actor/components/actor-header.hbs
Normal file
29
src/templates/sheets/actor/components/actor-header.hbs
Normal file
|
@ -0,0 +1,29 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
{{!--
|
||||
!-- Render an actor sheet header.
|
||||
!-- @param @partial-block: Properties to render in the second header row.
|
||||
--}}
|
||||
<header class="ds4-actor-header">
|
||||
<img class="ds4-actor-header__img" src="{{data.img}}" data-edit="img" alt="{{localize 'DS4.ActorImageAltText'}}"
|
||||
title="{{data.name}}" height="100" width="100" />
|
||||
<div class="ds4-actor-header__data">
|
||||
<div class="ds4-actor-header__data-row">
|
||||
<h1 class="ds4-actor-header__name">
|
||||
<label for="name-{{data._id}}" class="hidden">{{localize 'DS4.ActorName'}}</label>
|
||||
<input class="ds4-actor-header__name-input" name="name" type="text" id="name-{{data._id}}"
|
||||
value="{{data.name}}" placeholder="{{localize 'DS4.ActorName'}}" />
|
||||
</h1>
|
||||
{{> systems/ds4/templates/sheets/actor/components/actor-progression.hbs}}
|
||||
</div>
|
||||
<div class="ds4-actor-header__data-row">
|
||||
{{> @partial-block}}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
48
src/templates/sheets/actor/components/actor-progression.hbs
Normal file
48
src/templates/sheets/actor/components/actor-progression.hbs
Normal file
|
@ -0,0 +1,48 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="ds4-actor-progression">
|
||||
<div class="ds4-actor-progression__entry">
|
||||
<h2 class="ds4-actor-progression__label"><label for="data.combatValues.hitPoints.value-{{data._id}}"
|
||||
title="{{localize 'DS4.CombatValuesHitPointsCurrent'}}">{{localize
|
||||
"DS4.CombatValuesHitPointsCurrentAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="ds4-actor-progression__input" type="number" name="data.combatValues.hitPoints.value"
|
||||
id="data.combatValues.hitPoints.value-{{data._id}}" value="{{data.data.combatValues.hitPoints.value}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
{{#if (eq data.type "character")}}
|
||||
{{#if settings.showSlayerPoints}}
|
||||
<div class="ds4-actor-progression__entry">
|
||||
<h2 class="ds4-actor-progression__label"><label for="data.slayersPoints.value-{{data._id}}"
|
||||
title="{{localize 'DS4.CharacterSlayerPoints'}}">{{localize "DS4.CharacterSlayerPointsAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="ds4-actor-progression__input ds4-actor-progression__input--slayer-points" type="number"
|
||||
max="{{data.data.slayerPoints.max}}" min="0" step="1" name="data.slayerPoints.value"
|
||||
id="data.slayersPoints.value-{{data._id}}" value="{{data.data.slayerPoints.value}}" data-dtype="Number" />
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="ds4-actor-progression__entry">
|
||||
<h2 class="ds4-actor-progression__label"><label for="data.progression.level-{{data._id}}"
|
||||
title="{{localize 'DS4.CharacterProgressionLevel'}}">{{localize
|
||||
"DS4.CharacterProgressionLevelAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="ds4-actor-progression__input" type="number" min="0" name="data.progression.level"
|
||||
id="data.progression.level-{{data._id}}" value="{{data.data.progression.level}}" data-dtype="Number" />
|
||||
</div>
|
||||
<div class="ds4-actor-progression__entry">
|
||||
<h2 class="ds4-actor-progression__label"><label for="data.progression.experiencePoints-{{data._id}}"
|
||||
title="{{localize 'DS4.CharacterProgressionExperiencePoints'}}">{{localize
|
||||
"DS4.CharacterProgressionExperiencePointsAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="ds4-actor-progression__input" type="number" min="0" name="data.progression.experiencePoints"
|
||||
id="data.progression.experiencePoints-{{data._id}}" value="{{data.data.progression.experiencePoints}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
10
src/templates/sheets/actor/components/biography.hbs
Normal file
10
src/templates/sheets/actor/components/biography.hbs
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="ds4-biography">
|
||||
{{editor content=data.data.profile.biography target="data.profile.biography" button=true owner=owner
|
||||
editable=editable}}
|
||||
</div>
|
|
@ -1,46 +0,0 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="progression flexrow">
|
||||
<div class="progression-entry">
|
||||
<h2 class="progression-label"><label for="data.combatValues.hitPoints.value"
|
||||
title="{{localize 'DS4.CombatValuesHitPointsCurrent'}}">{{localize
|
||||
"DS4.CombatValuesHitPointsCurrentAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value" type="number" name="data.combatValues.hitPoints.value"
|
||||
id="data.combatValues.hitPoints.value" value="{{data.combatValues.hitPoints.value}}" data-dtype="Number" />
|
||||
</div>
|
||||
{{#if (eq actor.type "character")}}
|
||||
{{#if settings.showSlayerPoints}}
|
||||
<div class="progression-entry">
|
||||
<h2 class="progression-label"><label for="data.slayersPoints.value"
|
||||
title="{{localize 'DS4.CharacterSlayerPoints'}}">{{localize "DS4.CharacterSlayerPointsAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value progression-value--slayer-points" type="number" max="{{data.slayerPoints.max}}"
|
||||
min="0" step="1" name="data.slayerPoints.value" id="data.slayersPoints.value"
|
||||
value="{{data.slayerPoints.value}}" data-dtype="Number" />
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="progression-entry">
|
||||
<h2 class="progression-label"><label for="data.progression.level"
|
||||
title="{{localize 'DS4.CharacterProgressionLevel'}}">{{localize
|
||||
"DS4.CharacterProgressionLevelAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value" type="number" min="0" name="data.progression.level" id="data.progression.level"
|
||||
value="{{data.progression.level}}" data-dtype="Number" />
|
||||
</div>
|
||||
<div class="progression-entry">
|
||||
<h2 class="progression-label"><label for="data.progression.experiencePoints"
|
||||
title="{{localize 'DS4.CharacterProgressionExperiencePoints'}}">{{localize
|
||||
"DS4.CharacterProgressionExperiencePointsAbbr"}}</label>
|
||||
</h2>
|
||||
<input class="progression-value" type="number" min="0" name="data.progression.experiencePoints"
|
||||
id="data.progression.experiencePoints" value="{{data.progression.experiencePoints}}" data-dtype="Number" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -0,0 +1,63 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="ds4-actor-properties">
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.race-{{data._id}}">{{config.i18n.characterBaseInfo.race}}</label>
|
||||
<input type="text" name="data.baseInfo.race" id="data.baseInfo.race-{{data._id}}"
|
||||
value="{{data.data.baseInfo.race}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.culture-{{data._id}}">{{config.i18n.characterBaseInfo.culture}}</label>
|
||||
<input id="data.baseInfo.culture-{{data._id}}" type="text" name="data.baseInfo.culture"
|
||||
value="{{data.data.baseInfo.culture}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.progression.progressPoints.used-{{data._id}}">{{config.i18n.characterProgression.progressPoints}}</label>
|
||||
<div class="ds4-actor-properties__property-multi-input">
|
||||
<input id="data.progression.progressPoints.used-{{data._id}}" type="number"
|
||||
name="data.progression.progressPoints.used" value="{{data.data.progression.progressPoints.used}}"
|
||||
data-dtype="Number" />
|
||||
<span class="input-divider"> / </span>
|
||||
<label class="hidden" for="data.progression.progressPoints.total-{{data._id}}">Total
|
||||
Progression Points</label>
|
||||
<input type="number" id="data.progression.progressPoints.total-{{data._id}}"
|
||||
name="data.progression.progressPoints.total" value="{{data.data.progression.progressPoints.total}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.progression.talentPoints.used-{{data._id}}">{{config.i18n.characterProgression.talentPoints}}</label>
|
||||
<div class="ds4-actor-properties__property-multi-input">
|
||||
<input type="number" name="data.progression.talentPoints.used"
|
||||
id="data.progression.talentPoints.used-{{data._id}}" value="{{data.data.progression.talentPoints.used}}"
|
||||
data-dtype="Number" />
|
||||
<span class="input-divider"> / </span>
|
||||
<label for="data.progression.talentPoints.total-{{data._id}}" class="hidden">Total Talent Points</label>
|
||||
<input type="number" name="data.progression.talentPoints.total"
|
||||
id="data.progression.talentPoints.total-{{data._id}}"
|
||||
value="{{data.data.progression.talentPoints.total}}" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.class-{{data._id}}">{{config.i18n.characterBaseInfo.class}}</label>
|
||||
<input type="text" id="data.baseInfo.class-{{data._id}}" name="data.baseInfo.class"
|
||||
value="{{data.data.baseInfo.class}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.heroClass-{{data._id}}">{{config.i18n.characterBaseInfo.heroClass}}</label>
|
||||
<input type="text" id="data.baseInfo.heroClass-{{data._id}}" name="data.baseInfo.heroClass"
|
||||
value="{{data.data.baseInfo.heroClass}}" data-dtype="String" />
|
||||
</div>
|
||||
</div>
|
|
@ -7,6 +7,6 @@ SPDX-License-Identifier: MIT
|
|||
<div class="ds4-checks">
|
||||
{{#each config.i18n.checks as |check-label check-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/check.hbs check-key=check-key check-target-number=(lookup
|
||||
../data.checks check-key) check-label=check-label}}
|
||||
../data.data.checks check-key) check-label=check-label}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -9,20 +9,24 @@ SPDX-License-Identifier: MIT
|
|||
!--
|
||||
!-- @param combat-value-key: The key of the combat value
|
||||
!-- @param combat-value-data: The data for the combat value
|
||||
!-- @param combat-value-label: The label for the combat value
|
||||
!-- @param combat-value-title: The title for the combat value
|
||||
!-- @param combat-value-label: The label for the combat value (possibly an abbreviation)
|
||||
!-- @param actor-id: The id of the actor the core value belongs to
|
||||
--}}
|
||||
|
||||
<div class="ds4-combat-value">
|
||||
<div class="ds4-combat-value__value ds4-combat-value__value--{{combat-value-key}}"
|
||||
title="{{combat-value-label}}: {{combat-value-data.tooltip}}">
|
||||
title="{{combat-value-title}}: {{combat-value-data.tooltip}}">
|
||||
{{combat-value-data.total}}
|
||||
</div>
|
||||
<span title="{{combat-value-title}}" class="ds4-combat-value__label">{{combat-value-label}}</span>
|
||||
<div class="ds4-combat-value__formula">
|
||||
<span class="ds4-combat-value__formula-base"
|
||||
title="{{combat-value-label}} {{localize 'DS4.TooltipBaseValue'}}">{{combat-value-data.base}}</span>
|
||||
title="{{combat-value-title}} {{localize 'DS4.TooltipBaseValue'}}">{{combat-value-data.base}}</span>
|
||||
<span>+</span>
|
||||
<input class="ds4-combat-value__formula-modifier" type="number" name="data.combatValues.{{combat-value-key}}.mod"
|
||||
value='{{combat-value-data.mod}}' data-dtype="Number"
|
||||
title="{{combat-value-label}} {{localize 'DS4.TooltipModifier'}}" />
|
||||
<input class="ds4-combat-value__formula-modifier" type="number"
|
||||
id="data.combatValues.{{combat-value-key}}.mod-{{actor-id}}"
|
||||
name="data.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}' data-dtype="Number"
|
||||
title="{{combat-value-title}} {{localize 'DS4.TooltipModifier'}}" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,8 +6,10 @@ SPDX-License-Identifier: MIT
|
|||
--}}
|
||||
|
||||
<div class="ds4-combat-values">
|
||||
{{#each config.i18n.combatValues as |combat-value-label combat-value-key|}}
|
||||
{{#each config.i18n.combatValues as |combat-value-title combat-value-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/combat-value.hbs combat-value-key=combat-value-key
|
||||
combat-value-data=(lookup ../data.combatValues combat-value-key) combat-value-label=combat-value-label}}
|
||||
combat-value-data=(lookup ../data.data.combatValues combat-value-key) combat-value-label=(lookup
|
||||
../config.i18n.combatValuesSheet combat-value-key) combat-value-title=combat-value-title
|
||||
actor-id=../data._id}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -11,21 +11,22 @@ SPDX-License-Identifier: MIT
|
|||
!-- @param core-value-key: The key of the core value
|
||||
!-- @param core-value-data: The data for the core value
|
||||
!-- @param core-value-variant: The variant of the core value, i.e. attribute or trait
|
||||
!-- @param actor-id: The id of the actor the core value belongs to
|
||||
--}}
|
||||
|
||||
<div class="ds4-core-value ds4-core-value--{{core-value-variant}}">
|
||||
<label for="data.{{core-value-variant}}s.{{core-value-key}}.base"
|
||||
<label for="data.{{core-value-variant}}s.{{core-value-key}}.base-{{actor-id}}"
|
||||
class="ds4-core-value__label">{{core-value-label}}</label>
|
||||
<div class="ds4-core-value__value">
|
||||
<input class="ds4-core-value__value-input" type="number"
|
||||
name="data.{{core-value-variant}}s.{{core-value-key}}.base"
|
||||
id="data.{{core-value-variant}}s.{{core-value-key}}.base" value='{{core-value-data.base}}'
|
||||
id="data.{{core-value-variant}}s.{{core-value-key}}.base-{{actor-id}}" value='{{core-value-data.base}}'
|
||||
data-dtype="Number" title="{{core-value-label}} {{localize 'DS4.TooltipBaseValue'}}" />
|
||||
<span>+</span>
|
||||
<input class="ds4-core-value__value-input" type="number"
|
||||
name="data.{{core-value-variant}}s.{{core-value-key}}.mod"
|
||||
id="data.{{core-value-variant}}s.{{core-value-key}}.mod" value='{{core-value-data.mod}}' data-dtype="Number"
|
||||
title="{{core-value-label}} {{localize 'DS4.TooltipModifier'}}" />
|
||||
id="data.{{core-value-variant}}s.{{core-value-key}}.mod-{{actor-id}}" value='{{core-value-data.mod}}'
|
||||
data-dtype="Number" title="{{core-value-label}} {{localize 'DS4.TooltipModifier'}}" />
|
||||
<span class="ds4-core-value__value-arrow">➞</span>
|
||||
<span class="ds4-core-value__value-total"
|
||||
title="{{core-value-label}}: {{core-value-data.tooltip}}">{{core-value-data.total}}</span>
|
||||
|
|
|
@ -8,12 +8,12 @@ SPDX-License-Identifier: MIT
|
|||
<div class="ds4-core-values">
|
||||
{{#each config.i18n.attributes as |attribute-label attribute-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=attribute-label
|
||||
core-value-key=attribute-key core-value-data=(lookup ../data.attributes
|
||||
attribute-key) core-value-variant="attribute"}}
|
||||
core-value-key=attribute-key core-value-data=(lookup ../data.data.attributes
|
||||
attribute-key) core-value-variant="attribute" actor-id=../data._id}}
|
||||
{{/each}}
|
||||
{{#each config.i18n.traits as |trait-label trait-key|}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=trait-label
|
||||
core-value-key=trait-key
|
||||
core-value-data=(lookup ../data.traits trait-key) core-value-variant="trait"}}
|
||||
core-value-data=(lookup ../data.data.traits trait-key) core-value-variant="trait" actor-id=../data._id}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Oliver Rümpelein
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="ds4-actor-properties">
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.creatureType-{{data._id}}">{{config.i18n.creatureBaseInfo.creatureType}}</label>
|
||||
<select class="ds4-actor-properties__property-select" id="data.baseInfo.creatureType-{{data._id}}"
|
||||
name="data.baseInfo.creatureType" data-type="String">
|
||||
{{#select data.data.baseInfo.creatureType}}
|
||||
{{#each config.i18n.creatureTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.loot-{{data._id}}">{{config.i18n.creatureBaseInfo.loot}}</label>
|
||||
<input type="text" id="data.baseInfo.loot-{{data._id}}" name="data.baseInfo.loot"
|
||||
value="{{data.data.baseInfo.loot}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.foeFactor-{{data._id}}">{{config.i18n.creatureBaseInfo.foeFactor}}</label>
|
||||
<input type="text" id="data.baseInfo.foeFactor-{{data._id}}" name="data.baseInfo.foeFactor"
|
||||
value="{{data.data.baseInfo.foeFactor}}" data-dtype="Number" />
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.sizeCategory-{{data._id}}">{{config.i18n.creatureBaseInfo.sizeCategory}}</label>
|
||||
<select class="ds4-actor-properties__property-select" id="data.baseInfo.sizeCategory-{{data._id}}"
|
||||
name="data.baseInfo.sizeCategory" data-type="String">
|
||||
{{#select data.data.baseInfo.sizeCategory}}
|
||||
{{#each config.i18n.creatureSizeCategories as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="ds4-actor-properties__property">
|
||||
<label class="ds4-actor-properties__property-label"
|
||||
for="data.baseInfo.experiencePoints-{{data._id}}">{{config.i18n.creatureBaseInfo.experiencePoints}}</label>
|
||||
<input type="text" id="data.baseInfo.experiencePoints-{{data._id}}" name="data.baseInfo.experiencePoints"
|
||||
value="{{data.data.baseInfo.experiencePoints}}" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
<h4 class="ds4-currency-title">{{localize 'DS4.CharacterCurrency'}}</h4>
|
||||
<div class="ds4-currency">
|
||||
{{#each data.currency as |value key|}}
|
||||
{{#each data.data.currency as |value key|}}
|
||||
<label for="data.currency.{{key}}" class="flex05">{{lookup ../config.i18n.characterCurrency key}}</label>
|
||||
<input class="ds4-currency__value ds4-currency__value--{{key}} item-change" type="number" min="0" step="1"
|
||||
name="data.currency.{{key}}" id="data.currency.{{key}}" value="{{value}}" data-dtype="Number" />
|
||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
{{!-- image --}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/rollable-image.hbs rollable=itemData.data.rollable
|
||||
src=itemData.img alt=(localize "DS4.EntityImageAltText" name=itemData.name) title=itemData.name
|
||||
src=itemData.img alt=(localize "DS4.DocumentImageAltText" name=itemData.name) title=itemData.name
|
||||
rollableTitle=(localize "DS4.RollableImageRollableTitle" name=itemData.name) rollableClass="rollable-item"}}
|
||||
|
||||
{{!-- amount --}}
|
||||
|
|
|
@ -69,8 +69,8 @@ SPDX-License-Identifier: MIT
|
|||
</div>
|
||||
|
||||
{{!-- armor type --}}
|
||||
<div title="{{lookup ../../config.i18n.armorTypes itemData.dataData.armorType}}">
|
||||
{{lookup ../../config.i18n.armorTypesAbbr itemData.dataData.armorType}}
|
||||
<div title="{{lookup ../../config.i18n.armorTypes itemData.data.armorType}}">
|
||||
{{lookup ../../config.i18n.armorTypesAbbr itemData.data.armorType}}
|
||||
</div>
|
||||
|
||||
{{!-- armor value --}}
|
||||
|
|
29
src/templates/sheets/actor/components/profile.hbs
Normal file
29
src/templates/sheets/actor/components/profile.hbs
Normal file
|
@ -0,0 +1,29 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="ds4-profile">
|
||||
{{#each data.data.profile as |profile-data-value profile-data-key|}}
|
||||
{{#if (and (ne profile-data-key 'biography') (ne profile-data-key 'specialCharacteristics'))}}
|
||||
<div class="ds4-profile__entry">
|
||||
<label class="ds4-profile__entry-label" for="data.profile.{{profile-data-key}}">
|
||||
{{lookup ../config.i18n.characterProfile profile-data-key}}
|
||||
</label>
|
||||
<input class="ds4-profile__entry-input" type="text" name="data.profile.{{profile-data-key}}"
|
||||
value="{{profile-data-value}}"
|
||||
data-dtype="{{lookup ../config.i18n.characterProfileDTypes profile-data-key}}" />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<div class="ds4-profile__entry">
|
||||
<label class="ds4-profile__entry-label" for="data.profile.specialCharacteristics">
|
||||
{{lookup config.i18n.characterProfile 'specialCharacteristics'}}
|
||||
</label>
|
||||
<textarea class="ds4-profile__entry-input ds4-profile__entry-input--multiline"
|
||||
name="data.profile.specialCharacteristics" data-dtype="String"
|
||||
rows="4">{{data.data.profile.specialCharacteristics}}</textarea>
|
||||
</div>
|
||||
</div>
|
|
@ -6,66 +6,21 @@ SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
|||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<form class="{{cssClass}} flexcol" autocomplete="off">
|
||||
{{!-- Sheet Header --}}
|
||||
<header class="sheet-header">
|
||||
<img class="profile-img" src="{{actor.img}}" data-edit="img" title="{{actor.name}}" height="100" width="100" />
|
||||
<div class="header-fields flexrow">
|
||||
<h1 class="charname">
|
||||
<label for="actor.name" class="hidden">Name</label>
|
||||
<input name="name" type="text" id="actor.name" value="{{actor.name}}" placeholder="Name" />
|
||||
</h1>
|
||||
{{> systems/ds4/templates/sheets/actor/components/character-progression.hbs}}
|
||||
<form class="{{cssClass}} ds4-sheet" autocomplete="off">
|
||||
{{!-- Header --}}
|
||||
{{#> systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/creature-properties.hbs}}
|
||||
{{/systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
|
||||
|
||||
<div class="flexrow basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{config.i18n.creatureBaseInfo.creatureType}}</label>
|
||||
<select name="data.baseInfo.creatureType" data-type="String">
|
||||
{{#select data.baseInfo.creatureType}}
|
||||
{{#each config.i18n.creatureTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.loot">{{config.i18n.creatureBaseInfo.loot}}</label>
|
||||
<input type="text" name="data.baseInfo.loot" value="{{data.baseInfo.loot}}" data-dtype="String" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.foeFactor">{{config.i18n.creatureBaseInfo.foeFactor}}</label>
|
||||
<input type="text" name="data.baseInfo.foeFactor" value="{{data.baseInfo.foeFactor}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{config.i18n.creatureBaseInfo.sizeCategory}}</label>
|
||||
<select name="data.baseInfo.sizeCategory" data-type="String">
|
||||
{{#select data.baseInfo.sizeCategory}}
|
||||
{{#each config.i18n.creatureSizeCategories as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label class="basic-property-label"
|
||||
for="data.baseInfo.experiencePoints">{{config.i18n.creatureBaseInfo.experiencePoints}}</label>
|
||||
<input type="text" name="data.baseInfo.experiencePoints" value="{{data.baseInfo.experiencePoints}}"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{!-- Sheet Tab Navigation --}}
|
||||
<nav class="sheet-tabs tabs" data-group="primary">
|
||||
<a class="item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a>
|
||||
<a class="item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
|
||||
<a class="item" data-tab="special-creature-abilities">{{localize 'DS4.HeadingSpecialCreatureAbilities'}}</a>
|
||||
<a class="item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a>
|
||||
<a class="item" data-tab="description">{{localize 'DS4.HeadingDescription'}}</a>
|
||||
<nav class="ds4-sheet-tab-nav sheet-tabs tabs" data-group="primary">
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="special-creature-abilities">{{localize
|
||||
'DS4.HeadingSpecialCreatureAbilities'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="description">{{localize 'DS4.HeadingDescription'}}</a>
|
||||
</nav>
|
||||
|
||||
{{!-- Sheet Body --}}
|
||||
|
|
|
@ -5,7 +5,7 @@ SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
|||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="tab talents-abilities" data-group="primary" data-tab="talents-abilities">
|
||||
<div class="tab abilities" data-group="primary" data-tab="abilities">
|
||||
{{!-- TALENT --}}
|
||||
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeTalentPlural'}}</h4>
|
||||
{{#unless (isEmpty itemsByType.talent)}}
|
|
@ -5,6 +5,13 @@ SPDX-License-Identifier: MIT
|
|||
--}}
|
||||
|
||||
<div class="tab biography" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.profile.biography target="data.profile.biography" button=true owner=owner
|
||||
editable=editable}}
|
||||
<div class="ds4-biography-tab-content">
|
||||
<!-- beautify ignore:start -->
|
||||
<!-- prettier-ignore-start -->
|
||||
{{!-- remove indentation to avoid annoying Handlebars auto-indent --}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/profile.hbs}}
|
||||
{{> systems/ds4/templates/sheets/actor/components/biography.hbs}}
|
||||
<!-- beautify ignore:end -->
|
||||
<!-- prettier-ignore-end -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,6 @@ SPDX-License-Identifier: MIT
|
|||
--}}
|
||||
|
||||
<div class="tab description" data-group="primary" data-tab="description">
|
||||
{{editor content=data.baseInfo.description target="data.baseInfo.description" button=true owner=owner
|
||||
{{editor content=data.data.baseInfo.description target="data.baseInfo.description" button=true owner=owner
|
||||
editable=editable}}
|
||||
</div>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
{{!--
|
||||
SPDX-FileCopyrightText: 2021 Johannes Loher
|
||||
SPDX-FileCopyrightText: 2021 Gesina Schwalbe
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
--}}
|
||||
|
||||
<div class="tab profile" data-group="primary" data-tab="profile">
|
||||
<div class="grid grid-2col">
|
||||
{{#each data.profile as |profile-data-value profile-data-key|}}
|
||||
{{#if (and (ne profile-data-key 'biography') (ne profile-data-key 'specialCharacteristics'))}}
|
||||
<div class="profile-entry">
|
||||
<label for="data.profile.{{profile-data-key}}">
|
||||
{{lookup ../config.i18n.characterProfile profile-data-key}}
|
||||
</label>
|
||||
<input type="text" name="data.profile.{{profile-data-key}}" value="{{profile-data-value}}"
|
||||
data-dtype="{{lookup ../config.i18n.characterProfileDTypes profile-data-key}}" />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<div>
|
||||
<label for="data.profile.specialCharacteristics">
|
||||
{{lookup config.i18n.characterProfile 'specialCharacteristics'}}
|
||||
</label>
|
||||
<textarea name="data.profile.specialCharacteristics" data-dtype="String"
|
||||
rows="4">{{data.profile.specialCharacteristics}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -7,35 +7,36 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
<form class="{{cssClass}}" autocomplete="off">
|
||||
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
<div class="grid grid-3col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorType"}}</label>
|
||||
<select name="data.armorType" data-type="String">
|
||||
{{#select data.armorType}}
|
||||
{{#each config.i18n.armorTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label for="data.armorMaterialType">{{localize "DS4.ArmorMaterialType"}}</label>
|
||||
<select name="data.armorMaterialType" data-type="String">
|
||||
{{#select data.armorMaterialType}}
|
||||
{{#each config.i18n.armorMaterialTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorValue"}}</label>
|
||||
<input type="text" name="data.armorValue" value="{{data.armorValue}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-3col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorType"}}</label>
|
||||
<select name="data.armorType" data-type="String">
|
||||
{{#select data.data.armorType}}
|
||||
{{#each config.i18n.armorTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label for="data.armorMaterialType">{{localize "DS4.ArmorMaterialType"}}</label>
|
||||
<select name="data.armorMaterialType" data-type="String">
|
||||
{{#select data.data.armorMaterialType}}
|
||||
{{#each config.i18n.armorMaterialTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorValue"}}</label>
|
||||
<input type="text" name="data.armorValue" value="{{data.data.armorValue}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
</form>
|
||||
|
|
|
@ -8,11 +8,11 @@ SPDX-License-Identifier: MIT
|
|||
{{!-- Template for the common body (navigation & body sections) of all items. --}}
|
||||
|
||||
{{!-- Sheet Tab Navigation --}}
|
||||
<nav class="sheet-tabs tabs" data-group="primary">
|
||||
<a class="item" data-tab="description">{{localize "DS4.HeadingDescription"}}</a>
|
||||
<a class="item" data-tab="effects">{{localize "DS4.HeadingEffects"}}</a>
|
||||
<nav class="ds4-sheet-tab-nav sheet-tabs tabs" data-group="primary">
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="description">{{localize "DS4.HeadingDescription"}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="effects">{{localize "DS4.HeadingEffects"}}</a>
|
||||
{{#if isPhysical}}
|
||||
<a class="item" data-tab="details">{{localize "DS4.HeadingDetails"}}</a>
|
||||
<a class="ds4-sheet-tab-nav__item item" data-tab="details">{{localize "DS4.HeadingDetails"}}</a>
|
||||
{{/if}}
|
||||
</nav>
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ SPDX-License-Identifier: MIT
|
|||
--}}
|
||||
|
||||
<header class="sheet-header">
|
||||
<img class="profile-img" src="{{item.img}}" data-edit="img" title="{{item.name}}" />
|
||||
<img class="profile-img" src="{{data.img}}" data-edit="img" title="{{data.name}}" />
|
||||
<div class="header-fields flexrow">
|
||||
<h1 class="charname"><input name="name" type="text" value="{{item.name}}" placeholder="Name" /></h1>
|
||||
<h1 class="charname"><input name="name" type="text" value="{{data.name}}" placeholder="Name" /></h1>
|
||||
<h2 class="item-type">{{lookup config.i18n.itemTypes item.type}}</h2>
|
||||
{{> @partial-block}}
|
||||
</div>
|
||||
|
|
|
@ -7,15 +7,16 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
<form class="{{cssClass}}" autocomplete="off">
|
||||
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
<div class="grid grid-1col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorValue"}}</label>
|
||||
<input type="text" name="data.armorValue" value="{{data.armorValue}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
</div>
|
||||
<div class="grid grid-1col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.ArmorValue"}}</label>
|
||||
<input type="text" name="data.armorValue" value="{{data.data.armorValue}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
</form>
|
||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="grid grid-3col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.SpecialCreatureAbilityExperiencePoints"}}</label>
|
||||
<input type="number" min="0" step="1" name="data.experiencePoints" value="{{data.experiencePoints}}"
|
||||
<input type="number" min="0" step="1" name="data.experiencePoints" value="{{data.data.experiencePoints}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,9 +16,9 @@ SPDX-License-Identifier: MIT
|
|||
<label>{{localize localizeString}}</label>
|
||||
<div class="unit-data-pair">
|
||||
<input class="item-num-val" type="text" data-dtype="String" name="data.{{property}}.value"
|
||||
value="{{lookup (lookup data property) 'value'}}" />
|
||||
value="{{lookup (lookup data.data property) 'value'}}" />
|
||||
<select name="data.{{property}}.unit" data-type="String">
|
||||
{{#select (lookup (lookup data property) 'unit')}}
|
||||
{{#select (lookup (lookup data.data property) 'unit')}}
|
||||
{{#if (eq unitType 'temporal')}}
|
||||
{{#each (lookup config.i18n 'temporalUnitsAbbr') as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="basic-property">
|
||||
<label for="data.spellType">{{localize "DS4.SpellType"}}</label>
|
||||
<select id="data.spellType" name="data.spellType" data-type="String">
|
||||
{{#select data.spellType}}
|
||||
{{#select data.data.spellType}}
|
||||
{{#each config.i18n.spellTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -57,7 +57,7 @@ SPDX-License-Identifier: MIT
|
|||
</div>
|
||||
<div class="basic-property">
|
||||
<label for="data.bonus">{{localize "DS4.SpellBonus"}}</label>
|
||||
<input id="data.bonus" type="text" name="data.bonus" value="{{data.bonus}}" data-dtype="String" />
|
||||
<input id="data.bonus" type="text" name="data.bonus" value="{{data.data.bonus}}" data-dtype="String" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
@ -67,7 +67,7 @@ SPDX-License-Identifier: MIT
|
|||
<div class="side-property">
|
||||
<label for="data.spellCategory">{{localize "DS4.SpellCategory"}}</label>
|
||||
<select id="data.spellCategory" name="data.spellCategory" data-type="String">
|
||||
{{#select data.spellCategory}}
|
||||
{{#select data.data.spellCategory}}
|
||||
{{#each config.i18n.spellCategories as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
@ -82,21 +82,21 @@ SPDX-License-Identifier: MIT
|
|||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsHealer'}}">
|
||||
<label for="data.minimumLevels.healer">{{localize "DS4.SpellMinimumLevelsHealerAbbr"}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.healer"
|
||||
id="data.minimumLevels.healer" value="{{data.minimumLevels.healer}}" />
|
||||
id="data.minimumLevels.healer" value="{{data.data.minimumLevels.healer}}" />
|
||||
</div>
|
||||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsWizard'}}">
|
||||
<label for="data.minimumLevels.wizard">{{localize "DS4.SpellMinimumLevelsWizardAbbr"}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.wizard"
|
||||
id="data.minimumLevels.wizard" value="{{data.minimumLevels.wizard}}" />
|
||||
id="data.minimumLevels.wizard" value="{{data.data.minimumLevels.wizard}}" />
|
||||
</div>
|
||||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsSorcerer'}}">
|
||||
<label for="data.minimumLevels.sorcerer">{{localize "DS4.SpellMinimumLevelsSorcererAbbr"}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.sorcerer"
|
||||
id="data.minimumLevels.sorcerer" value="{{data.minimumLevels.sorcerer}}" />
|
||||
id="data.minimumLevels.sorcerer" value="{{data.data.minimumLevels.sorcerer}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.price">{{localize "DS4.SpellPrice"}}</label>
|
||||
<span name="data.price" id="data.price">{{data.price}}</span>
|
||||
<span name="data.price" id="data.price">{{data.data.price}}</span>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
|
||||
|
|
|
@ -13,25 +13,27 @@ Additional elements of the side-properties div can be handed over via the @parti
|
|||
<div class="tab flexrow description" data-group="primary" data-tab="description">
|
||||
<div class="side-properties">
|
||||
{{#if isOwned}}
|
||||
{{#if (ne data.equipped undefined)}}<div class="side-property">
|
||||
{{#if (ne data.data.equipped undefined)}}<div class="side-property">
|
||||
<label for="data.equipped">{{localize 'DS4.ItemEquipped'}}</label>
|
||||
<input type="checkbox" name="data.equipped" data-dtype="Boolean" {{checked data.equipped}} title="{{localize 'DS4.ItemEquipped'}}">
|
||||
<input type="checkbox" name="data.equipped" data-dtype="Boolean" {{checked data.data.equipped}}
|
||||
title="{{localize 'DS4.ItemEquipped'}}">
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="side-property">
|
||||
<label for="data.actor">{{localize 'DS4.ItemOwner'}}</label>
|
||||
<a class="entity-link" draggable="true" data-entity="Actor" data-id="{{actor._id}}"><i
|
||||
<a class="entity-link" draggable="true" data-entity="Actor" data-id="{{actor.id}}"><i
|
||||
class="fas fa-user"></i>{{actor.name}}</a>
|
||||
</div>
|
||||
{{#if isPhysical}}
|
||||
<div class="side-property">
|
||||
<label for="data.quantity">{{localize 'DS4.Quantity'}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.quantity" value="{{data.quantity}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.storageLocation">{{localize 'DS4.StorageLocation'}}</label>
|
||||
<input type="text" data-dtype="String" name="data.storageLocation" value="{{data.storageLocation}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.quantity">{{localize 'DS4.Quantity'}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" name="data.quantity"
|
||||
value="{{data.data.quantity}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.storageLocation">{{localize 'DS4.StorageLocation'}}</label>
|
||||
<input type="text" data-dtype="String" name="data.storageLocation" value="{{data.data.storageLocation}}" />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span>{{localize "DS4.NotOwned"}}</span>
|
||||
|
@ -39,6 +41,6 @@ Additional elements of the side-properties div can be handed over via the @parti
|
|||
{{> @partial-block}}
|
||||
</div>
|
||||
<div class="description" title="{{localize 'DS4.HeadingDescription'}}">
|
||||
{{editor content=data.description target="data.description" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.data.description target="data.description" button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,13 +11,13 @@ SPDX-License-Identifier: MIT
|
|||
<div class="side-properties">
|
||||
<div class="side-property">
|
||||
<label for="data.price">{{localize "DS4.PriceGold"}}</label>
|
||||
<input type="number" min="0" max="99999" step="0.01" data-dtype="Number"
|
||||
name="data.price" value="{{data.price}}" />
|
||||
<input type="number" min="0" max="99999" step="0.01" data-dtype="Number" name="data.price"
|
||||
value="{{data.data.price}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.availability">{{localize "DS4.ItemAvailability"}}</label>
|
||||
<select name="data.availability" data-type="String">
|
||||
{{#select data.availability}}
|
||||
{{#select data.data.availability}}
|
||||
{{#each config.i18n.itemAvailabilities as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: MIT
|
|||
</div>
|
||||
</li>
|
||||
{{#each item.effects as |effect id|}}
|
||||
<li class="effect flexrow" data-effect-id="{{effect._id}}">
|
||||
<h4 class="effect-name">{{effect.label}}</h4>
|
||||
<li class="effect flexrow" data-effect-id="{{effect.id}}">
|
||||
<h4 class="effect-name">{{effect.data.label}}</h4>
|
||||
<div class="effect-controls">
|
||||
<a class="effect-control" data-action="edit" title="{{localize 'DS4.UserInteractionEditEffect'}}">
|
||||
<i class="fas fa-edit"></i></a>
|
||||
|
|
|
@ -13,9 +13,9 @@ SPDX-License-Identifier: MIT
|
|||
{{#*inline "talentRankBasicProperty" }}
|
||||
<div class="basic-property">
|
||||
<label for="data.rank.{{property}}">{{localize localizeString}}</label>
|
||||
<input type="number" min="0" step="1" data-dtype="Number" {{disabled}}
|
||||
{{#if (eq property 'base') }}max="{{data.rank.max}}"{{/if}}
|
||||
name="data.rank.{{property}}" value="{{lookup data.rank property}}" />
|
||||
<input type="number" min="0" step="1" data-dtype="Number" {{disabled}} {{#if (eq property 'base' )
|
||||
}}max="{{data.data.rank.max}}" {{/if}} name="data.rank.{{property}}"
|
||||
value="{{lookup data.data.rank property}}" />
|
||||
</div>
|
||||
{{/inline}}
|
||||
|
||||
|
@ -25,15 +25,17 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
<form class="{{cssClass}}" autocomplete="off">
|
||||
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
<div class="grid grid-4col basic-properties">
|
||||
{{> talentRankBasicProperty data=data property='base' localizeString='DS4.TalentRankBase' }}
|
||||
{{> talentRankBasicProperty data=data property='max' localizeString='DS4.TalentRankMax'}}
|
||||
{{> talentRankBasicProperty data=data property='mod' localizeString='DS4.TalentRankMod'}}
|
||||
{{> talentRankBasicProperty data=data property='total' localizeString='DS4.TalentRankTotal' disabled='disabled'}}
|
||||
</div>
|
||||
<div class="grid grid-4col basic-properties">
|
||||
{{> talentRankBasicProperty data=data property='base' localizeString='DS4.TalentRankBase' }}
|
||||
{{> talentRankBasicProperty data=data property='max' localizeString='DS4.TalentRankMax'}}
|
||||
{{> talentRankBasicProperty data=data property='mod' localizeString='DS4.TalentRankMod'}}
|
||||
{{> talentRankBasicProperty data=data property='total' localizeString='DS4.TalentRankTotal'
|
||||
disabled='disabled'}}
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
|
||||
</form>
|
||||
|
|
|
@ -7,30 +7,31 @@ SPDX-License-Identifier: MIT
|
|||
|
||||
<form class="{{cssClass}}" autocomplete="off">
|
||||
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
<div class="grid grid-3col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.AttackType"}}</label>
|
||||
<select name="data.attackType" data-type="String">
|
||||
{{#select data.attackType}}
|
||||
{{#each config.i18n.attackTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.WeaponBonus"}}</label>
|
||||
<input type="number" name="data.weaponBonus" value="{{data.weaponBonus}}"
|
||||
placeholder="0" data-dtype="Number" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.OpponentDefense"}}</label>
|
||||
<input type="number" name="data.opponentDefense"
|
||||
value="{{data.opponentDefense}}" placeholder="0" data-dtype="Number" />
|
||||
</div>
|
||||
<div class="grid grid-3col basic-properties">
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.AttackType"}}</label>
|
||||
<select name="data.attackType" data-type="String">
|
||||
{{#select data.data.attackType}}
|
||||
{{#each config.i18n.attackTypes as |value key|}}
|
||||
<option value="{{key}}">{{value}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.WeaponBonus"}}</label>
|
||||
<input type="number" name="data.weaponBonus" value="{{data.data.weaponBonus}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
<div class="basic-property">
|
||||
<label>{{localize "DS4.OpponentDefense"}}</label>
|
||||
<input type="number" name="data.opponentDefense" value="{{data.data.opponentDefense}}" placeholder="0"
|
||||
data-dtype="Number" />
|
||||
</div>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
|
||||
|
||||
{{!-- Common Item body --}}
|
||||
{{#> systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
{{#>
|
||||
systems/ds4/templates/sheets/item/components/body.hbs}}{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
</form>
|
||||
|
|
Loading…
Reference in a new issue