diff --git a/docs/develop/en/migrations.md b/docs/develop/en/migrations.md
new file mode 100644
index 00000000..a8ebc4df
--- /dev/null
+++ b/docs/develop/en/migrations.md
@@ -0,0 +1,26 @@
+# What are data migrations for?
+
+Sometimes one may need to perform a concrete operation on every Document, that is, every actor or every item of a type. For instance, there are some cases in which the `template.json` needs to be updated, such as when adding/removing/renaming a property to a Document's (that is, an actor or item) data. However, Foundry might not implement automatically those changes as one needs:
+- When creating a new property, Foundry automatically adds the property to all existing documents of the chosen type, setting its value to the default value indicated on the `template.json`. However, it is not rare that we might want to calculate the value of the new property form some other data in the document.
+- When deleting a property, Foundry will remove it from documents but it is possible that we want to keep that value inside a different property or use it to transform another property.
+- Renaming a property can be seen as a mix of the two previous examples: first, one wants to add a *new* property (the renamed one) inside which one saves the value of the old-named property (i.e., one *calculates* the value of the new property from the value of the old one). Once the value is saved on the new property, one wants to delete the old-named property.
+
+For all this cases (and maybe more), data migrations are used. A data migration is just a mass update of Documents (of a type) to perform a transformation on the data they contain. There are several strategies one could use for implementing such a task (see e.g. [DnD](https://github.com/foundryvtt/dnd5e/blob/master/module/migration.mjs) or [Pathfinder](https://github.com/foundryvtt/pf2e/tree/be77d68bf011a6a4de40c44068a146579c73b4ff/src/module/migration) systems; see also this [YouTube](https://www.youtube.com/watch?v=Hl23n3MvtaI&t) video for a comprehensive discussion on the topic).
+
+
+# Our migration model
+
+> [!NOTE]
+> After this section, explaining how our migration model works, there is a short outline detailing the steps one must follow to add a new migration.
+
+We use a strategy inspired on that of Pathfinder system, simpler and adapted to our needs. Each migration must have an integer version number which will be used to keep track of the already applied migrations and is to be specified as an object implementing the [`Migration`](/src/module/migration/migrations/Migration.d.ts) interface.
+
+The whole system is implemented inside `/src/module/migration/migrate.js`. This module exports a function `applyMigrations()` which is called inside `Hooks.once('ready', ...)` in `/src/animabf.mjs`. A particular migration must be implemented on a module inside the `/src/module/migration/migrations/` folder whose name must start by a number followed by a meaningful description of the migration's purpose. Each migration module must export an object implementing the interface `Migration` defined inside `/src/module/migration/migrations/Migration.d.ts`, where there is documentation on the migration's elements.
+
+Finally, `/src/module/migration/migrations/index.js` allows using the `/src/module/migrations` module as a migration list, since it exports every migration in the system.
+
+# How to add a new migration
+
+1. Create a new migration file inside `/src/module/migration/migrations`. Its name should start by the migration number and be self-explaining; something like `42-purpose-of-this-migration.js`.
+2. Inside that file, write and export the migration object, implementing the transformations required for the migration.
+3. Export the migration object from the `/src/module/migration/migrations/index.js`.
diff --git a/docs/develop/es/es.md b/docs/develop/es/es.md
index 63228f6a..d14ec722 100644
--- a/docs/develop/es/es.md
+++ b/docs/develop/es/es.md
@@ -75,4 +75,5 @@ b) Para continuar tu trabajo o el trabajo de otro: Lo mismo que lo anterior pero
- [Cómo publicar una nueva versión del sistema](publish-new-version.md)
- [Cómo crear un nuevo tipo de item](add-new-item.md)
+- [Cómo crear una migración de datos](./migrations.md)
- [Test en Cypress](cypress_integration_tests.md)
diff --git a/docs/develop/es/migrations.md b/docs/develop/es/migrations.md
new file mode 100644
index 00000000..ee338c05
--- /dev/null
+++ b/docs/develop/es/migrations.md
@@ -0,0 +1,27 @@
+# Para qué sirven las migraciones?
+
+En ocasiones, es necesario realizar operaciones concretas en cada Documento (es decir, en cada actor o cada item de un tipo). Por ejemplo, hay algunos casos en los que el archivo `template.json` necesita actualizarse, como cuando se añade/elimina/renombra una propiedad en los datos de un Documento (i.e., un actor o item). Sin embargo, Foundry en general no implementará los cambios deseados automáticamente:
+- Al crear una propiedad nueva, Foundry la crea automáticamente en todos los Documentos existentes del tipo correspondiente, estableciendo como valor para la misma el valor *por defecto* que se indica en el `template.json`. Sin embargo, no es raro calcular el nuevo valor en función de otras propiedades en el documento.
+- Al borrar una propiedad, Foundry la eliminará de los documentos pero es posible que queramos mantener el valor correspondiente dentro de otra propiedad, ya sea tal cual estaba o transformándolo en el proceso.
+- Renombrar una propiedad puede verse como una concatenación de los dos ejemplos anteriores: primero, queremos añadir una *nueva* propiedad (la que tiene el nuevo nombre), en la cual guardaremos el valor de la propiedad *vieja* (la que tiene el nombre anterior). Esto es, se *calcula* el valor de la nueva propiedad en función de la propiedad anterior. Una vez el valor está a salvo en la nueva propiedad, podemos eliminar la propiedad antigua.
+
+Para todos estos casos (y probablemente más), se usan las migraciones de datos. Una migración de datos es simplemente una actualización masiva de Documentos (de un tipo dado) para realizar una transformación de los datos que contienen. Hay distintas estrategias que podrían usarse para implementar esta tarea (ver, por ejemplo, los sistemas para [DnD](https://github.com/foundryvtt/dnd5e/blob/master/module/migration.mjs) o [Pathfinder](https://github.com/foundryvtt/pf2e/tree/be77d68bf011a6a4de40c44068a146579c73b4ff/src/module/migration); también este [vídeo de Youtube](https://www.youtube.com/watch?v=Hl23n3MvtaI&t) para una discusión clara del tema).
+
+
+# Nuestro modelo de migraciones
+
+> [!NOTE]
+> Tras esta sección, que explica cómo funciona nuestro modelo de migraciones, hay un esquema detallando los pasos a seguir para añadir una nueva migración al sistema.
+
+Usamos una estrategia inspirada en la que usan en el sistema de Pathfinder, simplificada y adaptada a nuestras necesidades. Cada migración debe tener un número (entero) de versión, que se usará para tener en cuenta qué migraciones ya han sido aplicadas y cuales no. Cada migración se especificará en un objeto que implementará la interfaz [`Migration`](/src/module/migration/migrations/Migration.d.ts).
+
+Todo el sistema de migraciones está implementado en [`/src/module/migration/migrate.js`](/src/module/migration/migrate.js). Dicho módulo exporta una función `applyMigrations()` que es llamada dentro de `Hooks.once('ready', ...)` en [`/src/animabf.mjs`](/src/animabf.mjs). Cada migración concreta debe ser implementada en un módulo dentro de la carpeta `/src/module/migration/migrations/`, cuyo nombre debe comenzar por un número entero seguido de un nombre que describa el propósito de la migración. Cada módulo de migración debe exportar un objeto implementando la interfaz `Migration` definida en [`/src/module/migration/migrations/Migration.d.ts`](/src/module/migration/migrations/Migration.d.ts) (allí se encuentran documentados los elementos que debe contener dicho objeto).
+
+Finalmente, [`/src/module/migration/migrations/index.js`](/src/module/migration/migrations/index.js) permite usar el módulo `/src/module/migrations` como una lista de migraciones, dado que debe exportar todas las migraciones del sistema.
+
+
+# Cómo añadir una migración nueva
+
+1. Crear un nuevo archivo de migración dentro de `/src/module/migration/migrations`. El nombre debe empezar por el número de la migración y ser autoexplicativo; algo como `42-purpose-of-this-migration.js`.
+2. Dentro de ese archivo, definir y exportar el objeto de la de la migración, que implementará las transformaciones requeridas para la migración de datos.
+3. Exportar el archivo de migración desde el archivo `/src/module/migration/migrations/index.js`.
diff --git a/docs/develop/fr/migrations.md b/docs/develop/fr/migrations.md
new file mode 100644
index 00000000..e093fcca
--- /dev/null
+++ b/docs/develop/fr/migrations.md
@@ -0,0 +1,26 @@
+# A quoi servent les migrations ?
+
+Parfois il est nécessaire de réaliser des opérations concrètes sur chaque Document, c'est à dire chaque actor ou chaque item d'un type donné. Par exemple, il y a certains cas où il est nécessaire de mettre à jour le fichier `template.json`, comme lorsque l'on ajoute/supprime/renomme une propriété aux données d'un Document (i.e, un actor ou item). Cependant, en général, Foundry n'implémentera pas automatiquement les changements souhaités :
+- A la création d'une nouvelle propriété, Foundry l'ajoute automatiquement à tous les documents d'un type donné, définissant sa valeur à la valeur par défaut indiquée dans le `template.json`. Cependant, il n'est pas rare que nous souhaitions calculer la valeur de cette nouvelle propriété en fonction d'autres propriétés dans le document.
+- A la suppression d'une propriété, Foundry va la retirer des documents mais il est possible que nous souhaitions conserver cette valeur à l'intéreur d'une autre propriété ou l'utiliser pour transformer une autre propriété.
+- Renommer une propriété peut se voir comme une combinaison des deux exemples précédents: premièrement, nous voulons ajouter une *nouvelle* propriété (celle avec le nouveau nom) dans laquelle nous stockons la valeur de l'ancienne propriété (celle avec l'ancien nom). Autrement dit, la valeur de la nouvelle propriété est calculée en fonction de l'ancienne propriété. Une fois que la valeur est en sécurité dans la nouvelle propriété, nous pouvons supprimer l'ancienne propriété.
+
+Pour tous ces cas (et probablement d'autres), les migrations de données sont utilisées. Une migration de données est simplement une mise à jour massive de documents (d'un type donné) pour effectuer une transformation des données qu'ils contiennent. Il existe différentes stratégies qui pourraient être utilisées pour mettre en œuvre cette tâche (voir par exemple, les systèmes pour [DnD](https://github.com/foundryvtt/dnd5e/blob/master/module/migration.mjs) ou [Pathfinder](https://github.com/foundryvtt/pf2e/tree/be77d68bf011a6a4de40c44068a146579c73b4ff/src/module/migration); voir également la [vidéo YouTube](https://www.youtube.com/watch?v=Hl23n3MvtaI&t) pour une discussion claire sur le sujet.
+
+
+# Notre modèle de migrations
+
+> [!NOTE]
+> Après cette section, expliquant comment notre modèle de migration fonctionne, il y a un court schèma qui détaille les étapes nécessaires requises pour ajouter une nouvelle migration.
+
+Nous utilisons une stratégie inspirée de celle utilisée par le système Pathfinder, plus simple et adaptée à nos besoins. Chaque migration doit avoir un numéro (entier) de version qui sera utilisée pour garder une trace des migrations déjà appliquées et celles qui ne l'ont pas encore été. Chaque migration est spécifiée dans un objet qui implémente l'interface [`Migration`](/src/module/migration/migrations/Migration.d.ts).
+
+Le système entier est implémenté dans `/src/module/migration/migrate.js`. Ce module exporte une fonction `applyMigrations()` qui est appelée par `Hooks.once('ready', ...)` dans le fichier `/src/animabf.mjs`. Une migration spécifique doit être implémentée à l'intérieur du répertoire `/src/module/migration/migrations/` doit commencer par un nombre suivi par une description significative de l'objectif de la migration. Chaque migration doit exporter un objet implémentant l'interface `Migration` définie à l'intérieur de `/src/module/migration/migrations/Migration.d.ts`, où se trouvent les éléments documentant la migration.
+
+Enfin, `/src/module/migration/migrations/index.js` permet d'utiliser le module `/src/module/migrations` comme une liste de migrations, car il doit exporter toutes les migrations du système.
+
+# Comment ajouter une nouvelle migration
+
+1. Créez un nouveau fichier de migrations dans `/src/module/migration/migrations`. Son nom doit commencer par son numéro de migration et s'expliquer par lui-même; comme ci-après `42-purpose-of-this-migration.js`.
+2. A l'intérieur de ce fichier, écrivez et exportez l'objet de la migration, implémentant les transformations requises pour la migration des données.
+3. Exportez l'objet de la migration à partir du fichier `/src/module/migration/migrations/index.js`.
diff --git a/src/animabf.mjs b/src/animabf.mjs
index a599164b..c13cca25 100644
--- a/src/animabf.mjs
+++ b/src/animabf.mjs
@@ -10,6 +10,7 @@ import { ABFConfig } from './module/ABFConfig';
import ABFItem from './module/items/ABFItem';
import { registerCombatWebsocketRoutes } from './module/combat/websocket/registerCombatWebsocketRoutes';
import { attachCustomMacroBar } from './utils/attachCustomMacroBar';
+import { applyMigrations } from './module/migration/migrate';
/* ------------------------------------ */
/* Initialize system */
@@ -67,6 +68,8 @@ Hooks.once('ready', () => {
registerCombatWebsocketRoutes();
attachCustomMacroBar();
+
+ applyMigrations();
});
// Add any additional hooks if necessary
diff --git a/src/lang/en.json b/src/lang/en.json
index b9a13712..2c6ab287 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -493,6 +493,10 @@
"dialogs.accept": "Accept",
"dialogs.cancel": "Cancel",
"dialogs.continue": "Continue",
+ "dialogs.migrations.title": "Migration available",
+ "dialogs.migrations.content": "A migration is about to start. To prevent data loss, please back up your data folder befor applying the migration. Are you ready to continue?",
+ "dialogs.migrations.success": "Migration #{version} \"{title}\" has been successfully applied.",
+ "dialogs.migrations.error": "Migration #{version} has failed to apply:
{error}",
"dialogs.items.advantage.content": "Advantage name",
"dialogs.items.ammo.content": "Ammo name",
"dialogs.items.armors.content": "Armor name",
diff --git a/src/lang/es.json b/src/lang/es.json
index 354bbf2b..146791d8 100644
--- a/src/lang/es.json
+++ b/src/lang/es.json
@@ -494,6 +494,10 @@
"dialogs.accept": "Aceptar",
"dialogs.cancel": "Cancelar",
"dialogs.continue": "Continuar",
+ "dialogs.migrations.title": "Migración disponible",
+ "dialogs.migrations.content": "Está a punto de comenzar una migración. Para evitar pérdidas de datos, haga una copia de seguridad de su carpeta de datos antes de continuar. ¿Desea continuar con la migración?",
+ "dialogs.migrations.success":"La migración #{version} \"{title}\" se ha aplicado con éxito.",
+ "dialogs.migrations.error": "La migración #{version} ha fallado con el error:
{error}",
"dialogs.items.advantage.content": "Nombre de la ventaja",
"dialogs.items.ammo.content": "Nombre de la munición",
"dialogs.items.armors.content": "Nombre de la armadura",
diff --git a/src/lang/fr.json b/src/lang/fr.json
index c51ac9f3..5ef5cfd0 100644
--- a/src/lang/fr.json
+++ b/src/lang/fr.json
@@ -495,6 +495,10 @@
"dialogs.accept": "Accepter",
"dialogs.cancel": "Annuler",
"dialogs.continue": "Continuer",
+ "dialogs.migrations.title": "Migration available",
+ "dialogs.migrations.content": "A migration is about to start. To prevent data loss, please back up your data folder befor applying the migration. Are you ready to continue?",
+ "dialogs.migrations.success": "Migration #{version} \"{title}\" has been successfully applied.",
+ "dialogs.migrations.error": "Migration #{version} has failed to apply:
{error}",
"dialogs.items.advantage.content": "Nom de l'Avantage",
"dialogs.items.ammo.content": "Nom des Munitions",
"dialogs.items.armors.content": "Nom de l'Armure",
diff --git a/src/module/actor/ABFActor.ts b/src/module/actor/ABFActor.ts
index eb8c4691..343eb259 100644
--- a/src/module/actor/ABFActor.ts
+++ b/src/module/actor/ABFActor.ts
@@ -26,7 +26,7 @@ export class ABFActor extends Actor {
`Upgrading actor ${this.name} (${this._id}) from version ${this.system.version} to ${INITIAL_ACTOR_DATA.version}`
);
- this.updateSource({ version: INITIAL_ACTOR_DATA.version });
+ this.updateSource({ 'system.version': INITIAL_ACTOR_DATA.version });
}
}
diff --git a/src/module/actor/constants.js b/src/module/actor/constants.js
index 5ac5217e..b39cfc15 100644
--- a/src/module/actor/constants.js
+++ b/src/module/actor/constants.js
@@ -1,4 +1,3 @@
-/** @type {import("../types/Actor").ABFActorDataSourceData} */
export const INITIAL_ACTOR_DATA = {
version: 1,
ui: {
diff --git a/src/module/dialogs/ABFDialogs.ts b/src/module/dialogs/ABFDialogs.ts
index a03b76cc..dacd56f4 100644
--- a/src/module/dialogs/ABFDialogs.ts
+++ b/src/module/dialogs/ABFDialogs.ts
@@ -11,15 +11,15 @@ export const ABFDialogs = {
body: string,
{ onConfirm, onCancel }: { onConfirm?: () => void; onCancel?: () => void } = {}
) =>
- new Promise(resolve => {
+ new Promise(resolve => {
new ConfirmationDialog(title, body, {
onConfirm: () => {
onConfirm?.();
- resolve();
+ resolve("confirm");
},
onCancel: () => {
onCancel?.();
- resolve();
+ resolve("cancel");
}
});
})
diff --git a/src/module/dialogs/ConfirmationDialog.ts b/src/module/dialogs/ConfirmationDialog.ts
index e5377ae7..3ee397dc 100644
--- a/src/module/dialogs/ConfirmationDialog.ts
+++ b/src/module/dialogs/ConfirmationDialog.ts
@@ -17,7 +17,7 @@ export class ConfirmationDialog extends GenericDialog {
class: 'confirmation-dialog',
content: `
${title}
- ${body}
+ ${body}
`,
buttons: [
{ id: 'on-cancel-button', fn: onCancel, content: (game as Game).i18n.localize('dialogs.cancel') },
diff --git a/src/module/migration/migrate.js b/src/module/migration/migrate.js
new file mode 100644
index 00000000..dedcc4a0
--- /dev/null
+++ b/src/module/migration/migrate.js
@@ -0,0 +1,168 @@
+import { ABFSettingsKeys } from '../../utils/registerSettings';
+import { ABFActor } from '../actor/ABFActor';
+import { ABFDialogs } from '../dialogs/ABFDialogs';
+import * as MigrationList from './migrations';
+
+/** @typedef {import('./migrations/Migration').Migration} Migration */
+
+/**
+ * @param {Migration} migration
+ * @returns {boolean} Wether the migration applies or not.
+ */
+function migrationApplies(migration) {
+ /** @type {number} */
+ const currentVersion = game.settings.get(
+ 'animabf',
+ ABFSettingsKeys.SYSTEM_MIGRATION_VERSION
+ );
+ if (currentVersion < migration.version) {
+ return true;
+ }
+ if (game.settings.get('animabf', ABFSettingsKeys.DEVELOP_MODE)) {
+ console.warn(
+ `AnimaBF | Migration ${migration.version} needs not to be applied, current system migration version is ${currentVersion}.`
+ );
+ }
+ return false;
+}
+
+/**
+ * @param {Migration} migration
+ */
+async function migrateAllActors(migration) {
+ if (migration.updateActor) {
+ // migrate world actors and unlinked token actors
+ const unlinkedTokenActors = game.scenes
+ .map(scene =>
+ scene.tokens
+ .filter(token => !token.actorLink && token.actor)
+ .map(token => token.actor)
+ )
+ .flat();
+
+ // add the actors in the compendia
+ const actorPacks = await Promise.all(
+ game.packs
+ .filter(pack => pack.metadata.type === 'Actor')
+ .map(async actorPack => {
+ const packObj = { pack: actorPack, wasLocked: actorPack.locked };
+ await actorPack.configure({ locked: false });
+ return packObj;
+ })
+ );
+ const compendiaActors = (
+ await Promise.all(
+ actorPacks.map(packObject => {
+ return packObject.pack.getDocuments();
+ })
+ )
+ ).flat();
+
+ /** @type {ABFActor[]} */
+ const actors = [...game.actors, ...unlinkedTokenActors, ...compendiaActors];
+
+ for (const actor of actors) {
+ console.log(`AnimaBF | Migrating actor ${actor.name} (${actor.id}).`);
+ const system = (await migration.updateActor(actor)).system;
+ await actor.update({ system });
+ }
+
+ // Lock again packs which where locked
+ actorPacks
+ .filter(packObject => packObject.wasLocked)
+ .forEach(async packObject => {
+ await packObject.pack.configure({ locked: true });
+ });
+ }
+}
+
+/**
+ * @param {Migration} migration
+ * @todo Implement this function
+ */
+function migrateItems(migration) {
+ if (migration.preUpdateItem || migration.updateItem) {
+ throw new Error(
+ 'AnimaBF | Trying to update items with a migration, but `migrateItems()` function in `migrate.js` not defined yet'
+ );
+ }
+}
+
+/**
+ * @param {Migration} migration
+ * @todo Implement this function
+ */
+function migrateTokens(migration) {
+ if (migration.updateToken) {
+ throw new Error(
+ 'AnimaBF | Trying to update tokens with a migration, but `migrateTokens()` function in `migrate.js` not defined yet'
+ );
+ }
+}
+
+/**
+ * @param {Migration} migration - The migration version to be applied
+ * @returns {Promise} - Whether the migration has been succesfully applied or not.
+ */
+async function applyMigration(migration) {
+ try {
+ console.log(`AnimaBF | Applying migration ${migration.version}.`);
+
+ await migrateAllActors(migration);
+ migrateItems(migration);
+ migrateTokens(migration);
+
+ console.log(`AnimaBF | Migration ${migration.version} completed.`);
+ game.settings.set(
+ 'animabf',
+ ABFSettingsKeys.SYSTEM_MIGRATION_VERSION,
+ migration.version
+ );
+ // TODO: add french translation for the warning dialog also.
+ await ABFDialogs.prompt(
+ game.i18n.format('dialogs.migrations.success', {
+ version: migration.version,
+ title: migration.title
+ })
+ );
+ return true;
+ } catch (err) {
+ console.error(
+ `AnimaBF | Error when trying to apply migration ${migration.version}:\n${err}`
+ );
+ await ABFDialogs.prompt(
+ game.i18n.format('dialogs.migrations.error', {
+ version: migration.version,
+ error: err
+ })
+ );
+ }
+ return false;
+}
+
+/** This is the only function on the script meant to be called from outside the script */
+export async function applyMigrations() {
+ if (!game.user.isGM) {
+ return;
+ }
+
+ const migrations = Object.values(MigrationList).filter(migration =>
+ migrationApplies(migration)
+ );
+ migrations.sort((a, b) => a.version - b.version);
+
+ for (const migration of migrations) {
+ const result = await ABFDialogs.confirm(
+ game.i18n.localize('dialogs.migrations.title'),
+ `${game.i18n.localize('dialogs.migrations.content')}
` +
+ 'Details of the migration (only English available):
' +
+ `Title: ${migration.title}
` +
+ `Description: ${migration.description}`
+ );
+ if (result === 'confirm') {
+ await applyMigration(migration);
+ } else {
+ break;
+ }
+ }
+}
diff --git a/src/module/migration/migrations/1-fix-defaul-fumble.js b/src/module/migration/migrations/1-fix-defaul-fumble.js
new file mode 100644
index 00000000..c904f869
--- /dev/null
+++ b/src/module/migration/migrations/1-fix-defaul-fumble.js
@@ -0,0 +1,19 @@
+/** @typedef {import('./Migration').Migration} Migration
+
+/** @type Migration */
+export const Migration1DefaultFumble = {
+ version: 1,
+ title: 'Reseting default values for fumbles and open range in character sheets',
+ description: `This Migration changes only actors, implementing the following changes:
+ 1. If 'system.settings.fumbles.value' is equal to 0, then it is set to the right default value: 3.
+ 2. If 'system.settings.openRolls.value' is equal to 0, then it is set to the right default value: 90.`,
+ updateActor(actor) {
+ if (actor.system.general.settings.fumbles.value === 0) {
+ actor.system.general.settings.fumbles.value = 3;
+ }
+ if (actor.system.general.settings.openRolls.value === 0) {
+ actor.system.general.settings.openRolls.value = 90;
+ }
+ return actor;
+ }
+};
diff --git a/src/module/migration/migrations/Migration.d.ts b/src/module/migration/migrations/Migration.d.ts
new file mode 100644
index 00000000..fdc3e28e
--- /dev/null
+++ b/src/module/migration/migrations/Migration.d.ts
@@ -0,0 +1,75 @@
+import { ABFItemBaseDataSource } from '../../../animabf.types';
+import { TokenData } from '../../../types/foundry-vtt-types/src/foundry/common/data/module.mjs';
+import { ABFActor } from '../../actor/ABFActor';
+import ABFItem from '../../items/ABFItem';
+import { ABFActorDataSourceData } from '../../types/Actor';
+
+/**
+ * This is the base class for a migration, coppied and modified from pf2e system.
+ * If you make a change to the database schema (i.e. anything in template.json),
+ * you probably should create a migration. To do so, there are several steps:
+ * - Make a class that inherits this base class and implements `updateActor` or `updateItem` using a
+ * new value for the version
+ * - Add this class to getAllMigrations() in src/module/migrations/index.js
+ * - Test that your changes work.
+ */
+export interface Migration {
+ /**
+ * This is the migration version.
+ */
+ readonly version: number;
+
+ /**
+ * This is a short title describing the purpose of the migration.
+ * The title is displayed on the warning dialog before applying the migration.
+ */
+ readonly title: string,
+
+ /**
+ * This is a longer description of the changes in the migration, supposed to be detailed but concise.
+ * To be displayed on the warning dialog before applying the migration.
+ */
+ readonly description: string,
+
+ /**
+ * Update the actor to the latest schema version.
+ * @param actor - The actor to migrate
+ * @returns The actor after the changes in the migration.
+ */
+ updateActor?(actor: ABFActor): ABFActor | Promise;
+
+ /**
+ * Update the item to the latest schema version, handling changes that must happen before any other migration in a
+ * given list.
+ * @param item - Item to update.
+ * @param actor - If the item is part of an actor, this is set to the actor itself
+ * @returns The item after the changes in the migration
+ */
+ preUpdateItem?(item: ABFItem, actor?: ABFActor): ABFItem | Promise;
+
+ /**
+ * Update the item to the latest schema version.
+ * @param item - Item to update.
+ * @param actor - If the item is part of an actor, this is set to the actor itself
+ * @returns The item after the changes in the migration
+ */
+ updateItem?(item: ABFItem, actor?: ABFActor): ABFItem | Promise;
+
+ /**
+ * Update the token to the latest schema version.
+ * @param tokenData Token data to update. This should be a `TokenData` from the previous version.
+ * @returns The updated version of `TokenData`.
+ */
+ updateToken?(
+ tokenData: TokenData,
+ actor: Readonly,
+ scene: Readonly
+ ): TokenData;
+
+ /**
+ * Run migrations for this schema version.
+ * Sometimes there needs to be custom steps run during a migration. For instance, if the change
+ * isn't actor or item related. This function will be called during the migration.
+ */
+ migrate?(): void;
+}
diff --git a/src/module/migration/migrations/index.js b/src/module/migration/migrations/index.js
new file mode 100644
index 00000000..797dac38
--- /dev/null
+++ b/src/module/migration/migrations/index.js
@@ -0,0 +1 @@
+export { Migration1DefaultFumble } from './1-fix-defaul-fumble';
diff --git a/src/module/types/Actor.d.ts b/src/module/types/Actor.d.ts
new file mode 100644
index 00000000..8d7e4992
--- /dev/null
+++ b/src/module/types/Actor.d.ts
@@ -0,0 +1,3 @@
+import { INITIAL_ACTOR_DATA } from "../actor/constants";
+
+export type ABFActorDataSourceData = typeof INITIAL_ACTOR_DATA;
diff --git a/src/module/types/Actor.ts b/src/module/types/Actor.ts
deleted file mode 100644
index 4a0652ef..00000000
--- a/src/module/types/Actor.ts
+++ /dev/null
@@ -1 +0,0 @@
-export type ABFActorDataSourceData = any;
diff --git a/src/packs/armors/LOG b/src/packs/armors/LOG
deleted file mode 100644
index d1389bda..00000000
--- a/src/packs/armors/LOG
+++ /dev/null
@@ -1 +0,0 @@
-2023/08/03-12:26:23.160508 7f84d77fe700 Delete type=3 #1
diff --git a/src/packs/magic/LOG b/src/packs/magic/LOG
deleted file mode 100644
index 00346446..00000000
--- a/src/packs/magic/LOG
+++ /dev/null
@@ -1 +0,0 @@
-2023/08/03-12:26:23.171296 7f84d6ffd700 Delete type=3 #1
diff --git a/src/packs/npcs/000003.log b/src/packs/npcs/000003.log
deleted file mode 100644
index 6d27f8e8..00000000
Binary files a/src/packs/npcs/000003.log and /dev/null differ
diff --git a/src/packs/npcs/000005.ldb b/src/packs/npcs/000005.ldb
new file mode 100644
index 00000000..9841291f
Binary files /dev/null and b/src/packs/npcs/000005.ldb differ
diff --git a/src/packs/npcs/000006.log b/src/packs/npcs/000006.log
new file mode 100644
index 00000000..d74cc929
Binary files /dev/null and b/src/packs/npcs/000006.log differ
diff --git a/src/packs/npcs/CURRENT b/src/packs/npcs/CURRENT
index 1a848522..cacca757 100644
--- a/src/packs/npcs/CURRENT
+++ b/src/packs/npcs/CURRENT
@@ -1 +1 @@
-MANIFEST-000002
+MANIFEST-000004
diff --git a/src/packs/npcs/LOG b/src/packs/npcs/LOG
deleted file mode 100644
index dcac0286..00000000
--- a/src/packs/npcs/LOG
+++ /dev/null
@@ -1 +0,0 @@
-2023/08/03-12:26:23.237511 7f84d67fc700 Delete type=3 #1
diff --git a/src/packs/npcs/MANIFEST-000002 b/src/packs/npcs/MANIFEST-000002
deleted file mode 100644
index bbbc5856..00000000
Binary files a/src/packs/npcs/MANIFEST-000002 and /dev/null differ
diff --git a/src/packs/npcs/MANIFEST-000004 b/src/packs/npcs/MANIFEST-000004
new file mode 100644
index 00000000..0e5c63c3
Binary files /dev/null and b/src/packs/npcs/MANIFEST-000004 differ
diff --git a/src/packs/psychic_powers/LOG b/src/packs/psychic_powers/LOG
deleted file mode 100644
index c06425ba..00000000
--- a/src/packs/psychic_powers/LOG
+++ /dev/null
@@ -1 +0,0 @@
-2023/08/03-12:26:23.180681 7f84d5ffb700 Delete type=3 #1
diff --git a/src/packs/weapons/LOG b/src/packs/weapons/LOG
deleted file mode 100644
index 31e3a51e..00000000
--- a/src/packs/weapons/LOG
+++ /dev/null
@@ -1 +0,0 @@
-2023/08/03-12:26:23.149886 7f84d67fc700 Delete type=3 #1
diff --git a/src/system.json b/src/system.json
index f2f91c2d..d8d64cc3 100644
--- a/src/system.json
+++ b/src/system.json
@@ -2,7 +2,7 @@
"id": "animabf",
"title": "Anima Beyond Fantasy",
"description": "Unofficial Anima Beyond Fantasy system for Foundry VTT",
- "version": "1.17.1-beta1",
+ "version": "1.17.1-beta3",
"compatibility": { "minimum": "11", "verified": "11.315" },
"url": "https://github.com/AnimaBeyondDevelop/AnimaBeyondFoundry",
"manifest": "https://raw.githubusercontent.com/AnimaBeyondDevelop/AnimaBeyondFoundry/main/src/system.json",
diff --git a/src/template.json b/src/template.json
index 8903c0d0..d3e28dd8 100644
--- a/src/template.json
+++ b/src/template.json
@@ -13,8 +13,8 @@
},
"general": {
"settings": {
- "openRolls": { "value": 0 },
- "fumbles": { "value": 0 },
+ "openRolls": { "value": 90 },
+ "fumbles": { "value": 3 },
"openOnDoubles": { "value": false },
"defenseType": { "value": "" }
},
diff --git a/src/utils/registerSettings.ts b/src/utils/registerSettings.ts
index dabc1ef6..a15328e0 100644
--- a/src/utils/registerSettings.ts
+++ b/src/utils/registerSettings.ts
@@ -3,7 +3,8 @@ export enum ABFSettingsKeys {
ROUND_DAMAGE_IN_MULTIPLES_OF_5 = 'ROUND_DAMAGE_IN_MULTIPLES_OF_5',
SEND_ROLL_MESSAGES_ON_COMBAT_BY_DEFAULT = 'SEND_ROLL_MESSAGES_ON_COMBAT_BY_DEFAULT',
USE_DAMAGE_TABLE = 'USE_DAMAGE_TABLE',
- DEVELOP_MODE = 'DEVELOP_MODE'
+ DEVELOP_MODE = 'DEVELOP_MODE',
+ SYSTEM_MIGRATION_VERSION = 'SYSTEM_MIGRATION_VERSION'
}
export const registerSettings = () => {
@@ -53,4 +54,12 @@ export const registerSettings = () => {
default: false,
type: Boolean
});
+
+ // This is for migration purposes, it stores the last migration version runned for the world.
+ typedGame.settings.register('animabf', ABFSettingsKeys.SYSTEM_MIGRATION_VERSION, {
+ config: false,
+ scope: 'world',
+ type: Number,
+ default: 0
+ });
};