Merge branch '55-creatures-compendium' of git.f3l.de:dungeonslayers/ds4 into 55-creatures-compendium

This commit is contained in:
Sascha Martens 2021-07-21 15:12:44 +02:00
commit 0d79e38898
96 changed files with 3236 additions and 3108 deletions

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2021 Johannes Loher
SPDX-License-Identifier: MIT

View file

@ -2,7 +2,7 @@
"private": true, "private": true,
"name": "dungeonslayers4", "name": "dungeonslayers4",
"description": "An implementation of the Dungeonslayers 4 game system for Foundry Virtual Tabletop.", "description": "An implementation of the Dungeonslayers 4 game system for Foundry Virtual Tabletop.",
"version": "0.8.0", "version": "1.1.3",
"license": "https://git.f3l.de/dungeonslayers/ds4#licensing", "license": "https://git.f3l.de/dungeonslayers/ds4#licensing",
"homepage": "https://git.f3l.de/dungeonslayers/ds4", "homepage": "https://git.f3l.de/dungeonslayers/ds4",
"repository": { "repository": {
@ -52,32 +52,32 @@
"postinstall": "husky install" "postinstall": "husky install"
}, },
"devDependencies": { "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", "@rollup/plugin-node-resolve": "^13.0.0",
"@types/fs-extra": "^9.0.11", "@types/fs-extra": "^9.0.12",
"@types/jest": "^26.0.23", "@types/jest": "^26.0.24",
"@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.0", "@typescript-eslint/parser": "^4.28.2",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"eslint": "^7.29.0", "eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^24.3.6", "eslint-plugin-jest": "^24.3.6",
"eslint-plugin-prettier": "^3.4.0", "eslint-plugin-prettier": "^3.4.0",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-sass": "^5.0.0", "gulp-sass": "^5.0.0",
"husky": "^6.0.0", "husky": "^7.0.1",
"jest": "^27.0.5", "jest": "^27.0.6",
"jest-junit": "^12.2.0", "jest-junit": "^12.2.0",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"rollup": "^2.52.3", "rollup": "^2.53.1",
"rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-typescript2": "^0.30.0",
"sass": "1.32.8", "sass": "1.35.2",
"semver": "^7.3.5", "semver": "^7.3.5",
"ts-jest": "^27.0.3", "ts-jest": "^27.0.3",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"typescript": "^4.3.4", "typescript": "^4.3.5",
"yargs": "^17.0.1" "yargs": "^17.0.1"
}, },
"lint-staged": { "lint-staged": {

View file

@ -5,7 +5,23 @@
import evaluateCheck from "../../src/module/rolls/check-evaluation"; 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", () => { describe("evaluateCheck with no dice", () => {
it("should throw an error.", () => { it("should throw an error.", () => {

View file

@ -5,7 +5,7 @@
"DS4.UserInteractionAddEffect": "Neuer Effekt", "DS4.UserInteractionAddEffect": "Neuer Effekt",
"DS4.UserInteractionEditEffect": "Effekt bearbeiten", "DS4.UserInteractionEditEffect": "Effekt bearbeiten",
"DS4.UserInteractionDeleteEffect": "Effekt löschen", "DS4.UserInteractionDeleteEffect": "Effekt löschen",
"DS4.EntityImageAltText": "Bild von {name}", "DS4.DocumentImageAltText": "Bild von {name}",
"DS4.RollableImageRollableTitle": "Für {name} würfeln", "DS4.RollableImageRollableTitle": "Für {name} würfeln",
"DS4.DiceOverlayImageAltText": "Bild eines W20", "DS4.DiceOverlayImageAltText": "Bild eines W20",
"DS4.NotOwned": "Nicht besessen", "DS4.NotOwned": "Nicht besessen",
@ -15,7 +15,7 @@
"DS4.HeadingEffects": "Effekte", "DS4.HeadingEffects": "Effekte",
"DS4.HeadingInventory": "Inventar", "DS4.HeadingInventory": "Inventar",
"DS4.HeadingProfile": "Profil", "DS4.HeadingProfile": "Profil",
"DS4.HeadingTalentsAbilities": "Talente & Fähigkeiten", "DS4.HeadingAbilities": "Fähigkeiten",
"DS4.HeadingSpells": "Zaubersprüche", "DS4.HeadingSpells": "Zaubersprüche",
"DS4.HeadingDescription": "Beschreibung", "DS4.HeadingDescription": "Beschreibung",
"DS4.HeadingSpecialCreatureAbilities": "Besondere Fähigkeiten", "DS4.HeadingSpecialCreatureAbilities": "Besondere Fähigkeiten",
@ -121,6 +121,8 @@
"DS4.SpellMinimumLevelsSorcerer": "Zugangsstufe für Schwarzmagier", "DS4.SpellMinimumLevelsSorcerer": "Zugangsstufe für Schwarzmagier",
"DS4.SpellMinimumLevelsSorcererAbbr": "Zugangsstufe Sch", "DS4.SpellMinimumLevelsSorcererAbbr": "Zugangsstufe Sch",
"DS4.SpellPrice": "Preis (Gold)", "DS4.SpellPrice": "Preis (Gold)",
"DS4.ActorName": "Name",
"DS4.ActorImageAltText": "Bild des Aktors",
"DS4.ActorTypeCharacter": "Charakter", "DS4.ActorTypeCharacter": "Charakter",
"DS4.ActorTypeCreature": "Kreatur", "DS4.ActorTypeCreature": "Kreatur",
"DS4.Attribute": "Attribut", "DS4.Attribute": "Attribut",
@ -144,6 +146,14 @@
"DS4.CombatValuesRangedAttack": "Schießen", "DS4.CombatValuesRangedAttack": "Schießen",
"DS4.CombatValuesSpellcasting": "Zaubern", "DS4.CombatValuesSpellcasting": "Zaubern",
"DS4.CombatValuesTargetedSpellcasting": "Zielzaubern", "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.CharacterBaseInfoRace": "Volk",
"DS4.CharacterBaseInfoClass": "Klasse", "DS4.CharacterBaseInfoClass": "Klasse",
"DS4.CharacterBaseInfoHeroClass": "Heldenklasse", "DS4.CharacterBaseInfoHeroClass": "Heldenklasse",

View file

@ -5,7 +5,7 @@
"DS4.UserInteractionAddEffect": "Add Effect", "DS4.UserInteractionAddEffect": "Add Effect",
"DS4.UserInteractionEditEffect": "Edit Effect", "DS4.UserInteractionEditEffect": "Edit Effect",
"DS4.UserInteractionDeleteEffect": "Delete Effect", "DS4.UserInteractionDeleteEffect": "Delete Effect",
"DS4.EntityImageAltText": "Image of {name}", "DS4.DocumentImageAltText": "Image of {name}",
"DS4.RollableImageRollableTitle": "Roll for {name}", "DS4.RollableImageRollableTitle": "Roll for {name}",
"DS4.DiceOverlayImageAltText": "Image of a d20", "DS4.DiceOverlayImageAltText": "Image of a d20",
"DS4.NotOwned": "No owner", "DS4.NotOwned": "No owner",
@ -15,7 +15,7 @@
"DS4.HeadingEffects": "Effects", "DS4.HeadingEffects": "Effects",
"DS4.HeadingInventory": "Inventory", "DS4.HeadingInventory": "Inventory",
"DS4.HeadingProfile": "Profile", "DS4.HeadingProfile": "Profile",
"DS4.HeadingTalentsAbilities": "Talents & Abilities", "DS4.HeadingAbilities": "Abilities",
"DS4.HeadingSpells": "Spells", "DS4.HeadingSpells": "Spells",
"DS4.HeadingDescription": "Description", "DS4.HeadingDescription": "Description",
"DS4.HeadingSpecialCreatureAbilities": "Special Abilities", "DS4.HeadingSpecialCreatureAbilities": "Special Abilities",
@ -121,6 +121,8 @@
"DS4.SpellMinimumLevelsSorcerer": "Minimum level for Sorcerers", "DS4.SpellMinimumLevelsSorcerer": "Minimum level for Sorcerers",
"DS4.SpellMinimumLevelsSorcererAbbr": "Min lvl SRC", "DS4.SpellMinimumLevelsSorcererAbbr": "Min lvl SRC",
"DS4.SpellPrice": "Price (Gold)", "DS4.SpellPrice": "Price (Gold)",
"DS4.ActorName": "Name",
"DS4.ActorImageAltText": "Image of the Actor",
"DS4.ActorTypeCharacter": "Character", "DS4.ActorTypeCharacter": "Character",
"DS4.ActorTypeCreature": "Creature", "DS4.ActorTypeCreature": "Creature",
"DS4.Attribute": "Attribute", "DS4.Attribute": "Attribute",
@ -144,6 +146,14 @@
"DS4.CombatValuesRangedAttack": "Ranged Attack", "DS4.CombatValuesRangedAttack": "Ranged Attack",
"DS4.CombatValuesSpellcasting": "Spellcasting", "DS4.CombatValuesSpellcasting": "Spellcasting",
"DS4.CombatValuesTargetedSpellcasting": "Targeted 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.CharacterBaseInfoRace": "Race",
"DS4.CharacterBaseInfoClass": "Class", "DS4.CharacterBaseInfoClass": "Class",
"DS4.CharacterBaseInfoHeroClass": "Hero Class", "DS4.CharacterBaseInfoHeroClass": "Hero Class",

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

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

View file

@ -7,50 +7,56 @@
import { ModifiableData, ModifiableDataBase, ResourceData, UsableResource } from "../common/common-data"; import { ModifiableData, ModifiableDataBase, ResourceData, UsableResource } from "../common/common-data";
import { DS4 } from "../config"; import { DS4 } from "../config";
import { DS4ItemData } from "../item/item-data";
export type DS4ActorData = DS4CharacterData | DS4CreatureData; declare global {
interface SourceConfig {
type ActorType = keyof typeof DS4.i18n.actorTypes; Actor: DS4ActorDataSource;
}
export interface DS4ActorDataHelper<T, U extends ActorType> extends Actor.Data<T, DS4ItemData> {
type: U;
} }
type DS4CharacterData = DS4ActorDataHelper<DS4CharacterDataData, "character">; export type DS4ActorDataSource = DS4CharacterDataSource | DS4CreatureDataSource;
type DS4CreatureData = DS4ActorDataHelper<DS4CreatureDataData, "creature">;
interface DS4CharacterDataSource {
type: "character";
data: DS4CharacterDataSourceData;
}
interface DS4CreatureDataSource {
type: "creature";
data: DS4CreatureDataSourceData;
}
// templates // templates
interface DS4ActorDataDataBase { interface DS4ActorDataSourceDataBase {
attributes: DS4ActorDataDataAttributes; attributes: DS4ActorDataSourceDataAttributes;
traits: DS4ActorDataDataTraits; traits: DS4ActorDataSourceDataTraits;
combatValues: DS4ActorDataDataCombatValues; 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 { export function isAttribute(value: unknown): value is Attribute {
return (Object.keys(DS4.i18n.attributes) as Array<unknown>).includes(value); 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 { export function isTrait(value: unknown): value is Trait {
return (Object.keys(DS4.i18n.traits) as Array<unknown>).includes(value); 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" [Key in keyof typeof DS4.i18n.combatValues]: Key extends "hitPoints"
? ResourceData<number> ? ResourceData<number>
: ModifiableData<number>; : ModifiableData<number>;
}; };
type CombatValue = keyof DS4ActorDataDataCombatValues; type CombatValue = keyof DS4ActorDataSourceDataCombatValues;
export function isCombatValue(value: string): value is CombatValue { export function isCombatValue(value: string): value is CombatValue {
return (Object.keys(DS4.i18n.combatValues) as Array<unknown>).includes(value); return (Object.keys(DS4.i18n.combatValues) as Array<unknown>).includes(value);
@ -58,33 +64,44 @@ export function isCombatValue(value: string): value is CombatValue {
// types // types
interface DS4CharacterDataData extends DS4ActorDataDataBase { interface DS4CreatureDataSourceData extends DS4ActorDataSourceDataBase {
baseInfo: DS4CharacterDataDataBaseInfo; baseInfo: DS4CreatureDataSourceDataBaseInfo;
progression: DS4CharacterDataDataProgression;
language: DS4CharacterDataDataLanguage;
profile: DS4CharacterDataDataProfile;
currency: DS4CharacterDataDataCurrency;
slayerPoints: DS4CharacterDataDataSlayerPoints;
} }
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; race: string;
class: string; class: string;
heroClass: string; heroClass: string;
culture: string; culture: string;
} }
export interface DS4CharacterDataDataProgression { export interface DS4CharacterDataSourceDataProgression {
level: number; level: number;
experiencePoints: number; experiencePoints: number;
talentPoints: UsableResource<number>; talentPoints: UsableResource<number>;
progressPoints: UsableResource<number>; progressPoints: UsableResource<number>;
} }
export interface DS4CharacterDataDataLanguage { export interface DS4CharacterDataSourceDataProfile {
languages: string;
alphabets: string;
}
export interface DS4CharacterDataDataProfile {
biography: string; biography: string;
gender: string; gender: string;
birthday: string; birthday: string;
@ -97,29 +114,12 @@ export interface DS4CharacterDataDataProfile {
specialCharacteristics: string; specialCharacteristics: string;
} }
export interface DS4CharacterDataDataCurrency { export interface DS4CharacterDataSourceDataCurrency {
gold: number; gold: number;
silver: number; silver: number;
copper: number; copper: number;
} }
export interface DS4CharacterDataDataSlayerPoints { export interface DS4CharacterDataSourceDataSlayerPoints {
value: number; 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;

View file

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

View file

@ -5,22 +5,27 @@
import { ModifiableDataBaseTotal } from "../common/common-data"; import { ModifiableDataBaseTotal } from "../common/common-data";
import { DS4 } from "../config"; import { DS4 } from "../config";
import { getGame } from "../helpers";
import { DS4Item } from "../item/item"; import { DS4Item } from "../item/item";
import { ItemType } from "../item/item-data"; import { DS4ArmorDataProperties, DS4ShieldDataProperties } from "../item/item-data-properties";
import { DS4ArmorPreparedData, DS4ShieldPreparedData } from "../item/item-prepared-data"; import { ItemType } from "../item/item-data-source";
import { createCheckRoll } from "../rolls/check-factory"; import { createCheckRoll } from "../rolls/check-factory";
import { DS4ActorData, isAttribute, isTrait } from "./actor-data"; import { Check } from "./actor-data-properties";
import { Check, DS4ActorPreparedData } from "./actor-prepared-data"; import { isAttribute, isTrait } from "./actor-data-source";
declare global {
interface DocumentClassConfig {
Actor: typeof DS4Actor;
}
}
/** /**
* The Actor class for DS4 * The Actor class for DS4
*/ */
export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData> { export class DS4Actor extends Actor {
/** @override */ /** @override */
prepareData(): void { prepareData(): void {
this.data = duplicate(this._data) as DS4ActorPreparedData; this.data.reset();
if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN;
if (!this.data.name) this.data.name = "New " + this.entity;
this.prepareBaseData(); this.prepareBaseData();
this.prepareEmbeddedEntities(); this.prepareEmbeddedEntities();
this.applyActiveEffectsToBaseData(); 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 { applyActiveEffectsToBaseData(): void {
// reset overrides because our variant of applying active effects does not set them, it only adds overrides // reset overrides because our variant of applying active effects does not set them, it only adds overrides
this.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 * @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> = {}; const overrides: Record<string, unknown> = {};
// Organize non-disabled effects by their application priority // Organize non-disabled effects by their application priority
const changes = this.effects.reduce( const changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[] =
(changes: Array<ActiveEffectChange & { effect: ActiveEffect<DS4Actor> }>, e) => { this.effects.reduce(
if (e.data.disabled) return changes; (changes: (foundry.data.ActiveEffectData["changes"][number] & { effect: ActiveEffect })[], e) => {
const item = this._getOriginatingItemOfActiveEffect(e); if (e.data.disabled) return changes;
if (item?.isNonEquippedEuipable()) 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( const newChanges = e.data.changes.filter(predicate).flatMap((c) => {
e.data.changes.filter(predicate).flatMap((c) => { const changeSource = c.toObject();
const duplicatedChange = duplicate(c); changeSource.priority = changeSource.priority ?? changeSource.mode * 10;
duplicatedChange.priority = duplicatedChange.priority ?? duplicatedChange.mode * 10; return Array(factor).fill({ ...changeSource, effect: e });
return Array(factor).fill({ });
...duplicatedChange,
effect: e, return changes.concat(newChanges);
}); },
}), [],
); );
}, changes.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
[],
);
changes.sort((a, b) => a.priority - b.priority);
// Apply all changes // Apply all changes
for (const change of changes) { for (const change of changes) {
@ -102,11 +114,11 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
} }
// Expand the set of final overrides // 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 { protected getOriginatingItemOfActiveEffect(effect: ActiveEffect): DS4Item | undefined {
return this.items.find((item) => item.uuid === effect.data.origin) ?? 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 return this.items
.map((item) => item.data) .map((item) => item.data)
.filter( .filter(
(data): data is DS4ArmorPreparedData | DS4ShieldPreparedData => (data): data is foundry.data.ItemData & (DS4ArmorDataProperties | DS4ShieldDataProperties) =>
data.type === "armor" || data.type === "shield", data.type === "armor" || data.type === "shield",
) )
.filter((data) => data.data.equipped) .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. * This only differs from the base implementation by also allowing negative values.
* @override * @override
*/ */
async modifyTokenAttribute(attribute: string, value: number, isDelta = false, isBar = true): Promise<this> { async modifyTokenAttribute(
const current = getProperty(this.data.data, attribute); 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 // Determine the updates to make to the actor data
let updates: Record<string, number>; let updates: Record<string, number>;
@ -291,10 +308,10 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
*/ */
async rollCheck(check: Check): Promise<void> { async rollCheck(check: Check): Promise<void> {
await createCheckRoll(this.data.data.checks[check], { 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, maximumCoupResult: this.data.data.rolling.maximumCoupResult,
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult, 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 { attribute, trait } = await this.selectAttributeAndTrait();
const checkTargetNumber = this.data.data.attributes[attribute].total + this.data.data.traits[trait].total; const checkTargetNumber = this.data.data.attributes[attribute].total + this.data.data.traits[trait].total;
await createCheckRoll(checkTargetNumber, { 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, maximumCoupResult: this.data.data.rolling.maximumCoupResult,
minimumFumbleResult: this.data.data.rolling.minimumFumbleResult, minimumFumbleResult: this.data.data.rolling.minimumFumbleResult,
flavor: game.i18n.format("DS4.ActorGenericCheckFlavor", { flavor: getGame().i18n.format("DS4.ActorGenericCheckFlavor", {
actor: this.name, actor: this.name,
attribute: DS4.i18n.attributes[attribute], attribute: DS4.i18n.attributes[attribute],
trait: DS4.i18n.traits[trait], trait: DS4.i18n.traits[trait],
@ -324,27 +341,27 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
const attributeIdentifier = "attribute-trait-selection-attribute"; const attributeIdentifier = "attribute-trait-selection-attribute";
const traitIdentifier = "attribute-trait-selection-trait"; const traitIdentifier = "attribute-trait-selection-trait";
return Dialog.prompt({ 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", { content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
selects: [ selects: [
{ {
label: game.i18n.localize("DS4.Attribute"), label: getGame().i18n.localize("DS4.Attribute"),
identifier: attributeIdentifier, identifier: attributeIdentifier,
options: DS4.i18n.attributes, options: DS4.i18n.attributes,
}, },
{ {
label: game.i18n.localize("DS4.Trait"), label: getGame().i18n.localize("DS4.Trait"),
identifier: traitIdentifier, identifier: traitIdentifier,
options: DS4.i18n.traits, options: DS4.i18n.traits,
}, },
], ],
}), }),
label: game.i18n.localize("DS4.GenericOkButton"), label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (html) => { callback: (html) => {
const selectedAttribute = html.find(`#${attributeIdentifier}`).val(); const selectedAttribute = html.find(`#${attributeIdentifier}`).val();
if (!isAttribute(selectedAttribute)) { if (!isAttribute(selectedAttribute)) {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorUnexpectedAttribute", { getGame().i18n.format("DS4.ErrorUnexpectedAttribute", {
actualAttribute: selectedAttribute, actualAttribute: selectedAttribute,
expectedTypes: Object.keys(DS4.i18n.attributes) expectedTypes: Object.keys(DS4.i18n.attributes)
.map((attribute) => `'${attribute}'`) .map((attribute) => `'${attribute}'`)
@ -355,7 +372,7 @@ export class DS4Actor extends Actor<DS4ActorData, DS4Item, DS4ActorPreparedData>
const selectedTrait = html.find(`#${traitIdentifier}`).val(); const selectedTrait = html.find(`#${traitIdentifier}`).val();
if (!isTrait(selectedTrait)) { if (!isTrait(selectedTrait)) {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorUnexpectedTrait", { getGame().i18n.format("DS4.ErrorUnexpectedTrait", {
actualTrait: selectedTrait, actualTrait: selectedTrait,
expectedTypes: Object.keys(DS4.i18n.traits) expectedTypes: Object.keys(DS4.i18n.traits)
.map((attribute) => `'${attribute}'`) .map((attribute) => `'${attribute}'`)

View file

@ -7,31 +7,26 @@
import { ModifiableDataBaseTotal } from "../../common/common-data"; import { ModifiableDataBaseTotal } from "../../common/common-data";
import { DS4 } from "../../config"; import { DS4 } from "../../config";
import { getCanvas } from "../../helpers"; import { getCanvas, getGame } from "../../helpers";
import { DS4Item } from "../../item/item"; import { DS4Item } from "../../item/item";
import { DS4ItemData } from "../../item/item-data"; import { DS4Settings, getDS4Settings } from "../../settings";
import { getDS4Settings } from "../../settings";
import notifications from "../../ui/notifications"; import notifications from "../../ui/notifications";
import { DS4Actor } from "../actor"; import { isCheck } from "../actor-data-properties";
import { isCheck } from "../actor-prepared-data";
/** /**
* The base Sheet class for all DS4 Actors * The base Sheet class for all DS4 Actors
*/ */
export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> { export class DS4ActorSheet extends ActorSheet<ActorSheet.Options, DS4ActorSheetData> {
// TODO(types): Improve mergeObject in upstream so that it isn't necessary to provide all parameters (see https://github.com/League-of-Foundry-Developers/foundry-vtt-types/issues/272)
/** @override */ /** @override */
static get defaultOptions(): BaseEntitySheet.Options { static get defaultOptions(): ActorSheet.Options {
const superDefaultOptions = super.defaultOptions; return foundry.utils.mergeObject(super.defaultOptions, {
return mergeObject(superDefaultOptions, {
...superDefaultOptions,
classes: ["ds4", "sheet", "actor"], classes: ["ds4", "sheet", "actor"],
height: 620, height: 620,
scrollY: [ scrollY: [
".values", ".values",
".inventory", ".inventory",
".spells", ".spells",
".talents-abilities", ".abilities",
".profile", ".profile",
".biography", ".biography",
".special-creature-abilities", ".special-creature-abilities",
@ -57,14 +52,14 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
* object itemsByType. * object itemsByType.
* @returns The data fed to the template of the actor sheet * @returns The data fed to the template of the actor sheet
*/ */
async getData(): Promise<ActorSheet.Data<DS4Actor>> { async getData(): Promise<DS4ActorSheetData> {
const itemsByType = Object.fromEntries( const itemsByType = Object.fromEntries(
Object.entries(this.actor.itemTypes).map(([itemType, items]) => { Object.entries(this.actor.itemTypes).map(([itemType, items]) => {
return [itemType, items.map((item) => item.data).sort((a, b) => (a.sort || 0) - (b.sort || 0))]; return [itemType, items.map((item) => item.data).sort((a, b) => (a.sort || 0) - (b.sort || 0))];
}), }),
); );
const data = { const data = {
...this._addTooltipsToData(await super.getData()), ...this.addTooltipsToData(await super.getData()),
// Add the localization config to the data: // Add the localization config to the data:
config: DS4, config: DS4,
// Add the items explicitly sorted by type to the data: // Add the items explicitly sorted by type to the data:
@ -74,21 +69,23 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
return data; return data;
} }
protected _addTooltipsToData(data: ActorSheet.Data<DS4Actor>): ActorSheet.Data<DS4Actor> { protected addTooltipsToData(data: ActorSheet.Data): ActorSheet.Data {
const valueGroups = [data.data.attributes, data.data.traits, data.data.combatValues]; const valueGroups = [data.data.data.attributes, data.data.data.traits, data.data.data.combatValues];
valueGroups.forEach((valueGroup) => { valueGroups.forEach((valueGroup) => {
Object.values(valueGroup).forEach((attribute: ModifiableDataBaseTotal<number> & { tooltip?: string }) => { Object.values(valueGroup).forEach((attribute: ModifiableDataBaseTotal<number> & { tooltip?: string }) => {
attribute.tooltip = this._getTooltipForValue(attribute); attribute.tooltip = this.getTooltipForValue(attribute);
}); });
}); });
return data; return data;
} }
protected _getTooltipForValue(value: ModifiableDataBaseTotal<number>): string { protected getTooltipForValue(value: ModifiableDataBaseTotal<number>): string {
return `${value.base} (${game.i18n.localize("DS4.TooltipBaseValue")}) + ${value.mod} (${game.i18n.localize( return `${value.base} (${getGame().i18n.localize("DS4.TooltipBaseValue")}) + ${
"DS4.TooltipModifier", value.mod
)}) ${game.i18n.localize("DS4.TooltipEffects")} ${value.total}`; } (${getGame().i18n.localize("DS4.TooltipModifier")}) ${getGame().i18n.localize("DS4.TooltipEffects")} ${
value.total
}`;
} }
/** @override */ /** @override */
@ -99,18 +96,18 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
if (!this.options.editable) return; if (!this.options.editable) return;
// Add Inventory Item // 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 // Update Inventory Item
html.find(".item-edit").on("click", (ev) => { html.find(".item-edit").on("click", (ev) => {
const li = $(ev.currentTarget).parents(".item"); const li = $(ev.currentTarget).parents(".item");
const id = li.data("itemId"); const id = li.data("itemId");
const item = this.actor.getOwnedItem(id); const item = this.actor.items.get(id);
if (!item) { 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) { if (!item.sheet) {
throw new Error(game.i18n.localize("DS4.ErrorUnexpectedError")); throw new Error(getGame().i18n.localize("DS4.ErrorUnexpectedError"));
} }
item.sheet.render(true); item.sheet.render(true);
}); });
@ -118,38 +115,36 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
// Delete Inventory Item // Delete Inventory Item
html.find(".item-delete").on("click", (ev) => { html.find(".item-delete").on("click", (ev) => {
const li = $(ev.currentTarget).parents(".item"); 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)); 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 * @param event - The originating click event
*/ */
protected _onItemCreate(event: JQuery.ClickEvent): Promise<DS4ItemData> { protected onItemCreate(event: JQuery.ClickEvent): void {
event.preventDefault(); event.preventDefault();
const header = event.currentTarget; const header = event.currentTarget;
// Get the type of item to create.
// Grab any data associated with this control. const { type, ...data } = foundry.utils.deepClone(header.dataset);
const { type, ...data } = duplicate(header.dataset);
// Initialize a default name.
const name = `New ${type.capitalize()}`; const name = `New ${type.capitalize()}`;
// Prepare the item object.
const itemData = { const itemData = {
name: name, name: name,
type: type, type: type,
data: data, data: data,
}; };
// Finally, create the item! DS4Item.create(itemData, { parent: this.actor });
return this.actor.createOwnedItem(itemData);
} }
/** /**
@ -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'. * Assumes the item property is given as the value of the HTML element property 'data-property'.
* @param ev - The originating change event * @param ev - The originating change event
*/ */
protected _onItemChange(ev: JQuery.ChangeEvent): void { protected onItemChange(ev: JQuery.ChangeEvent): void {
ev.preventDefault(); ev.preventDefault();
const el: HTMLFormElement = $(ev.currentTarget).get(0); const el: HTMLFormElement = $(ev.currentTarget).get(0);
const id = $(ev.currentTarget).parents(".item").data("itemId"); const id = $(ev.currentTarget).parents(".item").data("itemId");
const item = duplicate(this.actor.getOwnedItem(id)); 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"); const property: string | undefined = $(ev.currentTarget).data("property");
// Early return: // Early return:
@ -175,8 +174,8 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
// Set new value // Set new value
const newValue = this.getValue(el); const newValue = this.getValue(el);
setProperty(item, property, newValue); foundry.utils.setProperty(itemObject, property, newValue);
this.actor.updateOwnedItem(item); item.update(itemObject);
} }
/** /**
@ -238,10 +237,13 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
* Handle clickable item rolls. * Handle clickable item rolls.
* @param event - The originating click event * @param event - The originating click event
*/ */
protected _onRollItem(event: JQuery.ClickEvent): void { protected onRollItem(event: JQuery.ClickEvent): void {
event.preventDefault(); event.preventDefault();
const id = $(event.currentTarget).parents(".item").data("itemId"); 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 })); 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. * Handle clickable check rolls.
* @param event - The originating click event * @param event - The originating click event
*/ */
protected _onRollCheck(event: JQuery.ClickEvent): void { protected onRollCheck(event: JQuery.ClickEvent): void {
event.preventDefault(); event.preventDefault();
const check = event.currentTarget.dataset["check"]; const check = event.currentTarget.dataset["check"];
this.actor.rollCheck(check).catch((e) => notifications.error(e, { log: true })); 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; const check = target.dataset.check;
if (!check) return super._onDragStart(event); 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 = { const dragData = {
actorId: this.actor.id, actorId: this.actor.id,
@ -277,18 +279,11 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
} }
/** @override */ /** @override */
protected async _onDropItem( protected async _onDropItem(event: DragEvent, data: ActorSheet.DropData.Item): Promise<unknown> {
event: DragEvent,
data: { type: "Item" } & (
| { data: DeepPartial<ActorSheet.OwnedItemData<DS4Actor>> }
| { pack: string }
| { id: string }
),
): Promise<boolean | undefined | ActorSheet.OwnedItemData<DS4Actor>> {
const item = await DS4Item.fromDropData(data); const item = await DS4Item.fromDropData(data);
if (item && !this.actor.canOwnItemType(item.data.type)) { if (item && !this.actor.canOwnItemType(item.data.type)) {
notifications.warn( notifications.warn(
game.i18n.format("DS4.WarningActorCannotOwnItem", { getGame().i18n.format("DS4.WarningActorCannotOwnItem", {
actorName: this.actor.name, actorName: this.actor.name,
actorType: this.actor.data.type, actorType: this.actor.data.type,
itemName: item.name, itemName: item.name,
@ -300,3 +295,9 @@ export class DS4ActorSheet extends ActorSheet<ActorSheet.Data<DS4Actor>> {
return super._onDropItem(event, data); return super._onDropItem(event, data);
} }
} }
interface DS4ActorSheetData extends ActorSheet.Data<ActorSheet.Options> {
config: typeof DS4;
itemsByType: Record<string, foundry.data.ItemData[]>;
settings: DS4Settings;
}

View file

@ -9,10 +9,8 @@ import { DS4ActorSheet } from "./actor-sheet";
*/ */
export class DS4CharacterActorSheet extends DS4ActorSheet { export class DS4CharacterActorSheet extends DS4ActorSheet {
/** @override */ /** @override */
static get defaultOptions(): BaseEntitySheet.Options { static get defaultOptions(): ActorSheet.Options {
const superDefaultOptions = super.defaultOptions; return foundry.utils.mergeObject(super.defaultOptions, {
return mergeObject(superDefaultOptions, {
...superDefaultOptions,
classes: ["ds4", "sheet", "actor", "character"], classes: ["ds4", "sheet", "actor", "character"],
}); });
} }

View file

@ -9,10 +9,8 @@ import { DS4ActorSheet } from "./actor-sheet";
*/ */
export class DS4CreatureActorSheet extends DS4ActorSheet { export class DS4CreatureActorSheet extends DS4ActorSheet {
/** @override */ /** @override */
static get defaultOptions(): BaseEntitySheet.Options { static get defaultOptions(): ActorSheet.Options {
const superDefaultOptions = super.defaultOptions; return foundry.utils.mergeObject(super.defaultOptions, {
return mergeObject(superDefaultOptions, {
...superDefaultOptions,
classes: ["ds4", "sheet", "actor", "creature"], classes: ["ds4", "sheet", "actor", "creature"],
}); });
} }

View file

@ -15,8 +15,6 @@ export interface HasTotal<T> {
total: T; total: T;
} }
export interface ModifiableDataTotal<T> extends ModifiableData<T>, HasTotal<T> {}
export interface ModifiableDataBaseTotal<T> extends ModifiableDataBase<T>, HasTotal<T> {} export interface ModifiableDataBaseTotal<T> extends ModifiableDataBase<T>, HasTotal<T> {}
export interface ResourceData<T> extends ModifiableData<T> { export interface ResourceData<T> extends ModifiableData<T> {
@ -27,6 +25,10 @@ export interface HasMax<T> {
max: 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 ResourceDataBaseTotalMax<T> extends ResourceData<T>, HasBase<T>, HasTotal<T>, HasMax<T> {}
export interface UsableResource<T> { export interface UsableResource<T> {

View file

@ -162,6 +162,20 @@ export const DS4 = {
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting", 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 * Define the base info of a character
*/ */

41
src/module/fonts.ts Normal file
View 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}`)));
}

View file

@ -2,10 +2,20 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
declare namespace ClientSettings { declare global {
interface Values { namespace ClientSettings {
"ds4.systemMigrationVersion": number; interface Values {
"ds4.useSlayingDiceForAutomatedChecks": boolean; "ds4.systemMigrationVersion": number;
"ds4.showSlayerPoints": boolean; "ds4.useSlayingDiceForAutomatedChecks": boolean;
"ds4.showSlayerPoints": boolean;
}
}
namespace PoolTerm {
interface Modifiers {
x: (this: PoolTerm, modifier: string) => void;
}
} }
} }
export {};

View file

@ -6,29 +6,33 @@
export default async function registerHandlebarsPartials(): Promise<void> { export default async function registerHandlebarsPartials(): Promise<void> {
const templatePaths = [ 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/check.hbs",
"systems/ds4/templates/sheets/actor/components/checks.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-value.hbs",
"systems/ds4/templates/sheets/actor/components/combat-values.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-value.hbs",
"systems/ds4/templates/sheets/actor/components/core-values.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/currency.hbs",
"systems/ds4/templates/sheets/actor/components/item-list-entry.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/item-list-header.hbs",
"systems/ds4/templates/sheets/actor/components/items-overview.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-add-button.hbs",
"systems/ds4/templates/sheets/actor/components/overview-control-buttons.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/rollable-image.hbs",
"systems/ds4/templates/sheets/actor/components/talent-rank-equation.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/biography.hbs",
"systems/ds4/templates/sheets/actor/tabs/character-inventory.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/creature-inventory.hbs",
"systems/ds4/templates/sheets/actor/tabs/description.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/special-creature-abilities.hbs",
"systems/ds4/templates/sheets/actor/tabs/spells.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/actor/tabs/values.hbs",
"systems/ds4/templates/sheets/item/components/body.hbs", "systems/ds4/templates/sheets/item/components/body.hbs",
"systems/ds4/templates/sheets/item/components/sheet-header.hbs", "systems/ds4/templates/sheets/item/components/sheet-header.hbs",

View file

@ -4,7 +4,14 @@
export function getCanvas(): Canvas { export function getCanvas(): Canvas {
if (!(canvas instanceof Canvas) || !canvas.ready) { if (!(canvas instanceof Canvas) || !canvas.ready) {
throw new Error(game.i18n.localize("DS4.ErrorCanvasIsNotInitialized")); throw new Error(getGame().i18n.localize("DS4.ErrorCanvasIsNotInitialized"));
} }
return canvas; 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;
}

View file

@ -2,25 +2,27 @@
// //
// SPDX-License-Identifier: MIT // 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 { DS4Item } from "../item/item";
import { DS4ItemData } from "../item/item-data";
import { createRollCheckMacro } from "../macros/roll-check"; import { createRollCheckMacro } from "../macros/roll-check";
import { createRollItemMacro } from "../macros/roll-item"; import { createRollItemMacro } from "../macros/roll-item";
import notifications from "../ui/notifications"; import notifications from "../ui/notifications";
export default function registerForHotbarDropHook(): void { 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) { switch (data.type) {
case "Item": { case "Item": {
if (!("data" in data)) { if (!isItemDropData(data) || !("data" in data)) {
return notifications.warn(game.i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems")); return notifications.warn(
getGame().i18n.localize("DS4.WarningMacrosCanOnlyBeCreatedForOwnedItems"),
);
} }
const itemData = data.data as DS4ItemData; const itemData = data.data;
if (!DS4Item.rollableItemTypes.includes(itemData.type)) { if (!DS4Item.rollableItemTypes.includes(itemData.type)) {
return notifications.warn( return notifications.warn(
game.i18n.format("DS4.WarningItemIsNotRollable", { getGame().i18n.format("DS4.WarningItemIsNotRollable", {
name: itemData.name, name: itemData.name,
id: itemData._id, id: itemData._id,
type: itemData.type, type: itemData.type,
@ -31,10 +33,16 @@ export default function registerForHotbarDropHook(): void {
} }
case "Check": { case "Check": {
if (!("data" in data) || typeof data.data !== "string" || !isCheck(data.data)) { 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); 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";
}

View file

@ -4,12 +4,15 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4ActiveEffect } from "../active-effect";
import { DS4Actor } from "../actor/actor"; import { DS4Actor } from "../actor/actor";
import { DS4CharacterActorSheet } from "../actor/sheets/character-sheet"; import { DS4CharacterActorSheet } from "../actor/sheets/character-sheet";
import { DS4CreatureActorSheet } from "../actor/sheets/creature-sheet"; import { DS4CreatureActorSheet } from "../actor/sheets/creature-sheet";
import { DS4 } from "../config"; import { DS4 } from "../config";
import { preloadFonts as preloadFonts } from "../fonts";
import registerHandlebarsHelpers from "../handlebars/handlebars-helpers"; import registerHandlebarsHelpers from "../handlebars/handlebars-helpers";
import registerHandlebarsPartials from "../handlebars/handlebars-partials"; import registerHandlebarsPartials from "../handlebars/handlebars-partials";
import { getGame } from "../helpers";
import { DS4Item } from "../item/item"; import { DS4Item } from "../item/item";
import { DS4ItemSheet } from "../item/item-sheet"; import { DS4ItemSheet } from "../item/item-sheet";
import logger from "../logger"; import logger from "../logger";
@ -28,7 +31,7 @@ export default function registerForInitHook(): void {
async function init() { async function init() {
logger.info(`Initializing the DS4 Game System\n${DS4.ASCII}`); logger.info(`Initializing the DS4 Game System\n${DS4.ASCII}`);
game.ds4 = { getGame().ds4 = {
DS4Actor, DS4Actor,
DS4Item, DS4Item,
DS4, DS4,
@ -39,8 +42,9 @@ async function init() {
CONFIG.DS4 = DS4; CONFIG.DS4 = DS4;
CONFIG.Actor.entityClass = DS4Actor; CONFIG.Actor.documentClass = DS4Actor;
CONFIG.Item.entityClass = DS4Item; CONFIG.Item.documentClass = DS4Item;
CONFIG.ActiveEffect.documentClass = DS4ActiveEffect;
CONFIG.Actor.typeLabels = DS4.i18n.actorTypes; CONFIG.Actor.typeLabels = DS4.i18n.actorTypes;
CONFIG.Item.typeLabels = DS4.i18n.itemTypes; CONFIG.Item.typeLabels = DS4.i18n.itemTypes;
@ -60,6 +64,24 @@ async function init() {
Items.unregisterSheet("core", ItemSheet); Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("ds4", DS4ItemSheet, { makeDefault: true }); Items.registerSheet("ds4", DS4ItemSheet, { makeDefault: true });
preloadFonts();
await registerHandlebarsPartials(); await registerHandlebarsPartials();
registerHandlebarsHelpers(); 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;
}
}

View file

@ -6,6 +6,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4 } from "../config"; import { DS4 } from "../config";
import { getGame } from "../helpers";
export default function registerForSetupHooks(): void { export default function registerForSetupHooks(): void {
Hooks.once("setup", () => { Hooks.once("setup", () => {
@ -21,7 +22,7 @@ function localizeAndSortConfigObjects() {
const localizeObject = <T extends { [s: string]: string }>(obj: T, sort = true): T => { const localizeObject = <T extends { [s: string]: string }>(obj: T, sort = true): T => {
const localized = Object.entries(obj).map(([key, value]) => { 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])); if (sort) localized.sort((a, b) => a[1].localeCompare(b[1]));
return Object.fromEntries(localized); return Object.fromEntries(localized);

View 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 {}

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

View file

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

View file

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

View file

@ -5,19 +5,17 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4 } from "../config"; import { DS4 } from "../config";
import { getGame } from "../helpers";
import notifications from "../ui/notifications"; import notifications from "../ui/notifications";
import { DS4Item } from "./item"; import { isDS4ItemDataTypePhysical } from "./item-data-source";
import { isDS4ItemDataTypePhysical } from "./item-data";
/** /**
* The Sheet class for DS4 Items * The Sheet class for DS4 Items
*/ */
export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> { export class DS4ItemSheet extends ItemSheet<ItemSheet.Options, DS4ItemSheetData> {
/** @override */ /** @override */
static get defaultOptions(): BaseEntitySheet.Options { static get defaultOptions(): ItemSheet.Options {
const superDefaultOptions = super.defaultOptions; return foundry.utils.mergeObject(super.defaultOptions, {
return mergeObject(superDefaultOptions, {
...superDefaultOptions,
width: 540, width: 540,
height: 400, height: 400,
classes: ["ds4", "sheet", "item"], classes: ["ds4", "sheet", "item"],
@ -33,7 +31,7 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
} }
/** @override */ /** @override */
async getData(): Promise<ItemSheet.Data<DS4Item>> { async getData(): Promise<DS4ItemSheetData> {
const data = { const data = {
...(await super.getData()), ...(await super.getData()),
config: DS4, config: DS4,
@ -45,11 +43,14 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
} }
/** @override */ /** @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 position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body"); if (position) {
const bodyHeight = position.height - 192; const sheetBody = this.element.find(".sheet-body");
sheetBody.css("height", bodyHeight); const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
}
return position; return position;
} }
@ -70,23 +71,25 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
event.preventDefault(); event.preventDefault();
if (this.item.isOwned) { 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 a = event.currentTarget;
const li = $(a).parents(".effect"); const li = $(a).parents(".effect");
switch (a.dataset["action"]) { switch (a.dataset["action"]) {
case "create": case "create":
return this._createActiveEffect(); return this.createActiveEffect();
case "edit": case "edit":
const id = li.data("effectId"); const id = li.data("effectId");
const effect = this.item.effects.get(id); const effect = this.item.effects.get(id);
if (!effect) { 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); return effect.sheet.render(true);
case "delete": { 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. * Create a new ActiveEffect for the item using default data.
*/ */
protected async _createActiveEffect(): Promise<ActiveEffect.Data> { protected async createActiveEffect(): Promise<ActiveEffect | undefined> {
const label = `New Effect`;
const createData = { const createData = {
label: label, label: "New Effect",
changes: [], icon: "icons/svg/aura.svg",
duration: {},
transfer: true,
}; };
const effect = ActiveEffect.create(createData, this.item); return ActiveEffect.create(createData, { parent: this.item });
return effect.create({});
} }
} }
interface DS4ItemSheetData extends ItemSheet.Data<ItemSheet.Options> {
config: typeof DS4;
isOwned: boolean;
actor: DS4ItemSheet["item"]["actor"];
isPhysical: boolean;
}

View file

@ -3,26 +3,29 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4Actor } from "../actor/actor";
import { DS4 } from "../config"; import { DS4 } from "../config";
import { getGame } from "../helpers";
import { createCheckRoll } from "../rolls/check-factory"; import { createCheckRoll } from "../rolls/check-factory";
import notifications from "../ui/notifications"; import notifications from "../ui/notifications";
import { AttackType, DS4ItemData, ItemType } from "./item-data"; import { AttackType, ItemType } from "./item-data-source";
import { DS4ItemPreparedData } from "./item-prepared-data";
import { calculateSpellPrice } from "./type-specific-helpers/spell"; import { calculateSpellPrice } from "./type-specific-helpers/spell";
declare global {
interface DocumentClassConfig {
Item: typeof DS4Item;
}
}
/** /**
* The Item class for DS4 * The Item class for DS4
*/ */
export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> { export class DS4Item extends Item {
/** /** @override */
* @override
*/
prepareData(): void { prepareData(): void {
super.prepareData(); super.prepareData();
this.prepareDerivedData();
} }
/** @override */
prepareDerivedData(): void { prepareDerivedData(): void {
if (this.data.type === "talent") { if (this.data.type === "talent") {
const data = this.data.data; 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. * Roll a check for an action with this item.
*/ */
async roll(): Promise<void> { 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) { switch (this.data.type) {
case "weapon": case "weapon":
return this.rollWeapon(); return this.rollWeapon();
case "spell": case "spell":
return this.rollSpell(); return this.rollSpell();
default: 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")) { if (!(this.data.type === "weapon")) {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorWrongItemType", { getGame().i18n.format("DS4.ErrorWrongItemType", {
actualType: this.data.type, actualType: this.data.type,
expectedType: "weapon", expectedType: "weapon",
id: this.id, id: this.id,
@ -91,7 +92,7 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
if (!this.data.data.equipped) { if (!this.data.data.equipped) {
return notifications.warn( return notifications.warn(
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", { getGame().i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
name: this.name, name: this.name,
id: this.id, id: this.id,
type: this.data.type, 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 if (!this.actor) {
const ownerDataData = actor.data.data; 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 weaponBonus = this.data.data.weaponBonus;
const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType); const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType);
const checkTargetNumber = ownerDataData.combatValues[combatValue].total + weaponBonus; const checkTargetNumber = ownerDataData.combatValues[combatValue].total + weaponBonus;
await createCheckRoll(checkTargetNumber, { 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, maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult, 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> { protected async rollSpell(): Promise<void> {
if (!(this.data.type === "spell")) { if (!(this.data.type === "spell")) {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorWrongItemType", { getGame().i18n.format("DS4.ErrorWrongItemType", {
actualType: this.data.type, actualType: this.data.type,
expectedType: "spell", expectedType: "spell",
id: this.id, id: this.id,
@ -127,7 +131,7 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
if (!this.data.data.equipped) { if (!this.data.data.equipped) {
return notifications.warn( return notifications.warn(
game.i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", { getGame().i18n.format("DS4.WarningItemMustBeEquippedToBeRolled", {
name: this.name, name: this.name,
id: this.id, id: this.id,
type: this.data.type, 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 if (!this.actor) {
const ownerDataData = actor.data.data; 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; const spellBonus = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined;
if (spellBonus === undefined) { if (spellBonus === undefined) {
notifications.info( notifications.info(
game.i18n.format("DS4.InfoManuallyEnterSpellBonus", { getGame().i18n.format("DS4.InfoManuallyEnterSpellBonus", {
name: this.name, name: this.name,
spellBonus: this.data.data.bonus, spellBonus: this.data.data.bonus,
}), }),
@ -150,10 +157,10 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
const checkTargetNumber = ownerDataData.combatValues[spellType].total + (spellBonus ?? 0); const checkTargetNumber = ownerDataData.combatValues[spellType].total + (spellBonus ?? 0);
await createCheckRoll(checkTargetNumber, { 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, maximumCoupResult: ownerDataData.rolling.maximumCoupResult,
minimumFumbleResult: ownerDataData.rolling.minimumFumbleResult, 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 { melee, ranged } = { ...DS4.i18n.attackTypes };
const identifier = "attack-type-selection"; const identifier = "attack-type-selection";
return Dialog.prompt({ 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", { content: await renderTemplate("systems/ds4/templates/dialogs/simple-select-form.hbs", {
selects: [ selects: [
{ {
label: game.i18n.localize("DS4.AttackType"), label: getGame().i18n.localize("DS4.AttackType"),
identifier, identifier,
options: { melee, ranged }, options: { melee, ranged },
}, },
], ],
}), }),
label: game.i18n.localize("DS4.GenericOkButton"), label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (html) => { callback: (html) => {
const selectedAttackType = html.find(`#${identifier}`).val(); const selectedAttackType = html.find(`#${identifier}`).val();
if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") { if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorUnexpectedAttackType", { getGame().i18n.format("DS4.ErrorUnexpectedAttackType", {
actualType: selectedAttackType, actualType: selectedAttackType,
expectedTypes: "'melee', 'ranged'", expectedTypes: "'melee', 'ranged'",
}), }),
@ -190,11 +197,4 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
return `${attackType}Attack` as const; 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;
}
} }

View file

@ -3,9 +3,9 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { hoursPerDay, minutesPerHour, secondsPerMinute, secondsPerRound } from "../../common/time-helpers"; 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 spellPriceFactor = calculateSpellPriceFactor(data.cooldownDuration);
const baseSpellPrices = [ const baseSpellPrices = [
data.minimumLevels.healer !== null ? 10 + (data.minimumLevels.healer - 1) * 35 : null, data.minimumLevels.healer !== null ? 10 + (data.minimumLevels.healer - 1) * 35 : null,

View file

@ -8,24 +8,17 @@ const loggingSeparator = "|";
type LogLevel = "debug" | "info" | "warning" | "error"; type LogLevel = "debug" | "info" | "warning" | "error";
type LoggingFunction = (...data: unknown[]) => void; type LoggingFunction = (...data: unknown[]) => void;
class Logger { const getLoggingFunction = (type: LogLevel = "info"): LoggingFunction => {
readonly debug: LoggingFunction; const log = { debug: console.debug, info: console.info, warning: console.warn, error: console.error }[type];
readonly info: LoggingFunction; return (...data: unknown[]) => log(loggingContext, loggingSeparator, ...data);
readonly warn: LoggingFunction; };
readonly error: LoggingFunction;
constructor() { const logger = Object.freeze({
this.debug = this.getLoggingFunction("debug"); debug: getLoggingFunction("debug"),
this.info = this.getLoggingFunction("info"); info: getLoggingFunction("info"),
this.warn = this.getLoggingFunction("warning"); warn: getLoggingFunction("warning"),
this.error = this.getLoggingFunction("error"); 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; export default logger;

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4Actor } from "../actor/actor"; 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 * Gets the currently active actor based on how {@link ChatMessage} determines
@ -13,13 +13,13 @@ import { getCanvas } from "../helpers";
export function getActiveActor(): DS4Actor | undefined { export function getActiveActor(): DS4Actor | undefined {
const speaker = ChatMessage.getSpeaker(); 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) { 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) { if (speakerActor) {
return speakerActor as DS4Actor; return speakerActor;
} }
} }

View file

@ -2,8 +2,9 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { Check } from "../actor/actor-prepared-data"; import { Check } from "../actor/actor-data-properties";
import { DS4 } from "../config"; import { DS4 } from "../config";
import { getGame } from "../helpers";
import notifications from "../ui/notifications"; import notifications from "../ui/notifications";
import { getActiveActor } from "./helpers"; import { getActiveActor } from "./helpers";
@ -15,13 +16,13 @@ import { getActiveActor } from "./helpers";
*/ */
export async function createRollCheckMacro(check: Check, slot: string): Promise<void> { export async function createRollCheckMacro(check: Check, slot: string): Promise<void> {
const macro = await getOrCreateRollCheckMacro(check); 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 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, (m) => m.name === DS4.i18n.checks[check] && m.data.command === command,
); );
if (existingMacro) { if (existingMacro) {
@ -36,7 +37,7 @@ async function getOrCreateRollCheckMacro(check: Check): Promise<Macro | null> {
img: DS4.icons.checks[check], img: DS4.icons.checks[check],
flags: { "ds4.checkMacro": true }, 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> { export async function rollCheck(check: Check): Promise<void> {
const actor = getActiveActor(); const actor = getActiveActor();
if (!actor) { 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 })); return actor.rollCheck(check).catch((e) => notifications.error(e, { log: true }));

View file

@ -2,15 +2,17 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "../helpers";
import notifications from "../ui/notifications"; import notifications from "../ui/notifications";
import { getActiveActor } from "./helpers"; import { getActiveActor } from "./helpers";
/** /**
* Executes the roll generic check macro. * Executes the roll generic check macro.
*/ */
export async function rollGenericCheck(): Promise<void> { export async function rollGenericCheck(): Promise<void> {
const actor = getActiveActor(); const actor = getActiveActor();
if (!actor) { 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 })); return actor.rollGenericCheck().catch((e) => notifications.error(e, { log: true }));

View file

@ -2,7 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4ItemData } from "../item/item-data"; import { getGame } from "../helpers";
import notifications from "../ui/notifications"; import notifications from "../ui/notifications";
import { getActiveActor } from "./helpers"; import { getActiveActor } from "./helpers";
@ -12,15 +12,15 @@ import { getActiveActor } from "./helpers";
* @param itemData - The item data * @param itemData - The item data
* @param slot - The hotbar slot to use * @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); 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 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) { if (existingMacro) {
return existingMacro; return existingMacro;
} }
@ -33,7 +33,7 @@ async function getOrCreateRollItemMacro(itemData: DS4ItemData): Promise<Macro |
img: itemData.img, img: itemData.img,
flags: { "ds4.itemMacro": true }, 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> { export async function rollItem(itemId: string): Promise<void> {
const actor = getActiveActor(); const actor = getActiveActor();
if (!actor) { if (!actor) {
return notifications.warn(game.i18n.localize("DS4.WarningMustControlActorToUseRollItemMacro")); return notifications.warn(getGame().i18n.localize("DS4.WarningMustControlActorToUseRollItemMacro"));
} }
const item = actor.items?.get(itemId); const item = actor.items?.get(itemId);
if (!item) { if (!item) {
return notifications.warn( return notifications.warn(
game.i18n.format("DS4.WarningControlledActorDoesNotHaveItem", { getGame().i18n.format("DS4.WarningControlledActorDoesNotHaveItem", {
actorName: actor.name, actorName: actor.name,
actorId: actor.id, actorId: actor.id,
itemId, itemId,

View file

@ -2,25 +2,25 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "./helpers";
import logger from "./logger"; import logger from "./logger";
import { migrate as migrate001 } from "./migrations/001"; import { migrate as migrate001 } from "./migrations/001";
import { migrate as migrate002 } from "./migrations/002"; import { migrate as migrate002 } from "./migrations/002";
import { migrate as migrate003 } from "./migrations/003"; import { migrate as migrate003 } from "./migrations/003";
import { migrate as migrate004 } from "./migrations/004"; import { migrate as migrate004 } from "./migrations/004";
import notifications from "./ui/notifications"; import notifications from "./ui/notifications";
async function migrate(): Promise<void> { async function migrate(): Promise<void> {
if (!game.user?.isGM) { if (!getGame().user?.isGM) {
return; return;
} }
const oldMigrationVersion = game.settings.get("ds4", "systemMigrationVersion"); const oldMigrationVersion = getGame().settings.get("ds4", "systemMigrationVersion");
const targetMigrationVersion = migrations.length; const targetMigrationVersion = migrations.length;
if (isFirstWorldStart(oldMigrationVersion)) { if (isFirstWorldStart(oldMigrationVersion)) {
game.settings.set("ds4", "systemMigrationVersion", targetMigrationVersion); getGame().settings.set("ds4", "systemMigrationVersion", targetMigrationVersion);
return; return;
} }
@ -28,7 +28,7 @@ async function migrate(): Promise<void> {
} }
async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> { async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion: number): Promise<void> {
if (!game.user?.isGM) { if (!getGame().user?.isGM) {
return; return;
} }
@ -36,7 +36,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
if (migrationsToExecute.length > 0) { if (migrationsToExecute.length > 0) {
notifications.info( notifications.info(
game.i18n.format("DS4.InfoSystemUpdateStart", { getGame().i18n.format("DS4.InfoSystemUpdateStart", {
currentVersion: oldMigrationVersion, currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion, targetVersion: targetMigrationVersion,
}), }),
@ -48,10 +48,10 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
logger.info("executing migration script ", currentMigrationVersion); logger.info("executing migration script ", currentMigrationVersion);
try { try {
await migration(); await migration();
game.settings.set("ds4", "systemMigrationVersion", currentMigrationVersion); getGame().settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
} catch (err) { } catch (err) {
notifications.error( notifications.error(
game.i18n.format("DS4.ErrorDuringMigration", { getGame().i18n.format("DS4.ErrorDuringMigration", {
currentVersion: oldMigrationVersion, currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion, targetVersion: targetMigrationVersion,
migrationVersion: currentMigrationVersion, migrationVersion: currentMigrationVersion,
@ -65,7 +65,7 @@ async function migrateFromTo(oldMigrationVersion: number, targetMigrationVersion
} }
notifications.info( notifications.info(
game.i18n.format("DS4.InfoSystemUpdateCompleted", { getGame().i18n.format("DS4.InfoSystemUpdateCompleted", {
currentVersion: oldMigrationVersion, currentVersion: oldMigrationVersion,
targetVersion: targetMigrationVersion, targetVersion: targetMigrationVersion,
}), }),

View file

@ -2,14 +2,18 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import logger from "../logger"; import {
getCompendiumMigrator,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateScenes,
} from "./migrationHelpers";
export async function migrate(): Promise<void> { export async function migrate(): Promise<void> {
for (const a of game.actors?.entities ?? []) { await migrateActors(getActorUpdateData);
const updateData = getActorUpdateData(); await migrateScenes(getSceneUpdateData);
logger.info(`Migrating actor ${a.name}`); await migrateCompendiums(migrateCompendium);
await a.update(updateData, { enforceTypes: false });
}
} }
function getActorUpdateData(): Record<string, unknown> { function getActorUpdateData(): Record<string, unknown> {
@ -32,3 +36,6 @@ function getActorUpdateData(): Record<string, unknown> {
}; };
return updateData; return updateData;
} }
const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
const migrateCompendium = getCompendiumMigrator({ getActorUpdateData, getSceneUpdateData });

View file

@ -2,142 +2,33 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import logger from "../logger"; import {
getActorUpdateDataGetter,
getCompendiumMigrator,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateItems,
migrateScenes,
} from "./migrationHelpers";
export async function migrate(): Promise<void> { export async function migrate(): Promise<void> {
await migrateItems(); await migrateItems(getItemUpdateData);
await migrateActors(); await migrateActors(getActorUpdateData);
await migrateScenes(); await migrateScenes(getSceneUpdateData);
await migrateCompendiums(); await migrateCompendiums(migrateCompendium);
} }
async function migrateItems() { function getItemUpdateData(
for (const item of game.items?.entities ?? []) { itemData: Partial<foundry.data.ItemData["_source"]>,
try { ): DeepPartial<foundry.data.ItemData["_source"]> | undefined {
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>) {
if (!["equipment", "trinket"].includes(itemData.type ?? "")) return 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() { const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
for (const actor of game.actors?.entities ?? []) { const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
try { const migrateCompendium = getCompendiumMigrator(
const updateData = getActorUpdateData(actor._data); { getItemUpdateData, getActorUpdateData, getSceneUpdateData },
if (updateData) { { migrateToTemplateEarly: false },
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 });
}

View file

@ -2,31 +2,24 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import logger from "../logger"; import {
getActorUpdateDataGetter,
getCompendiumMigrator,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateItems,
migrateScenes,
} from "./migrationHelpers";
export async function migrate(): Promise<void> { export async function migrate(): Promise<void> {
await migrateItems(); await migrateItems(getItemUpdateData);
await migrateActors(); await migrateActors(getActorUpdateData);
await migrateScenes(); await migrateScenes(getSceneUpdateData);
await migrateCompendiums(); await migrateCompendiums(migrateCompendium);
} }
async function migrateItems() { function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
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>) {
if (!["loot"].includes(itemData.type ?? "")) return undefined; if (!["loot"].includes(itemData.type ?? "")) return undefined;
return { return {
data: { data: {
@ -35,113 +28,9 @@ function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
}; };
} }
async function migrateActors() { const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
for (const actor of game.actors?.entities ?? []) { const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
try { const migrateCompendium = getCompendiumMigrator(
const updateData = getActorUpdateData(actor._data); { getItemUpdateData, getActorUpdateData },
if (updateData) { { migrateToTemplateEarly: false },
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 });
}

View file

@ -2,157 +2,39 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { DS4SpellDataData } from "../item/item-data"; import {
import logger from "../logger"; getActorUpdateDataGetter,
getCompendiumMigrator,
getSceneUpdateDataGetter,
migrateActors,
migrateCompendiums,
migrateItems,
migrateScenes,
} from "./migrationHelpers";
export async function migrate(): Promise<void> { export async function migrate(): Promise<void> {
await migrateItems(); await migrateItems(getItemUpdateData);
await migrateActors(); await migrateActors(getActorUpdateData);
await migrateScenes(); await migrateScenes(getSceneUpdateData);
await migrateCompendiums(); await migrateCompendiums(migrateCompendium);
} }
async function migrateItems() { function getItemUpdateData(itemData: Partial<foundry.data.ItemData["_source"]>) {
for (const item of game.items?.entities ?? []) { if (itemData.type !== "spell") return;
try { const cooldownDurationUnit: string | undefined = itemData.data?.cooldownDuration.unit;
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>) {
if (!["spell"].includes(itemData.type ?? "")) return undefined;
const updateData: Record<string, unknown> = { const updateData: Record<string, unknown> = {
"-=data.scrollPrice": null, data: {
"data.minimumLevels": { healer: null, wizard: null, sorcerer: null }, "-=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; return updateData;
} }
async function migrateActors() { const getActorUpdateData = getActorUpdateDataGetter(getItemUpdateData);
for (const actor of game.actors?.entities ?? []) { const getSceneUpdateData = getSceneUpdateDataGetter(getActorUpdateData);
try { const migrateCompendium = getCompendiumMigrator({ getItemUpdateData, getActorUpdateData, getSceneUpdateData });
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 });
}

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

View file

@ -3,6 +3,8 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "../helpers";
export default function evaluateCheck( export default function evaluateCheck(
dice: number[], dice: number[],
checkTargetNumber: number, checkTargetNumber: number,
@ -39,7 +41,7 @@ function assignSubChecksToDice(
const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber); const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);
if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) { 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); const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);
@ -86,11 +88,7 @@ function shouldUseCoupForLastSubCheck(
); );
} }
interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result { interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {}
success?: boolean;
failure?: boolean;
count?: number;
}
function evaluateDiceWithSubChecks( function evaluateDiceWithSubChecks(
results: DieWithSubCheck[], results: DieWithSubCheck[],

View file

@ -3,6 +3,8 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "../helpers";
/** /**
* Provides default values for all arguments the `CheckFactory` expects. * Provides default values for all arguments the `CheckFactory` expects.
*/ */
@ -10,7 +12,7 @@ class DefaultCheckOptions implements DS4CheckFactoryOptions {
readonly maximumCoupResult = 1; readonly maximumCoupResult = 1;
readonly minimumFumbleResult = 20; readonly minimumFumbleResult = 20;
readonly useSlayingDice = false; readonly useSlayingDice = false;
readonly rollMode: Const.DiceRollMode = "roll"; readonly rollMode: foundry.CONST.DiceRollMode = "roll";
readonly flavor: undefined; readonly flavor: undefined;
mergeWith(other: Partial<DS4CheckFactoryOptions>): DS4CheckFactoryOptions { mergeWith(other: Partial<DS4CheckFactoryOptions>): DS4CheckFactoryOptions {
@ -37,15 +39,16 @@ class CheckFactory {
private options: DS4CheckFactoryOptions; private options: DS4CheckFactoryOptions;
async execute(): Promise<ChatMessage> { async execute(): Promise<ChatMessage | undefined> {
const innerFormula = ["ds", this.createCheckTargetNumberModifier(), this.createCoupFumbleModifier()].filterJoin( const innerFormula = ["ds", this.createCheckTargetNumberModifier(), this.createCoupFumbleModifier()].filterJoin(
"", "",
); );
const formula = this.options.useSlayingDice ? `{${innerFormula}}x` : innerFormula; const formula = this.options.useSlayingDice ? `{${innerFormula}}x` : innerFormula;
const roll = Roll.create(formula); const roll = Roll.create(formula);
const speaker = ChatMessage.getSpeaker();
return roll.toMessage( return roll.toMessage(
{ speaker: ChatMessage.getSpeaker(), flavor: this.options.flavor }, { speaker, flavor: this.options.flavor },
{ rollMode: this.options.rollMode, create: true }, { rollMode: this.options.rollMode, create: true },
); );
} }
@ -85,7 +88,7 @@ export async function createCheckRoll(
const newOptions: Partial<DS4CheckFactoryOptions> = { const newOptions: Partial<DS4CheckFactoryOptions> = {
maximumCoupResult: gmModifierData.maximumCoupResult ?? options.maximumCoupResult, maximumCoupResult: gmModifierData.maximumCoupResult ?? options.maximumCoupResult,
minimumFumbleResult: gmModifierData.minimumFumbleResult ?? options.minimumFumbleResult, minimumFumbleResult: gmModifierData.minimumFumbleResult ?? options.minimumFumbleResult,
useSlayingDice: game.settings.get("ds4", "useSlayingDiceForAutomatedChecks"), useSlayingDice: getGame().settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
rollMode: gmModifierData.rollMode ?? options.rollMode, rollMode: gmModifierData.rollMode ?? options.rollMode,
flavor: options.flavor, flavor: options.flavor,
}; };
@ -113,13 +116,13 @@ async function askGmModifier(
{ template, title }: { template?: string; title?: string } = {}, { template, title }: { template?: string; title?: string } = {},
): Promise<Partial<IntermediateGmModifierData>> { ): Promise<Partial<IntermediateGmModifierData>> {
const usedTemplate = template ?? "systems/ds4/templates/dialogs/roll-options.hbs"; 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 = { const templateData = {
title: usedTitle, title: usedTitle,
checkTargetNumber: checkTargetNumber, checkTargetNumber: checkTargetNumber,
maximumCoupResult: options.maximumCoupResult ?? defaultCheckOptions.maximumCoupResult, maximumCoupResult: options.maximumCoupResult ?? defaultCheckOptions.maximumCoupResult,
minimumFumbleResult: options.minimumFumbleResult ?? defaultCheckOptions.minimumFumbleResult, 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, rollModes: CONFIG.Dice.rollModes,
}; };
const renderedHtml = await renderTemplate(usedTemplate, templateData); const renderedHtml = await renderTemplate(usedTemplate, templateData);
@ -131,11 +134,11 @@ async function askGmModifier(
buttons: { buttons: {
ok: { ok: {
icon: '<i class="fas fa-check"></i>', icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("DS4.GenericOkButton"), label: getGame().i18n.localize("DS4.GenericOkButton"),
callback: (html) => { callback: (html) => {
if (!("jquery" in html)) { if (!("jquery" in html)) {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorUnexpectedHtmlType", { getGame().i18n.format("DS4.ErrorUnexpectedHtmlType", {
exType: "JQuery", exType: "JQuery",
realType: "HTMLElement", realType: "HTMLElement",
}), }),
@ -144,7 +147,7 @@ async function askGmModifier(
const innerForm = html[0].querySelector("form"); const innerForm = html[0].querySelector("form");
if (!innerForm) { if (!innerForm) {
throw new Error( throw new Error(
game.i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }), getGame().i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
); );
} }
resolve(innerForm); resolve(innerForm);
@ -153,7 +156,7 @@ async function askGmModifier(
}, },
cancel: { cancel: {
icon: '<i class="fas fa-times"></i>', icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("DS4.GenericCancelButton"), label: getGame().i18n.localize("DS4.GenericCancelButton"),
}, },
}, },
default: "ok", default: "ok",
@ -174,13 +177,11 @@ function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmM
const chosenMinimumFumbleResult = parseInt(formData["minimum-fumble-result"]?.value); const chosenMinimumFumbleResult = parseInt(formData["minimum-fumble-result"]?.value);
const chosenRollMode = formData["roll-mode"]?.value; const chosenRollMode = formData["roll-mode"]?.value;
const invalidNumbers = [NaN, Infinity, -Infinity];
return { return {
checkTargetNumber: invalidNumbers.includes(chosenCheckTargetNumber) ? undefined : chosenCheckTargetNumber, checkTargetNumber: Number.isSafeInteger(chosenCheckTargetNumber) ? chosenCheckTargetNumber : undefined,
gmModifier: invalidNumbers.includes(chosenGMModifier) ? undefined : chosenGMModifier, gmModifier: Number.isSafeInteger(chosenGMModifier) ? chosenGMModifier : undefined,
maximumCoupResult: invalidNumbers.includes(chosenMaximumCoupResult) ? undefined : chosenMaximumCoupResult, maximumCoupResult: Number.isSafeInteger(chosenMaximumCoupResult) ? chosenMaximumCoupResult : undefined,
minimumFumbleResult: invalidNumbers.includes(chosenMinimumFumbleResult) ? undefined : chosenMinimumFumbleResult, minimumFumbleResult: Number.isSafeInteger(chosenMinimumFumbleResult) ? chosenMinimumFumbleResult : undefined,
rollMode: Object.values(CONST.DICE_ROLL_MODES).includes(chosenRollMode) ? chosenRollMode : undefined, rollMode: Object.values(CONST.DICE_ROLL_MODES).includes(chosenRollMode) ? chosenRollMode : undefined,
}; };
} }
@ -190,7 +191,7 @@ function parseDialogFormData(formData: HTMLFormElement): Partial<IntermediateGmM
*/ */
interface GmModifierData { interface GmModifierData {
gmModifier: number; gmModifier: number;
rollMode: Const.DiceRollMode; rollMode: foundry.CONST.DiceRollMode;
} }
/** /**
@ -221,6 +222,6 @@ export interface DS4CheckFactoryOptions {
maximumCoupResult: number; maximumCoupResult: number;
minimumFumbleResult: number; minimumFumbleResult: number;
useSlayingDice: boolean; useSlayingDice: boolean;
rollMode: Const.DiceRollMode; rollMode: foundry.CONST.DiceRollMode;
flavor?: string; flavor?: string;
} }

View file

@ -3,6 +3,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "../helpers";
import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation"; 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` * - Roll a check with a racial ability that makes `5` a coup and default fumble: `/r dsv19c5`
*/ */
export class DS4Check extends DiceTerm { export class DS4Check extends DiceTerm {
constructor({ modifiers = [], options }: Partial<DiceTerm.TermData> = {}) { constructor({ modifiers = [], results = [], options }: Partial<DiceTerm.TermData> = {}) {
super({ super({
faces: 20, faces: 20,
modifiers: modifiers, results,
options: options, modifiers,
options,
}); });
// Parse and store check target number // Parse and store check target number
@ -49,7 +51,7 @@ export class DS4Check extends DiceTerm {
? parseInt(parseMinimumFumbleResult) ? parseInt(parseMinimumFumbleResult)
: DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT; : DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
if (this.minimumFumbleResult <= this.maximumCoupResult) 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 // Parse and store no fumble
@ -57,6 +59,10 @@ export class DS4Check extends DiceTerm {
if (noFumbleModifier) { if (noFumbleModifier) {
this.canFumble = false; this.canFumble = false;
} }
if (this.results.length > 0) {
this.evaluateResults();
}
} }
coup: boolean | null = null; coup: boolean | null = null;
@ -72,14 +78,14 @@ export class DS4Check extends DiceTerm {
} }
/** @override */ /** @override */
get total(): number | null { get total(): string | number | null | undefined {
if (this.fumble) return 0; if (this.fumble) return 0;
return super.total; return super.total;
} }
/** @override */ /** @override */
evaluate({ minimize = false, maximize = false } = {}): this { _evaluateSync({ minimize = false, maximize = false } = {}): this {
super.evaluate({ minimize, maximize }); super._evaluateSync({ minimize, maximize });
this.evaluateResults(); this.evaluateResults();
return this; return this;
} }
@ -102,17 +108,14 @@ export class DS4Check extends DiceTerm {
this.fumble = results[0].failure ?? false; this.fumble = results[0].failure ?? false;
} }
/** @override */ /**
static fromResults<T extends DS4Check>( * @override
this: ConstructorOf<T>, * @remarks "min" and "max" are filtered out because they are irrelevant for
options: Partial<DiceTerm.TermData>, * {@link DS4Check}s and only result in some dice rolls being highlighted
results: DiceTerm.Result[], * incorrectly.
): T { */
const term = new this(options); getResultCSS(result: DiceTerm.Result): (string | null)[] {
term.results = results; return super.getResultCSS(result).filter((cssClass) => cssClass !== "min" && cssClass !== "max");
term.evaluateResults();
term._evaluated = true;
return term;
} }
static readonly DEFAULT_CHECK_TARGET_NUMBER = 10; static readonly DEFAULT_CHECK_TARGET_NUMBER = 10;

View file

@ -2,6 +2,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "../helpers";
import { DS4Check } from "./check"; import { DS4Check } from "./check";
export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>> extends Roll<D> { 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. * template if the first dice term is a ds4 check.
* @override * @override
*/ */
async render(chatOptions: Roll.ChatOptions = {}): Promise<string> { async render(chatOptions: Parameters<Roll["render"]>[0] = {}): Promise<string> {
chatOptions = mergeObject( chatOptions = foundry.utils.mergeObject(
{ {
user: game.user?._id, user: getGame().user?.id,
flavor: null, flavor: null,
template: DS4Roll.CHAT_TEMPLATE, template: DS4Roll.CHAT_TEMPLATE,
blind: false, blind: false,
@ -25,7 +26,7 @@ export class DS4Roll<D extends Record<string, unknown> = Record<string, unknown>
const isPrivate = chatOptions.isPrivate; const isPrivate = chatOptions.isPrivate;
// Execute the roll, if needed // Execute the roll, if needed
if (!this._rolled) this.roll(); if (!this._evaluated) this.evaluate();
// Define chat data // Define chat data
const firstDiceTerm = this.dice[0]; const firstDiceTerm = this.dice[0];

View file

@ -3,14 +3,14 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "../helpers";
import { DS4Check } from "./check"; import { DS4Check } from "./check";
export default function registerSlayingDiceModifier(): void { export default function registerSlayingDiceModifier(): void {
DicePool.MODIFIERS.x = slay; PoolTerm.MODIFIERS.x = slay;
DicePool.POOL_REGEX = /^{([^}]+)}([A-z]([A-z0-9<=>]+)?)?$/;
} }
function slay(this: DicePool, modifier: string): void { function slay(this: PoolTerm, modifier: string): void {
const rgx = /[xX]/; const rgx = /[xX]/;
const match = modifier.match(rgx); const match = modifier.match(rgx);
if (!match || !this.rolls) return; if (!match || !this.rolls) return;
@ -21,11 +21,12 @@ function slay(this: DicePool, modifier: string): void {
checked++; checked++;
if (diceTerm instanceof DS4Check && diceTerm.coup) { if (diceTerm instanceof DS4Check && diceTerm.coup) {
const formula = `dsv${diceTerm.checkTargetNumber}c${diceTerm.maximumCoupResult}:${diceTerm.minimumFumbleResult}n`; 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.rolls.push(additionalRoll);
this.results.push({ result: additionalRoll.total ?? 0, active: true }); 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"));
} }
} }

View file

@ -2,11 +2,13 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { getGame } from "./helpers";
export function registerSystemSettings(): void { export function registerSystemSettings(): void {
/** /**
* Track the migrations version of the latest migration that has been applied * 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", name: "System Migration Version",
scope: "world", scope: "world",
config: false, config: false,
@ -14,7 +16,7 @@ export function registerSystemSettings(): void {
default: -1, default: -1,
}); });
game.settings.register("ds4", "useSlayingDiceForAutomatedChecks", { getGame().settings.register("ds4", "useSlayingDiceForAutomatedChecks", {
name: "DS4.SettingUseSlayingDiceForAutomatedChecksName", name: "DS4.SettingUseSlayingDiceForAutomatedChecksName",
hint: "DS4.SettingUseSlayingDiceForAutomatedChecksHint", hint: "DS4.SettingUseSlayingDiceForAutomatedChecksHint",
scope: "world", scope: "world",
@ -23,7 +25,7 @@ export function registerSystemSettings(): void {
default: false, default: false,
}); });
game.settings.register("ds4", "showSlayerPoints", { getGame().settings.register("ds4", "showSlayerPoints", {
name: "DS4.SettingShowSlayerPointsName", name: "DS4.SettingShowSlayerPointsName",
hint: "DS4.SettingShowSlayerPointsHint", hint: "DS4.SettingShowSlayerPointsHint",
scope: "world", scope: "world",
@ -33,7 +35,7 @@ export function registerSystemSettings(): void {
}); });
} }
interface DS4Settings { export interface DS4Settings {
systemMigrationVersion: number; systemMigrationVersion: number;
useSlayingDiceForAutomatedChecks: boolean; useSlayingDiceForAutomatedChecks: boolean;
showSlayerPoints: boolean; showSlayerPoints: boolean;
@ -41,8 +43,8 @@ interface DS4Settings {
export function getDS4Settings(): DS4Settings { export function getDS4Settings(): DS4Settings {
return { return {
systemMigrationVersion: game.settings.get("ds4", "systemMigrationVersion"), systemMigrationVersion: getGame().settings.get("ds4", "systemMigrationVersion"),
useSlayingDiceForAutomatedChecks: game.settings.get("ds4", "useSlayingDiceForAutomatedChecks"), useSlayingDiceForAutomatedChecks: getGame().settings.get("ds4", "useSlayingDiceForAutomatedChecks"),
showSlayerPoints: game.settings.get("ds4", "showSlayerPoints"), showSlayerPoints: getGame().settings.get("ds4", "showSlayerPoints"),
}; };
} }

View file

@ -12,18 +12,18 @@
{"_id":"0vIgZkHBeEPut73w","name":"Elfenbogen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"0vIgZkHBeEPut73w","name":"Elfenbogen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"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}]} {"_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&auml;nk heilt W20 Lebenskraft.</p>","quantity":1,"price":10,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"19bmt5UJrT3T36wE"} {"_id":"19bmt5UJrT3T36wE","name":"Heiltrank","type":"loot","img":"icons/consumables/potions/bottle-round-corked-red.webp","data":{"description":"<p>Dieses oftmals rote Getr&auml;nk heilt W20 Lebenskraft.</p>","quantity":1,"price":10,"availability":"unset","storageLocation":"-"},"effects":[],"folder":null,"sort":0,"permission":{"default":0,"onhXSUZqbwjdEiuE":3},"flags":{}}
{"name":"Unverwundbartrank","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Der Charakter erh&auml;lt f&uuml;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&auml;ssig ist.</p>","quantity":1,"price":1000,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"1HjmUAR5mf59yXlw"} {"_id":"1HjmUAR5mf59yXlw","name":"Unverwundbartrank","type":"loot","img":"icons/consumables/potions/potion-flask-corked-shiny-red.webp","data":{"description":"<p>Der Charakter erh&auml;lt f&uuml;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&auml;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":"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&auml;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}]} {"_id":"1hmprC7XVhIPemy5","name":"Kurzbogen +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&uuml;stung +2 mit verzierten L&ouml;wenk&ouml;pfen, die <em>Laufen</em> +1,5m gew&auml;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":"Rüstung des Löwen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Eine Plattenr&uuml;stung +2 mit verzierten L&ouml;wenk&ouml;pfen, die <em>Laufen</em> +1,5m gew&auml;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"} {"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":"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":"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&auml;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}]} {"_id":"2JQowFF6ZjF90OFI","name":"Hellebarde","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;mtlicher Zauber seines Tr&auml;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":"Abklingring","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Dieser einfache Abklingring senkt die Abklingzeit s&auml;mtlicher Zauber seines Tr&auml;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&uuml;r K&Ouml;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&uuml;r K&Ouml;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":"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&auml;ndig, Initiative -2, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"2ydkhz5gDjxAiaYy","name":"Bihänder +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative -2, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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}]} {"_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":"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":"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":[]} {"_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&uuml;stung +2 gew&auml;hrt ihrem Tr&auml;ger +1 auf <em>K&ouml;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":"55AkLjiaIn0SWO9k","name":"Rüstung des Kriegers","type":"armor","img":"icons/equipment/chest/breastplate-collared-steel.webp","data":{"description":"<p>Diese aufwendig verzierte Plattenr&uuml;stung +2 gew&auml;hrt ihrem Tr&auml;ger +1 auf <em>K&ouml;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","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":"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":"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}]} {"_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&ouml;dlicher Langbogen +2 mit <strong>Fieser Schuss +II</strong> und <strong>Scharfsch&uuml;tze +II</strong>.</p>\n<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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"} {"name":"Immertreff","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Ein t&ouml;dlicher Langbogen +2 mit <strong>Fieser Schuss +II</strong> und <strong>Scharfsch&uuml;tze +II</strong>.</p>\n<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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}]} {"_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&uuml;stung +1 gew&auml;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"} {"name":"Gewänder des Adlers","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese hellbeige, mit Adlerfedern verzierte Lederr&uuml;stung +1 gew&auml;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&auml;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}]} {"_id":"6WqPqjMQMMPjV99U","name":"Kurzbogen +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;nke erh&ouml;hen die Lebenskraft um W20 f&uuml;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&auml;nke erh&ouml;hen die Lebenskraft um W20 f&uuml;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":"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":"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}]} {"_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":"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":"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&auml;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}]} {"_id":"9MsBlnMhvAMe4zTk","name":"Leichte Armbrust +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;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&auml;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":"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":[]} {"_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>&Ouml;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"} {"name":"Smaragd-Schlüssel","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"loot","data":{"description":"<p>Einmal alle 24h kann man damit den Zauber <em>&Ouml;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 &amp; 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":"Aw9aoumlI69gYv75","name":"Lederschienen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>An Arm &amp; 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":"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":"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}]} {"_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&auml;rbte Plattenr&uuml;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"} {"name":"Blutrüstung","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>In diese rotgef&auml;rbte Plattenr&uuml;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":[]} {"_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":"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&auml;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":"JZkzRagRS8TKZplw","name":"Zwergenaxt +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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":[]} {"_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&szlig; riechende Trank verdoppelt ST f&uuml;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&szlig; riechende Trank verdoppelt ST f&uuml;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&uuml;cken trifft.</p>\n<p>Zweih&auml;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":"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&uuml;cken trifft.</p>\n<p>Zweih&auml;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&auml;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"} {"name":"Robe der Macht","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese violette Robe +3 verleiht ihrem Tr&auml;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}]} {"_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":"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":"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":[]} {"_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&ouml;&szlig;e f&uuml;r W20 Minuten. K&Ouml;R, ST und H&Auml; 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&ouml;&szlig;e f&uuml;r W20 Minuten. K&Ouml;R, ST und H&Auml; 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}]} {"_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&ouml;ht die Werte von <em>Zaubern</em> und <em>Zielzauber</em> f&uuml;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&ouml;ht die Werte von <em>Zaubern</em> und <em>Zielzauber</em> f&uuml;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":"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&auml;ndig, Initiative -6, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"N3RcggWJuKGtKZyP","name":"Schlachtbeil +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative -6, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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&auml;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":"NHV9ho8tGutv0mrS","name":"Zwergenaxt +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;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":"NMmZ3Uu9uwAHc5IP","name":"Schwere Armbrust +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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}]} {"_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&auml;nke halbieren (abrunden) die Abklingzeit aller Zauber f&uuml;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&auml;nke halbieren (abrunden) die Abklingzeit aller Zauber f&uuml;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&auml;ndig, Initiative -6, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"Nb65CmtFiiWPpnNZ","name":"Schlachtbeil +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative -6, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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}]} {"_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&uuml;nliche Getr&auml;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&uuml;nliche Getr&auml;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&ouml;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"} {"name":"Elfenstiefel","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Diese bequemen Stiefel erh&ouml;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":[]} {"_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&auml;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"} {"name":"Elfischer Tarnumhang","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Dieser Umhang, in den Feengarn eingewebt wurde, verleiht zus&auml;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&auml;re Zwergenaxt +1 mit <strong>Brutaler Hieb +II</strong>.</p>\n<p>Zweih&auml;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"} {"name":"Orkspalter","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Eine alte, legend&auml;re Zwergenaxt +1 mit <strong>Brutaler Hieb +II</strong>.</p>\n<p>Zweih&auml;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&auml;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":"RKKhoedvylYWFBN3","name":"Kurzbogen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;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}]} {"_id":"RWSRxi9np1UrEi7N","name":"Schwere Armbrust +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&uuml;nger.</p>","quantity":1,"price":5000,"availability":"unset","storageLocation":"-"},"flags":{},"img":"icons/svg/mystery-man.svg","effects":[],"_id":"RlA4PIa9hnsqoqFi"} {"_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&uuml;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)","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":"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}]} {"_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&ouml;ht f&uuml;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&ouml;ht f&uuml;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&auml;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":"S0EV25lf5TbN6COJ","name":"Schwere Armbrust +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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":"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}]} {"_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":"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":"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":[]} {"_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&uuml;r W20 Runden erh&ouml;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&uuml;r W20 Runden erh&ouml;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":"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&auml;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":"Tu0Mf3Qib68wxpRs","name":"Zwergenaxt +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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":[]} {"_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}]} {"_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":"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 &auml;u&szlig;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":"Elfischer Sattel","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>In diesen &auml;u&szlig;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&szlig;en</em>-Proben mit B&ouml;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"} {"name":"Armreif des Bogners","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Verleiht auf <em>Schie&szlig;en</em>-Proben mit B&ouml;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":"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":[]} {"_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"} {"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}]} {"_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&uuml;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&uuml;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":"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&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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}]} {"_id":"WiW9Sad1D2HjkNMz","name":"Langbogen +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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&ouml;&szlig;ert den Trinkenden auf das Doppelte seiner normalen Gr&ouml;&szlig;e f&uuml;r W20/2 Minuten. K&Ouml;R, ST und H&Auml; 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&ouml;&szlig;ert den Trinkenden auf das Doppelte seiner normalen Gr&ouml;&szlig;e f&uuml;r W20/2 Minuten. K&Ouml;R, ST und H&Auml; 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":"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&auml;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}]} {"_id":"XSXEDiMN27cuEoOx","name":"Leichte Armbrust +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&ouml;ht f&uuml;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&ouml;ht f&uuml;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":"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}]} {"_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&uuml;stung gew&auml;hrt ihrem Tr&auml;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"} {"name":"Kundschafterpanzer","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese mit braunen Fellschulterpolstern verzierte Kettenr&uuml;stung gew&auml;hrt ihrem Tr&auml;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&uuml;r W20 Minuten kann der Trinker Magie, Unsichtbares und Verborgenes (Fallen, Geheimt&uuml;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&uuml;r W20 Minuten kann der Trinker Magie, Unsichtbares und Verborgenes (Fallen, Geheimt&uuml;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":"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":"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":[]} {"_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":"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&auml;ndig, Initiative -2, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"dIA53f3kvU9JgGvg","name":"Bihänder +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative -2, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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}]} {"_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":"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&auml;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}]} {"_id":"dq4wK5bmMvOfwoWH","name":"Kampfstab +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;hren ihrem Tr&auml;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"} {"name":"Fäustlinge der Verstümmelung","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Diese blutverschmutzten Wildlederhandschuhe gew&auml;hren ihrem Tr&auml;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":"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&auml;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":"eMeS2JSyiRJ5xO48","name":"Streithammer +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;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}]} {"_id":"eMyZ5EJ9SsiXaeKK","name":"Streithammer +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&auml;rbung, wirkt dieser Trank den Zauber <em>Allheilung</em> auf den Trinkenden (keine Probe n&ouml;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&auml;rbung, wirkt dieser Trank den Zauber <em>Allheilung</em> auf den Trinkenden (keine Probe n&ouml;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":"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":"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}]} {"_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}]} {"_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"} {"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&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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}]} {"_id":"gVwoOpuHkmwvcUow","name":"Langbogen +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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&auml;nk gew&auml;hrt f&uuml;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&auml;nk gew&auml;hrt f&uuml;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":"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":"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":"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":"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}]} {"_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&uuml;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&uuml;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":"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":"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":"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":"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&auml;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":"iORLpplub64kuxb4","name":"Streithammer +3","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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":"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":"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":"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}]} {"_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&ouml;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":"Schutzring +1","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Erh&ouml;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&ouml;ht solch ein Trank <em>Schlagen</em> und <em>Abwehr</em> f&uuml;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&ouml;ht solch ein Trank <em>Schlagen</em> und <em>Abwehr</em> f&uuml;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&szlig;e Robe, die ihrem Tr&auml;ger +1 auf Heilzauber gew&auml;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":"Robe des Heilers","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Eine wei&szlig;e Robe, die ihrem Tr&auml;ger +1 auf Heilzauber gew&auml;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&ouml;se Zungen behaupten, dass dieser Ring, in den <em>Unsichtbarkeit</em> eingebettet ist, seinen Tr&auml;ger zu seinem abh&auml;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":"Unsichtbarkeitsring","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>B&ouml;se Zungen behaupten, dass dieser Ring, in den <em>Unsichtbarkeit</em> eingebettet ist, seinen Tr&auml;ger zu seinem abh&auml;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":"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>&Uuml;ber eine Waffe gesch&uuml;ttet, verleiht dieser meist silberne Trank dieser f&uuml;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>&Uuml;ber eine Waffe gesch&uuml;ttet, verleiht dieser meist silberne Trank dieser f&uuml;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":[]} {"_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&uuml;ckten Orkschamanen entwickelte, dampfende Getr&auml;nk heilt drei Kampfrunden jeweils W20 Lebenskraft. In der vierten Runde explodiert der Trinker und verursacht in 2m Radius Schaden in H&ouml;he der erw&uuml;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&uuml;ckten Orkschamanen entwickelte, dampfende Getr&auml;nk heilt drei Kampfrunden jeweils W20 Lebenskraft. In der vierten Runde explodiert der Trinker und verursacht in 2m Radius Schaden in H&ouml;he der erw&uuml;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&ouml;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":"Karten des Schummlers","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"loot","data":{"description":"<p>In dieses wundersch&ouml;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&auml;nk gew&auml;hrt f&uuml;r W20 Stunden dem Trinker die zwergische Volksf&auml;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&auml;nk gew&auml;hrt f&uuml;r W20 Stunden dem Trinker die zwergische Volksf&auml;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":"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&auml;ger des Ringes zu einem vorher festgelegten Zauber aktionsfrei wechseln, ohne daf&uuml;r w&uuml;rfeln zu m&uuml;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":"Spruchspeicherring","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Einmal pro Tag kann der Tr&auml;ger des Ringes zu einem vorher festgelegten Zauber aktionsfrei wechseln, ohne daf&uuml;r w&uuml;rfeln zu m&uuml;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&ouml;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&ouml;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&szlig;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":[]} {"_id":"oWvJfxEBr83QxO9Q","name":"Speer","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zerbricht bei Schie&szlig;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 &auml;lter, Haare und N&auml;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 &auml;lter, Haare und N&auml;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":"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&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"oqnI982dhCya94Tu","name":"Elfenbogen +2","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":[]} {"_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&ouml;rte.</p>\n<p>Zweih&auml;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"} {"name":"Zornhammer","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Ein schwerer, schlichter Streithammer +3, der einst einem Zwergenhelden geh&ouml;rte.</p>\n<p>Zweih&auml;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":[]} {"_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&ouml;ht dieser Trank f&uuml;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&ouml;ht dieser Trank f&uuml;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":"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":[]} {"_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&ouml;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"} {"name":"Schutzring +3","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"equipment","data":{"description":"<p>Erh&ouml;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}]} {"_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&szlig;em Fell geschneiderte Lederr&uuml;stung gew&auml;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"} {"name":"Fellmantel des Heilers","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese aus wei&szlig;em Fell geschneiderte Lederr&uuml;stung gew&auml;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&auml;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}]} {"_id":"s1xaEPPKJGMImZ9m","name":"Streitaxt +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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":"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":"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":[]} {"_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":"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":[]} {"_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&uuml;nen Stoffr&auml;ndern ges&auml;umte Kettenr&uuml;stung +1 gew&auml;hrt ihrem Tr&auml;ger +3 auf <em>Schie&szlig;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":"Rüstung des Schützen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"armor","data":{"description":"<p>Diese mit dunkelgr&uuml;nen Stoffr&auml;ndern ges&auml;umte Kettenr&uuml;stung +1 gew&auml;hrt ihrem Tr&auml;ger +3 auf <em>Schie&szlig;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&auml;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&auml;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":"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":"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&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":"uDdLTyyNqeXzCssp","name":"Elfenbogen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;ndig, Initiative +1, F&uuml;r Zwerge auf Grund der Gr&ouml;&szlig;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":[]} {"_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&auml;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":"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&auml;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&uuml;r W20 Runden mit seinem normalen Laufen-Wert wie eine Spinne klettern, selbst kopf&uuml;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&uuml;r W20 Runden mit seinem normalen Laufen-Wert wie eine Spinne klettern, selbst kopf&uuml;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":"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}]} {"_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&auml;nke gew&auml;hren f&uuml;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&auml;nke gew&auml;hren f&uuml;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&uuml;llten Beh&auml;lter enth&auml;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"} {"name":"Geisterbote","permission":{"default":0,"TAIf4a1vtFG928pw":3},"type":"loot","data":{"description":"<p>Jeder dieser mit Rauch gef&uuml;llten Beh&auml;lter enth&auml;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&auml;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":"vqKLn65gjoced8jV","name":"Streitaxt","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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}]} {"_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&auml;lt W20 Nahkampfangriffe bzw. reicht f&uuml;r W20 Fernkampfgeschosse</p>","quantity":1,"price":0.5,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/tools/laboratory/bowl-liquid-black.webp","effects":[]} {"_id":"zXgxu2gCkaTSSSMU","name":"Waffenpaste","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Macht WB+1; h&auml;lt W20 Nahkampfangriffe bzw. reicht f&uuml;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&uuml;r beide Schl&ouml;sser extra ermitteln</p>","quantity":1,"price":8,"availability":"village","storageLocation":"-"},"flags":{},"img":"icons/sundries/survival/cuffs-shackles-steel.webp","effects":[]} {"_id":"zjefX9KYvMphtVwX","name":"Handschellen","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Preis f&uuml;r beide Schl&ouml;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&auml;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}]} {"_id":"zoPPqqDyRTvmV2do","name":"Hellebarde +1","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"weapon","data":{"description":"<p>Zweih&auml;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&ouml;ht die Werte von <em>Schie&szlig;en</em> und <em>Zielzauber</em> f&uuml;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&ouml;ht die Werte von <em>Schie&szlig;en</em> und <em>Zielzauber</em> f&uuml;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&ouml;he, 11+ kein Heileffekt</p>","quantity":1,"price":2.5,"availability":"hamlet","storageLocation":"-"},"flags":{},"img":"icons/tools/laboratory/bowl-herbs-green.webp","effects":[]} {"_id":"zquQpOOVr5lIbnmL","name":"Heilkraut","permission":{"default":0,"onhXSUZqbwjdEiuE":3},"type":"loot","data":{"description":"<p>Probenwert 10: 1-10 heilt LK in Ergebnish&ouml;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":[]} {"_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":[]}

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

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

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

View file

@ -19,6 +19,7 @@
padding-right: 1px; padding-right: 1px;
& > label { & > label {
font-size: 0.9em;
font-weight: bold; font-weight: bold;
} }

View file

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

View file

@ -6,13 +6,14 @@
@use "../utils/mixins"; @use "../utils/mixins";
@use "../utils/variables"; @use "../utils/variables";
@use "../utils/typography";
.ds4-combat-value { .ds4-combat-value {
$size: 3.75rem; $size: 3.75rem;
display: grid; display: grid;
place-items: center; place-items: center;
row-gap: 0.5em; row-gap: 0.125em;
&__value { &__value {
$combat-values-icons-path: "#{variables.$official-icons-path}/combat-values"; $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 { &__formula {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;

View file

@ -75,4 +75,10 @@
.tox-edit-area { .tox-edit-area {
padding: 0 8px; padding: 0 8px;
} }
.tox-toolbar-overlord {
background-color: transparent;
.tox-toolbar__primary {
background: transparent;
}
}
} }

View file

@ -25,6 +25,7 @@ header.sheet-header {
flex: 0 0 100px; flex: 0 0 100px;
height: 100px; height: 100px;
margin: variables.$margin-sm 10px variables.$margin-sm 0; margin: variables.$margin-sm 10px variables.$margin-sm 0;
border: none;
} }
.header-fields { .header-fields {

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

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
header.sheet-header { .ds4-sheet {
.character-values { display: flex;
flex: 0 0 100%; flex-direction: column;
} flex-wrap: nowrap;
} }

View file

@ -6,16 +6,13 @@
@use "../utils/variables"; @use "../utils/variables";
nav.tabs { .ds4-sheet-tab-nav {
height: auto;
border-top: variables.$border-groove;
border-bottom: variables.$border-groove; border-bottom: variables.$border-groove;
.item { border-top: variables.$border-groove;
height: auto;
&__item {
font-weight: bold; font-weight: bold;
white-space: nowrap; white-space: nowrap;
} }
.item.active {
text-decoration: none;
}
} }

View file

@ -20,10 +20,11 @@
/* Styles limited to ds4 sheets */ /* Styles limited to ds4 sheets */
.ds4 { .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/apps");
@include meta.load-css("components/basic_property"); @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/check");
@include meta.load-css("components/checks"); @include meta.load-css("components/checks");
@include meta.load-css("components/combat_value"); @include meta.load-css("components/combat_value");
@ -34,7 +35,10 @@
@include meta.load-css("components/description"); @include meta.load-css("components/description");
@include meta.load-css("components/forms"); @include meta.load-css("components/forms");
@include meta.load-css("components/item_list"); @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/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("components/talent_rank_equation");
@include meta.load-css("tabs/biography");
} }

View file

@ -5,6 +5,7 @@
*/ */
@font-face { @font-face {
font-display: swap;
font-family: "Lora"; font-family: "Lora";
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
@ -12,6 +13,7 @@
} }
@font-face { @font-face {
font-display: swap;
font-family: "Lora"; font-family: "Lora";
font-style: normal; font-style: normal;
font-weight: bold; font-weight: bold;
@ -19,6 +21,7 @@
} }
@font-face { @font-face {
font-display: swap;
font-family: "Lora"; font-family: "Lora";
font-style: italic; font-style: italic;
font-weight: normal; font-weight: normal;
@ -26,6 +29,7 @@
} }
@font-face { @font-face {
font-display: swap;
font-family: "Lora"; font-family: "Lora";
font-style: italic; font-style: italic;
font-weight: bold; font-weight: bold;
@ -33,6 +37,7 @@
} }
@font-face { @font-face {
font-display: swap;
font-family: "Wood Stamp"; font-family: "Wood Stamp";
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;

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

View file

@ -2,9 +2,9 @@
"name": "ds4", "name": "ds4",
"title": "Dungeonslayers 4", "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/.", "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", "version": "1.1.3",
"minimumCoreVersion": "0.7.9", "minimumCoreVersion": "0.8.8",
"compatibleCoreVersion": "0.7.10", "compatibleCoreVersion": "0.8.8",
"templateVersion": 6, "templateVersion": 6,
"author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug, Max Tharr, Sascha Martens", "author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug, Max Tharr, Sascha Martens",
"authors": [ "authors": [
@ -93,7 +93,7 @@
"primaryTokenAttribute": "combatValues.hitPoints", "primaryTokenAttribute": "combatValues.hitPoints",
"url": "https://git.f3l.de/dungeonslayers/ds4", "url": "https://git.f3l.de/dungeonslayers/ds4",
"manifest": "https://git.f3l.de/dungeonslayers/ds4/-/raw/latest/src/system.json?inline=false", "manifest": "https://git.f3l.de/dungeonslayers/ds4/-/raw/latest/src/system.json?inline=false",
"download": "https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/0.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", "license": "https://git.f3l.de/dungeonslayers/ds4#licensing",
"initiative": "@combatValues.initiative.total", "initiative": "@combatValues.initiative.total",
"manifestPlusVersion": "1.0.0", "manifestPlusVersion": "1.0.0",

View file

@ -170,29 +170,6 @@
"shield": { "shield": {
"templates": ["base", "physical", "equipable", "protective"] "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": { "spell": {
"templates": ["base", "equipable"], "templates": ["base", "equipable"],
"spellType": "spellcasting", "spellType": "spellcasting",
@ -220,6 +197,29 @@
"sorcerer": null "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": { "specialCreatureAbility": {
"templates": ["base"], "templates": ["base"],
"experiencePoints": 0 "experiencePoints": 0

View file

@ -6,84 +6,19 @@ SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
--}} --}}
<form class="{{cssClass}} flexcol" autocomplete="off"> <form class="{{cssClass}} ds4-sheet" autocomplete="off">
{{!-- Sheet Header --}} {{!-- Header --}}
<header class="sheet-header"> {{#> systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
<img class="profile-img" src="{{actor.img}}" data-edit="img" alt="Actor Icon" title="{{actor.name}}" {{> systems/ds4/templates/sheets/actor/components/character-properties.hbs}}
height="100" width="100" /> {{/systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
<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>
{{!-- Sheet Tab Navigation --}} {{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" data-group="primary"> <nav class="ds4-sheet-tab-nav sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a>
<a class="item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
<a class="item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a>
<a class="item" data-tab="talents-abilities">{{localize 'DS4.HeadingTalentsAbilities'}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="abilities">{{localize 'DS4.HeadingAbilities'}}</a>
<a class="item" data-tab="profile">{{localize "DS4.HeadingProfile"}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a>
<a class="item" data-tab="biography">{{localize 'DS4.HeadingBiography'}}</a>
</nav> </nav>
<!-- beautify ignore:start --> <!-- beautify ignore:start -->
@ -99,11 +34,8 @@ SPDX-License-Identifier: MIT
{{!-- Spells Tab --}} {{!-- Spells Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/spells.hbs}} {{> systems/ds4/templates/sheets/actor/tabs/spells.hbs}}
{{!-- Talents Tab --}} {{!-- Abilities Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/talents-abilities.hbs}} {{> systems/ds4/templates/sheets/actor/tabs/abilities.hbs}}
{{! Profile Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/profile.hbs}}
{{!-- Biography Tab --}} {{!-- Biography Tab --}}
{{> systems/ds4/templates/sheets/actor/tabs/biography.hbs}} {{> systems/ds4/templates/sheets/actor/tabs/biography.hbs}}

View 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>

View 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>

View 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>

View file

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

View file

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

View file

@ -7,6 +7,6 @@ SPDX-License-Identifier: MIT
<div class="ds4-checks"> <div class="ds4-checks">
{{#each config.i18n.checks as |check-label check-key|}} {{#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 {{> 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}} {{/each}}
</div> </div>

View file

@ -9,20 +9,24 @@ SPDX-License-Identifier: MIT
!-- !--
!-- @param combat-value-key: The key of the combat value !-- @param combat-value-key: The key of the combat value
!-- @param combat-value-data: The data for 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">
<div class="ds4-combat-value__value ds4-combat-value__value--{{combat-value-key}}" <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}} {{combat-value-data.total}}
</div> </div>
<span title="{{combat-value-title}}" class="ds4-combat-value__label">{{combat-value-label}}</span>
<div class="ds4-combat-value__formula"> <div class="ds4-combat-value__formula">
<span class="ds4-combat-value__formula-base" <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> <span>+</span>
<input class="ds4-combat-value__formula-modifier" type="number" name="data.combatValues.{{combat-value-key}}.mod" <input class="ds4-combat-value__formula-modifier" type="number"
value='{{combat-value-data.mod}}' data-dtype="Number" id="data.combatValues.{{combat-value-key}}.mod-{{actor-id}}"
title="{{combat-value-label}} {{localize 'DS4.TooltipModifier'}}" /> name="data.combatValues.{{combat-value-key}}.mod" value='{{combat-value-data.mod}}' data-dtype="Number"
title="{{combat-value-title}} {{localize 'DS4.TooltipModifier'}}" />
</div> </div>
</div> </div>

View file

@ -6,8 +6,10 @@ SPDX-License-Identifier: MIT
--}} --}}
<div class="ds4-combat-values"> <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 {{> 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}} {{/each}}
</div> </div>

View file

@ -11,21 +11,22 @@ SPDX-License-Identifier: MIT
!-- @param core-value-key: The key of the core value !-- @param core-value-key: The key of the core value
!-- @param core-value-data: The data for 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 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}}"> <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> class="ds4-core-value__label">{{core-value-label}}</label>
<div class="ds4-core-value__value"> <div class="ds4-core-value__value">
<input class="ds4-core-value__value-input" type="number" <input class="ds4-core-value__value-input" type="number"
name="data.{{core-value-variant}}s.{{core-value-key}}.base" 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'}}" /> data-dtype="Number" title="{{core-value-label}} {{localize 'DS4.TooltipBaseValue'}}" />
<span>+</span> <span>+</span>
<input class="ds4-core-value__value-input" type="number" <input class="ds4-core-value__value-input" type="number"
name="data.{{core-value-variant}}s.{{core-value-key}}.mod" 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" id="data.{{core-value-variant}}s.{{core-value-key}}.mod-{{actor-id}}" value='{{core-value-data.mod}}'
title="{{core-value-label}} {{localize 'DS4.TooltipModifier'}}" /> data-dtype="Number" title="{{core-value-label}} {{localize 'DS4.TooltipModifier'}}" />
<span class="ds4-core-value__value-arrow">➞</span> <span class="ds4-core-value__value-arrow">➞</span>
<span class="ds4-core-value__value-total" <span class="ds4-core-value__value-total"
title="{{core-value-label}}: {{core-value-data.tooltip}}">{{core-value-data.total}}</span> title="{{core-value-label}}: {{core-value-data.tooltip}}">{{core-value-data.total}}</span>

View file

@ -8,12 +8,12 @@ SPDX-License-Identifier: MIT
<div class="ds4-core-values"> <div class="ds4-core-values">
{{#each config.i18n.attributes as |attribute-label attribute-key|}} {{#each config.i18n.attributes as |attribute-label attribute-key|}}
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=attribute-label {{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=attribute-label
core-value-key=attribute-key core-value-data=(lookup ../data.attributes core-value-key=attribute-key core-value-data=(lookup ../data.data.attributes
attribute-key) core-value-variant="attribute"}} attribute-key) core-value-variant="attribute" actor-id=../data._id}}
{{/each}} {{/each}}
{{#each config.i18n.traits as |trait-label trait-key|}} {{#each config.i18n.traits as |trait-label trait-key|}}
{{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=trait-label {{> systems/ds4/templates/sheets/actor/components/core-value.hbs core-value-label=trait-label
core-value-key=trait-key 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}} {{/each}}
</div> </div>

View file

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

View file

@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT
<h4 class="ds4-currency-title">{{localize 'DS4.CharacterCurrency'}}</h4> <h4 class="ds4-currency-title">{{localize 'DS4.CharacterCurrency'}}</h4>
<div class="ds4-currency"> <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> <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" <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" /> name="data.currency.{{key}}" id="data.currency.{{key}}" value="{{value}}" data-dtype="Number" />

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: MIT
{{!-- image --}} {{!-- image --}}
{{> systems/ds4/templates/sheets/actor/components/rollable-image.hbs rollable=itemData.data.rollable {{> 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"}} rollableTitle=(localize "DS4.RollableImageRollableTitle" name=itemData.name) rollableClass="rollable-item"}}
{{!-- amount --}} {{!-- amount --}}

View file

@ -69,8 +69,8 @@ SPDX-License-Identifier: MIT
</div> </div>
{{!-- armor type --}} {{!-- armor type --}}
<div title="{{lookup ../../config.i18n.armorTypes itemData.dataData.armorType}}"> <div title="{{lookup ../../config.i18n.armorTypes itemData.data.armorType}}">
{{lookup ../../config.i18n.armorTypesAbbr itemData.dataData.armorType}} {{lookup ../../config.i18n.armorTypesAbbr itemData.data.armorType}}
</div> </div>
{{!-- armor value --}} {{!-- armor value --}}

View 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>

View file

@ -6,66 +6,21 @@ SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
--}} --}}
<form class="{{cssClass}} flexcol" autocomplete="off"> <form class="{{cssClass}} ds4-sheet" autocomplete="off">
{{!-- Sheet Header --}} {{!-- Header --}}
<header class="sheet-header"> {{#> systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
<img class="profile-img" src="{{actor.img}}" data-edit="img" title="{{actor.name}}" height="100" width="100" /> {{> systems/ds4/templates/sheets/actor/components/creature-properties.hbs}}
<div class="header-fields flexrow"> {{/systems/ds4/templates/sheets/actor/components/actor-header.hbs}}
<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>{{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 --}} {{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" data-group="primary"> <nav class="ds4-sheet-tab-nav sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="values">{{localize 'DS4.HeadingValues'}}</a>
<a class="item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="inventory">{{localize 'DS4.HeadingInventory'}}</a>
<a class="item" data-tab="special-creature-abilities">{{localize 'DS4.HeadingSpecialCreatureAbilities'}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="special-creature-abilities">{{localize
<a class="item" data-tab="spells">{{localize 'DS4.HeadingSpells'}}</a> 'DS4.HeadingSpecialCreatureAbilities'}}</a>
<a class="item" data-tab="description">{{localize 'DS4.HeadingDescription'}}</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> </nav>
{{!-- Sheet Body --}} {{!-- Sheet Body --}}

View file

@ -5,7 +5,7 @@ SPDX-FileCopyrightText: 2021 Gesina Schwalbe
SPDX-License-Identifier: MIT 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 --}} {{!-- TALENT --}}
<h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeTalentPlural'}}</h4> <h4 class="ds4-item-list-title">{{localize 'DS4.ItemTypeTalentPlural'}}</h4>
{{#unless (isEmpty itemsByType.talent)}} {{#unless (isEmpty itemsByType.talent)}}

View file

@ -5,6 +5,13 @@ SPDX-License-Identifier: MIT
--}} --}}
<div class="tab biography" data-group="primary" data-tab="biography"> <div class="tab biography" data-group="primary" data-tab="biography">
{{editor content=data.profile.biography target="data.profile.biography" button=true owner=owner <div class="ds4-biography-tab-content">
editable=editable}} <!-- 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> </div>

View file

@ -5,6 +5,6 @@ SPDX-License-Identifier: MIT
--}} --}}
<div class="tab description" data-group="primary" data-tab="description"> <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}} editable=editable}}
</div> </div>

View file

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

View file

@ -7,35 +7,36 @@ SPDX-License-Identifier: MIT
<form class="{{cssClass}}" autocomplete="off"> <form class="{{cssClass}}" autocomplete="off">
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
<div class="grid grid-3col basic-properties"> <div class="grid grid-3col basic-properties">
<div class="basic-property"> <div class="basic-property">
<label>{{localize "DS4.ArmorType"}}</label> <label>{{localize "DS4.ArmorType"}}</label>
<select name="data.armorType" data-type="String"> <select name="data.armorType" data-type="String">
{{#select data.armorType}} {{#select data.data.armorType}}
{{#each config.i18n.armorTypes as |value key|}} {{#each config.i18n.armorTypes as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
{{/each}} {{/each}}
{{/select}} {{/select}}
</select> </select>
</div> </div>
<div class="basic-property"> <div class="basic-property">
<label for="data.armorMaterialType">{{localize "DS4.ArmorMaterialType"}}</label> <label for="data.armorMaterialType">{{localize "DS4.ArmorMaterialType"}}</label>
<select name="data.armorMaterialType" data-type="String"> <select name="data.armorMaterialType" data-type="String">
{{#select data.armorMaterialType}} {{#select data.data.armorMaterialType}}
{{#each config.i18n.armorMaterialTypes as |value key|}} {{#each config.i18n.armorMaterialTypes as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
{{/each}} {{/each}}
{{/select}} {{/select}}
</select> </select>
</div> </div>
<div class="basic-property"> <div class="basic-property">
<label>{{localize "DS4.ArmorValue"}}</label> <label>{{localize "DS4.ArmorValue"}}</label>
<input type="text" name="data.armorValue" value="{{data.armorValue}}" <input type="text" name="data.armorValue" value="{{data.data.armorValue}}" placeholder="0"
placeholder="0" data-dtype="Number" /> data-dtype="Number" />
</div> </div>
</div> </div>
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
{{!-- Common Item body --}} {{!-- 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> </form>

View file

@ -8,11 +8,11 @@ SPDX-License-Identifier: MIT
{{!-- Template for the common body (navigation & body sections) of all items. --}} {{!-- Template for the common body (navigation & body sections) of all items. --}}
{{!-- Sheet Tab Navigation --}} {{!-- Sheet Tab Navigation --}}
<nav class="sheet-tabs tabs" data-group="primary"> <nav class="ds4-sheet-tab-nav sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="description">{{localize "DS4.HeadingDescription"}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="description">{{localize "DS4.HeadingDescription"}}</a>
<a class="item" data-tab="effects">{{localize "DS4.HeadingEffects"}}</a> <a class="ds4-sheet-tab-nav__item item" data-tab="effects">{{localize "DS4.HeadingEffects"}}</a>
{{#if isPhysical}} {{#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}} {{/if}}
</nav> </nav>

View file

@ -6,9 +6,9 @@ SPDX-License-Identifier: MIT
--}} --}}
<header class="sheet-header"> <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"> <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> <h2 class="item-type">{{lookup config.i18n.itemTypes item.type}}</h2>
{{> @partial-block}} {{> @partial-block}}
</div> </div>

View file

@ -7,15 +7,16 @@ SPDX-License-Identifier: MIT
<form class="{{cssClass}}" autocomplete="off"> <form class="{{cssClass}}" autocomplete="off">
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
<div class="grid grid-1col basic-properties"> <div class="grid grid-1col basic-properties">
<div class="basic-property"> <div class="basic-property">
<label>{{localize "DS4.ArmorValue"}}</label> <label>{{localize "DS4.ArmorValue"}}</label>
<input type="text" name="data.armorValue" value="{{data.armorValue}}" <input type="text" name="data.armorValue" value="{{data.data.armorValue}}" placeholder="0"
placeholder="0" data-dtype="Number" /> data-dtype="Number" />
</div>
</div> </div>
</div>
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
{{!-- Common Item body --}} {{!-- 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> </form>

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT
<div class="grid grid-3col basic-properties"> <div class="grid grid-3col basic-properties">
<div class="basic-property"> <div class="basic-property">
<label>{{localize "DS4.SpecialCreatureAbilityExperiencePoints"}}</label> <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" /> placeholder="0" data-dtype="Number" />
</div> </div>
</div> </div>

View file

@ -16,9 +16,9 @@ SPDX-License-Identifier: MIT
<label>{{localize localizeString}}</label> <label>{{localize localizeString}}</label>
<div class="unit-data-pair"> <div class="unit-data-pair">
<input class="item-num-val" type="text" data-dtype="String" name="data.{{property}}.value" <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 name="data.{{property}}.unit" data-type="String">
{{#select (lookup (lookup data property) 'unit')}} {{#select (lookup (lookup data.data property) 'unit')}}
{{#if (eq unitType 'temporal')}} {{#if (eq unitType 'temporal')}}
{{#each (lookup config.i18n 'temporalUnitsAbbr') as |value key|}} {{#each (lookup config.i18n 'temporalUnitsAbbr') as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
@ -48,7 +48,7 @@ SPDX-License-Identifier: MIT
<div class="basic-property"> <div class="basic-property">
<label for="data.spellType">{{localize "DS4.SpellType"}}</label> <label for="data.spellType">{{localize "DS4.SpellType"}}</label>
<select id="data.spellType" name="data.spellType" data-type="String"> <select id="data.spellType" name="data.spellType" data-type="String">
{{#select data.spellType}} {{#select data.data.spellType}}
{{#each config.i18n.spellTypes as |value key|}} {{#each config.i18n.spellTypes as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
{{/each}} {{/each}}
@ -57,7 +57,7 @@ SPDX-License-Identifier: MIT
</div> </div>
<div class="basic-property"> <div class="basic-property">
<label for="data.bonus">{{localize "DS4.SpellBonus"}}</label> <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>
</div> </div>
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
@ -67,7 +67,7 @@ SPDX-License-Identifier: MIT
<div class="side-property"> <div class="side-property">
<label for="data.spellCategory">{{localize "DS4.SpellCategory"}}</label> <label for="data.spellCategory">{{localize "DS4.SpellCategory"}}</label>
<select id="data.spellCategory" name="data.spellCategory" data-type="String"> <select id="data.spellCategory" name="data.spellCategory" data-type="String">
{{#select data.spellCategory}} {{#select data.data.spellCategory}}
{{#each config.i18n.spellCategories as |value key|}} {{#each config.i18n.spellCategories as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
{{/each}} {{/each}}
@ -82,21 +82,21 @@ SPDX-License-Identifier: MIT
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsHealer'}}"> <div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsHealer'}}">
<label for="data.minimumLevels.healer">{{localize "DS4.SpellMinimumLevelsHealerAbbr"}}</label> <label for="data.minimumLevels.healer">{{localize "DS4.SpellMinimumLevelsHealerAbbr"}}</label>
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.healer" <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>
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsWizard'}}"> <div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsWizard'}}">
<label for="data.minimumLevels.wizard">{{localize "DS4.SpellMinimumLevelsWizardAbbr"}}</label> <label for="data.minimumLevels.wizard">{{localize "DS4.SpellMinimumLevelsWizardAbbr"}}</label>
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.wizard" <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>
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsSorcerer'}}"> <div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsSorcerer'}}">
<label for="data.minimumLevels.sorcerer">{{localize "DS4.SpellMinimumLevelsSorcererAbbr"}}</label> <label for="data.minimumLevels.sorcerer">{{localize "DS4.SpellMinimumLevelsSorcererAbbr"}}</label>
<input type="number" min="0" step="1" data-dtype="Number" name="data.minimumLevels.sorcerer" <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>
<div class="side-property"> <div class="side-property">
<label for="data.price">{{localize "DS4.SpellPrice"}}</label> <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> </div>
{{/systems/ds4/templates/sheets/item/components/body.hbs}} {{/systems/ds4/templates/sheets/item/components/body.hbs}}

View file

@ -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="tab flexrow description" data-group="primary" data-tab="description">
<div class="side-properties"> <div class="side-properties">
{{#if isOwned}} {{#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> <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> </div>
{{/if}} {{/if}}
<div class="side-property"> <div class="side-property">
<label for="data.actor">{{localize 'DS4.ItemOwner'}}</label> <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> class="fas fa-user"></i>{{actor.name}}</a>
</div> </div>
{{#if isPhysical}} {{#if isPhysical}}
<div class="side-property"> <div class="side-property">
<label for="data.quantity">{{localize 'DS4.Quantity'}}</label> <label for="data.quantity">{{localize 'DS4.Quantity'}}</label>
<input type="number" min="0" step="1" data-dtype="Number" name="data.quantity" value="{{data.quantity}}" /> <input type="number" min="0" step="1" data-dtype="Number" name="data.quantity"
</div> value="{{data.data.quantity}}" />
<div class="side-property"> </div>
<label for="data.storageLocation">{{localize 'DS4.StorageLocation'}}</label> <div class="side-property">
<input type="text" data-dtype="String" name="data.storageLocation" value="{{data.storageLocation}}" /> <label for="data.storageLocation">{{localize 'DS4.StorageLocation'}}</label>
</div> <input type="text" data-dtype="String" name="data.storageLocation" value="{{data.data.storageLocation}}" />
</div>
{{/if}} {{/if}}
{{else}} {{else}}
<span>{{localize "DS4.NotOwned"}}</span> <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}} {{> @partial-block}}
</div> </div>
<div class="description" title="{{localize 'DS4.HeadingDescription'}}"> <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>
</div> </div>

View file

@ -11,13 +11,13 @@ SPDX-License-Identifier: MIT
<div class="side-properties"> <div class="side-properties">
<div class="side-property"> <div class="side-property">
<label for="data.price">{{localize "DS4.PriceGold"}}</label> <label for="data.price">{{localize "DS4.PriceGold"}}</label>
<input type="number" min="0" max="99999" step="0.01" data-dtype="Number" <input type="number" min="0" max="99999" step="0.01" data-dtype="Number" name="data.price"
name="data.price" value="{{data.price}}" /> value="{{data.data.price}}" />
</div> </div>
<div class="side-property"> <div class="side-property">
<label for="data.availability">{{localize "DS4.ItemAvailability"}}</label> <label for="data.availability">{{localize "DS4.ItemAvailability"}}</label>
<select name="data.availability" data-type="String"> <select name="data.availability" data-type="String">
{{#select data.availability}} {{#select data.data.availability}}
{{#each config.i18n.itemAvailabilities as |value key|}} {{#each config.i18n.itemAvailabilities as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
{{/each}} {{/each}}

View file

@ -17,8 +17,8 @@ SPDX-License-Identifier: MIT
</div> </div>
</li> </li>
{{#each item.effects as |effect id|}} {{#each item.effects as |effect id|}}
<li class="effect flexrow" data-effect-id="{{effect._id}}"> <li class="effect flexrow" data-effect-id="{{effect.id}}">
<h4 class="effect-name">{{effect.label}}</h4> <h4 class="effect-name">{{effect.data.label}}</h4>
<div class="effect-controls"> <div class="effect-controls">
<a class="effect-control" data-action="edit" title="{{localize 'DS4.UserInteractionEditEffect'}}"> <a class="effect-control" data-action="edit" title="{{localize 'DS4.UserInteractionEditEffect'}}">
<i class="fas fa-edit"></i></a> <i class="fas fa-edit"></i></a>

View file

@ -13,9 +13,9 @@ SPDX-License-Identifier: MIT
{{#*inline "talentRankBasicProperty" }} {{#*inline "talentRankBasicProperty" }}
<div class="basic-property"> <div class="basic-property">
<label for="data.rank.{{property}}">{{localize localizeString}}</label> <label for="data.rank.{{property}}">{{localize localizeString}}</label>
<input type="number" min="0" step="1" data-dtype="Number" {{disabled}} <input type="number" min="0" step="1" data-dtype="Number" {{disabled}} {{#if (eq property 'base' )
{{#if (eq property 'base') }}max="{{data.rank.max}}"{{/if}} }}max="{{data.data.rank.max}}" {{/if}} name="data.rank.{{property}}"
name="data.rank.{{property}}" value="{{lookup data.rank property}}" /> value="{{lookup data.data.rank property}}" />
</div> </div>
{{/inline}} {{/inline}}
@ -25,15 +25,17 @@ SPDX-License-Identifier: MIT
<form class="{{cssClass}}" autocomplete="off"> <form class="{{cssClass}}" autocomplete="off">
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
<div class="grid grid-4col basic-properties"> <div class="grid grid-4col basic-properties">
{{> talentRankBasicProperty data=data property='base' localizeString='DS4.TalentRankBase' }} {{> talentRankBasicProperty data=data property='base' localizeString='DS4.TalentRankBase' }}
{{> talentRankBasicProperty data=data property='max' localizeString='DS4.TalentRankMax'}} {{> talentRankBasicProperty data=data property='max' localizeString='DS4.TalentRankMax'}}
{{> talentRankBasicProperty data=data property='mod' localizeString='DS4.TalentRankMod'}} {{> talentRankBasicProperty data=data property='mod' localizeString='DS4.TalentRankMod'}}
{{> talentRankBasicProperty data=data property='total' localizeString='DS4.TalentRankTotal' disabled='disabled'}} {{> talentRankBasicProperty data=data property='total' localizeString='DS4.TalentRankTotal'
</div> disabled='disabled'}}
</div>
{{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
{{!-- Common Item body --}} {{!-- 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> </form>

View file

@ -7,30 +7,31 @@ SPDX-License-Identifier: MIT
<form class="{{cssClass}}" autocomplete="off"> <form class="{{cssClass}}" autocomplete="off">
{{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}} {{#> systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
<div class="grid grid-3col basic-properties"> <div class="grid grid-3col basic-properties">
<div class="basic-property"> <div class="basic-property">
<label>{{localize "DS4.AttackType"}}</label> <label>{{localize "DS4.AttackType"}}</label>
<select name="data.attackType" data-type="String"> <select name="data.attackType" data-type="String">
{{#select data.attackType}} {{#select data.data.attackType}}
{{#each config.i18n.attackTypes as |value key|}} {{#each config.i18n.attackTypes as |value key|}}
<option value="{{key}}">{{value}}</option> <option value="{{key}}">{{value}}</option>
{{/each}} {{/each}}
{{/select}} {{/select}}
</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> </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}} {{/systems/ds4/templates/sheets/item/components/sheet-header.hbs}}
{{!-- Common Item body --}} {{!-- 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> </form>

2881
yarn.lock

File diff suppressed because it is too large Load diff