Skip to content

Commit

Permalink
Merge pull request #68 from burobrasil/gm/update-character-importer
Browse files Browse the repository at this point in the history
Atualização do Importador de Personagens do ODO
  • Loading branch information
jmonteiro authored Oct 7, 2024
2 parents aa86325 + fd05cf7 commit 24448d1
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 74 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
22 changes: 0 additions & 22 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
22 changes: 0 additions & 22 deletions src/lang/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
201 changes: 197 additions & 4 deletions src/module/api/characterImporter.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -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,
Expand All @@ -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);
}
};
23 changes: 0 additions & 23 deletions src/module/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 24448d1

Please sign in to comment.