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.SpellMinimumLevelsSorcerer": "Zugangsstufe für Schwarzmagier",
|
||||
"DS4.SpellMinimumLevelsSorcererAbbr": "Zugangsstufe Sch",
|
||||
"DS4.SpellScrollPriceGold": "Schriftrollenpreis (Gold)",
|
||||
"DS4.SpellPrice": "Preis (Gold)",
|
||||
"DS4.ActorTypeCharacter": "Charakter",
|
||||
"DS4.ActorTypeCreature": "Kreatur",
|
||||
"DS4.AttributeBody": "Körper",
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
"DS4.SpellMinimumLevelsWizardAbbr": "Min lvl WIZ",
|
||||
"DS4.SpellMinimumLevelsSorcerer": "Minimum level for Sorcerers",
|
||||
"DS4.SpellMinimumLevelsSorcererAbbr": "Min lvl SRC",
|
||||
"DS4.SpellScrollPriceGold": "Scroll Price (Gold)",
|
||||
"DS4.SpellPrice": "Price (Gold)",
|
||||
"DS4.ActorTypeCharacter": "Character",
|
||||
"DS4.ActorTypeCreature": "Creature",
|
||||
"DS4.AttributeBody": "Body",
|
||||
|
|
|
@ -111,6 +111,6 @@ export interface DS4CreatureDataDataBaseInfo {
|
|||
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: {
|
||||
rounds: "DS4.UnitRounds",
|
||||
minutes: "DS4.UnitMinutes",
|
||||
hours: "DS4.UnitHours",
|
||||
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",
|
||||
},
|
||||
|
||||
/**
|
||||
* Define abbreviations for available units
|
||||
* Define abbreviations for available duration units
|
||||
*/
|
||||
temporalUnitsAbbr: {
|
||||
rounds: "DS4.UnitRoundsAbbr",
|
||||
minutes: "DS4.UnitMinutesAbbr",
|
||||
hours: "DS4.UnitHoursAbbr",
|
||||
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",
|
||||
},
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ interface DS4ItemDataDataBase {
|
|||
interface DS4ItemDataDataPhysical {
|
||||
quantity: number;
|
||||
price: number;
|
||||
availability: "hamlet" | "village" | "city" | "elves" | "dwarves" | "nowhere" | "unset";
|
||||
availability: keyof typeof DS4.i18n.itemAvailabilities;
|
||||
storageLocation: string;
|
||||
}
|
||||
|
||||
|
@ -56,12 +56,13 @@ interface DS4ItemDataDataProtective {
|
|||
armorValue: number;
|
||||
}
|
||||
|
||||
interface UnitData<UnitType> {
|
||||
export interface UnitData<UnitType> {
|
||||
value: string;
|
||||
unit: UnitType;
|
||||
}
|
||||
type TemporalUnit = "rounds" | "minutes" | "hours" | "days" | "custom";
|
||||
type DistanceUnit = "meter" | "kilometer" | "custom";
|
||||
export type TemporalUnit = keyof typeof DS4.i18n.temporalUnits;
|
||||
type CustomTemporalUnit = keyof typeof DS4.i18n.customTemporalUnits;
|
||||
type DistanceUnit = keyof typeof DS4.i18n.distanceUnits;
|
||||
|
||||
// types
|
||||
|
||||
|
@ -71,15 +72,15 @@ export interface DS4WeaponDataData extends DS4ItemDataDataBase, DS4ItemDataDataP
|
|||
opponentDefense: number;
|
||||
}
|
||||
|
||||
export type AttackType = keyof typeof DS4["i18n"]["attackTypes"];
|
||||
export type AttackType = keyof typeof DS4.i18n.attackTypes;
|
||||
|
||||
export interface DS4ArmorDataData
|
||||
extends DS4ItemDataDataBase,
|
||||
DS4ItemDataDataPhysical,
|
||||
DS4ItemDataDataEquipable,
|
||||
DS4ItemDataDataProtective {
|
||||
armorMaterialType: "cloth" | "leather" | "chain" | "plate";
|
||||
armorType: "body" | "helmet" | "vambrace" | "greaves" | "vambraceGreaves";
|
||||
armorMaterialType: keyof typeof DS4.i18n.armorMaterialTypes;
|
||||
armorType: keyof typeof DS4.i18n.armorTypes;
|
||||
}
|
||||
|
||||
export interface DS4TalentDataData extends DS4ItemDataDataBase {
|
||||
|
@ -91,28 +92,18 @@ export interface DS4TalentRank extends ModifiableDataBase<number> {
|
|||
}
|
||||
|
||||
export interface DS4SpellDataData extends DS4ItemDataDataBase, DS4ItemDataDataEquipable {
|
||||
spellType: "spellcasting" | "targetedSpellcasting";
|
||||
spellType: keyof typeof DS4.i18n.spellTypes;
|
||||
bonus: string;
|
||||
spellCategory:
|
||||
| "healing"
|
||||
| "fire"
|
||||
| "ice"
|
||||
| "light"
|
||||
| "darkness"
|
||||
| "mindAffecting"
|
||||
| "electricity"
|
||||
| "none"
|
||||
| "unset";
|
||||
spellCategory: keyof typeof DS4.i18n.spellCategories;
|
||||
maxDistance: UnitData<DistanceUnit>;
|
||||
effectRadius: UnitData<DistanceUnit>;
|
||||
duration: UnitData<TemporalUnit>;
|
||||
duration: UnitData<CustomTemporalUnit>;
|
||||
cooldownDuration: UnitData<TemporalUnit>;
|
||||
minimumLevels: {
|
||||
healer: number | null;
|
||||
wizard: number | null;
|
||||
sorcerer: number | null;
|
||||
};
|
||||
scrollPrice: number;
|
||||
}
|
||||
|
||||
export interface DS4ShieldDataData
|
||||
|
|
|
@ -57,7 +57,9 @@ interface DS4ArmorPreparedDataData extends DS4ArmorDataData, DS4ItemPreparedData
|
|||
|
||||
interface DS4ShieldPreparedDataData extends DS4ShieldDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
interface DS4SpellPreparedDataData extends DS4SpellDataData, DS4ItemPreparedDataDataRollable {}
|
||||
interface DS4SpellPreparedDataData extends DS4SpellDataData, DS4ItemPreparedDataDataRollable {
|
||||
price: number | null;
|
||||
}
|
||||
|
||||
interface DS4EquipmentPreparedDataData extends DS4EquipmentDataData, DS4ItemPreparedDataDataRollable {}
|
||||
|
||||
|
|
|
@ -11,27 +11,12 @@ export class DS4ItemSheet extends ItemSheet<ItemSheet.Data<DS4Item>> {
|
|||
static get defaultOptions(): BaseEntitySheet.Options {
|
||||
const superDefaultOptions = super.defaultOptions;
|
||||
return mergeObject(superDefaultOptions, {
|
||||
...superDefaultOptions,
|
||||
width: 540,
|
||||
height: 400,
|
||||
classes: ["ds4", "sheet", "item"],
|
||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
|
||||
scrollY: [".sheet-body"],
|
||||
template: superDefaultOptions.template,
|
||||
viewPermission: superDefaultOptions.viewPermission,
|
||||
closeOnSubmit: superDefaultOptions.closeOnSubmit,
|
||||
submitOnChange: superDefaultOptions.submitOnChange,
|
||||
submitOnClose: superDefaultOptions.submitOnClose,
|
||||
editable: superDefaultOptions.editable,
|
||||
baseApplication: superDefaultOptions.baseApplication,
|
||||
top: superDefaultOptions.top,
|
||||
left: superDefaultOptions.left,
|
||||
popOut: superDefaultOptions.popOut,
|
||||
minimizable: superDefaultOptions.minimizable,
|
||||
resizable: superDefaultOptions.resizable,
|
||||
id: superDefaultOptions.id,
|
||||
dragDrop: superDefaultOptions.dragDrop,
|
||||
filters: superDefaultOptions.filters,
|
||||
title: superDefaultOptions.title,
|
||||
scrollY: [".tab.description", ".tab.effects", ".tab.details"],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { createCheckRoll } from "../rolls/check-factory";
|
|||
import notifications from "../ui/notifications";
|
||||
import { AttackType, DS4ItemData, ItemType } from "./item-data";
|
||||
import { DS4ItemPreparedData } from "./item-prepared-data";
|
||||
import { calculateSpellPrice } from "./type-specific-helpers/spell";
|
||||
|
||||
/**
|
||||
* The Item class for DS4
|
||||
|
@ -27,6 +28,9 @@ export class DS4Item extends Item<DS4ItemData, DS4ItemPreparedData> {
|
|||
} else {
|
||||
this.data.data.rollable = false;
|
||||
}
|
||||
if (this.data.type === "spell") {
|
||||
this.data.data.price = calculateSpellPrice(this.data.data);
|
||||
}
|
||||
}
|
||||
|
||||
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 migrate002 } from "./migrations/002";
|
||||
import { migrate as migrate003 } from "./migrations/003";
|
||||
import { migrate as migrate004 } from "./migrations/004";
|
||||
|
||||
import notifications from "./ui/notifications";
|
||||
|
||||
|
@ -72,7 +73,7 @@ function getTargetMigrationVersion(): number {
|
|||
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 {
|
||||
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;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
span {
|
||||
line-height: variables.$default-input-height;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
@include mixins.mark-invalid-or-disabled-input;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"version": "0.6.0",
|
||||
"minimumCoreVersion": "0.7.9",
|
||||
"compatibleCoreVersion": "0.7.9",
|
||||
"templateVersion": 5,
|
||||
"templateVersion": 6,
|
||||
"author": "Johannes Loher, Gesina Schwalbe, Oliver Rümpelein, Siegfried Krug",
|
||||
"authors": [
|
||||
{
|
||||
|
|
|
@ -212,14 +212,13 @@
|
|||
},
|
||||
"cooldownDuration": {
|
||||
"value": "",
|
||||
"unit": "custom"
|
||||
"unit": "rounds"
|
||||
},
|
||||
"minimumLevels": {
|
||||
"healer": null,
|
||||
"wizard": null,
|
||||
"sorcerer": null
|
||||
},
|
||||
"scrollPrice": 0
|
||||
}
|
||||
},
|
||||
"specialCreatureAbility": {
|
||||
"templates": ["base"],
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{{/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
|
||||
!-- directly handing over the latter two.
|
||||
!-- @param titleKey: The key of the localized title to use.
|
||||
|
@ -28,6 +28,11 @@
|
|||
titleKey=titleKey}}
|
||||
{{/inline}}
|
||||
|
||||
{{#*inline "customTemporalUnit"}}
|
||||
{{> unit unitNames=config.i18n.customTemporalUnits unitAbbrs=config.i18n.customTemporalUnitsAbbr unitDatum=unitDatum
|
||||
titleKey=titleKey}}
|
||||
{{/inline}}
|
||||
|
||||
{{#*inline "distanceUnit"}}
|
||||
{{> unit unitNames=config.i18n.distanceUnits unitAbbrs=config.i18n.distanceUnitsAbbr unitDatum=unitDatum
|
||||
titleKey=titleKey}}
|
||||
|
@ -72,7 +77,7 @@ titleKey=titleKey}}
|
|||
config=../../config}}
|
||||
|
||||
{{!-- duration --}}
|
||||
{{> temporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.data.duration config=../../config}}
|
||||
{{> customTemporalUnit titleKey='DS4.SpellDuration' unitDatum=itemData.data.duration config=../../config}}
|
||||
|
||||
{{!-- cooldown duration --}}
|
||||
{{> temporalUnit titleKey='DS4.SpellCooldownDuration' unitDatum=itemData.data.cooldownDuration
|
||||
|
|
|
@ -12,10 +12,16 @@
|
|||
<select name="data.{{property}}.unit" data-type="String">
|
||||
{{#select (lookup (lookup data property) 'unit')}}
|
||||
{{#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}}
|
||||
{{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}}
|
||||
{{/if}}
|
||||
{{/select}}
|
||||
|
@ -62,7 +68,7 @@
|
|||
</div>
|
||||
{{> unitDatum data=data property='maxDistance' localizeString='DS4.SpellMaxDistance' 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'
|
||||
}}
|
||||
<div class="side-property" title="{{localize 'DS4.SpellMinimumLevelsHealer'}}">
|
||||
|
@ -81,9 +87,8 @@
|
|||
id="data.minimumLevels.sorcerer" value="{{data.minimumLevels.sorcerer}}" />
|
||||
</div>
|
||||
<div class="side-property">
|
||||
<label for="data.scrollPrice">{{localize "DS4.SpellScrollPriceGold"}}</label>
|
||||
<input type="number" min="0" max="9999" step="0.01" data-dtype="Number" name="data.scrollPrice"
|
||||
id="data.scrollPrice" value="{{data.scrollPrice}}" />
|
||||
<label for="data.price">{{localize "DS4.SpellPrice"}}</label>
|
||||
<span name="data.price" id="data.price">{{data.price}}</span>
|
||||
</div>
|
||||
{{/systems/ds4/templates/sheets/item/components/body.hbs}}
|
||||
|
||||
|
|
Loading…
Reference in a new issue