Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atualização do Importador de Personagens do ODO #68

Merged
merged 4 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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