From 1119fefe01f5444196c83847723a7a7ed7fb0b52 Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 15:54:09 -0800 Subject: [PATCH 01/13] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Add=20types=20for?= =?UTF-8?q?=20Alchemy=20Trackers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/alchemy.d.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/alchemy.d.ts b/src/alchemy.d.ts index 5926e6c..1668cee 100644 --- a/src/alchemy.d.ts +++ b/src/alchemy.d.ts @@ -34,6 +34,7 @@ export interface AlchemyCharacter { spells: AlchemySpell[]; textBlocks: AlchemyTextBlockSection[]; weight?: string; + trackers?: AlchemyTracker[]; } interface AlchemyStat { @@ -138,3 +139,26 @@ interface AlchemyMovementMode { mode: string; distance: number; } + +interface AlchemyTracker { + name: string; + value: number; + max: number; + color: string; + type: string; + category: AlchemyTrackerCategory | string | null; + _id?: string; + sortOrder?: number; + readOnly?: boolean; +} + +export enum AlchemyTrackerCategory { + /** + * Used to track hit points, temporary hit points, etc. + */ + Health = 'health', + /** + * Used to track experience points, experience to next level, etc. + */ + Experience = 'experience', +} From 7ce63e521c89b06dc21d03a7af32c1e9b2c1a0ad Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 15:56:29 -0800 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=A7=AA=20Update=20exp=20test=20to?= =?UTF-8?q?=20expect=20trackers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/convert.exp.test.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/convert.exp.test.ts b/test/convert.exp.test.ts index 29fa6d9..dc22265 100644 --- a/test/convert.exp.test.ts +++ b/test/convert.exp.test.ts @@ -1,25 +1,30 @@ import { describe, expect, test } from '@jest/globals'; import { convertCharacter } from '../src'; +import { AlchemyTrackerCategory } from '../src/alchemy.d'; import { DdbCharacter } from '../src/ddb'; import { DeepPartial } from './test-helpers'; -describe('Convert DDB currentXp to Alchemy exp', () => { +describe('Convert DDB currentXp to Alchemy tracker', () => { test.each` currentXp | expected ${10} | ${10} ${0} | ${0} `( - 'returns exp=$expected when currentXp=$currentXp', + 'returns tracker.current=$expected when currentXp=$currentXp', ({ currentXp, expected }) => { const ddbChar: DeepPartial = { currentXp, }; const converted = convertCharacter(ddbChar as DdbCharacter, { - exp: true, + trackers: true, }); - expect(converted.exp).toEqual(expected); + expect( + converted.trackers?.find( + (t) => t.category === AlchemyTrackerCategory.Experience, + )?.value, + ).toEqual(expected); }, ); }); From 796309de6ff328ea174883e8713cc957c4742d37 Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 16:01:30 -0800 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=A7=AA=20Update=20current=20hp=20te?= =?UTF-8?q?sts=20to=20expect=20trackers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/convert.currentHp.test.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/test/convert.currentHp.test.ts b/test/convert.currentHp.test.ts index 7626942..75050b8 100644 --- a/test/convert.currentHp.test.ts +++ b/test/convert.currentHp.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, test } from '@jest/globals'; import { convertCharacter } from '../src'; +import { AlchemyTrackerCategory } from '../src/alchemy.d'; import { DdbCharacter, DdbStatType } from '../src/ddb'; import { DeepPartial } from './test-helpers'; @@ -34,10 +35,14 @@ describe('Convert DDB current HP to Alchemy current HP', () => { ddbChar.overrideHitPoints = overrideHitPoints; const converted = convertCharacter(ddbChar as DdbCharacter, { - currentHp: true, + trackers: true, }); - expect(converted.currentHp).toEqual(overrideHitPoints); + expect( + converted.trackers?.find( + (t) => t.category === AlchemyTrackerCategory.Health, + )?.value, + ).toEqual(overrideHitPoints); }); test.each` @@ -74,10 +79,14 @@ describe('Convert DDB current HP to Alchemy current HP', () => { con; const converted = convertCharacter(ddbChar as DdbCharacter, { - currentHp: true, + trackers: true, }); - expect(converted.currentHp).toEqual(expected); + expect( + converted.trackers?.find( + (t) => t.category === AlchemyTrackerCategory.Health, + )?.value, + ).toEqual(expected); }, ); @@ -91,9 +100,13 @@ describe('Convert DDB current HP to Alchemy current HP', () => { ddbChar.classes.push({ level: 1 }); const converted = convertCharacter(ddbChar as DdbCharacter, { - currentHp: true, + trackers: true, }); - expect(converted.currentHp).toEqual(2); + expect( + converted.trackers?.find( + (t) => t.category === AlchemyTrackerCategory.Health, + )?.value, + ).toEqual(2); }); }); From 470a8a4576f1421fa42d3cec34c2c049e0aa09bc Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 18:30:38 -0800 Subject: [PATCH 04/13] =?UTF-8?q?=F0=9F=A7=AA=20Update=20max=20hp=20tests?= =?UTF-8?q?=20to=20expect=20trackers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/convert.maxHp.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/convert.maxHp.test.ts b/test/convert.maxHp.test.ts index 30884b5..0c8e668 100644 --- a/test/convert.maxHp.test.ts +++ b/test/convert.maxHp.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, test } from '@jest/globals'; import { convertCharacter } from '../src'; +import { AlchemyTrackerCategory } from '../src/alchemy.d'; import { DdbCharacter, DdbStatType } from '../src/ddb'; import { DeepPartial } from './test-helpers'; @@ -48,10 +49,14 @@ describe('Convert DDB maxHP to Alchemy maxHP', () => { con; const converted = convertCharacter(ddbChar as DdbCharacter, { - maxHp: true, + trackers: true, }); - expect(converted.maxHp).toEqual(expected); + expect( + converted.trackers?.find( + (t) => t.category === AlchemyTrackerCategory.Health, + )?.max, + ).toEqual(expected); }, ); }); From f8456ce6310aef2bf3f9d0acb2042940edeab71e Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 18:31:08 -0800 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=A7=AA=20Update=20exp=20test=20to?= =?UTF-8?q?=20check=20value=20and=20max?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/convert.exp.test.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/convert.exp.test.ts b/test/convert.exp.test.ts index dc22265..b9683ee 100644 --- a/test/convert.exp.test.ts +++ b/test/convert.exp.test.ts @@ -10,7 +10,7 @@ describe('Convert DDB currentXp to Alchemy tracker', () => { ${10} | ${10} ${0} | ${0} `( - 'returns tracker.current=$expected when currentXp=$currentXp', + 'returns tracker.value=$expected when currentXp=$currentXp', ({ currentXp, expected }) => { const ddbChar: DeepPartial = { currentXp, @@ -20,11 +20,12 @@ describe('Convert DDB currentXp to Alchemy tracker', () => { trackers: true, }); - expect( - converted.trackers?.find( - (t) => t.category === AlchemyTrackerCategory.Experience, - )?.value, - ).toEqual(expected); + const expTracker = converted.trackers?.find( + (t) => t.category === AlchemyTrackerCategory.Experience, + ); + + expect(expTracker.value).toEqual(expected); + expect(expTracker.max).toEqual(355000); }, ); }); From 508e0a529cfc532d08296c6be03599098ba4d260 Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 18:32:35 -0800 Subject: [PATCH 06/13] =?UTF-8?q?=E2=9C=A8=20Update=20conversion=20process?= =?UTF-8?q?=20to=20include=20trackers=20for=20XP=20and=20HP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/convert.ts | 41 ++++++++++++++++++++++++++++++++--------- src/ddb.ts | 4 ++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/convert.ts b/src/convert.ts index 0a7a6de..414b0a4 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -12,7 +12,9 @@ import { AlchemySpellSlot, AlchemyStat, AlchemyTextBlockSection, -} from './alchemy'; + AlchemyTracker, + AlchemyTrackerCategory, +} from './alchemy.d'; import { DDB_SPEED_EQUALS_RE, DDB_SPEED_IS_RE, @@ -251,9 +253,7 @@ export const convertCharacter = ( ...shouldConvert(options, 'armorClass', () => getArmorClass(ddbCharacter)), ...shouldConvert(options, 'copper', () => ddbCharacter.currencies.cp), ...shouldConvert(options, 'classes', () => convertClasses(ddbCharacter)), - ...shouldConvert(options, 'currentHp', () => getCurrentHp(ddbCharacter)), ...shouldConvert(options, 'electrum', () => ddbCharacter.currencies.ep), - ...shouldConvert(options, 'exp', () => ddbCharacter.currentXp), ...shouldConvert(options, 'eyes', () => ddbCharacter.eyes), ...shouldConvert(options, 'gold', () => ddbCharacter.currencies.gp), ...shouldConvert(options, 'hair', () => ddbCharacter.hair), @@ -270,7 +270,6 @@ export const convertCharacter = ( isSpellcaster(ddbCharacter), ), ...shouldConvert(options, 'items', () => convertItems(ddbCharacter)), - ...shouldConvert(options, 'maxHp', () => getMaxHp(ddbCharacter)), ...shouldConvert(options, 'movementModes', () => getMovementModes(ddbCharacter), ), @@ -303,6 +302,7 @@ export const convertCharacter = ( ...shouldConvert(options, 'weight', () => ddbCharacter.weight ? ddbCharacter.weight.toString() : '', ), + ...shouldConvert(options, 'trackers', () => convertTrackers(ddbCharacter)), }); // Convert D&D Beyond style stat arrays to Alchemy style stat arrays @@ -317,7 +317,7 @@ const convertStatArray = (ddbCharacter: DdbCharacter): AlchemyStat[] => { const getStatValue = (ddbCharacter: DdbCharacter, statId: number): number => { // Start with whatever the base stat is at level 1 const baseStatValue = - ddbCharacter.stats.find((stat) => stat.id === statId)?.value || + ddbCharacter.stats?.find((stat) => stat.id === statId)?.value || BASE_STAT; // If there are any overrides, use the highest of those instead of the base value @@ -345,7 +345,7 @@ const getModifiers = ( ddbCharacter: DdbCharacter, options: object, ): DdbModifier[] => { - return Object.values(ddbCharacter.modifiers) + return Object.values(ddbCharacter.modifiers || {}) .flat() .filter((modifier) => Object.keys(options).every((key) => modifier[key] === options[key]), @@ -354,7 +354,7 @@ const getModifiers = ( // Find all applicable modifiers based on keys/values in `options` and sum them const sumModifiers = (ddbCharacter: DdbCharacter, options: object): number => { - return getModifiers(ddbCharacter, options).reduce( + return getModifiers(ddbCharacter, options)?.reduce( (total, modifier) => total + modifier.value, 0, ); @@ -362,7 +362,7 @@ const sumModifiers = (ddbCharacter: DdbCharacter, options: object): number => { // Find all applicable modifiers based on keys/values in `options` and take the highest const maxModifier = (ddbCharacter: DdbCharacter, options: object): number => { - return getModifiers(ddbCharacter, options).reduce( + return getModifiers(ddbCharacter, options)?.reduce( (max, modifier) => Math.max(max, modifier.value), 0, ); @@ -430,7 +430,7 @@ const getArmorClass = (ddbCharacter: DdbCharacter): number => { // Calculate the base HP of the character, inclusive of bonus from CON modifier. const getBaseHp = (ddbCharacter: DdbCharacter): number => { const conBonus = getStatBonus(ddbCharacter, CON); - const levels = ddbCharacter.classes.reduce( + const levels = ddbCharacter.classes?.reduce( (total, c) => total + c.level, 0, ); @@ -1042,3 +1042,26 @@ const convertSpellHigherLevels = (ddbSpell: DdbSpell): AlchemySpellAtHigherLevel } } */ + +const convertTrackers = (ddbCharacter: DdbCharacter): AlchemyTracker[] => { + return [ + { + name: 'XP', + category: AlchemyTrackerCategory.Experience, + color: 'Yellow', + max: 355000, + value: ddbCharacter.currentXp, + type: 'Bar', + sortOrder: 0, + }, + { + name: 'HP', + category: AlchemyTrackerCategory.Health, + color: 'Green', + max: getMaxHp(ddbCharacter), + value: getCurrentHp(ddbCharacter), + type: 'Bar', + sortOrder: 0, + }, + ]; +}; diff --git a/src/ddb.ts b/src/ddb.ts index 83f767b..1d62531 100644 --- a/src/ddb.ts +++ b/src/ddb.ts @@ -49,8 +49,8 @@ export interface DdbCharacter { gp: number; pp: number; }; - classes: DdbClass[]; - modifiers: { + classes?: DdbClass[]; + modifiers?: { race: DdbModifier[]; class: DdbModifier[]; item: DdbModifier[]; From 638524cd3ff30026060673d4f2d77de97d9ef2cc Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 18:33:03 -0800 Subject: [PATCH 07/13] =?UTF-8?q?=F0=9F=93=9D=20Update=20changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b001d8b..b56b754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Trackers (HP, XP) are now created in ddb2alchemy instead of in Alchemy itself. + ## [0.1.8] - 2023-10-12 ### Fixed From 43fa62ff2dfb11b0c137bf6dcfc7d17bbe41584e Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 21:04:15 -0800 Subject: [PATCH 08/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20to=20unio?= =?UTF-8?q?n=20types=20for=20tracker=20color,=20type,=20and=20category?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/alchemy.d.ts | 25 ++++++++++--------------- src/convert.ts | 5 ++--- test/convert.currentHp.test.ts | 13 +++---------- test/convert.exp.test.ts | 4 ++-- test/convert.maxHp.test.ts | 5 +---- 5 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/alchemy.d.ts b/src/alchemy.d.ts index 1668cee..4983e6d 100644 --- a/src/alchemy.d.ts +++ b/src/alchemy.d.ts @@ -144,21 +144,16 @@ interface AlchemyTracker { name: string; value: number; max: number; - color: string; - type: string; - category: AlchemyTrackerCategory | string | null; - _id?: string; + color: + | 'Blue' + | 'Green' + | 'Orange' + | 'Purple' + | 'Red' + | 'Theme Accent' + | 'Yellow'; + type: 'Bar' | 'Pip'; + category: 'health' | 'experience' | null; sortOrder?: number; readOnly?: boolean; } - -export enum AlchemyTrackerCategory { - /** - * Used to track hit points, temporary hit points, etc. - */ - Health = 'health', - /** - * Used to track experience points, experience to next level, etc. - */ - Experience = 'experience', -} diff --git a/src/convert.ts b/src/convert.ts index 414b0a4..6c97c21 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -13,7 +13,6 @@ import { AlchemyStat, AlchemyTextBlockSection, AlchemyTracker, - AlchemyTrackerCategory, } from './alchemy.d'; import { DDB_SPEED_EQUALS_RE, @@ -1047,7 +1046,7 @@ const convertTrackers = (ddbCharacter: DdbCharacter): AlchemyTracker[] => { return [ { name: 'XP', - category: AlchemyTrackerCategory.Experience, + category: 'experience', color: 'Yellow', max: 355000, value: ddbCharacter.currentXp, @@ -1056,7 +1055,7 @@ const convertTrackers = (ddbCharacter: DdbCharacter): AlchemyTracker[] => { }, { name: 'HP', - category: AlchemyTrackerCategory.Health, + category: 'health', color: 'Green', max: getMaxHp(ddbCharacter), value: getCurrentHp(ddbCharacter), diff --git a/test/convert.currentHp.test.ts b/test/convert.currentHp.test.ts index 75050b8..2503eb8 100644 --- a/test/convert.currentHp.test.ts +++ b/test/convert.currentHp.test.ts @@ -1,6 +1,5 @@ import { beforeEach, describe, expect, test } from '@jest/globals'; import { convertCharacter } from '../src'; -import { AlchemyTrackerCategory } from '../src/alchemy.d'; import { DdbCharacter, DdbStatType } from '../src/ddb'; import { DeepPartial } from './test-helpers'; @@ -39,9 +38,7 @@ describe('Convert DDB current HP to Alchemy current HP', () => { }); expect( - converted.trackers?.find( - (t) => t.category === AlchemyTrackerCategory.Health, - )?.value, + converted.trackers?.find((t) => t.category === 'health')?.value, ).toEqual(overrideHitPoints); }); @@ -83,9 +80,7 @@ describe('Convert DDB current HP to Alchemy current HP', () => { }); expect( - converted.trackers?.find( - (t) => t.category === AlchemyTrackerCategory.Health, - )?.value, + converted.trackers?.find((t) => t.category === 'health')?.value, ).toEqual(expected); }, ); @@ -104,9 +99,7 @@ describe('Convert DDB current HP to Alchemy current HP', () => { }); expect( - converted.trackers?.find( - (t) => t.category === AlchemyTrackerCategory.Health, - )?.value, + converted.trackers?.find((t) => t.category === 'health')?.value, ).toEqual(2); }); }); diff --git a/test/convert.exp.test.ts b/test/convert.exp.test.ts index b9683ee..0aaf9b2 100644 --- a/test/convert.exp.test.ts +++ b/test/convert.exp.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from '@jest/globals'; import { convertCharacter } from '../src'; -import { AlchemyTrackerCategory } from '../src/alchemy.d'; + import { DdbCharacter } from '../src/ddb'; import { DeepPartial } from './test-helpers'; @@ -21,7 +21,7 @@ describe('Convert DDB currentXp to Alchemy tracker', () => { }); const expTracker = converted.trackers?.find( - (t) => t.category === AlchemyTrackerCategory.Experience, + (t) => t.category === 'experience', ); expect(expTracker.value).toEqual(expected); diff --git a/test/convert.maxHp.test.ts b/test/convert.maxHp.test.ts index 0c8e668..cc8981b 100644 --- a/test/convert.maxHp.test.ts +++ b/test/convert.maxHp.test.ts @@ -1,6 +1,5 @@ import { beforeEach, describe, expect, test } from '@jest/globals'; import { convertCharacter } from '../src'; -import { AlchemyTrackerCategory } from '../src/alchemy.d'; import { DdbCharacter, DdbStatType } from '../src/ddb'; import { DeepPartial } from './test-helpers'; @@ -53,9 +52,7 @@ describe('Convert DDB maxHP to Alchemy maxHP', () => { }); expect( - converted.trackers?.find( - (t) => t.category === AlchemyTrackerCategory.Health, - )?.max, + converted.trackers?.find((t) => t.category === 'health')?.max, ).toEqual(expected); }, ); From c50e6e75b7992c29bd52879b052d093b83f7d459 Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 21:09:57 -0800 Subject: [PATCH 09/13] =?UTF-8?q?=F0=9F=94=A5=20Remove=20deprecated=20fiel?= =?UTF-8?q?ds=20for=20HP=20and=20exp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/alchemy.d.ts | 3 --- src/convert.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/alchemy.d.ts b/src/alchemy.d.ts index 4983e6d..d142ec9 100644 --- a/src/alchemy.d.ts +++ b/src/alchemy.d.ts @@ -4,9 +4,7 @@ export interface AlchemyCharacter { armorClass: number; copper?: number; classes: AlchemyClass[]; - currentHp: number; electrum?: number; - exp: number; eyes?: string; gold?: number; hair?: string; @@ -16,7 +14,6 @@ export interface AlchemyCharacter { items: AlchemyItem[]; isNPC: boolean; isSpellcaster: Boolean; - maxHp: number; movementModes: AlchemyMovementMode[]; name: string; platinum?: number; diff --git a/src/convert.ts b/src/convert.ts index 6c97c21..c91cc48 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -192,14 +192,11 @@ export const DEFAULT_ALCHEMY_CHARACTER: AlchemyCharacter = { abilityScores: [], armorClass: 0, classes: [], - currentHp: 0, - exp: 0, imageUri: '', initiativeBonus: 0, isNPC: false, isSpellcaster: false, items: [], - maxHp: 0, movementModes: [], name: '', proficiencies: [], From 61ed00691d91f6fcfaa32297d271c1bcaafe1fb4 Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Thu, 23 Nov 2023 21:11:40 -0800 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=94=96=20v0.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b56b754..51976b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.0] - 2023-11-24 + ### Changed - Trackers (HP, XP) are now created in ddb2alchemy instead of in Alchemy itself. diff --git a/package.json b/package.json index cbc1345..2a14ca5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ddb2alchemy", - "version": "0.1.8", + "version": "0.2.0", "description": "Convert D&D Beyond characters for use with the Alchemy VTT.", "main": "src/index.ts", "license": "MIT", From 6330e1d3fb8920059a46e6b0a828ebd6fe3a42dd Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Sun, 26 Nov 2023 20:07:04 -0800 Subject: [PATCH 11/13] =?UTF-8?q?=E2=9C=A8=20Set=20XP=20tracker=20to=20mor?= =?UTF-8?q?e=20sensible=20values=20for=20experience-based=20leveling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of 0 / 355000, set the XP tracker's current `value` to the amount of experience earned within the current level and the `max` to the experience required to reach the next level. --- src/convert.ts | 15 +++- src/fifth-edition.ts | 150 +++++++++++++++++++++++++++++++++++++++ test/convert.exp.test.ts | 33 +++++++-- 3 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 src/fifth-edition.ts diff --git a/src/convert.ts b/src/convert.ts index c91cc48..58b9b80 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -26,6 +26,11 @@ import { DdbSpell, DdbSpellActivationType, } from './ddb'; +import { + getBaseExperienceForLevel, + getExperienceRequiredForNextLevel, + getLevelFromExp, +} from './fifth-edition'; // Shared between both platforms const STR = 1; @@ -1040,13 +1045,19 @@ const convertSpellHigherLevels = (ddbSpell: DdbSpell): AlchemySpellAtHigherLevel */ const convertTrackers = (ddbCharacter: DdbCharacter): AlchemyTracker[] => { + const totalExp = ddbCharacter.currentXp; + const level = getLevelFromExp(totalExp); + const baseExp = getBaseExperienceForLevel(level); + const nextLevelExp = getExperienceRequiredForNextLevel(totalExp); + const expIntoLevel = totalExp - baseExp; + return [ { name: 'XP', category: 'experience', color: 'Yellow', - max: 355000, - value: ddbCharacter.currentXp, + max: nextLevelExp, + value: expIntoLevel, type: 'Bar', sortOrder: 0, }, diff --git a/src/fifth-edition.ts b/src/fifth-edition.ts new file mode 100644 index 0000000..217eb6e --- /dev/null +++ b/src/fifth-edition.ts @@ -0,0 +1,150 @@ +/** + * Returns the experience required for the next level given a 5e character's + * total experience. + * @param currentExp The character's current total experience. + * @returns The amount of experience required for the next level. + */ +export const getExperienceRequiredForNextLevel = ( + currentExp: number, +): number => { + if (currentExp < 300) { + return 300; + } else if (currentExp < 900) { + return 900; + } else if (currentExp < 2700) { + return 2700; + } else if (currentExp < 6500) { + return 6500; + } else if (currentExp < 14000) { + return 14000; + } else if (currentExp < 23000) { + return 23000; + } else if (currentExp < 34000) { + return 34000; + } else if (currentExp < 48000) { + return 48000; + } else if (currentExp < 64000) { + return 64000; + } else if (currentExp < 85000) { + return 85000; + } else if (currentExp < 100000) { + return 100000; + } else if (currentExp < 120000) { + return 120000; + } else if (currentExp < 140000) { + return 140000; + } else if (currentExp < 165000) { + return 165000; + } else if (currentExp < 195000) { + return 195000; + } else if (currentExp < 225000) { + return 225000; + } else if (currentExp < 265000) { + return 265000; + } else if (currentExp < 305000) { + return 305000; + } else if (currentExp < 355000) { + return 355000; + } else { + return NaN; + } +}; + +/** + * Returns the minimum experience required for a level given a 5e character's + * current level. + * @param currentLevel The character's current level. + * @returns The minimum amount of experience required for the level. + */ +export const getBaseExperienceForLevel = (level: number): number => { + if (level <= 1) { + return 0; + } else if (level === 2) { + return 300; + } else if (level === 3) { + return 900; + } else if (level === 4) { + return 2700; + } else if (level === 5) { + return 6500; + } else if (level === 6) { + return 14000; + } else if (level === 7) { + return 23000; + } else if (level === 8) { + return 34000; + } else if (level === 9) { + return 48000; + } else if (level === 10) { + return 64000; + } else if (level === 11) { + return 85000; + } else if (level === 12) { + return 100000; + } else if (level === 13) { + return 120000; + } else if (level === 14) { + return 140000; + } else if (level === 15) { + return 165000; + } else if (level === 16) { + return 195000; + } else if (level === 17) { + return 225000; + } else if (level === 18) { + return 265000; + } else if (level === 19) { + return 305000; + } else { + return 355000; + } +}; + +/** + * Returns the level corresponding to a given amount of experience. + * @param exp The character's current total experience. + * @returns The character's level. + */ +export const getLevelFromExp = (exp: number): number => { + if (exp < 300) { + return 1; + } else if (exp < 900) { + return 2; + } else if (exp < 2700) { + return 3; + } else if (exp < 6500) { + return 4; + } else if (exp < 14000) { + return 5; + } else if (exp < 23000) { + return 6; + } else if (exp < 34000) { + return 7; + } else if (exp < 48000) { + return 8; + } else if (exp < 64000) { + return 9; + } else if (exp < 85000) { + return 10; + } else if (exp < 100000) { + return 11; + } else if (exp < 120000) { + return 12; + } else if (exp < 140000) { + return 13; + } else if (exp < 165000) { + return 14; + } else if (exp < 195000) { + return 15; + } else if (exp < 225000) { + return 16; + } else if (exp < 265000) { + return 17; + } else if (exp < 305000) { + return 18; + } else if (exp < 355000) { + return 19; + } else { + return 20; + } +}; diff --git a/test/convert.exp.test.ts b/test/convert.exp.test.ts index 0aaf9b2..c2a07f5 100644 --- a/test/convert.exp.test.ts +++ b/test/convert.exp.test.ts @@ -6,12 +6,31 @@ import { DeepPartial } from './test-helpers'; describe('Convert DDB currentXp to Alchemy tracker', () => { test.each` - currentXp | expected - ${10} | ${10} - ${0} | ${0} + currentXp | expectedValue | expectedMax + ${0} | ${0} | ${300} + ${300} | ${0} | ${900} + ${600} | ${300} | ${900} + ${900} | ${0} | ${2700} + ${1500} | ${600} | ${2700} + ${2700} | ${0} | ${6500} + ${6500} | ${0} | ${14000} + ${14000} | ${0} | ${23000} + ${23000} | ${0} | ${34000} + ${34000} | ${0} | ${48000} + ${48000} | ${0} | ${64000} + ${64000} | ${0} | ${85000} + ${85000} | ${0} | ${100000} + ${100000} | ${0} | ${120000} + ${120000} | ${0} | ${140000} + ${140000} | ${0} | ${165000} + ${165000} | ${0} | ${195000} + ${195000} | ${0} | ${225000} + ${225000} | ${0} | ${265000} + ${265000} | ${0} | ${305000} + ${305000} | ${0} | ${355000} `( - 'returns tracker.value=$expected when currentXp=$currentXp', - ({ currentXp, expected }) => { + 'returns tracker.value=$expectedValue and tracker.max=$expectedMax when currentXp=$currentXp', + ({ currentXp, expectedValue, expectedMax }) => { const ddbChar: DeepPartial = { currentXp, }; @@ -24,8 +43,8 @@ describe('Convert DDB currentXp to Alchemy tracker', () => { (t) => t.category === 'experience', ); - expect(expTracker.value).toEqual(expected); - expect(expTracker.max).toEqual(355000); + expect(expTracker.value).toEqual(expectedValue); + expect(expTracker.max).toEqual(expectedMax); }, ); }); From a8554f640fb07eb37785836e38eec9a3f18a2d1c Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Sun, 26 Nov 2023 20:43:41 -0800 Subject: [PATCH 12/13] =?UTF-8?q?=F0=9F=91=94=20Adjust=20XP=20tracker=20to?= =?UTF-8?q?=20show=20`current=20total=20/=20next=20level`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of adjusting the value relative to the current level (which would look nice on the bar), this instead shows the current total experience as the value, and the next level as the max. This is closer to what D&D Beyond shows, although it has a nicer bar that shows the progress to the next level. We don't really have a way to do that solely with a tracker at the moment, though. --- src/convert.ts | 11 +---- src/fifth-edition.ts | 99 ---------------------------------------- test/convert.exp.test.ts | 39 ++++++++-------- 3 files changed, 21 insertions(+), 128 deletions(-) diff --git a/src/convert.ts b/src/convert.ts index 58b9b80..c1fd8f4 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -26,11 +26,7 @@ import { DdbSpell, DdbSpellActivationType, } from './ddb'; -import { - getBaseExperienceForLevel, - getExperienceRequiredForNextLevel, - getLevelFromExp, -} from './fifth-edition'; +import { getExperienceRequiredForNextLevel } from './fifth-edition'; // Shared between both platforms const STR = 1; @@ -1046,10 +1042,7 @@ const convertSpellHigherLevels = (ddbSpell: DdbSpell): AlchemySpellAtHigherLevel const convertTrackers = (ddbCharacter: DdbCharacter): AlchemyTracker[] => { const totalExp = ddbCharacter.currentXp; - const level = getLevelFromExp(totalExp); - const baseExp = getBaseExperienceForLevel(level); const nextLevelExp = getExperienceRequiredForNextLevel(totalExp); - const expIntoLevel = totalExp - baseExp; return [ { @@ -1057,7 +1050,7 @@ const convertTrackers = (ddbCharacter: DdbCharacter): AlchemyTracker[] => { category: 'experience', color: 'Yellow', max: nextLevelExp, - value: expIntoLevel, + value: totalExp, type: 'Bar', sortOrder: 0, }, diff --git a/src/fifth-edition.ts b/src/fifth-edition.ts index 217eb6e..25b934c 100644 --- a/src/fifth-edition.ts +++ b/src/fifth-edition.ts @@ -46,105 +46,6 @@ export const getExperienceRequiredForNextLevel = ( } else if (currentExp < 355000) { return 355000; } else { - return NaN; - } -}; - -/** - * Returns the minimum experience required for a level given a 5e character's - * current level. - * @param currentLevel The character's current level. - * @returns The minimum amount of experience required for the level. - */ -export const getBaseExperienceForLevel = (level: number): number => { - if (level <= 1) { return 0; - } else if (level === 2) { - return 300; - } else if (level === 3) { - return 900; - } else if (level === 4) { - return 2700; - } else if (level === 5) { - return 6500; - } else if (level === 6) { - return 14000; - } else if (level === 7) { - return 23000; - } else if (level === 8) { - return 34000; - } else if (level === 9) { - return 48000; - } else if (level === 10) { - return 64000; - } else if (level === 11) { - return 85000; - } else if (level === 12) { - return 100000; - } else if (level === 13) { - return 120000; - } else if (level === 14) { - return 140000; - } else if (level === 15) { - return 165000; - } else if (level === 16) { - return 195000; - } else if (level === 17) { - return 225000; - } else if (level === 18) { - return 265000; - } else if (level === 19) { - return 305000; - } else { - return 355000; - } -}; - -/** - * Returns the level corresponding to a given amount of experience. - * @param exp The character's current total experience. - * @returns The character's level. - */ -export const getLevelFromExp = (exp: number): number => { - if (exp < 300) { - return 1; - } else if (exp < 900) { - return 2; - } else if (exp < 2700) { - return 3; - } else if (exp < 6500) { - return 4; - } else if (exp < 14000) { - return 5; - } else if (exp < 23000) { - return 6; - } else if (exp < 34000) { - return 7; - } else if (exp < 48000) { - return 8; - } else if (exp < 64000) { - return 9; - } else if (exp < 85000) { - return 10; - } else if (exp < 100000) { - return 11; - } else if (exp < 120000) { - return 12; - } else if (exp < 140000) { - return 13; - } else if (exp < 165000) { - return 14; - } else if (exp < 195000) { - return 15; - } else if (exp < 225000) { - return 16; - } else if (exp < 265000) { - return 17; - } else if (exp < 305000) { - return 18; - } else if (exp < 355000) { - return 19; - } else { - return 20; } }; diff --git a/test/convert.exp.test.ts b/test/convert.exp.test.ts index c2a07f5..d3855ca 100644 --- a/test/convert.exp.test.ts +++ b/test/convert.exp.test.ts @@ -8,26 +8,25 @@ describe('Convert DDB currentXp to Alchemy tracker', () => { test.each` currentXp | expectedValue | expectedMax ${0} | ${0} | ${300} - ${300} | ${0} | ${900} - ${600} | ${300} | ${900} - ${900} | ${0} | ${2700} - ${1500} | ${600} | ${2700} - ${2700} | ${0} | ${6500} - ${6500} | ${0} | ${14000} - ${14000} | ${0} | ${23000} - ${23000} | ${0} | ${34000} - ${34000} | ${0} | ${48000} - ${48000} | ${0} | ${64000} - ${64000} | ${0} | ${85000} - ${85000} | ${0} | ${100000} - ${100000} | ${0} | ${120000} - ${120000} | ${0} | ${140000} - ${140000} | ${0} | ${165000} - ${165000} | ${0} | ${195000} - ${195000} | ${0} | ${225000} - ${225000} | ${0} | ${265000} - ${265000} | ${0} | ${305000} - ${305000} | ${0} | ${355000} + ${300} | ${300} | ${900} + ${900} | ${900} | ${2700} + ${2700} | ${2700} | ${6500} + ${6500} | ${6500} | ${14000} + ${14000} | ${14000} | ${23000} + ${23000} | ${23000} | ${34000} + ${34000} | ${34000} | ${48000} + ${48000} | ${48000} | ${64000} + ${64000} | ${64000} | ${85000} + ${85000} | ${85000} | ${100000} + ${100000} | ${100000} | ${120000} + ${120000} | ${120000} | ${140000} + ${140000} | ${140000} | ${165000} + ${165000} | ${165000} | ${195000} + ${195000} | ${195000} | ${225000} + ${225000} | ${225000} | ${265000} + ${265000} | ${265000} | ${305000} + ${305000} | ${305000} | ${355000} + ${355000} | ${355000} | ${0} `( 'returns tracker.value=$expectedValue and tracker.max=$expectedMax when currentXp=$currentXp', ({ currentXp, expectedValue, expectedMax }) => { From a429c8296114f52993ae45f69c1287801f847302 Mon Sep 17 00:00:00 2001 From: Isaac Overacker Date: Sun, 26 Nov 2023 23:19:33 -0800 Subject: [PATCH 13/13] =?UTF-8?q?=F0=9F=93=9D=20Update=20date=20on=20chang?= =?UTF-8?q?elog=20for=20v0.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51976b8..3b47438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.2.0] - 2023-11-24 +## [0.2.0] - 2023-11-26 ### Changed