diff --git a/README.md b/README.md index 66c16b3..0ad8d08 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Sistema do Old Dragon 2a Edição para FoundryVTT. - **Criação Personalizada:** Jogadores podem criar Personagens, Monstros & Inimigos, Raças, Classes e Itens (incluindo Armas, Armaduras, Escudos, Itens Gerais, Recipientes & Vasilhames, Montarias & Transportes) de forma personalizada; - **Cálculo Automático de Modificadores:** O módulo calcula automaticamente os modificadores de atributos, otimizando o processo de criação e evolução de personagens; - **Raças e Classes Dinâmicas:** Ao vincular uma Raça e Classe ao personagem, o módulo insere e calcula automaticamente os dados de movimento, experiência para o próximo nível, Base de Ataque (BA) e Jogadas de Proteção (JPD/JPC/JPS). Além disso, as Habilidades de Raça e Habilidades de Classe são exibidas dinâmicamente, de acordo com o nível atual do personagem. -- **Cálculo Automático de Carga:** O módulo calcula automaticamente a Carga Máxima e a Carga Atual do personagem. A Carga Máxima é representada pelo maior valor entre os atributos de Força e Constituição, além de bônus provenientes de equipamentos como a Mochila. A Carga Atual leva em consideração a soma da carga de todos os itens que personagem possui, incluindo moedas. +- **Cálculo Automático de Carga:** O módulo calcula automaticamente a Carga Máxima e a Carga Atual do personagem. A Carga Máxima é representada pelo maior valor entre os atributos de Força e Constituição, além de bônus provenientes de equipamentos como a Mochila. A Carga Atual leva em consideração a soma da carga de todos os itens que o personagem possui, incluindo moedas. - **Rolagens de Dados Aprimoradas para Personagens:** Inclui rolagens para Atributos, Jogadas de Proteção (JPD/JPC/JPS), Ataques (BAC/BAD), Dano e Chance de Nocaute com opções de Ajuste de Teste e Modificador Opcional; - **Rolagens de Dados Aprimoradas para Monstros & Inimigos:** Inclui rolagens para Jogada de Proteção (JP), Moral (MO), Dados de Vida/Pontos de Vida (DV/PV), Ataque (BA) e Dano com opções de Ajuste de Teste e Modificador Opcional; - **Gerenciamento de Magias e Equipamentos:** Permite adicionar, editar, mover, remover e lançar magias, bem como gerenciar equipamentos. Alterações em itens refletem dinamicamente no Peso Total, Valor Total e Carga Atual/Máxima do personagem; @@ -48,12 +48,17 @@ O sistema permite importar personagens criados na plataforma [ODO](https://olddr Passo a passo: Na aba "Actors", clique em "Importar Personagem do ODO". Insira o link da ficha do personagem escolhido no [ODO](https://olddragon.com.br/) (utilizando o botão de "Compartilhar" da ficha, ou copiando a URL no browser). Por último, basta clicar em "Importar". -Atualmente os campos Raça e Classe ainda não estão sendo importados. Por tanto, é necessária a vinculação manual da Raça e Classe ao personagem. - Esta funcionalidade ainda será aprimorada. ![Importação de personagem](./static/importacao-personagem.gif) +## Colaboradores (Collaborators) + +- [Guilherme Mir](https://github.com/guilhermemir) +- [Igor Téuri](https://github.com/igorteuri) +- [Brayan Hemel](https://www.youtube.com/@BrayanHemel) +- [Samuel Grave](https://github.com/itsmegrave) + ## Development ### Prerequisites diff --git a/src/lang/en.json b/src/lang/en.json index 58d1143..a7df588 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -109,28 +109,6 @@ "race_abilities": "Habilidades de Raça", "infravision": "Infravisão", "class": "Classe", - "classes": { - "guerreiro": "Guerreiro", - "barbaro": "Bárbaro", - "paladino": "Paladino", - "anao_aventureiro": "Anão Aventureiro", - "arqueiro": "Arqueiro", - "clerigo": "Clérigo", - "druida": "Druida", - "academico": "Acadêmico", - "xama": "Xamã", - "proscrito": "Proscrito", - "ladrao": "Ladrão", - "ranger": "Ranger", - "bardo": "Bardo", - "assassino": "Assassino", - "halfling_aventureiro": "Halfling Aventureiro", - "mago": "Mago", - "ilusionista": "Ilusionista", - "necromante": "Necromante", - "bruxo": "Bruxo", - "elfo_aventureiro": "Elfo Aventureiro" - }, "class_flavor": "Apresentação", "class_description": "Descrição", "class_ability_description": "Descrição", diff --git a/src/lang/pt-BR.json b/src/lang/pt-BR.json index 58d1143..a7df588 100644 --- a/src/lang/pt-BR.json +++ b/src/lang/pt-BR.json @@ -109,28 +109,6 @@ "race_abilities": "Habilidades de Raça", "infravision": "Infravisão", "class": "Classe", - "classes": { - "guerreiro": "Guerreiro", - "barbaro": "Bárbaro", - "paladino": "Paladino", - "anao_aventureiro": "Anão Aventureiro", - "arqueiro": "Arqueiro", - "clerigo": "Clérigo", - "druida": "Druida", - "academico": "Acadêmico", - "xama": "Xamã", - "proscrito": "Proscrito", - "ladrao": "Ladrão", - "ranger": "Ranger", - "bardo": "Bardo", - "assassino": "Assassino", - "halfling_aventureiro": "Halfling Aventureiro", - "mago": "Mago", - "ilusionista": "Ilusionista", - "necromante": "Necromante", - "bruxo": "Bruxo", - "elfo_aventureiro": "Elfo Aventureiro" - }, "class_flavor": "Apresentação", "class_description": "Descrição", "class_ability_description": "Descrição", diff --git a/src/module/api/characterImporter.js b/src/module/api/characterImporter.js index c672497..01b3b3b 100644 --- a/src/module/api/characterImporter.js +++ b/src/module/api/characterImporter.js @@ -1,11 +1,72 @@ +const RACE_UUIDS = { + anao: 'Compendium.olddragon2e.races.Item.d9seo5qPELZJetH6', + elfo: 'Compendium.olddragon2e.races.Item.qZ5T7ZHQpGlmdfOq', + gnomo: 'Compendium.olddragon2e.races.Item.GLAN1JCU7dTEVHmZ', + halfling: 'Compendium.olddragon2e.races.Item.3VqpR0B3GFRHm9i7', + humano: 'Compendium.olddragon2e.races.Item.LVAnPRB3y5OexOmz', + 'meio-elfo': 'Compendium.olddragon2e.races.Item.sdsNB4qd7pXkRBy9', +}; + +const CLASS_UUIDS = { + academico: 'Compendium.olddragon2e.classes.Item.UbJdOGEnK1AHoHrh', + 'anao-aventureiro': 'Compendium.olddragon2e.classes.Item.Y46BnHjmf9v2sYYA', + arqueiro: 'Compendium.olddragon2e.classes.Item.zVsnFVV3I7aOGLzK', + assassino: 'Compendium.olddragon2e.classes.Item.qmcr4miRTUGaUZgr', + barbaro: 'Compendium.olddragon2e.classes.Item.XyMxtlkHTVeuXict', + bardo: 'Compendium.olddragon2e.classes.Item.zhBTrsrVCJuh0TIP', + bruxo: 'Compendium.olddragon2e.classes.Item.RwWjaex47rIj9FwO', + clerigo: 'Compendium.olddragon2e.classes.Item.cYfvA9p2XprFpamU', + druida: 'Compendium.olddragon2e.classes.Item.tRhKnE5D6grdUwzL', + 'elfo-aventureiro': 'Compendium.olddragon2e.classes.Item.HufLva6gVWRi1l48', + guerreiro: 'Compendium.olddragon2e.classes.Item.bkzh1k7B0ncxQfHR', + 'halfling-aventureiro': 'Compendium.olddragon2e.classes.Item.BMGOU1kveWlIJNx8', + ilusionista: 'Compendium.olddragon2e.classes.Item.PBtvkfo69YBlKrGY', + ladrao: 'Compendium.olddragon2e.classes.Item.o8cAybQI9lQp2MTd', + mago: 'Compendium.olddragon2e.classes.Item.0VpxbklOWK0SaHMY', + necromante: 'Compendium.olddragon2e.classes.Item.auquWM2cFz5Otr9z', + paladino: 'Compendium.olddragon2e.classes.Item.UwfTTsIz4YlhQViE', + proscrito: 'Compendium.olddragon2e.classes.Item.9cxLzlDnQQTEwuhD', + ranger: 'Compendium.olddragon2e.classes.Item.fjNBciFT3punx7Ks', + xama: 'Compendium.olddragon2e.classes.Item.mLrl21J2PMKmGLuh', +}; + export const importActor = async (json) => { - const data = _jsonToActorData(json); + const data = await _jsonToActorData(json); + const actor = await Actor.create(data); + + const itemsToAdd = []; + if (data.system.race) { + itemsToAdd.push(data.system.race); + } + if (data.system.class) { + itemsToAdd.push(data.system.class); + } + if (itemsToAdd.length > 0) { + await actor.createEmbeddedDocuments('Item', itemsToAdd); + } - return Actor.create(data); + await _addRaceAndClassAbilities(actor, data.system.race, data.system.class); + await _addInventoryItems(actor, json.inventory_items); + + return actor; }; -const _jsonToActorData = (json) => { - return { +const _jsonToActorData = async (json) => { + const raceUUID = RACE_UUIDS[json.race]; + const classUUID = CLASS_UUIDS[json.class]; + + if (!raceUUID) { + ui.notifications.warn(`Raça "${json.race}" não encontrada.`); + } + + if (!classUUID) { + ui.notifications.warn(`Classe "${json.class}" não encontrada.`); + } + + const raceItem = raceUUID ? await fromUuid(raceUUID) : null; + const classItem = classUUID ? await fromUuid(classUUID) : null; + + const actorData = { name: json.name, type: 'character', system: { @@ -20,6 +81,7 @@ const _jsonToActorData = (json) => { inteligencia: json.inteligencia, sabedoria: json.sabedoria, carisma: json.carisma, + jp_race_bonus: json.race_jp, current_xp: json.experience_points, economy: { cp: json.money_cp, @@ -33,6 +95,137 @@ const _jsonToActorData = (json) => { personality: json.personality, background: json.background, }, + race: raceItem ? raceItem.toObject() : null, + class: classItem ? classItem.toObject() : null, }, }; + + if (json.picture) { + actorData.img = await _downloadAndSaveImage(json.picture); + } + + return actorData; +}; + +const _downloadAndSaveImage = async (url) => { + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Failed to fetch image from URL: ${url}`); + } + + const blob = await response.blob(); + const fileName = url.split('/').pop(); // Extrai a parte final da URL + const file = new File([blob], `${fileName}.webp`, { type: blob.type }); + + const worldName = game.world.id; + const folderPath = `worlds/${worldName}/assets/character-picture`; + const filePath = `${folderPath}/${fileName}.webp`; + + try { + await FilePicker.browse('data', folderPath); + } catch (e) { + if (e.message.includes('does not exist or is not accessible')) { + await FilePicker.createDirectory('data', folderPath); + } else { + throw e; + } + } + + await FilePicker.upload('data', folderPath, file, { bucket: null }); + + return filePath; +}; + +const _addRaceAndClassAbilities = async (actor, raceItem, classItem) => { + const itemsToAdd = []; + + if (raceItem && raceItem.system.race_abilities) { + const raceAbilities = await _getItemsFromUUIDs(raceItem.system.race_abilities); + itemsToAdd.push(...raceAbilities); + } + + if (classItem && classItem.system.class_abilities) { + const classAbilities = await _getItemsFromUUIDs(classItem.system.class_abilities); + itemsToAdd.push(...classAbilities); + } + + if (itemsToAdd.length > 0) { + await actor.createEmbeddedDocuments('Item', itemsToAdd); + } +}; + +const _getItemsFromUUIDs = async (uuids) => { + const items = []; + for (const uuid of uuids) { + const item = await fromUuid(uuid); + if (item) items.push(item.toObject()); + } + return items; +}; + +const _convertCost = (costInPC) => { + if (costInPC >= 100) { + return `${Math.floor(costInPC / 100)} PO`; + } else if (costInPC >= 10) { + return `${Math.floor(costInPC / 10)} PP`; + } else { + return `${costInPC} PC`; + } +}; + +const _determineWeaponType = (item) => { + if (item.throw_range) { + return 'throwing'; + } else if (item.shoot_range) { + return 'ranged'; + } else if (item.arrow || item.bolt || item.bolt_small) { + return 'ammunition'; + } else { + return 'melee'; + } +}; + +const _addInventoryItems = async (actor, inventoryItems) => { + const itemsToAdd = inventoryItems.map((item) => { + const isEquipped = item.name === 'Mochila' && item.concept === 'container' ? true : item.equipped; + + const itemData = { + name: item.name, + type: item.concept, + system: { + is_equipped: isEquipped, + description: item.description, + quantity: item.quantity, + cost: _convertCost(item.cost), + weight_in_load: item.weight_in_load, + weight_in_grams: item.weight_in_grams, + magic_item: item.magic_item, + damage_type: item.damage_type, + damage: item.damage, + bonus_damage: item.bonus_damage, + bonus_ba: item.bonus_ba, + bonus_ca: item.bonus_ca, + shoot_range: item.shoot_range, + throw_range: item.throw_range, + arrow: item.arrow, + bolt: item.bolt, + bolt_small: item.bolt_small, + polearm: item.polearm, + two_handed: item.two_handed, + versatile: item.versatile, + increases_load_by: item.increases_load_by, + }, + }; + + if (item.concept === 'weapon') { + itemData.system.type = _determineWeaponType(item); + } + + return itemData; + }); + + if (itemsToAdd.length > 0) { + await actor.createEmbeddedDocuments('Item', itemsToAdd); + } }; diff --git a/src/module/config.js b/src/module/config.js index a629987..02d441f 100644 --- a/src/module/config.js +++ b/src/module/config.js @@ -33,29 +33,6 @@ olddragon2e.alignment = { caotico: 'olddragon2e.caotico', }; -olddragon2e.classes = { - guerreiro: 'olddragon2e.classes.guerreiro', - barbaro: 'olddragon2e.classes.barbaro', - paladino: 'olddragon2e.classes.paladino', - anao_aventureiro: 'olddragon2e.classes.anao_aventureiro', - arqueiro: 'olddragon2e.classes.arqueiro', - clerigo: 'olddragon2e.classes.clerigo', - druida: 'olddragon2e.classes.druida', - academico: 'olddragon2e.classes.academico', - xama: 'olddragon2e.classes.xama', - proscrito: 'olddragon2e.classes.proscrito', - ladrao: 'olddragon2e.classes.ladrao', - ranger: 'olddragon2e.classes.ranger', - bardo: 'olddragon2e.classes.bardo', - assassino: 'olddragon2e.classes.assassino', - halfling_aventureiro: 'olddragon2e.classes.halfling_aventureiro', - mago: 'olddragon2e.classes.mago', - ilusionista: 'olddragon2e.classes.ilusionista', - necromante: 'olddragon2e.classes.necromante', - bruxo: 'olddragon2e.classes.bruxo', - elfo_aventureiro: 'olddragon2e.classes.elfo_aventureiro', -}; - olddragon2e.monster_concepts = { humanoide: 'olddragon2e.concepts.humanoide', humanoide_monstruoso: 'olddragon2e.concepts.humanoide_monstruoso',