diff --git a/system/assets/icons/actors/character.svg b/system/assets/icons/actors/character.svg new file mode 100644 index 0000000..1c7f963 --- /dev/null +++ b/system/assets/icons/actors/character.svg @@ -0,0 +1,1018 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/system/assets/icons/actors/npc.svg b/system/assets/icons/actors/npc.svg new file mode 100644 index 0000000..47069fc --- /dev/null +++ b/system/assets/icons/actors/npc.svg @@ -0,0 +1,1018 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/system/assets/icons/items/pecularity.svg b/system/assets/icons/items/peculiarity.svg similarity index 100% rename from system/assets/icons/items/pecularity.svg rename to system/assets/icons/items/peculiarity.svg diff --git a/system/assets/icons/items/technique.svg b/system/assets/icons/items/technique.svg new file mode 100644 index 0000000..c7b5564 --- /dev/null +++ b/system/assets/icons/items/technique.svg @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/system/scripts/actor.js b/system/scripts/actor.js index d7e8b08..54e693b 100644 --- a/system/scripts/actor.js +++ b/system/scripts/actor.js @@ -11,7 +11,10 @@ export class ActorL5r5e extends Actor { // data.type = "character"; // } - // Some tweak on actors + // Replace default image + data.img = `${CONFIG.l5r5e.paths.assets}icons/actors/${data.type}.svg`; + + // Some tweak on actors token data.token = data.token || {}; switch (data.type) { case "character": diff --git a/system/scripts/importer/importer-dialog.js b/system/scripts/importer/importer-dialog.js new file mode 100644 index 0000000..322e577 --- /dev/null +++ b/system/scripts/importer/importer-dialog.js @@ -0,0 +1,362 @@ +/** + * Import Compendium from raw csv text + */ +export class ImporterL5r5e extends FormApplication { + /** + * Selected compendium + * @private + */ + _selectedCompendium = { + name: "", + type: "", + }; + + /** + * Importable compendiums list (unlocked only) + * @private + */ + _compendiumsList = []; + + /** + * Assign the default options + * @override + */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + id: "l5r5e-importer-dialog", + classes: ["l5r5e", "importer-dialog"], + template: CONFIG.l5r5e.paths.templates + "importer/importer-dialog.html", + title: "L5R Import", + width: 400, + height: 660, + resizable: true, + }); + } + + /** + * Create dialog + */ + constructor(options = null) { + super(options); + this._loadCompendiumsList(); + this._selectCompendiumByName(this._compendiumsList[0]?.collection); + } + + /** + * Load compendiums list (unlocked only) + * @private + */ + _loadCompendiumsList() { + const packs = game.packs.filter((p) => { + return p.locked === false && ["Item", "Actor"].includes(p.metadata.entity); + }); + if (packs.length < 1) { + return []; + } + packs.sort((a, b) => a.collection.localeCompare(b.collection)); + this._compendiumsList = packs; + } + + /** + * Select a compendium and type by name + * @private + */ + _selectCompendiumByName(name) { + if (name == null) { + return; + } + this._selectedCompendium = { + name: name, + type: this._compendiumsList.find((e) => e.collection === name).entity, + }; + } + + /** + * Construct and return the data object used to render the HTML template for this form application. + * @param options + * @return {Object} + */ + getData(options = null) { + return { + data: { + selectedCompendium: this._selectedCompendium, + compendiumList: this._compendiumsList, + typesList: { + Item: ["item", "armor", "weapon", "technique", "property", "peculiarity", "advancement"], + Actor: ["character", "npc"], + }, + }, + }; + } + + /** + * Render the dialog + * @param force + * @param options + * @returns {Application} + */ + render(force, options) { + // GM only + if (!game.user.isGM) { + return; + } + + options = { + ...options, + }; + + if (force === undefined) { + force = true; + } + + return super.render(force, options); + } + + /** + * Listen to html elements + * @override + */ + activateListeners(html) { + super.activateListeners(html); + + html.find("#l5r5e-import-cn").on("change", async (event) => { + this._selectCompendiumByName(event.target.value); + this.render(false); + }); + } + + /** + * This method is called upon form submission after form data is validated + * @param event The initial triggering submission event + * @param formData The object of validated form data with which to update the object + * @returns A Promise which resolves once the update operation has completed + * @override + */ + async _updateObject(event, formData) { + await this.import( + formData.compendiumName, + formData.importType, + formData.importLang, + formData.importDesc, + formData.rawtext, + formData.separator + ); + } + + /** + * Main function for importing + * + * @param {String} compendiumName CompendiumCollection name + * @param {String} importType Item type (armor, character...) + * @param {String} rawText Text to import + * @param {String} separator Separator text/character ";", "|"... + */ + async import(compendiumName, importType, importLang, importDesc, rawText, separator) { + try { + if (!rawText || /^\s*$/.test(rawText)) { + throw "RawText is empty"; + } + if (!importType) { + throw "ImportType is empty"; + } + if (!importLang) { + throw "importLang is empty"; + } + if (!importDesc) { + throw "importDesc is empty"; + } + if (!separator) { + throw "Separator is empty"; + } + if (!separator.indexOf(rawText) < 0) { + throw "Separator was not found in RawText"; + } + + // Find the target compendium + const targetCompendium = await game.packs.find((p) => p.collection === compendiumName); + if (targetCompendium == null) { + throw "Could not find compendium with the name " + compendiumName; + } + + // useful props + const targetEntity = targetCompendium.metadata.entity; + // const targetSystem = targetCompendium.metadata.system; + + // console.log('ImporterL5r5e | ', targetCompendium, targetEntity, importType, importLang, importDesc, separator); return; + + // Parse data + let entitiesList = []; + const linesList = rawText.match(/[^\r\n]+/g); + for (let sCurrLine of linesList) { + sCurrLine = sCurrLine.trim(); + if (sCurrLine) { + let aRows = this._line2Row(sCurrLine, separator); + let oEntity = this._template(targetEntity, importType, importLang, importDesc, aRows); + + if (oEntity) { + entitiesList.push(oEntity); + } else { + console.warn(`ImporterL5r5e | wrong format for `, sCurrLine); + } + } + } //fr linesList + + if (entitiesList.length < 1) { + throw "entitiesList is empty"; + } + + // Create temporary Actor entities which impose structure on the imported data + let entities; + switch (targetEntity) { + case "Item": + entities = await Item.create(entitiesList, { temporary: true }); + break; + + case "Actor": + entities = await Actor.create(entitiesList, { temporary: true }); + break; + + default: + console.error("ImporterL5r5e | Unknown/unsupported TargetEntity " + targetEntity); + return; + } //swi + + if (entitiesList.length === 1) { + entities = [entities]; + } + if (entities.length < 1) { + throw "entities is empty"; + } + + // Save each temporary entity into the Compendium pack + for (let entity of entities) { + await targetCompendium.importEntity(entity); + //console.log(`ImporterL5r5e | Imported entity ${entity.name} into Compendium pack ${targetCompendium.collection}`); + } //fr + + //console.log(`ImporterL5r5e | Finished importing ${entities.length} entitie(s) into Compendium pack ${targetCompendium.collection}`); + new Dialog({ + title: `ImporterL5r5e | Job finished`, + content: `Finished importing ${entities.length} entitie(s) into Compendium pack ${targetCompendium.collection}`, + buttons: { + ok: { + label: "Ok", + }, + }, + default: "ok", + }).render(true); + } catch (sEx) { + console.error(sEx); + + new Dialog({ + title: `ImporterL5r5e | Job error`, + content: sEx, + buttons: { + ok: { + label: "Ok", + }, + }, + default: "ok", + }).render(true); + } + } + + /** + * Split and sanitize one line + * + * @param {String} str Text + * @param {String} separator Text separator + * @returns {String} + */ + _line2Row(str, separator) { + if (separator === "]") { + separator = "\\]"; + } + if (separator === "|") { + separator = "|"; + } + if (separator === "\\") { + separator = "\\\\"; + } + return str + .replace(new RegExp("^[" + separator + "]+|[" + separator + "]+$", "g"), "") // clean boundary separator + .replace(new RegExp("([\\S]+)[\\s]*[\\\\]{2}[\\s]*([\\S]+)", "g"), "$1
$2") // "\\" -> "br" + .split(separator) + .map((s) => s.trim()); + } + + /** + * Basic conversion to L5R5e template + * + * @param {String} targetEntity Entity Type (Item, Actor...) + * @param {String} importType Import type for Items (edges, skills...) + * @param {String} importLang Language en,fr + * @param {String} importDesc Desc full|light + * @param {String[]} rows Data's array + * @returns {Object} + */ + _template(targetEntity, importType, importLang, importDesc, rows) { + const isEng = importLang === "en"; + const isLight = importDesc === "light"; + + switch (targetEntity) { + case "Item": + // Subtypes of Item + switch (importType) { + case "item": + return { + name: rows[isEng ? 1 : 0], + img: rows[10] || "systems/l5r5e/assets/icons/fire.svg", + permission: { + default: 0, + }, + type: "item", + data: { + equipped: false, + quantity: 1, + weight: rows[3] || 0, + rarity: rows[2] || 0, + zeni: rows[4] || 0, + properties: ImporterL5r5e._formatProperties(rows[5]), + description: ImporterL5r5e._formatDescription( + rows[isEng ? (isLight ? 8 : 9) : isLight ? 6 : 7] + ), + }, + }; + } + break; + + case "Actor": + // Subtypes of Actor + switch (importType) { + case "character": + case "npc": + } + break; + } + + console.error(`ImporterL5r5e | Unknown/unsupported ${targetEntity} ImportType : ${importType}`); + } + + /** + * Basic Description formatting + * + * @param {String} str Text + * @returns {String} + */ + static _formatDescription(str) { + if (undefined === str) { + return null; + } + return "

" + str.replace(new RegExp("
", "g"), "

") + "

"; + } + + /** + * explode and get properties by name, store theirs ids + * @private + */ + static _formatProperties(str) { + // TODO explode and get properties by name, store theirs ids + return str; + } +} //cls diff --git a/system/scripts/items/item.js b/system/scripts/items/item.js index f908fde..c11f1a1 100644 --- a/system/scripts/items/item.js +++ b/system/scripts/items/item.js @@ -1 +1,10 @@ -export class ItemL5r5e extends Item {} +export class ItemL5r5e extends Item { + /** + * Create a new entity using provided input data + * @override + */ + static async create(data, options = {}) { + data.img = `${CONFIG.l5r5e.paths.assets}icons/items/${data.type}.svg`; + return super.create(data, options); + } +} diff --git a/system/scripts/main-l5r5e.js b/system/scripts/main-l5r5e.js index 92e4d6d..c61386f 100644 --- a/system/scripts/main-l5r5e.js +++ b/system/scripts/main-l5r5e.js @@ -3,6 +3,7 @@ import { L5R5E } from "./config.js"; import { HelpersL5r5e } from "./helpers.js"; import { RegisterSettings } from "./settings.js"; import { PreloadTemplates } from "./preloadTemplates.js"; +import { ImporterL5r5e } from "./importer/importer-dialog.js"; // Actors import { ActorL5r5e } from "./actor.js"; import { CharacterSheetL5r5e } from "./actors/character-sheet.js"; @@ -56,6 +57,7 @@ Hooks.once("init", async function () { game.l5r5e = { HelpersL5r5e, DicePickerDialog, + ImporterL5r5e, }; // Register custom system settings diff --git a/system/template.json b/system/template.json index 4c91e54..1e9c3c5 100644 --- a/system/template.json +++ b/system/template.json @@ -178,7 +178,7 @@ }, "technique": { "templates": ["advancement"], - "technique_type": "", + "technique_type": "kata", "effects": "", "description": "" }, diff --git a/system/templates/actors/character/narrative.html b/system/templates/actors/character/narrative.html index 4c44224..46bb788 100644 --- a/system/templates/actors/character/narrative.html +++ b/system/templates/actors/character/narrative.html @@ -15,9 +15,9 @@
{{ localize 'l5r5e.social.advantages' }} @@ -27,9 +27,9 @@
{{ localize 'l5r5e.social.disadvantages' }} diff --git a/system/templates/importer/importer-dialog.html b/system/templates/importer/importer-dialog.html new file mode 100644 index 0000000..4eef0f0 --- /dev/null +++ b/system/templates/importer/importer-dialog.html @@ -0,0 +1,67 @@ +
+ +
+ + +
+ +
+ +
+ {{#each data.typesList as |typesList primaryClass|}} + {{#ifCond ../data.selectedCompendium.type '==' primaryClass}} + {{#each typesList}} + + {{/each}} + {{/ifCond}} + {{/each}} +
+
+ +
+ + + +
+ +
+ + + +
+ +
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/system/templates/items/peculiarity/peculiarities.html b/system/templates/items/peculiarity/peculiarities.html index f9577b6..41f671b 100644 --- a/system/templates/items/peculiarity/peculiarities.html +++ b/system/templates/items/peculiarity/peculiarities.html @@ -2,8 +2,8 @@ {{ localize 'l5r5e.advancements.title' }} diff --git a/system/templates/items/peculiarity/peculiarity-entry.html b/system/templates/items/peculiarity/peculiarity-entry.html index a6cb621..b6d8bb9 100644 --- a/system/templates/items/peculiarity/peculiarity-entry.html +++ b/system/templates/items/peculiarity/peculiarity-entry.html @@ -1,9 +1,9 @@ -
  • -
      -
    • -
    • {{ advancement.name }}
    • -
    • -
    • +
    • +
        +
      • +
      • {{ peculiarity.name }}
      • +
      • +
      -
      {{{ advancement.data.description }}}
      +
      {{{ peculiarity.data.description }}}
    • \ No newline at end of file