import { RdDBaseActor } from "./actor/base-actor.js"; import { LOG_HEAD, SYSTEM_RDD } from "./constants.js"; import { Grammar } from "./grammar.js"; import { Monnaie } from "./item-monnaie.js"; import { RdDItem, TYPES } from "./item.js"; import { RdDTimestamp } from "./time/rdd-timestamp.js"; import { RdDRaretes } from "./item/raretes.js"; class Migration { get code() { return "sample"; } get version() { return "0.0.0"; } async migrate() { } async applyItemsUpdates(computeUpdates) { await game.actors.forEach(async (actor) => { const actorItemUpdates = computeUpdates(actor.items).filter(it => it != undefined); if (actorItemUpdates.length > 0) { console.log( this.code, `Applying updates on actor ${actor.name} items`, actorItemUpdates ); await actor.updateEmbeddedDocuments("Item", actorItemUpdates); } }); const itemUpdates = computeUpdates(game.items).filter(it => it != undefined); if (itemUpdates.length > 0) { console.log(this.code, "Applying updates on items", itemUpdates); await Item.updateDocuments(itemUpdates); } } } class _1_5_34_migrationPngWebp { get code() { return "migrationPngWebp"; } get version() { return "1.5.34"; } async migrate() { const regexOldPngJpg = /(systems\/foundryvtt-reve-de-dragon\/icons\/.*)\.(png|jpg)/; const replaceWithWebp = '$1.webp'; function convertImgToWebp(img) { return img.replace(regexOldPngJpg, replaceWithWebp); } function prepareDocumentsImgUpdate(documents) { return documents.filter(it => it.img && it.img.match(regexOldPngJpg)) .map(it => { return { _id: it.id, img: convertImgToWebp(it.img) } }); } const itemsUpdates = prepareDocumentsImgUpdate(game.items); const actorsUpdates = prepareDocumentsImgUpdate(game.actors); //Migrate system png to webp await Item.updateDocuments(itemsUpdates); await Actor.updateDocuments(actorsUpdates); game.actors.forEach(actor => { if (actor.token?.img && actor.token.img.match(regexOldPngJpg)) { actor.update({ "token.img": convertImgToWebp(actor.token.img) }); } const actorItemsToUpdate = prepareDocumentsImgUpdate(actor.items); actor.updateEmbeddedDocuments('Item', actorItemsToUpdate); }); } } class _10_0_16_MigrationSortsReserve extends Migration { get code() { return "creation-item-sort-reserve"; } get version() { return "10.0.16"; } async migrate() { await game.actors .filter((actor) => actor.type == "personnage") .filter((actor) => actor.system.reve?.reserve?.list?.length ?? 0 > 0) .forEach(async (actor) => { const sortsReserve = actor.system.reve.reserve.list.map(this.conversionSortReserve); console.log(`${LOG_HEAD} Migration des sorts en réserve de ${actor.name}`, sortsReserve); await actor.createEmbeddedDocuments("Item", sortsReserve, { renderSheet: false, }); await actor.update({ 'system.reve.reserve': undefined }) }); } conversionSortReserve(it) { return { type: 'sortreserve', name: it.sort.name, img: it.sort.img, system: { // ATTENTION, utilisation de data / _id possibles, encore présents pour les anciens sorts en réserve sortid: it.sort._id, draconic: it.sort.draconic, ptreve: (it.sort.system ?? it.sort.data).ptreve_reel, coord: it.coord, heurecible: 'Vaisseau', }, }; } } class _10_0_17_MigrationCompetenceCreature extends Migration { get code() { return "competences-creature-parade"; } get version() { return "10.0.17"; } async migrate() { await this.applyItemsUpdates(items => items .filter(it => it.type == "competencecreature" && it.system.isparade && it.system.categorie_parade == "") .map(it => { return { _id: it.id, "system.categorie_parade": "armes-naturelles" } })); await this.applyItemsUpdates(items => items .filter(it => it.type == "competencecreature" && it.system.iscombat) .map(it => { return { _id: it.id, "system.categorie": (Grammar.includesLowerCaseNoAccent(it.name, "lancee") ? "lancer" : "melee") } }) ); } } class _10_0_21_VehiculeStructureResistanceMax extends Migration { get code() { return "vehicule-structure-resistance-max"; } get version() { return "10.0.21"; } async migrate() { await game.actors .filter((actor) => actor.type == "vehicule") .forEach(async (actor) => { await actor.update({ 'system.etat.resistance.value': actor.system.resistance, 'system.etat.resistance.max': actor.system.resistance, 'system.etat.structure.value': actor.system.structure, 'system.etat.structure.max': actor.system.structure }) }); } } class _10_0_33_MigrationNomsDraconic extends Migration { get code() { return "competences-creature-parade"; } get version() { return "10.0.33"; } migrationNomDraconic(ancien) { if (typeof ancien == 'string') { switch (ancien) { case 'oniros': case "Voie d'Oniros": return "Voie d'Oniros"; case 'hypnos': case "Voie d'Hypnos": return "Voie d'Hypnos"; case 'narcos': case "Voie de Narcos": return "Voie de Narcos"; case 'thanatos': case "Voie de Thanatos": return "Voie de Thanatos"; } return ancien; } else if (typeof ancien.name == 'string') { return this.migrationNomDraconic(ancien.name) } return ancien; } async migrate() { await this.applyItemsUpdates(items => items .filter(it => ["sort", "sortreserve"].includes(it.type) && (typeof it.system.draconic == 'string') || (typeof it.system.draconic?.name == 'string')) .map(it => { return { _id: it.id, "system.draconic": this.migrationNomDraconic(it.system.draconic) } })); } } class _10_2_5_ArmesTirLancer extends Migration { constructor() { super(); this.dagues = { "system.competence": 'Dague', "system.lancer": 'Dague de jet', "system.portee_courte": 3, "system.portee_moyenne": 8, "system.portee_extreme": 15 } this.javelot = { "system.competence": 'Lance', "system.lancer": 'Javelot', "system.portee_courte": 6, "system.portee_moyenne": 12, "system.portee_extreme": 20 } this.fouet = { "system.competence": '', "system.lancer": 'Fouet', "system.portee_courte": 2, "system.portee_moyenne": 2, "system.portee_extreme": 3, "system.penetration": -1 } this.arc = { "system.competence": '', "system.tir": 'Arc' } this.arbalete = { "system.competence": '', "system.tir": 'Arbalète' } this.fronde = { "system.competence": '', "system.tir": 'Fronde' } this.mappings = { 'dague': { filter: it => true, updates: this.dagues }, 'dague de jet': { filter: it => true, updates: this.dagues }, 'javelot': { filter: it => true, updates: this.javelot }, 'lance': { filter: it => it.name == 'Javeline', updates: this.javelot }, 'fouet': { filter: it => true, updates: this.fouet }, 'arc': { filter: it => true, updates: this.arc }, 'arbalete': { filter: it => true, updates: this.arbalete }, 'fronde': { filter: it => true, updates: this.fronde }, } } get code() { return "separation-competences-tir-lancer"; } get version() { return "10.2.5"; } migrateArmeTirLancer(it) { let updates = mergeObject({ _id: it.id }, this.getMapping(it).updates); console.log(it.name, updates); return updates; } async migrate() { await this.applyItemsUpdates(items => items .filter(it => "arme" == it.type) .filter(it => this.isTirLancer(it)) .filter(it => this.getMapping(it).filter(it)) .map(it => this.migrateArmeTirLancer(it))); } isTirLancer(it) { return Object.keys(this.mappings).includes(this.getCompKey(it)); } getMapping(it) { return this.mappings[this.getCompKey(it)]; } getCompKey(it) { return Grammar.toLowerCaseNoAccent(it.system.competence); } } class _10_2_10_DesirLancinant_IdeeFixe extends Migration { get code() { return "desir-lancinat-idee-fixe"; } get version() { return "10.2.10"; } migrateQueue(it) { let categorie = undefined let name = it.name if (Grammar.toLowerCaseNoAccent(name).includes('desir')) { categorie = 'lancinant'; name = it.name.replace('Désir lancinant : ', ''); } if (Grammar.toLowerCaseNoAccent(name).includes('idee fixe')) { categorie = 'ideefixe'; name = it.name.replace('Idée fixe : ', '') } return { _id: it.id, name: name, 'system.ideefixe': undefined, 'system.lancinant': undefined, 'system.categorie': categorie } } async migrate() { await this.applyItemsUpdates(items => items .filter(it => ['queue', 'ombre'].includes(it.type)) .map(it => this.migrateQueue(it)) ); } } class _10_3_0_Inventaire extends Migration { get code() { return "migration-equipement-inventaire"; } get version() { return "10.3.0"; } async migrate() { await this.applyItemsUpdates(items => { return this._updatesMonnaies(items) .concat(this._updatesNonEquipe(items)) .concat(this._updatesObjets(items)) }); } _updatesNonEquipe(items) { return items .filter(it => ['munition'].includes(it.type)) .map(it => { return { _id: it.id, 'system.equipe': undefined } }); } _updatesObjets(items) { return items .filter(it => ['objet'].includes(it.type)) .map(it => { return { _id: it.id, 'system.resistance': undefined, 'system.equipe': undefined } }); } _updatesMonnaies(items) { return items .filter(it => ['monnaie'].includes(it.type) && it.system.valeur_deniers != undefined) .map(it => { return { _id: it.id, 'system.cout': it.system.valeur_deniers / 100, 'system.valeur_deniers': undefined } }); } } class _10_3_0_FrequenceEnvironnement extends Migration { get code() { return "migration-frequence-resources"; } get version() { return "10.3.0"; } async migrate() { await this.applyItemsUpdates(items => items.filter(it => ['herbe', 'ingredient'].includes(it.type)) .map(it => this._updatesFrequences(it))); } _updatesFrequences(it) { const rarete = RdDRaretes.byCode(it.system.rarete); return { _id: it.id, 'system.rarete': undefined, 'system.environnement': [{ milieu: it.system.milieu, rarete: rarete.code, frequence: rarete.frequence }] } } } class _10_3_17_Monnaies extends Migration { constructor() { super(); this.mapValeur = { "Etain (1 denier)": { name: 'Denier (étain)', system: { cout: 0.01 } }, "Bronze (10 deniers)": { name: "Sou (bronze)", system: { cout: 0.1 } }, "Argent (1 sol)": { name: "Sol (argent)", system: { cout: 1 } }, "Or (10 sols)": { name: "Dragon (or)", system: { cout: 10 } } }; } get code() { return "migration-monnaies"; } get version() { return "10.3.17"; } async migrate() { await this.applyItemsUpdates(items => this._updatesMonnaies(items)); } _updatesMonnaies(items) { return items .filter(it => 'monnaie' == it.type) .filter(it => this.mapValeur[it.name] != undefined) .map(it => { const correction = this.mapValeur[it.name]; return { _id: it.id, 'name': correction.name, 'system.cout': correction.system.cout, 'system.valeur_deniers': undefined } }); } } class _10_4_6_ServicesEnCommerces extends Migration { get code() { return "migration-service-acteurs"; } get version() { return "10.4.6"; } async migrate() { const servicesToMigrate = game.items.filter(it => it.type == 'service'); servicesToMigrate.forEach(async service => { const commerce = await this.convertServiceToCommerce(service); await RdDBaseActor.create(commerce, { renderSheet: false }); await service.delete(); }); } async convertServiceToCommerce(service) { return { name: service.name, img: service.img, type: 'commerce', system: { description: service.system.description, notesmj: service.system.descriptionmj, illimite: service.system.illimite }, items: await this.transformInventaireCommerce(service) } } async transformInventaireCommerce(service) { const serviceItems = (service.system.items ?? []); const commerceItems = await Promise.all(serviceItems.map(async (it) => { return await this.transformToItemBoutique(it); })); return commerceItems.concat(Monnaie.monnaiesStandard()); } async transformToItemBoutique(serviceRefItem) { const item = await RdDItem.getCorrespondingItem(serviceRefItem); const itemToCreate = { name: item.name, img: item.img, type: item.type, system: mergeObject({ cout: serviceRefItem.system.cout, quantite: serviceRefItem.system.quantite }, item.system, { overwrite: false }) }; return itemToCreate; } } class _10_5_0_UpdatePeriodicite extends Migration { get code() { return "migration-periodicite-poisons-maladies"; } get version() { return "10.5.0"; } async migrate() { await this.applyItemsUpdates(items => this._updatePeriodicite(items)); } _updatePeriodicite(items) { return items.filter(it => ['poison', 'maladie'].includes(it.type)) .filter(it => it.system.periodicite != "") .map(it => { let [incubation, periodicite] = this.getPeriodicite(it); const periode = periodicite.split(' '); let unite = periode.length == 2 ? RdDTimestamp.formulesPeriode().find(it => Grammar.includesLowerCaseNoAccent(periode[1], it.code))?.code : undefined if (unite && Number(periode[0])) { return { _id: it.id, 'system.periodicite': undefined, 'system.incubation': incubation, 'system.periode.nombre': Number.parseInt(periode[0]), 'system.periode.unite': unite }; } else { return { _id: it.id, 'system.periodicite': undefined, 'system.incubation': it.system.periodicite }; } }).filter(it => it != undefined); } getPeriodicite(it) { let p = it.system.periodicite.split(/[\/\\]/); switch (p.length) { case 2: return [p[0].trim(), p[1].trim()]; case 1: return ["", it.system.periodicite.trim()]; default: return [it.system.periodicite.trim(), ""]; } } } class _10_7_0_MigrationBlessures extends Migration { get code() { return "migration-blessures"; } get version() { return "10.7.0"; } async migrate() { const timestamp = game.system.rdd.calendrier.getTimestamp() await Promise.all(game.actors.filter(it => it.isPersonnage() || it.isCreature()) .map(async (actor) => { const legeres = actor.system.blessures?.legeres.liste.filter(it => it.active).map(it => this.creerBlessure(2, 'légère', it, timestamp)) ?? []; const graves = actor.system.blessures?.graves.liste.filter(it => it.active).map(it => this.creerBlessure(4, 'grave', it, timestamp)) ?? []; const critiques = actor.system.blessures?.critiques.liste.filter(it => it.active).map(it => this.creerBlessure(6, 'critique', it, timestamp)); const blessures = legeres.concat(graves).concat(critiques); if (blessures.length > 0) { await actor.createEmbeddedDocuments("Item", blessures); } await actor.update({ 'system.blessures.legeres.liste': [], 'system.blessures.graves.liste': [], 'system.blessures.critiques.liste': [] }) })); } creerBlessure(gravite, graviteTexte, blessure, timestamp) { const dateBlessure = timestamp.addJours(-blessure.jours); const datePremiereRecup = dateBlessure.addJours(gravite); return { name: `Blessure ${graviteTexte}`, type: 'blessure', img: `systems/foundryvtt-reve-de-dragon/icons/sante/blessure${blessure.psdone ? '-soins' : ''}.webp`, system: { gravite: gravite, difficulte: -gravite, debut: { indexDate: dateBlessure.indexDate, indexMinute: 0 }, fin: { indexDate: datePremiereRecup.indexDate, indexMinute: 0 }, premierssoins: { done: blessure.psdone, bonus: blessure.premiers_soins }, soinscomplets: { done: blessure.scdone, bonus: blessure.soins_complets }, localisation: blessure.localisation } } } } class _10_7_19_CategorieCompetenceCreature extends Migration { get code() { return "categorie-competence-creature"; } get version() { return "10.7.19"; } async migrate() { await this.applyItemsUpdates(items => items .filter(it => TYPES.competencecreature == it.type) .map(it => this.migrateCompetenceCreature(it)) ); } migrateCompetenceCreature(it) { const categorie = this.getCategorie(it) if (categorie == it.system.categorie) { return undefined } return { _id: it.id, 'system.categorie': categorie } } getCategorie(it) { if (it.system.ispossession) { return 'possession' } switch (it.system.categorie) { case "melee": if (it.system.isnaturelle) { return 'naturelle' } return 'melee' case "particuliere": case "specialisee": case "connaissance": return "generale" default: return it.system.categorie } } } export class Migrations { static getMigrations() { return [ new _1_5_34_migrationPngWebp(), new _10_0_16_MigrationSortsReserve(), new _10_0_17_MigrationCompetenceCreature(), new _10_0_21_VehiculeStructureResistanceMax(), new _10_0_33_MigrationNomsDraconic(), new _10_2_5_ArmesTirLancer(), new _10_2_10_DesirLancinant_IdeeFixe(), new _10_3_0_Inventaire(), new _10_3_0_FrequenceEnvironnement(), new _10_3_17_Monnaies(), new _10_4_6_ServicesEnCommerces(), new _10_5_0_UpdatePeriodicite(), new _10_7_0_MigrationBlessures(), new _10_7_19_CategorieCompetenceCreature(), ]; } constructor() { game.settings.register(SYSTEM_RDD, "systemMigrationVersion", { name: "System Migration Version", scope: "world", config: false, type: String, default: "0.0.0", }); } migrate() { const currentVersion = game.settings.get(SYSTEM_RDD, "systemMigrationVersion"); if (isNewerVersion(game.system.version, currentVersion)) { // if (true) { /* comment previous and uncomment here to test before upgrade */ const migrations = Migrations.getMigrations().filter(m => isNewerVersion(m.version, currentVersion)); if (migrations.length > 0) { migrations.sort((a, b) => this.compareVersions(a, b)); migrations.forEach(async (m) => { ui.notifications.info( `Executing migration ${m.code}: version ${currentVersion} is lower than ${m.version}` ); await m.migrate(); }); ui.notifications.info( `Migrations done, version will change to ${game.system.version}` ); } else { console.log( LOG_HEAD + `No migration needeed, version will change to ${game.system.version}` ); } game.settings.set( SYSTEM_RDD, "systemMigrationVersion", game.system.version ); } else { console.log(LOG_HEAD + `No system version changed`); } } compareVersions(a, b) { return isNewerVersion(a.version, b.version) ? 1 : isNewerVersion(b.version, a.version) ? -1 : 0; } }