Merge branch '084-automatically-calculate-scroll-price' into 'master'
Automatically calculate spell price Closes #84 See merge request dungeonslayers/ds4!106
This commit is contained in:
commit
441e907b8a
17 changed files with 279 additions and 57 deletions
|
@ -119,7 +119,7 @@
|
||||||
"DS4.SpellMinimumLevelsWizardAbbr": "Zugangsstufe Zau",
|
"DS4.SpellMinimumLevelsWizardAbbr": "Zugangsstufe Zau",
|
||||||
"DS4.SpellMinimumLevelsSorcerer": "Zugangsstufe für Schwarzmagier",
|
"DS4.SpellMinimumLevelsSorcerer": "Zugangsstufe für Schwarzmagier",
|
||||||
"DS4.SpellMinimumLevelsSorcererAbbr": "Zugangsstufe Sch",
|
"DS4.SpellMinimumLevelsSorcererAbbr": "Zugangsstufe Sch",
|
||||||
"DS4.SpellScrollPriceGold": "Schriftrollenpreis (Gold)",
|
"DS4.SpellPrice": "Preis (Gold)",
|
||||||
"DS4.ActorTypeCharacter": "Charakter",
|
"DS4.ActorTypeCharacter": "Charakter",
|
||||||
"DS4.ActorTypeCreature": "Kreatur",
|
"DS4.ActorTypeCreature": "Kreatur",
|
||||||
"DS4.AttributeBody": "Körper",
|
"DS4.AttributeBody": "Körper",
|
||||||
|
|
|
@ -119,7 +119,7 @@
|
||||||
"DS4.SpellMinimumLevelsWizardAbbr": "Min lvl WIZ",
|
"DS4.SpellMinimumLevelsWizardAbbr": "Min lvl WIZ",
|
||||||
"DS4.SpellMinimumLevelsSorcerer": "Minimum level for Sorcerers",
|
"DS4.SpellMinimumLevelsSorcerer": "Minimum level for Sorcerers",
|
||||||
"DS4.SpellMinimumLevelsSorcererAbbr": "Min lvl SRC",
|
"DS4.SpellMinimumLevelsSorcererAbbr": "Min lvl SRC",
|
||||||
"DS4.SpellScrollPriceGold": "Scroll Price (Gold)",
|
"DS4.SpellPrice": "Price (Gold)",
|
||||||
"DS4.ActorTypeCharacter": "Character",
|
"DS4.ActorTypeCharacter": "Character",
|
||||||
"DS4.ActorTypeCreature": "Creature",
|
"DS4.ActorTypeCreature": "Creature",
|
||||||
"DS4.AttributeBody": "Body",
|
"DS4.AttributeBody": "Body",
|
||||||
|
|
|
@ -111,6 +111,6 @@ export interface DS4CreatureDataDataBaseInfo {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatureType = "animal" | "construct" | "humanoid" | "magicalEntity" | "plantBeing" | "undead";
|
type CreatureType = keyof typeof DS4.i18n.creatureTypes;
|
||||||
|
|
||||||
type SizeCategory = "tiny" | "small" | "normal" | "large" | "huge" | "colossal";
|
type SizeCategory = keyof typeof DS4.i18n.creatureSizeCategories;
|
||||||
|
|
4
src/module/common/time-helpers.ts
Normal file
4
src/module/common/time-helpers.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export const secondsPerRound = 5;
|
||||||
|
export const secondsPerMinute = 60;
|
||||||
|
export const minutesPerHour = 60;
|
||||||
|
export const hoursPerDay = 24;
|
|
@ -261,24 +261,44 @@ export const DS4 = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define translations for available distance units
|
* Define translations for available duration units
|
||||||
*/
|
*/
|
||||||
temporalUnits: {
|
temporalUnits: {
|
||||||
rounds: "DS4.UnitRounds",
|
rounds: "DS4.UnitRounds",
|
||||||
minutes: "DS4.UnitMinutes",
|
minutes: "DS4.UnitMinutes",
|
||||||
hours: "DS4.UnitHours",
|
hours: "DS4.UnitHours",
|
||||||
days: "DS4.UnitDays",
|
days: "DS4.UnitDays",
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define translations for available duration units including "custom"
|
||||||
|
*/
|
||||||
|
customTemporalUnits: {
|
||||||
|
rounds: "DS4.UnitRounds",
|
||||||
|
minutes: "DS4.UnitMinutes",
|
||||||
|
hours: "DS4.UnitHours",
|
||||||
|
days: "DS4.UnitDays",
|
||||||
custom: "DS4.UnitCustom",
|
custom: "DS4.UnitCustom",
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define abbreviations for available units
|
* Define abbreviations for available duration units
|
||||||
*/
|
*/
|
||||||
temporalUnitsAbbr: {
|
temporalUnitsAbbr: {
|
||||||
rounds: "DS4.UnitRoundsAbbr",
|
rounds: "DS4.UnitRoundsAbbr",
|
||||||
minutes: "DS4.UnitMinutesAbbr",
|
minutes: "DS4.UnitMinutesAbbr",
|
||||||
hours: "DS4.UnitHoursAbbr",
|
hours: "DS4.UnitHoursAbbr",
|
||||||
days: "DS4.UnitDaysAbbr",
|
days: "DS4.UnitDaysAbbr",
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define abbreviations for available duration units including "custom"
|
||||||
|
*/
|
||||||
|
customTemporalUnitsAbbr: {
|
||||||
|
rounds: "DS4.UnitRoundsAbbr",
|
||||||
|
minutes: "DS4.UnitMinutesAbbr",
|
||||||
|
hours: "DS4.UnitHoursAbbr",
|
||||||
|
days: "DS4.UnitDaysAbbr",
|
||||||
custom: "DS4.UnitCustomAbbr",
|
custom: "DS4.UnitCustomAbbr",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ interface DS4ItemDataDataBase {
|
||||||
interface DS4ItemDataDataPhysical {
|
interface DS4ItemDataDataPhysical {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
price: number;
|
price: number;
|
||||||
availability: "hamlet" | "village" | "city" | "elves" | "dwarves" | "nowhere" | "unset";
|
availability: keyof typeof DS4.i18n.itemAvailabilities;
|
||||||
storageLocation: string;
|
storageLocation: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +56,13 @@ interface DS4ItemDataDataProtective {
|
||||||
armorValue: number;
|
armorValue: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UnitData<UnitType> {
|
export interface UnitData<UnitType> {
|
||||||
value: string;
|
value: string;
|
||||||
unit: UnitType;
|
unit: UnitType;
|
||||||
}
|
}
|
||||||
type TemporalUnit = "rounds" | "minutes" | "hours" | "days" | "custom";
|
export type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
|
||||||
type DistanceUnit = "meter" | "kilometer" | "custom";
|
type CustomTemporalUnit = keyof typeof DS4.i18n.customTemporalUnits;
|
||||||
|
type DistanceUnit = keyof typeof DS4.i18n.distanceUnits;
|
||||||
|
|
||||||
// types
|
// types
|
||||||
|
|
||||||
|
@ -71,15 +72,15 @@ export interface DS4WeaponDataData extends DS4ItemDataDataBase, DS4ItemDataDataP
|
||||||
opponentDefense: number;
|
opponentDefense: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttackType = keyof typeof DS4["i18n"]["attackTypes"];
|
export type AttackType = keyof typeof DS4.i18n.attackTypes;
|
||||||
|
|
||||||
export interface DS4ArmorDataData
|
export interface DS4ArmorDataData
|
||||||
extends DS4ItemDataDataBase,
|
extends DS4ItemDataDataBase,
|
||||||
DS4ItemDataDataPhysical,
|
DS4ItemDataDataPhysical,
|
||||||
DS4ItemDataDataEquipable,
|
DS4ItemDataDataEquipable,
|
||||||
DS4ItemDataDataProtective {
|
DS4ItemDataDataProtective {
|
||||||
armorMaterialType: "cloth" | "leather" | "chain" | "plate";
|
armorMaterialType: keyof typeof DS4.i18n.armorMaterialTypes;
|
||||||
armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves";
|
armorType: keyof typeof DS4.i18n.armorTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DS4TalentDataData extends DS4ItemDataDataBase {
|
export interface DS4TalentDataData extends DS4ItemDataDataBase {
|
||||||
|
@ -91,28 +92,18 @@ export interface DS4TalentRank extends ModifiableDataBase<number> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable {
|
export interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable {
|
||||||
spellType: "spellcasting" | "targetedSpellcasting";
|
spellType: keyof typeof DS4.i18n.spellTypes;
|
||||||
bonus: string;
|
bonus: string;
|
||||||
spellCategory:
|
spellCategory: keyof typeof DS4.i18n.spellCategories;
|
||||||
| "healing"
|
|
||||||
| "fire"
|
|
||||||
| "ice"
|
|
||||||
| "light"
|
|
||||||
| "darkness"
|
|
||||||
| "mindAffecting"
|
|
||||||
| "electricity"
|
|
||||||
| "none"
|
|
||||||
| "unset";
|
|
||||||
maxDistance: UnitData<DistanceUnit>;
|
maxDistance: UnitData<DistanceUnit>;
|
||||||
effectRadius: UnitData<DistanceUnit>;
|
effectRadius: UnitData<DistanceUnit>;
|
||||||
duration: UnitData<TemporalUnit>;
|
duration: UnitData<CustomTemporalUnit>;
|
||||||
cooldownDuration: UnitData<TemporalUnit>;
|
cooldownDuration: UnitData<TemporalUnit>;
|
||||||
minimumLevels: {
|
minimumLevels: {
|
||||||
healer: number | null;
|
healer: number | null;
|
||||||
wizard: number | null;
|
wizard: number | null;
|
||||||
sorcerer: number | null;
|
sorcerer: number | null;
|
||||||
};
|
};
|
||||||
scrollPrice: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DS4ShieldDataData
|
export interface DS4ShieldDataData
|
||||||
|
|
|
@ -57,7 +57,9 @@ interface DS4ArmorPreparedDataData extends DS4ArmorDataData, DS4ItemPreparedData
|
||||||
|
|
||||||
interface DS4ShieldPreparedDataData extends DS4ShieldDataData, DS4ItemPreparedDataDataRollable {}
|
interface DS4ShieldPreparedDataData extends DS4ShieldDataData, DS4ItemPreparedDataDataRollable {}
|
||||||
|
|
||||||
interface DS4SpellPreparedDataData extends DS4SpellDataData, DS4ItemPreparedDataDataRollable {}
|
interface DS4SpellPreparedDataData extends DS4SpellDataData, DS4ItemPreparedDataDataRollable {
|
||||||
|
price: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
interface DS4EquipmentPreparedDataData extends DS4EquipmentDataData, DS4ItemPreparedDataDataRollable {}
|
interface DS4EquipmentPreparedDataData extends DS4EquipmentDataData, DS4ItemPreparedDataDataRollable {}
|
||||||
|
|
||||||
|
|
|
@ -11,27 +11,12 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
||||||
static get defaultOptions(): BaseEntitySheet.Options {
|
static get defaultOptions(): BaseEntitySheet.Options {
|
||||||
const superDefaultOptions = super.defaultOptions;
|
const superDefaultOptions = super.defaultOptions;
|
||||||
return mergeObject(superDefaultOptions, {
|
return mergeObject(superDefaultOptions, {
|
||||||
|
...superDefaultOptions,
|
||||||
width: 540,
|
width: 540,
|
||||||
height: 400,
|
height: 400,
|
||||||
classes: ["ds4", "sheet", "item"],
|
classes: ["ds4", "sheet", "item"],
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
|
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
|
||||||
scrollY: [".sheet-body"],
|
scrollY: [".tab.description", ".tab.effects", ".tab.details"],
|
||||||
template: superDefaultOptions.template,
|
|
||||||
viewPermission: superDefaultOptions.viewPermission,
|
|
||||||
closeOnSubmit: superDefaultOptions.closeOnSubmit,
|
|
||||||
submitOnChange: superDefaultOptions.submitOnChange,
|
|
||||||
submitOnClose: superDefaultOptions.submitOnClose,
|
|
||||||
editable: superDefaultOptions.editable,
|
|
||||||
baseApplication: superDefaultOptions.baseApplication,
|
|
||||||
top: superDefaultOptions.top,
|
|
||||||
left: superDefaultOptions.left,
|
|
||||||
popOut: superDefaultOptions.popOut,
|
|
||||||
minimizable: superDefaultOptions.minimizable,
|
|
||||||
resizable: superDefaultOptions.resizable,
|
|
||||||
id: superDefaultOptions.id,
|
|
||||||
dragDrop: superDefaultOptions.dragDrop,
|
|
||||||
filters: superDefaultOptions.filters,
|
|
||||||
title: superDefaultOptions.title,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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, DS4ItemData, ItemType } from "./item-data";
|
||||||
import { DS4ItemPreparedData } from "./item-prepared-data";
|
import { DS4ItemPreparedData } from "./item-prepared-data";
|
||||||
|
import { calculateSpellPrice } from "./type-specific-helpers/spell";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Item class for DS4
|
* The Item class for DS4
|
||||||
|
@ -27,6 +28,9 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
||||||
} else {
|
} else {
|
||||||
this.data.data.rollable = false;
|
this.data.data.rollable = false;
|
||||||
}
|
}
|
||||||
|
if (this.data.type === "spell") {
|
||||||
|
this.data.data.price = calculateSpellPrice(this.data.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isNonEquippedEuipable(): boolean {
|
isNonEquippedEuipable(): boolean {
|
||||||
|
|
49
src/module/item/type-specific-helpers/spell.ts
Normal file
49
src/module/item/type-specific-helpers/spell.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { hoursPerDay, minutesPerHour, secondsPerMinute, secondsPerRound } from "../../common/time-helpers";
|
||||||
|
import { DS4SpellDataData, TemporalUnit, UnitData } from "../item-data";
|
||||||
|
|
||||||
|
export function calculateSpellPrice(data: DS4SpellDataData): number | null {
|
||||||
|
const spellPriceFactor = calculateSpellPriceFactor(data.cooldownDuration);
|
||||||
|
const baseSpellPrices = [
|
||||||
|
data.minimumLevels.healer !== null ? 10 + (data.minimumLevels.healer - 1) * 35 : null,
|
||||||
|
data.minimumLevels.wizard !== null ? 10 + (data.minimumLevels.wizard - 1) * 50 : null,
|
||||||
|
data.minimumLevels.sorcerer !== null ? 10 + (data.minimumLevels.sorcerer - 1) * 65 : null,
|
||||||
|
].filter((baseSpellPrice: number | null): baseSpellPrice is number => baseSpellPrice !== null);
|
||||||
|
const baseSpellPrice = Math.min(...baseSpellPrices);
|
||||||
|
return baseSpellPrice === Infinity ? null : baseSpellPrice * spellPriceFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSpellPriceFactor(temporalData: UnitData<TemporalUnit>): number {
|
||||||
|
let days: number;
|
||||||
|
if (Number.isNumeric(temporalData.value)) {
|
||||||
|
switch (temporalData.unit) {
|
||||||
|
case "days": {
|
||||||
|
days = temporalData.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "hours": {
|
||||||
|
days = temporalData.value / hoursPerDay;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "minutes": {
|
||||||
|
days = temporalData.value / (hoursPerDay * minutesPerHour);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rounds": {
|
||||||
|
days = (temporalData.value * secondsPerRound) / (hoursPerDay * minutesPerHour * secondsPerMinute);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (temporalData.unit) {
|
||||||
|
case "days": {
|
||||||
|
days = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
days = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.clamped(Math.floor(days), 0, 2) + 1;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
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 notifications from "./ui/notifications";
|
import notifications from "./ui/notifications";
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ function getTargetMigrationVersion(): number {
|
||||||
return migrations.length;
|
return migrations.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
const migrations: Array<() => Promise<void>> = [migrate001, migrate002, migrate003];
|
const migrations: Array<() => Promise<void>> = [migrate001, migrate002, migrate003, migrate004];
|
||||||
|
|
||||||
function isFirstWorldStart(migrationVersion: number): boolean {
|
function isFirstWorldStart(migrationVersion: number): boolean {
|
||||||
return migrationVersion < 0;
|
return migrationVersion < 0;
|
||||||
|
|
153
src/module/migrations/004.ts
Normal file
153
src/module/migrations/004.ts
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import { DS4SpellDataData } from "../item/item-data";
|
||||||
|
|
||||||
|
export async function migrate(): Promise<void> {
|
||||||
|
await migrateItems();
|
||||||
|
await migrateActors();
|
||||||
|
await migrateScenes();
|
||||||
|
await migrateCompendiums();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function migrateItems() {
|
||||||
|
for (const item of game.items?.entities ?? []) {
|
||||||
|
try {
|
||||||
|
const updateData = getItemUpdateData(item._data);
|
||||||
|
if (updateData) {
|
||||||
|
console.log(`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.`;
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItemUpdateData(itemData: DeepPartial<Item.Data>) {
|
||||||
|
if (!["spell"].includes(itemData.type ?? "")) return undefined;
|
||||||
|
const updateData: Record<string, unknown> = {
|
||||||
|
"-=data.scrollPrice": null,
|
||||||
|
"data.minimumLevels": { healer: null, wizard: null, sorcerer: null },
|
||||||
|
};
|
||||||
|
if (((itemData.data as DS4SpellDataData).cooldownDuration.unit as string) === "custom") {
|
||||||
|
updateData["data.cooldownDuration.unit"] = "rounds";
|
||||||
|
}
|
||||||
|
return updateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function migrateActors() {
|
||||||
|
for (const actor of game.actors?.entities ?? []) {
|
||||||
|
try {
|
||||||
|
const updateData = getActorUpdateData(actor._data);
|
||||||
|
if (updateData) {
|
||||||
|
console.log(`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.`;
|
||||||
|
console.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) {
|
||||||
|
console.log(`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.`;
|
||||||
|
console.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) {
|
||||||
|
console.log(`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.`;
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await compendium.configure({ locked: wasLocked });
|
||||||
|
}
|
|
@ -29,6 +29,10 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
span {
|
||||||
|
line-height: variables.$default-input-height;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
@include mixins.mark-invalid-or-disabled-input;
|
@include mixins.mark-invalid-or-disabled-input;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"minimumCoreVersion": "0.7.9",
|
"minimumCoreVersion": "0.7.9",
|
||||||
"compatibleCoreVersion": "0.7.9",
|
"compatibleCoreVersion": "0.7.9",
|
||||||
"templateVersion": 5,
|
"templateVersion": 6,
|
||||||
"author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug",
|
"author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -212,14 +212,13 @@
|
||||||
},
|
},
|
||||||
"cooldownDuration": {
|
"cooldownDuration": {
|
||||||
"value": "",
|
"value": "",
|
||||||
"unit": "custom"
|
"unit": "rounds"
|
||||||
},
|
},
|
||||||
"minimumLevels": {
|
"minimumLevels": {
|
||||||
"healer": null,
|
"healer": null,
|
||||||
"wizard": null,
|
"wizard": null,
|
||||||
"sorcerer": null
|
"sorcerer": null
|
||||||
},
|
}
|
||||||
"scrollPrice": 0
|
|
||||||
},
|
},
|
||||||
"specialCreatureAbility": {
|
"specialCreatureAbility": {
|
||||||
"templates": ["base"],
|
"templates": ["base"],
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
{{/inline}}
|
{{/inline}}
|
||||||
|
|
||||||
{{!--
|
{{!--
|
||||||
!-- Two templates based on the "unit" template for displaying values with unit.
|
!-- Three templates based on the "unit" template for displaying values with unit.
|
||||||
!-- Both accept a `config` object holding the unitNames and unitAbbr instead of
|
!-- Both accept a `config` object holding the unitNames and unitAbbr instead of
|
||||||
!-- directly handing over the latter two.
|
!-- directly handing over the latter two.
|
||||||
!-- @param titleKey: The key of the localized title to use.
|
!-- @param titleKey: The key of the localized title to use.
|
||||||
|
@ -28,6 +28,11 @@
|
||||||
titleKey=titleKey}}
|
titleKey=titleKey}}
|
||||||
{{/inline}}
|
{{/inline}}
|
||||||
|
|
||||||
|
{{#*inline "customTemporalUnit"}}
|
||||||
|
{{> unit unitNames=config.i18n.customTemporalUnits unitAbbrs=config.i18n.customTemporalUnitsAbbr unitDatum=unitDatum
|
||||||
|
titleKey=titleKey}}
|
||||||
|
{{/inline}}
|
||||||
|
|
||||||
{{#*inline "distanceUnit"}}
|
{{#*inline "distanceUnit"}}
|
||||||
{{> unit unitNames=config.i18n.distanceUnits unitAbbrs=config.i18n.distanceUnitsAbbr unitDatum=unitDatum
|
{{> unit unitNames=config.i18n.distanceUnits unitAbbrs=config.i18n.distanceUnitsAbbr unitDatum=unitDatum
|
||||||
titleKey=titleKey}}
|
titleKey=titleKey}}
|
||||||
|
@ -72,7 +77,7 @@ titleKey=titleKey}}
|
||||||
config=../../config}}
|
config=../../config}}
|
||||||
|
|
||||||
{{!-- duration --}}
|
{{!-- duration --}}
|
||||||
{{> temporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.data.duration config=../../config}}
|
{{> customTemporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.data.duration config=../../config}}
|
||||||
|
|
||||||
{{!-- cooldown duration --}}
|
{{!-- cooldown duration --}}
|
||||||
{{> temporalUnit titleKey='DS4.SpellCooldownDuration' unitDatum=itemData.data.cooldownDuration
|
{{> temporalUnit titleKey='DS4.SpellCooldownDuration' unitDatum=itemData.data.cooldownDuration
|
||||||
|
|
|
@ -12,10 +12,16 @@
|
||||||
<select name="data.{{property}}.unit" data-type="String">
|
<select name="data.{{property}}.unit" data-type="String">
|
||||||
{{#select (lookup (lookup data property) 'unit')}}
|
{{#select (lookup (lookup data property) 'unit')}}
|
||||||
{{#if (eq unitType 'temporal')}}
|
{{#if (eq unitType 'temporal')}}
|
||||||
{{#each (lookup config.i18n 'temporalUnitsAbbr') as |value key|}}<option value="{{key}}">{{value}}</option>
|
{{#each (lookup config.i18n 'temporalUnitsAbbr') as |value key|}}
|
||||||
|
<option value="{{key}}">{{value}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{else if (eq unitType 'customTemporal')}}
|
||||||
|
{{#each (lookup config.i18n 'customTemporalUnitsAbbr') as |value key|}}
|
||||||
|
<option value="{{key}}">{{value}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#each (lookup config.i18n 'distanceUnitsAbbr') as |value key|}}<option value="{{key}}">{{value}}</option>
|
{{#each (lookup config.i18n 'distanceUnitsAbbr') as |value key|}}
|
||||||
|
<option value="{{key}}">{{value}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/select}}
|
{{/select}}
|
||||||
|
@ -62,7 +68,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{> unitDatum data=data property='maxDistance' localizeString='DS4.SpellMaxDistance' unitType='distance' }}
|
{{> unitDatum data=data property='maxDistance' localizeString='DS4.SpellMaxDistance' unitType='distance' }}
|
||||||
{{> unitDatum data=data property='effectRadius' localizeString='DS4.SpellEffectRadius' unitType='distance' }}
|
{{> unitDatum data=data property='effectRadius' localizeString='DS4.SpellEffectRadius' unitType='distance' }}
|
||||||
{{> unitDatum data=data property='duration' localizeString='DS4.SpellDuration' unitType='temporal' }}
|
{{> unitDatum data=data property='duration' localizeString='DS4.SpellDuration' unitType='customTemporal' }}
|
||||||
{{> unitDatum data=data property='cooldownDuration' localizeString='DS4.SpellCooldownDuration' unitType='temporal'
|
{{> unitDatum data=data property='cooldownDuration' localizeString='DS4.SpellCooldownDuration' unitType='temporal'
|
||||||
}}
|
}}
|
||||||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsHealer'}}">
|
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsHealer'}}">
|
||||||
|
@ -81,9 +87,8 @@
|
||||||
id="data.minimumLevels.sorcerer" value="{{data.minimumLevels.sorcerer}}" />
|
id="data.minimumLevels.sorcerer" value="{{data.minimumLevels.sorcerer}}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="side-property">
|
<div class="side-property">
|
||||||
<label for="data.scrollPrice">{{localize "DS4.SpellScrollPriceGold"}}</label>
|
<label for="data.price">{{localize "DS4.SpellPrice"}}</label>
|
||||||
<input type="number" min="0" max="9999" step="0.01" data-dtype="Number" name="data.scrollPrice"
|
<span name="data.price" id="data.price">{{data.price}}</span>
|
||||||
id="data.scrollPrice" value="{{data.scrollPrice}}" />
|
|
||||||
</div>
|
</div>
|
||||||
{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue