..
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
/************************************************************************************/
|
||||
//import WFRP_Tables from "/systems/wfrp4e/modules/system/tables-wfrp4e.js";
|
||||
import { WH4FRPatchConfig } from "./config-patch.js";
|
||||
import {TranslatedCompendium} from "../../babele/script/translated-compendium.js";
|
||||
|
||||
/************************************************************************************/
|
||||
const _patch_eis = () => {
|
||||
if (game.wfrp4e?.config?.symptoms && game.wfrp4e.config.symptoms["delirium"] ) {
|
||||
game.wfrp4e.config.symptoms["delirium"] = "Délire";
|
||||
|
||||
game.wfrp4e.config.symptomDescriptions["delirium"] = "Votre sensibilité va et vient, avec des moments de clarté remplaçés subitement Your sensibility comes and goes, with moments of clarity replaced suddenly par des accès de délire, des hallucinations et de la terreur. Faites un <b>Test de Force Mentale Intermédiaire (+0)</b> chaque heure, et consultez la table <b><a class='table-click' data-table='delirium'>Délires</a></b> table.";
|
||||
game.wfrp4e.config.symptomTreatment["delirium"] = "Certaines autorités traitent le délire comme un fièvre, avec les mêmes prescriptions. Les médicaments coutent quelques sous à quelques pistoles, en seulement 10% sont efficaces.<br><br>Avec des soins corrects, un test de <b>Guérison Intermédiaire (0)</b> arrêtes les hallucinations pour <b><a class = 'chat-roll'>1d10</a></b> heures.<br><br>Il est aussi courant de soigner les patients délirants avec des drogues tranquillisantes, comme la Fleur de Lune ou même de la Racine de Mandragore, pour garder les patients calmes pendant la crise, les envoyant dans un sommeil agité jusqu'à ce qu'il erécupèrent ou meurent.";
|
||||
|
||||
game.wfrp4e.config.symptoms["swelling"] = "Gonflement";
|
||||
game.wfrp4e.config.symptomDescriptions["swelling"] = "Une partie du corps gonfle jusqu'à plusieurs fois sa taille normale, devenant rouge vif ou violette et devenant presque inutilisable. La partie du corps affectée correspond normalement à une plaie ou une morsure provoquant le gonflement, ou l'endroit où une maladie ou une infection est entrée dans le corps. <br> <br> <b> Tête </b>: Les yeux et la langue enflent, les joues deviennent livides, la mâchoire est toujours ouverte. Il est impossible de manger, mais des liquides légers peuvent être sirotés en petites quantités. Tous les tests nécessitant la parole sont plus difficiles de 3 niveaux. <br> <b> Bras </b>: le bras et la main gonflent, les articulations des épaules et du coude ne peuvent plus bouger et la main devient inutile. Pour la durée du gonflement, le bras compte comme Amputé. <br> <b> Corps </b>: Le corps entier gonfle jusqu'à ce que la victime ne puisse plus porter de vêtements. Tous les tests impliquant le mouvement deviennent difficiles de 3 niveaux. <br> <b> Jambe </b>: La jambe gonfle de manière grotesque, devenant aussi large que la partie la plus large de la cuisse sur toute sa longueur. Le pied est presque indiscernable. Pour la durée du gonflement, la jambe compte comme amputée (WFRP, page 180).";
|
||||
|
||||
game.wfrp4e.config.symptomTreatment["swelling"] = "La plupart des traitements consistent à plonger la partie affectée, ou parfois tout le corps, dans un bain d'eau glacée pour réduire la chaleur qui accompagne les gonflements. Un <b> Test de Guérison Difficile (-20) étendu </b> nécessitant +3 DR réduit le renflement de <b> <a class ='chat-roll'> 2d10 </a> </b> heures. Chaque test dure une heure. Le patient se retrouve avec l'Etat Exténué +1 pour chaque test effectué au cours du processus. <br> <br> A la place, certains médecins saignent le patient avec une lame ou des sangsues. Un <b>Test de Guérison étendu </b> réussi nécessitant +4 SL et des Outils (médecin) réduit le renflement de (<a class ='chat-roll'> 1d10 </a> + Bonus d'Endurance du patient) heures. Chaque test a une difficulté de base <b> impossible (-50) </b> et dure une demi-heure.";
|
||||
|
||||
game.wfrp4e.config.loreEffects["tzeentch"] = {
|
||||
label: "Domaine de Tzeentch",
|
||||
icon: "modules/wfrp4e-core/icons/spells/tzeentch.png",
|
||||
transfer: true,
|
||||
flags: {
|
||||
wfrp4e: {
|
||||
"effectApplication": "apply",
|
||||
"effectTrigger": "oneTime",
|
||||
"lore": true,
|
||||
"script": `
|
||||
if (this.actor.isOwner)
|
||||
args.actor.setupSkill("Résistance", {context : {failure: "1 Point de Corruption reçu", success : "1 Point de Chance gagné"}}).then(setupData => {
|
||||
args.actor.basicTest(setupData).then(test =>
|
||||
{
|
||||
if (test.result.result == "success" && args.actor.type == "character")
|
||||
{
|
||||
args.actor.update({"system.status.fortune.value" : args.actor.system.status.fortune.value + 1})
|
||||
}
|
||||
else if (test.result.result == "failure" && args.actor.type == "character")
|
||||
{
|
||||
args.actor.update({"system.status.corruption.value" : args.actor.system.status.corruption.value + 1})
|
||||
}
|
||||
})
|
||||
})`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
const _patch_up_in_arms = () => {
|
||||
|
||||
game.wfrp4e.config.hitLocationTables["quadruped"] = "Quadrupède";
|
||||
let batter = game.wfrp4e.config.groupAdvantageActions.find(ga => ga.name == "Batter")
|
||||
if (batter) {
|
||||
batter.name = "Battre"
|
||||
batter.description = "En face d'un adversaire plus expérimenté, quelque fois la force brute peut réussir là où d'autres approches échouent.",
|
||||
batter.effect = "<strong>Action Spéciale</strong>: Pour Battre votre adversaire, vous devez réussir un Test Opposé de Force contre votre adversaire. Celui qui obtient le DR le plus élevé gagne. Si vous gagnez ce Test, votre adversaire reçoit l'état @Condition[A Terre] et vous recevez +1 Avantage. Si vous perdez le Test Opposé, votre opposant gagne +1 Avantage and votre action est terminée. Vous ne gagnez pas d'Avantage due à la victoire d'un test Opposé lorsque vous gagnez ce test (ie 1 seul avantage octroyé au total si le Test Opposé de Force est remporté)."
|
||||
}
|
||||
let trick = game.wfrp4e.config.groupAdvantageActions.find(ga => ga.name == "Trick")
|
||||
if (trick) {
|
||||
trick.name = "Duper"
|
||||
trick.description = "Vous prenez un instant pour lancer de la poussière dans les yeux de votre adversaire ou bien lui lancer de l'huile enflammée. Cette manoeuvre est risquée, et peu d'ennemis se font avoir deux fois de la même manière",
|
||||
trick.effect = "<strong>Action Spéciale</strong>: Pour Duper votre adversaire, vous devez réussir un Test Opposé d'Agilité contre lui. Celui qui obtient le plus haut DR gagne le Test. Si vous gagnez le Test, vous recevez +1 Avantage. En fonction des circonstances, le MJ peut vous autoriser à attribuer à votre adversaire l'un des états suivants : @Condition[En Flammes], @Condition[Aveuglé], or @Condition[Empêtré]. Si vous perdez le Test Opposé, votre adversaire gagne +1 Avantage et votre action est terminée. Le MJ peut interdire certains de ces états si vous n'avez d'objet applicables dans votre main, ou bien si vous avez déja infligé un état identique precedemment à cet adversaire. Vous ne gagnez pas d'Avantages due à la victoire d'un test Opposé lorsque vous gagnez ce test (ie 1 seul avantage octroyé au total si le Test Opposé de Force est remporté)."
|
||||
}
|
||||
let addeffort = game.wfrp4e.config.groupAdvantageActions.find(ga => ga.name == "Additional Effort")
|
||||
if (addeffort) {
|
||||
addeffort.name = "Effort Supplémentaire"
|
||||
addeffort.description = "Dans des circonstances désespérées, vous pouvez utiliser l'élan que vous avez acquis pour augmenter vos chances de succès.",
|
||||
addeffort.effect = "<strong>Action Gratuite</strong>: Vous gagnez un bonus de +10% pour n'importe quel test. Vous pouvez dépenser des avantages supplémentaires pour ajouter +10% par Avantage dépensé en plus. Par exemple, vous pouvez dépenser 3 Avantages pour un bonus de +10%, 4 avantages pour un +30%, etc. Le Test qui en découle ne procure jamais d'Avantages pour le personnage qui le réalise."
|
||||
}
|
||||
let flee = game.wfrp4e.config.groupAdvantageActions.find(ga => ga.name == "Flee from Harm")
|
||||
if (flee) {
|
||||
flee.name = "Fuir le Mal"
|
||||
flee.description = "Vous profitez d'un moment de distraction de votre adversaire pour fuir le combat.",
|
||||
flee.effect = "<strong>Mouvement</strong>: Vous pouvez vous éloignez de votre adversaire sans aucune pénalité. Cela remplace la règle de @UUID[Compendium.wfrp4e-core.journal-entries.NS3YGlJQxwTggjRX.JournalEntryPage.bdfiyhEYtKs7irqc#disengaging]{Désengagement}."
|
||||
}
|
||||
let addaction = game.wfrp4e.config.groupAdvantageActions.find(ga => ga.name == "Additional Action")
|
||||
if (addaction) {
|
||||
addaction.name = "Action Supplémentaire"
|
||||
addaction.description = "Vous profitez d'une opportunité pour accomplir une action remarquable.",
|
||||
addaction.effect = "<strong>Action Gratuite</strong>: Vous réalisez une action supplémentaire. Cette action ne génère jamais d'Avantage pour vous. Vous pouvez réaliser cette dépense d'Avantages seulement une seule fois par Tour."
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
/* Manages /auberge command */
|
||||
const _manage_inn_roll = async (content, msg) => {
|
||||
// Split input into arguments
|
||||
let command = content.split(" ").map(function (item) {
|
||||
return item.trim();
|
||||
})
|
||||
|
||||
console.log("COMMANDES", command);
|
||||
if (command[0] == "/auberge" && command[1]) {
|
||||
msg["type"] = 0;
|
||||
msg["rollMode"] = "gmroll";
|
||||
var compendium = game.packs.get('wh4-fr-translation.plats-dauberges')
|
||||
const pack = game.packs.get(compendium);
|
||||
let rollList = await compendium.getDocuments()
|
||||
//console.log("AUBERGE", rollList)
|
||||
for (var i = 0; i < rollList.length; i++) {
|
||||
var rollTab = rollList[i];
|
||||
console.log("Got compendium...", rollList, rollTab.name);
|
||||
if (rollTab.name.toLowerCase().includes(command[1].toLowerCase())) {
|
||||
let my_rollTable;
|
||||
await compendium.getDocument(rollTab._id).then(mytab => my_rollTable = mytab);
|
||||
my_rollTable.draw({ rollMode: "gmroll" });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (content.includes("/auberge")) {
|
||||
msg["type"] = 0;
|
||||
msg["rollMode"] = "gmroll";
|
||||
msg["content"] = "Syntaxe : /auberge MOT_CLE, avec MOT_CLE parmi:<br>BoissonsBase, BoissonsFortes, Desserts, PlatsCommuns, PlatsExcellents, PlatsMaritimes, PlatsMédiocres, PlatsQualité, PlatsRivières<br>Des raccourcis sont possibles avec une partie du nom : /auberge Base (correspond à BoissonBase) ou /auberge Mari (correspond à PlatsMaritimes), etc."
|
||||
ChatMessage.create(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
let __eis_tables = {
|
||||
"animalmishap": 1, "beasthead": 1, "coincedentalenc": 1, "demonic-mien": 1,
|
||||
"expandedmutatemental": 1, "expandedmutatephys": 1, "fixations": 1,
|
||||
"harmfulenc": 1, "positiveenc": 1, "weather": 1, "mutatephys": 1,
|
||||
"vehiclemishap": 1, "quadruped": 1, "ridingmishap": 1, "weather": 1,
|
||||
"delirium": 1
|
||||
}
|
||||
let __dotr_tables = {
|
||||
"rowingboat": 1, "sailingboat": 1, "wind-direction": 1, "waterborne": 1, "traderumour": 1
|
||||
}
|
||||
let __wfrp4e_tables = {
|
||||
"career": 1, "critbody": 1, "critleg": 1, "doom": 1, "doom2": 1, "eyes": 1, "majormis": 1, "mutatemental": 1, "oops": 1, "species": 1, "travel": 1,
|
||||
"hitloc": 1, "critarm": 1, "crithead": 1, "delirium": 1, "event": 1, "hair": 1, "minormis": 1, "mutatephys": 1, "talents": 1, "wrath": 1,
|
||||
"astral": 1, "corruption": 1, "snake": 1, "spider": 1, "job": 1
|
||||
|
||||
}
|
||||
let __to_table_translate = [{ name: "traits", transl: "Traits" }, { name: "talents", transl: "Talents" }, { name: "skills", transl: "Compétences" },
|
||||
{ name: "careers", transl: "Carrières" }, { name: "spells", transl: "Sorts" }, { name: "prayers", transl: "Bénédictions et Miracles" },
|
||||
{ name: "injuries", transl: "Blessures" }, { name: "criticals", transl: "Critiques" }, { name: "trappings", transl: "Equipement" },
|
||||
{ name: "bestiary", transl: "Bestiaire" }, { name: "diseases", transl: "Maladies" }
|
||||
]
|
||||
|
||||
/************************************************************************************/
|
||||
const __create_translation_tables = async (compmod) => {
|
||||
for (let iterData of __to_table_translate) {
|
||||
|
||||
let entityName = compmod + '.' + iterData.name;
|
||||
let compData = game.packs.get(entityName);
|
||||
let compFull = await compData.getContent();
|
||||
let htmlTab = "<table border='1'><tbody>";
|
||||
for (let entryData of compFull) {
|
||||
htmlTab += "<tr><td>" + entryData.data.originalName + "</td><td>@Compendium[" + entityName + '.' + entryData.id + "]{" + entryData.name + "}</td></tr>\n";
|
||||
};
|
||||
htmlTab += "</table>";
|
||||
let myjournal = await JournalEntry.create({ name: 'Traduction des ' + iterData.transl, content: htmlTab });
|
||||
game.journal.insert(myjournal);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************
|
||||
* The default static compendium of translation tables must be aut-mapped to the relevant
|
||||
* compendium content name (ie either wfrp4e or wfrp4e-core up to now).
|
||||
* */
|
||||
const __auto_patch_translation_journal_compendium = async (compmod) => {
|
||||
if (game.user.isGM) {
|
||||
let compData = game.packs.get("WH4-fr-translation.tables-des-traductions");
|
||||
compData.locked = false;
|
||||
let translEntries = await compData.getContent();
|
||||
for (let entryData of translEntries) {
|
||||
let mydata = foundry.utils.duplicate(entryData.data);
|
||||
mydata.content = mydata.content.replace(/wfrp4e-content/g, compmod);
|
||||
entryData.update(mydata);
|
||||
}
|
||||
compData.locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
const loadCompendiumData = async (compendium) => {
|
||||
const pack = game.packs.get(compendium);
|
||||
return await pack?.getDocuments() ?? [];
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/************************************************************************************/
|
||||
const loadCompendium = async (compendium, filter = item => true) => {
|
||||
let compendiumData = await loadCompendiumData(compendium);
|
||||
return compendiumData.filter(filter);
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
const patch_core_tables = (tableList) => {
|
||||
if (game.version) { // v9 and above
|
||||
return;
|
||||
}
|
||||
FilePicker.browse("data", "modules/WH4-fr-translation/tables/").then(resp => {
|
||||
for (var file of resp.files) {
|
||||
let filename = file.substring(file.lastIndexOf("/") + 1, file.indexOf(".json"))
|
||||
if (tableList[filename] == 1) { // Matching table name -> patch !
|
||||
fetch(file).then(r => r.json()).then(records => {
|
||||
//console.log("Patch !!!", filename, records);
|
||||
//WFRP_Tables[filename] = records;
|
||||
game.wfrp4e.tables[filename] = records;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
const patch_trade_gazeteer = () => {
|
||||
if (game.wfrp4e.config.trade?.gazetteer) {
|
||||
fetch("modules/wh4-fr-translation/trade/gazetteer_dotr.json").then(r => r.json()).then(records => {
|
||||
game.wfrp4e.config.trade.gazetteer = records;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
const __check_fix_wrong_modules = (chatFlag, patchFinished) => {
|
||||
|
||||
WH4FRPatchConfig.perform_patch();
|
||||
|
||||
let coreFound = false;
|
||||
game.modules.forEach((module, name) => {
|
||||
|
||||
//console.log("TESTING MODULE", module)
|
||||
if (module.id == "wfrp4e-core" && module.active) {
|
||||
coreFound = true
|
||||
patch_core_tables(__wfrp4e_tables);
|
||||
}
|
||||
if (module.id == "wfrp4e-eis" && module.active) {
|
||||
patch_core_tables(__eis_tables);
|
||||
_patch_eis();
|
||||
}
|
||||
if (module.id == "wfrp4e-dotr" && module.active) {
|
||||
patch_core_tables(__dotr_tables);
|
||||
patch_trade_gazeteer()
|
||||
}
|
||||
|
||||
if (module.id == "wfrp4e-up-in-arms" && module.active) {
|
||||
_patch_up_in_arms();
|
||||
}
|
||||
|
||||
if (module.id == 'ogre-kingdom-wfrp4e' && module.active) {
|
||||
WH4FRPatchConfig.perform_ogrekingdom_patch();
|
||||
if (game.user.isGM && chatFlag)
|
||||
ChatMessage.create({ content: "<div><strong>Le module Ogre-Kingdom a été detecté et automatiquement patché.</strong></div>", whisper: ChatMessage.getWhisperRecipients("GM") });
|
||||
}
|
||||
|
||||
if (module.id == "wfrp4e-ew" && module.active && game.user.isGM && chatFlag) {
|
||||
ChatMessage.create({
|
||||
content: "<div><strong>Vous avez le module EW (wfrp4e-ew ?) installé. Malheureusement, ce module n'est pas compatible avec" +
|
||||
" les traductions et vient casser le fonctionnement de la traduction. Veuillez recopier les compendiums dans votre monde, désactiver le module et re-démarrer le monde</strong></div>",
|
||||
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||
});
|
||||
}
|
||||
});
|
||||
if ( !coreFound) {
|
||||
if (game.user.isGM && chatFlag) {
|
||||
ChatMessage.create({
|
||||
content: "<div><strong>Le module Core WFRP4E n'a pas été trouvé ou activé. Veuillez acheter et/ou activer ce module de base avant de continuer.</strong></div>",
|
||||
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (game.user.isGM && patchFinished) {
|
||||
ChatMessage.create({
|
||||
content: "<div>Les modules WFRP4E ont été <strong>patchés avec succés</strong>. Vous pouvez y aller et que <strong>Shallya vous garde !</strong></div><div>Derniers changements : Support WFRP4E v8.3.X</div></ul>",
|
||||
user: game.user.id,
|
||||
whisper: ChatMessage.getWhisperRecipients("GM")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const __history = [
|
||||
"Nouveautés 7.1.6: <ul><li>Traduction des acteurs de EiS et Middenheim grâce à Eltrys et Kyllian !</li></ul>"
|
||||
]
|
||||
|
||||
/************************************************************************************/
|
||||
const convertColumnToMulti = (table) => {
|
||||
let columns = table.columns;
|
||||
let newTable = foundry.utils.duplicate(table)
|
||||
|
||||
delete newTable.columns;
|
||||
newTable.rows = table.rows.map(i => { return { range: {} } })
|
||||
newTable.multi = table.columns
|
||||
|
||||
for (let column of columns) {
|
||||
for (let row of newTable.rows) {
|
||||
row[column] = {}
|
||||
row.range[column] = []
|
||||
}
|
||||
}
|
||||
|
||||
for (let index in newTable.rows) {
|
||||
for (let column in table.rows[index].range) {
|
||||
newTable.rows[index][column] = { name: table.rows[index].name }
|
||||
newTable.rows[index].range[column] = table.rows[index].range[column]
|
||||
}
|
||||
}
|
||||
return newTable;
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
const __patchStyleSheet = () => {
|
||||
for (let styleSheet of document.styleSheets) {
|
||||
if (styleSheet.href && styleSheet.href.includes("wfrp4e.css")) {
|
||||
let found = 0
|
||||
for (let i = 0; i < styleSheet.cssRules.length; i++) {
|
||||
let rule = styleSheet.cssRules[i]
|
||||
if (rule.selectorText == '.sidebar-popout') {
|
||||
console.log("DELETE RULE", rule)
|
||||
found = i
|
||||
/*rule.cssText = ".sidebar-popout { }"
|
||||
rule.style.width = ""
|
||||
console.log("Found rul2", rule)*/
|
||||
}
|
||||
}
|
||||
styleSheet.deleteRule(found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
const __add_actors_translation = () => {
|
||||
const lang = game.settings.get('core', 'language');
|
||||
if (lang == "fr") {
|
||||
let pack_array = [];
|
||||
for (let metadata of game.packs) {
|
||||
if (!game.babele.isTranslated(metadata) &&
|
||||
metadata.collection != "wfrp4e-core.actors" &&
|
||||
metadata.collection != "wfrp4e-core.bestiary" &&
|
||||
metadata.collection != "wfrp4e-ua1.actors" &&
|
||||
metadata.collection != "wfrp4e-altdorf.actors" &&
|
||||
metadata.collection != "wfrp4e-rnhd.actors" &&
|
||||
metadata.collection != "wfrp4e-dotr.actors" &&
|
||||
metadata.collection != "wfrp4e-eis.actors" &&
|
||||
metadata.collection != "wfrp4e-pbtt.actors" &&
|
||||
metadata.collection != "wfrp4e-middenheim.actors" &&
|
||||
metadata.documentName === 'Actor') {
|
||||
//console.log("REPLACE PACK : ", metadata);
|
||||
let translations = {
|
||||
"label": metadata.name,
|
||||
"mapping": {
|
||||
"name": "name",
|
||||
"description": "system.details.biography.value",
|
||||
"items": {
|
||||
"path": "items",
|
||||
"converter": "bestiary_traits"
|
||||
},
|
||||
"characteristics": {
|
||||
"path": "system.characteristics",
|
||||
"converter": "npc_characteristics"
|
||||
},
|
||||
"details": {
|
||||
"path": "system.details",
|
||||
"converter": "npc_details"
|
||||
}
|
||||
}
|
||||
}
|
||||
//console.log(metadata, translations)
|
||||
game.babele.packs.set(metadata.metadata.id, new TranslatedCompendium(metadata.metadata, translations))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Close the warning dialog
|
||||
if ( game.wfrp4e.warnDialog) {
|
||||
game.wfrp4e.warnDialog.close();
|
||||
game.wfrp4e.warnDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************************/
|
||||
/* Hook for specific command */
|
||||
Hooks.on("chatMessage", (html, content, msg) => {
|
||||
|
||||
if (content.toLowerCase().includes('auberge')) {
|
||||
_manage_inn_roll(content, msg);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/************************************************************************************/
|
||||
/* Additionnal hooks ready */
|
||||
Hooks.on('ready', () => {
|
||||
|
||||
if (game.user.isGM) {
|
||||
game.wfrp4e.warnDialog = new Dialog({
|
||||
title: "Chargement de la traduction FR - Merci d'attendre",
|
||||
content: `<img src="modules/wh4-fr-translation/images/warhammer-v1-logo.webp"><p class="wfrp4-fr-message">Les moines de l'ordre de l'Enclume, associés aux légistes de Talabaheim, préparent la traduction Bretonnienne de ce monde...</h3><p class="wfrp4-fr-message">Merci d'attendre la disparition de ce message pour vaquer dans l'Empire.</p> <p class="wfrp4-fr-message">Si une bénédiction de Shallya apparait dans le tchat, c'est que tout s'est bien passé.</p>`,
|
||||
buttons: {
|
||||
}
|
||||
})
|
||||
game.wfrp4e.warnDialog.render(true, {focus: true, left: 20, top: 20});
|
||||
let chatData = {
|
||||
user: game.user.id,
|
||||
rollMode: game.settings.get("core", "rollMode"),
|
||||
whisper: ChatMessage.getWhisperRecipients('GM'),
|
||||
content: `<div>Les modules WFRP4E sont <strong>en cours de patch pour traduction</strong>... Merci <strong>d'attendre le message de fin</strong> (dans environ 20 secondes)<p>Pour rappel et à titre informatif, il faut activer ce module de traduction FR <strong>avant</strong> de procéder à l'importation des données des modules C7 (core compris). Autrement dit, activez ce module FR <strong>AVANT</strong> tout autre module.</p><p>Support et informations sur le Discord FR de Foundry : https://discord.gg/pPSDNJk</p></div>`
|
||||
}
|
||||
ChatMessage.create(chatData);
|
||||
}
|
||||
|
||||
// Patch function for effects
|
||||
game.wfrp4e.utility.findKey = warhammer.utility.findKey
|
||||
|
||||
/** New modifiers */
|
||||
game.wfrp4e.config.difficultyModifiers = {
|
||||
"veasy": 60,
|
||||
"easy": 40,
|
||||
"banal": 30,
|
||||
"average": 20,
|
||||
"medium": 10,
|
||||
"challenging": 0,
|
||||
"difficult": -10,
|
||||
"hard": -20,
|
||||
"vhard": -30,
|
||||
"doom": -40
|
||||
}
|
||||
game.wfrp4e.config.difficultyLabels = {
|
||||
|
||||
"veasy": "Très Facile (+60)",
|
||||
"easy": "Facile (+40)",
|
||||
"banal": "Banal (+30)",
|
||||
"average": "Accessible (+20)",
|
||||
"medium": "Faisable (+10)",
|
||||
"challenging": "Intermédiaire (+0)",
|
||||
"difficult": "Complexe (-10)",
|
||||
"hard": "Difficile (-20)",
|
||||
"vhard": "Très Difficile (-30)",
|
||||
"doom": "Maudit (-40)"
|
||||
}
|
||||
|
||||
game.wfrp4e.warnDialog.render(true, {focus: true, left: 20, top: 20});
|
||||
//setTimeout( __check_fix_wrong_modules, 2000, true, false);
|
||||
setTimeout(__check_fix_wrong_modules, 20000, true, true);
|
||||
setTimeout(__add_actors_translation, 21000, false, true);
|
||||
|
||||
//__patchStyleSheet()
|
||||
|
||||
/* ATTEMPT !!
|
||||
loadCompendium("wfrp4e-core.spells").then( comp => {
|
||||
let sorted = {};
|
||||
for (let spell of comp) {
|
||||
if ( !sorted[spell.data.data.lore.value]) {
|
||||
sorted[spell.data.data.lore.value] = [];
|
||||
}
|
||||
sorted[spell.data.data.lore.value].push( foundry.utils.duplicate(spell.data) );
|
||||
}
|
||||
console.log("SORTED", sorted);
|
||||
});*/
|
||||
|
||||
/*
|
||||
let compmod = "wfrp4e";
|
||||
// Check various settings in the installation
|
||||
game.modules.forEach((module, name) => {
|
||||
if ( name == "wfrp4e-core" && module.active) {
|
||||
compmod = "wfrp4e-core";
|
||||
}
|
||||
} );
|
||||
__auto_patch_translation_journal_compendium( compmod )
|
||||
/* Uncomment this to auto-create the translation tables
|
||||
Auto-create translation journal tables
|
||||
__create_translation_tables(compmod);
|
||||
*/
|
||||
|
||||
});
|
||||
@@ -0,0 +1,698 @@
|
||||
/************************************************************************************/
|
||||
import loadScripts from "./loadScripts.js";
|
||||
import statParserFR from "./import-stat-2.js";
|
||||
|
||||
/************************************************************************************/
|
||||
var compmod = "wfrp4e-core";
|
||||
const vo_conditions = {
|
||||
"ablaze": "Ablaze",
|
||||
"bleeding": "Bleeding",
|
||||
"blinded": "Blinded",
|
||||
"broken": "Broken",
|
||||
"deafened": "Deafened",
|
||||
"entangled": "Entangled",
|
||||
"fatigued": "Fatigued",
|
||||
"poisoned": "Poisoned",
|
||||
"prone": "Prone",
|
||||
"stunned": "Stunned",
|
||||
"surprised": "Surprised",
|
||||
"unconscious": "Unconscious",
|
||||
"grappling": "Grappling",
|
||||
"fear": "Fear",
|
||||
"defeated": "Defeated"
|
||||
}
|
||||
|
||||
const __SELECT_BONUS_PREFIX_D = {
|
||||
"initiative": 1,
|
||||
"intelligence": 1,
|
||||
"endurance": 1,
|
||||
"agilite": 1,
|
||||
"agilité": 1
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
export class WFRP4FrTranslation {
|
||||
|
||||
static parseSpellContent(spell) {
|
||||
if (spell.system.range?.value) {
|
||||
spell.system.range.value = this.processSpellContent(spell.system.range.value)
|
||||
}
|
||||
if (spell.system.damage?.value) {
|
||||
spell.system.damage.value = this.processSpellContent(spell.system.damage.value)
|
||||
}
|
||||
if (spell.system.duration?.value) {
|
||||
spell.system.duration.value = this.processSpellContent(spell.system.duration?.value)
|
||||
}
|
||||
if ( spell.system.range?.value) {
|
||||
spell.system.range.value = this.processSpellContent(spell.system.range?.value)
|
||||
}
|
||||
if (spell.system.target?.value ) {
|
||||
spell.system.target.value = this.processSpellContent(spell.system.target?.value)
|
||||
}
|
||||
}
|
||||
|
||||
static processSpellContent(value) {
|
||||
//console.log("Spell duration/range/damage/target :", value);
|
||||
if (value == "") return ""; // Hop !
|
||||
if (value == "Touch") return "Contact"; // Hop !
|
||||
if (value == "You") return "Vous"; // Hop !
|
||||
if (value == "Instant") return "Instantané"; // Hop !
|
||||
let translw = value;
|
||||
let re = /(.*)\s+[Bb]onus\s*(\w*)/i;
|
||||
let res = re.exec(value);
|
||||
//console.log("RES1:", res);
|
||||
let unit = "";
|
||||
if (res) { // Test "<charac> Bonus <unit>" pattern
|
||||
if (res[1]) { // We have char name, then convert it
|
||||
translw = game.i18n.localize(res[1].trim());
|
||||
let bonusPrefix = (translw.toLowerCase() in __SELECT_BONUS_PREFIX_D) ? "Bonus d'" : "Bonus de ";
|
||||
translw = bonusPrefix + translw
|
||||
}
|
||||
unit = res[2];
|
||||
} else {
|
||||
re = /(\d+) (\w+)/i;
|
||||
res = re.exec(value);
|
||||
if (res) { // Test : "<number> <unit>" pattern
|
||||
translw = res[1];
|
||||
unit = res[2];
|
||||
} else { // Test
|
||||
re = /(\w+) (\w+)/i;
|
||||
res = re.exec(value);
|
||||
if (res) { // Test : "<charac> <unit>" pattern
|
||||
translw = game.i18n.localize(res[1].trim());
|
||||
unit = res[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unit == "hour") unit = "heure";
|
||||
if (unit == "hours") unit = "heures";
|
||||
if (unit == "days") unit = "jours";
|
||||
if (unit == "yard") unit = "mètre";
|
||||
if (unit == "yards") unit = "mètres";
|
||||
if (unit == "Bonus") { // Another weird management
|
||||
console.log("Translating bonus", unit);
|
||||
translw = "Bonus de " + translw;
|
||||
} else {
|
||||
translw += " " + unit;
|
||||
}
|
||||
return translw;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
Hooks.once('init', () => {
|
||||
|
||||
// Check various settings in the installation
|
||||
game.modules.forEach((module, id) => {
|
||||
if (id == "wfrp4e-core" && module.active) {
|
||||
compmod = "wfrp4e-core";
|
||||
}
|
||||
});
|
||||
game.wfrp4efr = {
|
||||
compmod: compmod,
|
||||
vo_conditions: vo_conditions
|
||||
}
|
||||
|
||||
game.wfrp4e.apps.StatBlockParser.parseStatBlock = async function (statString, type = "npc") {
|
||||
return statParserFR(statString, type);
|
||||
}
|
||||
|
||||
console.log("WFRP4E-FR | Loading Babele translation module ...");
|
||||
loadScripts();
|
||||
|
||||
/*---------------------------------------------------------------------*/
|
||||
/* DEPRECATED : game.wfrp4e.entities.ItemWfrp4e.prototype.computeSpellDamage = function (formula, isMagicMissile) {
|
||||
try {
|
||||
|
||||
formula = formula.toLowerCase();
|
||||
|
||||
if (isMagicMissile) // If it's a magic missile, damage includes willpower bonus
|
||||
{
|
||||
formula += "+ " + this.actor.characteristics["wp"].bonus
|
||||
}
|
||||
|
||||
// Specific case, to avoid wrong matching with "Force"
|
||||
if (formula.includes("toughness bonus")) {
|
||||
formula = formula.replace("toughness bonus", this.actor.characteristics["t"].bonus);
|
||||
}
|
||||
|
||||
// Specific case, to avoid wrong matching with "Force"
|
||||
if (formula.includes("force mentale")) {
|
||||
// Determine if it's looking for the bonus or the value
|
||||
if (formula.includes('bonus')) {
|
||||
formula = formula.replace("bonus de force mentale", this.actor.characteristics["wp"].bonus);
|
||||
formula = formula.replace("force mentale bonus", this.actor.characteristics["wp"].bonus);
|
||||
} else
|
||||
formula = formula.replace("force mentale", this.actor.characteristics["wp"].value);
|
||||
}
|
||||
|
||||
// Iterate through characteristics
|
||||
for (let ch in this.actor.characteristics) {
|
||||
// If formula includes characteristic name
|
||||
while (formula.includes(this.actor.characteristics[ch].label.toLowerCase())) {
|
||||
// Determine if it's looking for the bonus or the value
|
||||
if (formula.includes('bonus')) {
|
||||
formula = formula.replace("bonus de " + game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].bonus);
|
||||
formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase() + " bonus", this.actor.characteristics[ch].bonus);
|
||||
}
|
||||
else
|
||||
formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].value);
|
||||
}
|
||||
}
|
||||
|
||||
return eval(formula);
|
||||
}
|
||||
catch (e) {
|
||||
throw ui.notifications.error("Error: could not parse spell damage. See console for details")
|
||||
}
|
||||
}*/
|
||||
|
||||
/*---------------------------------------------------------------------*/
|
||||
/* DEPRECATED : game.wfrp4e.entities.ItemWfrp4e.prototype.computeSpellPrayerFormula = function (type, aoe = false, formulaOverride) {
|
||||
let formula = formulaOverride || this[type]?.value
|
||||
if (Number.isNumeric(formula))
|
||||
return formula
|
||||
|
||||
//console.log("Compute FR")
|
||||
formula = formula.toLowerCase();
|
||||
|
||||
// Do not process these special values
|
||||
if (formula != game.i18n.localize("You").toLowerCase() && formula != game.i18n.localize("Special").toLowerCase() && formula != game.i18n.localize("Instant").toLowerCase()) {
|
||||
// Specific case, to avoid wrong matching with "Force"
|
||||
if (formula.includes("force mentale")) {
|
||||
// Determine if it's looking for the bonus or the value
|
||||
if (formula.includes('bonus')) {
|
||||
formula = formula.replace("bonus de force mentale", this.actor.characteristics["wp"].bonus);
|
||||
formula = formula.replace("force mentale bonus", this.actor.characteristics["wp"].bonus);
|
||||
}
|
||||
else
|
||||
formula = formula.replace("force mentale", this.actor.characteristics["wp"].value);
|
||||
}
|
||||
if (formula.includes("yard"))
|
||||
formula = formula.replace('yard', "mètre");
|
||||
if (formula.includes("yds"))
|
||||
formula = formula.replace('yds', "m.");
|
||||
// Iterate through remaining characteristics
|
||||
for (let ch in this.actor.characteristics) {
|
||||
// If formula includes characteristic name
|
||||
//console.log("Testing :", ch, WFRP4E.characteristics[ch].toLowerCase());
|
||||
if (formula.includes(game.wfrp4e.config.characteristics[ch].toLowerCase())) {
|
||||
// Determine if it's looking for the bonus or the value
|
||||
if (formula.includes('bonus')) {
|
||||
formula = formula.replace("bonus de " + game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].bonus);
|
||||
formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase() + " bonus", this.actor.characteristics[ch].bonus);
|
||||
}
|
||||
else
|
||||
formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If AoE - wrap with AoE ( )
|
||||
if (aoe)
|
||||
formula = "AoE (" + formula.capitalize() + ")";
|
||||
|
||||
//console.log("calculateSpellAttributes -> " + formula );
|
||||
return formula.capitalize();
|
||||
}*/
|
||||
|
||||
/*---------------------------------------------------------------------*/
|
||||
// Converters area
|
||||
if (game.babele !== 'undefined') {
|
||||
|
||||
game.babele.register({
|
||||
module: 'wh4-fr-translation',
|
||||
lang: 'fr',
|
||||
dir: 'compendium'
|
||||
})
|
||||
|
||||
game.babele.registerConverters({
|
||||
|
||||
"career_skills": (skills_list) => {
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("skill")
|
||||
//DEBUG: console.log( "Thru here ...", compendium, skills_list);
|
||||
if (skills_list) {
|
||||
let i;
|
||||
let len = skills_list.length;
|
||||
let re = /(.*)\((.*)\)/i;
|
||||
for (i = 0; i < len; i++) {
|
||||
skills_list[i] = skills_list[i].trim();
|
||||
for (let compData of validCompendiums) {
|
||||
let translItem = game.babele.translate(compData.metadata.id, { name: skills_list[i], type: "skill" }, true)
|
||||
let transl = translItem?.name || undefined
|
||||
if (!transl) transl = skills_list[i]
|
||||
//console.log("List ...", skills_list[i], compData.metadata.id, translItem);
|
||||
if (transl == skills_list[i]) {
|
||||
let res = re.exec(skills_list[i]);
|
||||
if (res) {
|
||||
//console.log("Matched/split:", res[1], res[2]);
|
||||
let subword = game.i18n.localize(res[2].trim());
|
||||
let s1 = res[1].trim() + " ()";
|
||||
translItem = game.babele.translate(compData.metadata.id, { name: s1, type: "skill" }, true)
|
||||
let translw = translItem?.name || undefined
|
||||
if (translw && translw != s1) {
|
||||
let res2 = re.exec(translw);
|
||||
transl = res2[1] + "(" + subword + ")";
|
||||
} else {
|
||||
s1 = res[1].trim() + " ( )";
|
||||
translItem = game.babele.translate(compData.metadata.id, { name: s1, type: "skill" }, true)
|
||||
translw = translItem?.name || undefined
|
||||
if(translw) {
|
||||
let res2 = re.exec(translw);
|
||||
transl = res2[1] + "(" + subword + ")";
|
||||
} else {
|
||||
transl = res[1] + " (" + subword + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skills_list[i] = transl;
|
||||
if (translItem?.system)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return skills_list
|
||||
},
|
||||
"role_skills": (skills) => {
|
||||
let skills_list = skills.split(',');
|
||||
let skillsFR = game.babele.converters.career_skills(skills_list);
|
||||
return skillsFR.join(', ')
|
||||
},
|
||||
"process_effects": (effectsData, translations, data, tc, tc_translations) => {
|
||||
//console.log("Effects :", effectsData, translations, data, tc, tc_translations)
|
||||
for (let e of effectsData) {
|
||||
let origName = e.name
|
||||
e.name = tc_translations.name || game.i18n.localize(e.name)
|
||||
if ( e.flags?.wfrp4e?.scriptData) {
|
||||
for (let script of e.flags.wfrp4e.scriptData) {
|
||||
if (script?.label) {
|
||||
// Quand le label du script est strictement identique au nom de l'item concerné
|
||||
if ( script.label.toLowerCase() == origName.toLowerCase() ) {
|
||||
script.label = e.name
|
||||
} else if (script.label.toLowerCase().includes("tests to affect")) {
|
||||
script.label = script.label.replace("Tests to affect", "Tests relatifs à ")
|
||||
} else if (script.label.toLowerCase().includes("using torn muscle")) {
|
||||
script.label = script.label.replace("Using Torn Muscle", "Utilisation du muscle déchiré ")
|
||||
} else {
|
||||
script.label = game.i18n.localize(script.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return effectsData
|
||||
},
|
||||
"resultConverter": (results, translated) => {
|
||||
//console.log("STUF PARSING", results, translated)
|
||||
if (translated) {
|
||||
for (let data of results) {
|
||||
if (translated[`${data.range[0]}-${data.range[1]}`]) {
|
||||
data.text = translated[`${data.range[0]}-${data.range[1]}`]
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
// Auto patch
|
||||
if (results[0].text.includes("wfrp4e-core.career-descriptions")) {
|
||||
if (game.system.version.match("7.")) {
|
||||
results[0].text = "wfrp4e-core.journals"
|
||||
} else {
|
||||
results[0].text = "wfrp4e-core.journal-entries"
|
||||
}
|
||||
}
|
||||
if (results[0].text.includes("wfrp4e-core.journal")) {
|
||||
for (let data of results) {
|
||||
let career = data.text.match(/{(.*)}/)
|
||||
//console.log(">>>>>", career)
|
||||
if (career?.[1]) {
|
||||
let careerFR = game.babele.converters.career_careergroup(career[1])
|
||||
data.text = data.text.replace(career[1], careerFR)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (results[0].documentCollection) {
|
||||
return game.babele.converters.tableResults(results)
|
||||
}
|
||||
return results
|
||||
},
|
||||
|
||||
"npc_details": (details) => {
|
||||
let newDetails = foundry.utils.duplicate(details);
|
||||
if (details.species?.value)
|
||||
newDetails.species.value = game.i18n.localize(details.species.value);
|
||||
if (details.gender?.value)
|
||||
newDetails.gender.value = game.i18n.localize(details.gender.value);
|
||||
if (details.class?.value)
|
||||
newDetails.class.value = game.i18n.localize(details.class.value);
|
||||
return newDetails;
|
||||
},
|
||||
|
||||
"career_talents": (talents_list) => {
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("talent")
|
||||
let i;
|
||||
if (talents_list) {
|
||||
let len = talents_list.length;
|
||||
let re = /(.*)\((.*)\)/i;
|
||||
for (i = 0; i < len; i++) {
|
||||
for (let compData of validCompendiums) {
|
||||
let translItem = game.babele.translate(compData.metadata.id, { name: talents_list[i] }, true);
|
||||
// Specific case management
|
||||
if (talents_list[i] === "Trapper") {
|
||||
translItem = game.babele.translate(compData.metadata.id, { name: "a7v422EZcOUUC20X" }, true);
|
||||
}
|
||||
if (talents_list[i] === "Inspiring") {
|
||||
translItem = game.babele.translate(compData.metadata.id, { name: "WCXnFSV4WOSmzzc4" }, true);
|
||||
}
|
||||
//console.log("Search talent name:", compData.metadata.id, talents_list[i], translItem);
|
||||
let transl = translItem?.name || undefined
|
||||
if (!transl) transl = talents_list[i]
|
||||
if (transl == talents_list[i]) {
|
||||
let res = re.exec(talents_list[i]);
|
||||
if (res) {
|
||||
let subword = game.i18n.localize(res[2].trim());
|
||||
let s1 = res[1].trim(); // No () in talents table
|
||||
translItem = game.babele.translate(compData.metadata.id, { name: s1 }, true)
|
||||
let translw = translItem?.name || undefined
|
||||
if (translw && translw != s1) {
|
||||
transl = translw + " (" + subword + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
talents_list[i] = transl;
|
||||
if (translItem?.system)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return talents_list;
|
||||
},
|
||||
"npc_characteristics": (chars) => { // Auto-convert char names in the sheet
|
||||
for (let key in chars) {
|
||||
let char = chars[key];
|
||||
//console.log("Was here !", key, char );
|
||||
let abrev = char["abrev"];
|
||||
let toTransl = "CHAR." + abrev;
|
||||
if (game.i18n.localize(toTransl) != toTransl) { // Manages unknown language
|
||||
char["label"] = game.i18n.localize("CHAR." + abrev);
|
||||
char["abrev"] = game.i18n.localize("CHARAbbrev." + abrev);
|
||||
}
|
||||
}
|
||||
return chars;
|
||||
},
|
||||
"bestiary_traits": (beast_traits, translations) => {
|
||||
if (!beast_traits) {
|
||||
console.log("No beast traits found here ...")
|
||||
return beast_traits
|
||||
}
|
||||
//console.log("TRANS:", beast_traits)
|
||||
for (let trait_en of beast_traits) {
|
||||
let special = "";
|
||||
let nbt = "";
|
||||
let name_en = trait_en.name.trim(); // strip \r in some traits name
|
||||
if (!trait_en.name || trait_en.name.length == 0) {
|
||||
console.log("Wrong item name found!!!!")
|
||||
continue
|
||||
}
|
||||
//console.log(">>>>>>>> Parsing", trait_en.name)
|
||||
if (trait_en.type == "trait") {
|
||||
//console.log("Trait translation", compmod, trait_en)
|
||||
if (name_en.includes("Tentacles")) { // Process specific Tentacles case
|
||||
let re = /(.d*)x Tentacles/i;
|
||||
let res = re.exec(name_en);
|
||||
if (res && res[1])
|
||||
nbt = res[1] + "x ";
|
||||
name_en = "Tentacles";
|
||||
} else if (name_en.includes("(") && name_en.includes(")")) { // Then process specific traits name with (xxxx) inside
|
||||
let re = /(.*) \((.*)\)/i;
|
||||
let res = re.exec(name_en);
|
||||
name_en = res[1]; // Get the root traits name
|
||||
special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
|
||||
}
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("trait")
|
||||
for (let compData of validCompendiums) {
|
||||
let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true)
|
||||
if (trait_fr?.system) {
|
||||
trait_fr.name = trait_fr.name || trait_en.name
|
||||
trait_en.name = nbt + trait_fr.name + special;
|
||||
trait_en.system.description.value = trait_fr.system.description.value;
|
||||
if (trait_en.system?.specification && isNaN(trait_en.system.specification.value)) { // This is a string, so translate it
|
||||
//console.log("Translating : ", trait_en.system.specification.value);
|
||||
trait_en.system.specification.value = game.i18n.localize(trait_en.system.specification.value.trim());
|
||||
}
|
||||
break // Translation has been found, skip other compendiums
|
||||
}
|
||||
}
|
||||
} else if (trait_en.type == "skill") {
|
||||
if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside
|
||||
let re = /(.*) +\((.*)\)/i;
|
||||
let res = re.exec(name_en);
|
||||
name_en = res[1].trim(); // Get the root skill name
|
||||
special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
|
||||
}
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("skill")
|
||||
for (let compData of validCompendiums) {
|
||||
let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true)
|
||||
if (trait_fr?.system) {
|
||||
//console.log(">>>>> Skill ?", name_en, special, trait_fr.name, trait_fr);
|
||||
trait_fr.name = trait_fr.name || name_en
|
||||
trait_en.name = trait_fr.name + special;
|
||||
trait_en.system.description.value = trait_fr.system.description.value;
|
||||
break; // Translation has been found, skip other compendiums
|
||||
}
|
||||
}
|
||||
} else if (trait_en.type == "prayer") {
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("prayer")
|
||||
for (let compData of validCompendiums) {
|
||||
let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true)
|
||||
if (trait_fr?.system) {
|
||||
//DEBUG : console.log(">>>>> Prayer ?", name_en, special, trait_fr.name );
|
||||
WFRP4FrTranslation.parseSpellContent(trait_en)
|
||||
trait_fr.name = trait_fr.name || name_en
|
||||
trait_en.name = trait_fr.name + special;
|
||||
if (trait_fr.system?.description?.value) {
|
||||
trait_en.system.description.value = trait_fr.system.description.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (trait_en.type == "spell") {
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("spell")
|
||||
for (let compData of validCompendiums) {
|
||||
let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true)
|
||||
if (trait_fr?.system) {
|
||||
//DEBUG : console.log(">>>>> Spell ?", name_en, special, trait_fr);
|
||||
WFRP4FrTranslation.parseSpellContent(trait_en)
|
||||
trait_fr.name = trait_fr.name || name_en
|
||||
trait_en.name = trait_fr.name + special;
|
||||
if (trait_fr.system?.description?.value) {
|
||||
trait_en.system.description.value = trait_fr.system.description.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (trait_en.type == "talent") {
|
||||
if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside
|
||||
let re = /(.*) +\((.*)\)/i;
|
||||
let res = re.exec(name_en);
|
||||
name_en = res[1].trim(); // Get the root talent name, no parenthesis this time...
|
||||
special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
|
||||
}
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("talent")
|
||||
for (let compData of validCompendiums) {
|
||||
if (name_en === "Trapper") {
|
||||
name_en = "a7v422EZcOUUC20X"
|
||||
}
|
||||
if (name_en === "Inspiring") {
|
||||
name_en = "WCXnFSV4WOSmzzc4"
|
||||
}
|
||||
let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true)
|
||||
if (trait_fr?.system) {
|
||||
trait_fr.name = trait_fr.name || name_en // Security since babele v10
|
||||
//console.log(">>>>> Talent ?", trait_fr, name_en, special, trait_fr.name);
|
||||
if (trait_fr.name && (trait_fr.name == "Sprinter" || trait_fr.name != name_en)) { // Talent translated!
|
||||
trait_en.name = trait_fr.name.trim() + special
|
||||
if (trait_fr.system?.description?.value) { // Why ???
|
||||
trait_en.system.description.value = trait_fr.system.description.value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (trait_en.type == "career") {
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("career")
|
||||
for (let compData of validCompendiums) {
|
||||
let career_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true);
|
||||
if (career_fr?.system) {
|
||||
trait_en.name = career_fr.name || trait_en.name
|
||||
// DEBG: console.log(">>>>> Career ?", career_fr.name );
|
||||
trait_en.system = foundry.utils.duplicate(career_fr.system);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (trait_en.type == "vehicleRole") {
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("vehicleRole")
|
||||
for (let compData of validCompendiums) {
|
||||
let role_fr = game.babele.translate(compData.metadata.id, trait_en, true);
|
||||
if (role_fr?.system) {
|
||||
trait_en.name = role_fr.name || trait_en.name
|
||||
// DEBG: console.log(">>>>> Role ?", role_fr.name );
|
||||
trait_en.system = foundry.utils.duplicate(role_fr.system);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (trait_en.type == "trapping" || trait_en.type == "weapon" || trait_en.type == "armour" || trait_en.type == "container" || trait_en.type == "money") {
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag(["trapping"], ["weapon", "armour", "container", "money"])
|
||||
for (let compData of validCompendiums) {
|
||||
let trapping_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true);
|
||||
if (trapping_fr?.system) {
|
||||
//console.log(">>>>> Trapping ?", name_en, trapping_fr.name);
|
||||
trait_en.name = trapping_fr.name || trait_en.name
|
||||
if (trapping_fr.system?.description?.value) {
|
||||
trait_en.system.description.value = trapping_fr.system.description.value
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//console.log(">>>>>>>><OUTPUT", beast_traits)
|
||||
return beast_traits
|
||||
},
|
||||
// To avoid duplicateing class for all careers
|
||||
"generic_localization": (value) => {
|
||||
let ret = value
|
||||
if (value) {
|
||||
ret = game.i18n.localize(value.trim());
|
||||
if (!ret) ret = value
|
||||
}
|
||||
return ret
|
||||
},
|
||||
"trapping_qualities_flaws": (value) => {
|
||||
if (value) {
|
||||
let newQF = [];
|
||||
//console.log("ATOUTS", value, typeof(value));
|
||||
let list = value
|
||||
if (typeof (value) == "string") {
|
||||
let myList = value.split(",")
|
||||
list = []
|
||||
for (let l of myList) {
|
||||
list.push({ name: l.trim() })
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
newQF[i] = foundry.utils.duplicate(list[i])
|
||||
if (newQF[i].name == "Trap Blade") {
|
||||
newQF[i].name = "TrapBlade"; // Auto-patch, without space!
|
||||
//console.log("PATCHED", trim);
|
||||
}
|
||||
let oldName = newQF[i].name
|
||||
newQF[i].name = game.i18n.localize(oldName)
|
||||
if (!newQF[i].name) newQF[i].name = oldName
|
||||
}
|
||||
return newQF;
|
||||
}
|
||||
},
|
||||
// Search back in careers the translated name of the groupe (as it is the name of the level career itself)
|
||||
"career_careergroup": (value) => {
|
||||
// Manage exception - Dirty hack
|
||||
if (value == 'Slayer') {
|
||||
return "Tueur";
|
||||
}
|
||||
if (value == 'Druidic Priest') {
|
||||
return "Druide";
|
||||
}
|
||||
//console.log("Carre groupe : ", value )
|
||||
// Per default
|
||||
let validCompendiums = game.wfrp4e.tags.getPacksWithTag("career")
|
||||
for (let compData of validCompendiums) {
|
||||
let newName = game.babele.translate(compData.metadata.id, { name: value }).name
|
||||
if (!newName) newName = value
|
||||
return newName
|
||||
}
|
||||
ui.notifications.error("Impossible de trouver la carrière " + value + ". Elle n'est probablement pas traduite.", { permanent: true })
|
||||
return value
|
||||
},
|
||||
|
||||
"mutations_modifier": (value) => { // This is really UGLYYYY i know, but i started like this and discovered afterward that many strings were not easy to automate... Sorry :)
|
||||
//console.log("Parsing mutation :", value);
|
||||
if (!value) return;
|
||||
value = value.toLowerCase();
|
||||
value = value.replace("gain a broken condition if you fail a test derived from ", "Gagnez un état Brisé si vous échouez à un test dérivé de ");
|
||||
value = value.replace("weapon skill", "Capacité de Combat");
|
||||
value = value.replace("ballistic skill", "Capacité de Tir");
|
||||
value = value.replace("strength", "Force");
|
||||
value = value.replace("toughness", "Endurance");
|
||||
value = value.replace("agility", "Agilité");
|
||||
value = value.replace("dexterity", "Dextérité");
|
||||
value = value.replace("willpower", "Force Mentale");
|
||||
value = value.replace("fellowship", "Sociabilité");
|
||||
value = value.replace("initiative", "Initiative");
|
||||
value = value.replace("intelligence", "Intelligence");
|
||||
value = value.replace("armor points to the head", "PA à la Tête");
|
||||
value = value.replace("subject to frenzy", "Sujet à la Frénésie");
|
||||
value = value.replace("you do not scar", "Aucune cicatrice");
|
||||
value = value.replace("movement", "Mouvement");
|
||||
value = value.replace("armor points to all locations", "PA sur tout le corps");
|
||||
value = value.replace("to any test when alone", "à tout les tests lorsque seul");
|
||||
value = value.replace("track", "Pistage");
|
||||
value = value.replace("to any test not hurting another", "à tout les Tests n'aggressant pas autrui");
|
||||
value = value.replace("on tests to hurt", "pour les tests impliquant une agression")
|
||||
value = value.replace("to all language tests when speaking", "à tout les Tests de Langue lorsque vous parlez");
|
||||
value = value.replace("on perception tests involving sight", "aux Tests de Perception impliquant la Vue");
|
||||
value = value.replace("to all Sociabilité tests", "à tout les Tests de Sociabilité");
|
||||
return value;
|
||||
},
|
||||
"talent_name": (name, translation) => {
|
||||
console.log("NAME !!!", name, translation)
|
||||
},
|
||||
"effects": (effects, translations) => {
|
||||
if (!effects) return;
|
||||
if (!translations) return;
|
||||
for (let i = 0; i < effects.length; i++) {
|
||||
let effect = effects[i];
|
||||
//console.log("EFFECT LABEL1:", effect );
|
||||
effect.label = translations['label' + i];
|
||||
}
|
||||
return effects
|
||||
},
|
||||
"diseases_effects": (effects, translations) => {
|
||||
if (!effects) return;
|
||||
for (let i = 0; i < effects.length; i++) {
|
||||
let effect = effects[i];
|
||||
let label = effect.label;
|
||||
let gravity = "";
|
||||
if (label.includes("(") && label.includes(")")) { // Then process specific skills name with (xxxx) inside
|
||||
let re = /(.*) +\((.*)\)/i;
|
||||
let res = re.exec(label);
|
||||
label = res[1].trim(); // Get the gravity
|
||||
gravity = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword
|
||||
}
|
||||
effect.label = game.i18n.localize(label) + gravity;
|
||||
}
|
||||
},
|
||||
// Auto-translate duration
|
||||
"spells_duration_range_target_damage": (value) => {
|
||||
return WFRP4FrTranslation.processSpellContent(value);
|
||||
},
|
||||
// Auto-translate disease duration units
|
||||
"disease_duration_unit": (unit) => {
|
||||
return game.i18n.localize(unit).capitalize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------*/
|
||||
Hooks.once('ready', () => {
|
||||
|
||||
import("https://www.uberwald.me/fvtt_appcount/count-class-ready.js").then(moduleCounter => {
|
||||
console.log("ClassCounter loaded", moduleCounter)
|
||||
moduleCounter.ClassCounter.registerUsageCount("wh4-fr-translation")
|
||||
}).catch(err =>
|
||||
console.log("No stats available, giving up.")
|
||||
)
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,506 @@
|
||||
/************************************************************************************/
|
||||
// Some internal test strings
|
||||
let strfr = `LA TERREUR DE LA TEUFEL, TROLL DE RIVIÈRE RUSÉ
|
||||
M CC CT F E I Ag Dex Int FM Soc B
|
||||
4 40 15 55 45 20 15 15 30 20 5 38
|
||||
Traits : Amphibie, Arme +9, Armure (2), Dur à Cuire, Insensible
|
||||
à la douleur, Morsure +8, Régénération, Taille (Grande), Vision
|
||||
Nocturne, Vomissement`
|
||||
|
||||
let str1 = `JABBERSLYTHE
|
||||
M WS BS S T I Agi Dex
|
||||
Int WP Fel W
|
||||
7 45 40 55
|
||||
50 20 35 - 10 20 - 20
|
||||
Traits: Armour 3, Bestial, Bite+9, Bounce, Corrosive
|
||||
Blood, Distracting, Infected, Maddening Aura (see
|
||||
page 17), Night Vision, Size (Enormous), Tail +8,
|
||||
Tongue Attack +5 (12), Venom, Weapon +9.
|
||||
`;
|
||||
let str = `REINER AND DIETER LEDERMANN
|
||||
SMUGGLERS (BRASS 3)
|
||||
M WS BS S
|
||||
T
|
||||
I
|
||||
Agi Dex Int WP Fel W
|
||||
4
|
||||
33 33 32 35 38 41 39 33 37 38 12
|
||||
Traits: Weapon (Dagger +5, Sword +7)
|
||||
Skills: Bribery 43, Charm 43, Cool 42,
|
||||
Consume Alcohol 45, Gossip 43, Haggle 43,
|
||||
Lore (Local 38), Perception 43,
|
||||
Secret Signs (Smuggler) 37
|
||||
Talents: Briber, Criminal, Dealmaker,
|
||||
Etiquette (Criminals, Doktor, Guilder)
|
||||
Trappings: Dagger, Hand Weapon (Sword)
|
||||
`
|
||||
//import ItemWfrp4e from "/systems/wfrp4e/modules/item/item-wfrp4e.js"
|
||||
//import ItemWfrp4e from "/systems/wfrp4e/wfrp4e.js"
|
||||
|
||||
/************************************************************************************/
|
||||
import "./xregexp-all.js";
|
||||
const us_carac = 'm\\s+ws\\s+bs\\s+s\\s+t\\s+i\\s+agi?\\s+dex\\s+int\\s+\\wp\\s+fel\\s+w';
|
||||
const fr_carac = 'm\\s+cc\\s+ct\\s+f\\s+e\\s+i\\s+agi?\\s+dex\\s+int\\s+fm\\s+soc\\s+b';
|
||||
const carac_val = '(?<m>[0-9\\-]+)\\s+(?<ws>[0-9\\-]+)\\s+(?<bs>[0-9\\-]+)\\s+(?<s>[0-9\\-]+)\\s+(?<t>[0-9\\-]+)\\s+(?<i>[0-9\\-]+)\\s+(?<ag>[0-9\\-]+)\\s+(?<dex>[0-9\\-]+)\\s+(?<int>[0-9\\-]+)\\s+(?<wp>[0-9\\-]+)\\s+(?<fel>[0-9\\-]+)\\s+(?<w>[0-9\\-\*]+)';
|
||||
const name_val = '(?<name>[a-zA-Zéèêâôïäüù\\s\\-,\']*)[\\s\\r\\na-zA-Zéèêâôïäüù]*(?<tiers>.*|[\\(\\)a-z0-9]+)';
|
||||
let sectionDataFR = [
|
||||
{ name: "trait", toFind: "Traits\\s*:", secondParse: '(?<name>[a-zöàéè\\s]*)[\\s\\+]*(?<value>.*|[\\+0-9]+)', index: -1 },
|
||||
{ name: "skill", toFind: "Compétences\\s*:", secondParse: '(?<name>[a-zàéè\\s\\(\\)]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "talent", toFind: "Talents\\s*:", secondParse: '(?<name>[a-zöàéè\\-\\!\\(\\)\\s\\/\'’]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "mutation", toFind: "Mutations\\s*:", secondParse: '(?<name>[a-zöàéè\\s]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "trapping", toFind: "Equipement\\s*:", secondParse: '(?<name>[a-zöàéè0-9\\s(\\)\\-]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "spell", toFind: "Sorts\\s*\\([a-z\\s]*\\)*:", secondParse: '(?<name>[a-zöàéè\\s]*)', index: -1 },
|
||||
{ name: "spellpetty", toFind: "Spells\\s*\\(Magie Mineure\\)*:", secondParse: '(?<name>[a-zö\\-\\s]*)', index: -1 },
|
||||
{ name: "spellarcane", toFind: "Spells\\s*\\(Arcane[a-z\\s]*\\)*:", secondParse: '(?<name>[a-zö\\-\\s]*)', index: -1 },
|
||||
{ name: "spellarcane", toFind: "Spells\\s*\\(Domaine\\s*(?<lore>[a-z\\s]*)\\)*:", secondParse: '(?<name>[a-zö\\-\\s]*)', index: -1 }
|
||||
];
|
||||
let sectionDataUS = [
|
||||
{ name: "trait", toFind: "Traits\\s*:", secondParse: '(?<name>[a-z\\s]*)[\\s\\+]*(?<value>.*|[\\+0-9]+)', index: -1 },
|
||||
{ name: "skill", toFind: "Skills\\s*:", secondParse: '(?<name>[a-z\\s\\(\\)]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "talent", toFind: "Talents\\s*:", secondParse: '(?<name>[a-z\\-\\s\\!\\(\\)\\/\'’]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "mutation", toFind: "Mutations\\s*:", secondParse: '(?<name>[a-zö\\s]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "trapping", toFind: "Trappings\\s*:", secondParse: '(?<name>[a-zö0-9\\s\\(\\)\\-]*)[\\s\\+]*(?<value>.*|[0-9]+)', index: -1 },
|
||||
{ name: "spellpetty", toFind: "Spells\\s*\\(Petty\\s*[a-z\\s]*\\)*:", secondParse: '(?<name>[a-zö\\-\\s]*)', index: -1 },
|
||||
{ name: "spellarcane", toFind: "Spells\\s*\\(Arcane\\s*[a-z\\s]*\\)*:", secondParse: '(?<name>[a-zö\\-\\s]*)', index: -1 },
|
||||
{ name: "spelllore", toFind: "Spells\\s*\\(Lore\\s*of\\s*(?<lore>[a-z\\s]*)\\)*:", secondParse: '(?<name>[a-zö\\-\\s]*)', index: -1 }
|
||||
]
|
||||
let moneyUS = [ {name:" gold crown", key: "gc"}, {name: " gc", key: "gc"},
|
||||
{name:" silver shilling", key:"ss"}, {name:" ss", key: "ss"}, {name:"/-", key: "ss"}, {name:" brass penn", key: "bp"}, {name:" bp", key: "bp"} ]
|
||||
let moneyFR = [ {name:" couronnes d", key: "gc"}, {name: " co", key: "gc"},
|
||||
{name:" pistoles ", key:"ss"}, {name:" pa", key: "ss"}, {name:"/-", key: "ss"}, {name:" sous de cuivre", key: "bp"}, {name:" sc", key: "bp"} ]
|
||||
let regSep = XRegExp('\\s*,\\s*', 'gi'); // Term separator, with auto trim
|
||||
let regLine1 = XRegExp('[\\r\\n\\.]', 'gi'); // Term separator, with auto trim
|
||||
let regName = XRegExp(name_val, 'gi');
|
||||
|
||||
// Used to detect/manage the skill groupings in the statblock
|
||||
const __SkillGroupsUS = ["Melee", "Lore", "Trade"]
|
||||
const __SkillGroupsFR = ["Corps à corps", "Domaine", "Métier"]
|
||||
// Used to auto update the system.tests.value field
|
||||
const __hasTestValue = {
|
||||
"etiquette": true,
|
||||
"resistance": true,
|
||||
"acute sense": true,
|
||||
"strider": true,
|
||||
"savant": true,
|
||||
"craftsman": true
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
async function __findItem(itemName, itemType, location = null) {
|
||||
let toSearch = itemName.toLowerCase().trim();
|
||||
let items = game.items.contents.filter(i => i.type == itemType)
|
||||
|
||||
console.log("Searching for", toSearch, itemType)
|
||||
|
||||
// Search imported items first
|
||||
for (let i of items) {
|
||||
if (i.name.toLowerCase() == toSearch.toLowerCase() && i.type == itemType)
|
||||
return i.toObject();
|
||||
}
|
||||
let itemList
|
||||
|
||||
// find pack -> search pack -> return entity
|
||||
if (location) {
|
||||
let pack = game.packs.find(p => {
|
||||
location.split(".")[0] == p.metadata.package && location.split(".")[1] == p.metadata.name
|
||||
})
|
||||
if (pack) {
|
||||
await pack.getIndex().then(index => itemList = index);
|
||||
let searchResult = itemList.find(t => (t.translated && t.type == itemType && (t.flags?.babele?.originalName.toLowerCase() == toSearch || s.flags?.babele?.originalName.toLowerCase().split("(")[0].trim() == toSearch)) || (t.type == itemType && (t.name.toLowerCase() == toSearch || t.name.toLowerCase().split("(")[0].trim() == toSearch)));
|
||||
if (searchResult) {
|
||||
let item = await pack.getDocument(searchResult._id)
|
||||
return item.toObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If all else fails, search each pack
|
||||
for (let p of game.wfrp4e.tags.getPacksWithTag(itemType)) {
|
||||
await p.getIndex().then(index => itemList = index);
|
||||
console.log("Seatch", itemType, toSearch, itemList);
|
||||
let searchResult = itemList.find(t => (t.translated && t.type == itemType && (t.flags?.babele?.originalName.toLowerCase() == toSearch || t.flags?.babele?.originalName.toLowerCase().split("(")[0].trim() == toSearch)) || (t.type == itemType && (t.name.toLowerCase() == toSearch || t.name.toLowerCase().split("(")[0].trim() == toSearch)));
|
||||
if (searchResult) {
|
||||
let item = await p.getDocument(searchResult._id)
|
||||
return item.toObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************************/
|
||||
async function __findSkill(skillName, value = undefined) {
|
||||
let toSearch = skillName.toLowerCase().trim();
|
||||
let parseStr = '(?<name>[a-z\\s]*)[\\s\\+]*(?<specialized>[a-z\\s\\(\\)]*)';
|
||||
let skillSplit = XRegExp.exec(skillName, XRegExp(parseStr, 'gi'));
|
||||
|
||||
// First try world items
|
||||
let item
|
||||
let worldItem = game.items.contents.filter(i => i.type == "skill" && i.name.toLowerCase() == toSearch)[0];
|
||||
if (worldItem) {
|
||||
item = worldItem.toObject()
|
||||
} else {
|
||||
let packs = game.wfrp4e.tags.getPacksWithTag("skill");
|
||||
for (let pack of packs) {
|
||||
let skillList = await pack.getIndex();
|
||||
// Search for specific skill (won't find unlisted specializations)
|
||||
let searchResult = skillList.find(s => (s.type == "skill" && s.translated && s.flags?.babele?.originalName.toLowerCase() == toSearch) || (s.type == "skill" && s.name.toLowerCase() == toSearch));
|
||||
if (!searchResult) {
|
||||
let toSearchClean = toSearch.split("(")[0].trim();
|
||||
searchResult = skillList.find(s => (s.type == "skill" && s.translated && s.flags?.babele?.originalName.toLowerCase().split("(")[0].trim() == toSearchClean) ||
|
||||
(s.type == "skill" && s.name.toLowerCase().split("(")[0].trim() == toSearchClean));
|
||||
}
|
||||
if (searchResult) {
|
||||
let dbSkill;
|
||||
await pack.getDocument(searchResult._id).then(packSkill => dbSkill = packSkill);
|
||||
item = dbSkill.toObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item) {
|
||||
//console.log("Skill name1", skillSplit)
|
||||
if (skillSplit.specialized && (item.name.includes('()') || item.name.includes('( )'))) {
|
||||
let spec = XRegExp.replace(skillSplit.specialized, "(", "");
|
||||
spec = XRegExp.replace(spec, ")", "");
|
||||
let skillSplit2 = XRegExp.exec(item.name, XRegExp(parseStr, 'gi'));
|
||||
item.name = skillSplit2.name + '(' + game.i18n.localize(spec.trim()) + ')'
|
||||
//dbSkill.update( { name: } );
|
||||
}
|
||||
//game.babele.translate('wfrp4e-core.skills', dbSkill);
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
async function __findTalent(talentName) {
|
||||
let parseStr = '(?<name>[a-z\\s\\!\\/\']*)[\\s\\+]*(?<specialized>[a-z\\s\\(\\)\'’]*)';
|
||||
let talentSplit = XRegExp.exec(talentName, XRegExp(parseStr, 'gi'));
|
||||
let toSearch = talentSplit.name.toLowerCase().trim();
|
||||
|
||||
//console.log("Talent name", toSearch, talentName, talentSplit)
|
||||
|
||||
// First try world items
|
||||
let item
|
||||
let worldItem = game.items.contents.filter(i => i.type == "talent" && i.name.toLowerCase() == toSearch)[0];
|
||||
if (worldItem) {
|
||||
item = worldItem.toObject()
|
||||
} else {
|
||||
let packs = game.wfrp4e.tags.getPacksWithTag("talent");
|
||||
for (let pack of packs) {
|
||||
let talentList = await pack.getIndex();
|
||||
// Search for specific talent (won't find unlisted specializations)
|
||||
let searchResult = talentList.find(s => (s.type == "talent" && s.translated && s.flags?.babele?.originalName.toLowerCase() == toSearch) || (s.type == "talent" && s.name.toLowerCase() == toSearch));
|
||||
if (!searchResult) {
|
||||
let toSearchClean = toSearch.split("(")[0].trim();
|
||||
searchResult = talentList.find(s => (s.type == "talent" && s.translated && s.flags?.babele?.originalName.toLowerCase().split("(")[0].trim() == toSearchClean) ||
|
||||
(s.type == "talent" && s.name.toLowerCase().split("(")[0].trim() == toSearchClean));
|
||||
}
|
||||
if (searchResult) {
|
||||
let dbTalent;
|
||||
//console.log("Talent name1", talentSplit)
|
||||
await pack.getDocument(searchResult._id).then(packTalent => dbTalent = packTalent);
|
||||
item = dbTalent.toObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item) {
|
||||
if (talentSplit.specialized) {
|
||||
let spec = XRegExp.replace(talentSplit.specialized, "(", "");
|
||||
spec = XRegExp.replace(spec, ")", "");
|
||||
spec = spec.trim()
|
||||
let addToName = true
|
||||
//console.log("Talent name2", dbTalent.name, spec, game.i18n.localize( spec.trim()) )
|
||||
if (toSearch == 'doomed') {
|
||||
item.system.description.value += `<br><br><em>${spec}</em>`;
|
||||
addToName = false // Very specific case
|
||||
}
|
||||
if (__hasTestValue[toSearch]) {
|
||||
item.system.tests.value = game.i18n.localize(spec);
|
||||
}
|
||||
item.name = talentSplit.name
|
||||
if (addToName) {
|
||||
item.name += '(' + game.i18n.localize(spec) + ')'
|
||||
}
|
||||
item.system.advances.value = 1 // Set 1 advance
|
||||
}
|
||||
// Specific Talent post-processing
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
function __patchName(name) {
|
||||
if (name.toLowerCase == 'magic sense')
|
||||
name = 'Magical Sense'
|
||||
return name
|
||||
}
|
||||
|
||||
/************************************************************************************/
|
||||
export default async function statParserFR(statString, type = "npc") {
|
||||
let model = foundry.utils.duplicate(game.model.Actor[type]);
|
||||
|
||||
// Patch wront/strange carac value before processing
|
||||
statString = statString.replace(/ –/g, " 0")
|
||||
let moneys = { gc: 0, ss: 0, bp: 0 }
|
||||
|
||||
let statNameReg = us_carac
|
||||
let sectionData = foundry.utils.duplicate(sectionDataUS)
|
||||
let skillGrouping = __SkillGroupsUS
|
||||
let moneyLang = moneyUS
|
||||
// Detect French stat block
|
||||
if (statString.includes('CC') && statString.includes('CT') && statString.includes('FM')) {
|
||||
//ui.notifications.warn("Le parsing de stablock en Français n'est pas encore prêt")
|
||||
statNameReg = fr_carac
|
||||
sectionData = foundry.utils.duplicate(sectionDataFR)
|
||||
skillGrouping = __SkillGroupsFR
|
||||
moneyLang = moneyFR
|
||||
}
|
||||
|
||||
let reg1 = XRegExp(statNameReg, 'gi')
|
||||
let res = reg1.test(statString)
|
||||
if (res) { //stat block identified go on
|
||||
let globalItemList = []
|
||||
|
||||
// Extract the name
|
||||
let res1 = XRegExp.exec(statString, reg1)
|
||||
let pnjStr = statString.substring(0, res1.index)
|
||||
console.log("REG", res1, pnjStr, res)
|
||||
let nameRes = XRegExp.exec(pnjStr, regName)
|
||||
console.log("REG", nameRes, regName)
|
||||
//console.log(nameRes)
|
||||
if (nameRes.tiers && nameRes.tiers.length > 0 && hasProperty(model, "details.status.value")) {
|
||||
let regTiers = XRegExp("(?<name>[A-Za-z]*)\\s+(?<level>[0-9]*)");
|
||||
let resTiers = XRegExp.exec(nameRes.tiers, regTiers);
|
||||
console.log(resTiers);
|
||||
model.details.status.value = game.i18n.localize(resTiers.name.trim()) + " " + resTiers.level;
|
||||
}
|
||||
let baseName = nameRes.name.split("\n")
|
||||
// Compute the PNJ name
|
||||
let pnjName = baseName[0].split("—")[0].split(" ").filter(f => !!f)
|
||||
pnjName = pnjName.map(word => {
|
||||
if (word == "VON")
|
||||
return word.toLowerCase();
|
||||
|
||||
word = word.toLowerCase();
|
||||
word = word[0].toUpperCase() + word.substring(1, word.length);
|
||||
return word;
|
||||
})
|
||||
pnjName = pnjName.join(" ")
|
||||
if (baseName[1]) {
|
||||
let careerName = baseName[1].split(",")[0]
|
||||
//console.log("CAREER", careerName)
|
||||
let career = await __findItem(careerName, "career")
|
||||
if (career) {
|
||||
globalItemList.push(career)
|
||||
}
|
||||
}
|
||||
// Get the carac values
|
||||
let reg2 = XRegExp(carac_val, 'gi')
|
||||
let resCarac = XRegExp.exec(statString, reg2) // resr contains all carac found
|
||||
|
||||
// Setup carac
|
||||
console.log("CARAC", resCarac)
|
||||
if (resCarac["Agi"]) resCarac["Ag"] = resCarac["Agi"] // Auto patch
|
||||
model.details.move.value = Number(resCarac["m"])
|
||||
for (let key in model.characteristics) {
|
||||
if (resCarac[key] === '-') resCarac[key] = 0
|
||||
model.characteristics[key].initial = Number(resCarac[key])
|
||||
}
|
||||
console.log("CARAC", model.characteristics);
|
||||
|
||||
// Search position of skills/talents/...
|
||||
for (let def of sectionData) {
|
||||
def.regDef = XRegExp(def.toFind, 'gi');
|
||||
let res = XRegExp.exec(statString, def.regDef);
|
||||
if (res) {
|
||||
def.index = res.index // Position of the string in the whole statblock
|
||||
def.lore = res.lore // Extraction of the lore, when present
|
||||
} // Get the index in the string
|
||||
//console.log(" Parsing", def.name, res);
|
||||
}
|
||||
// Sort to split position of various substring
|
||||
sectionData.sort(function (a, b) { return a.index - b.index; });
|
||||
|
||||
// Then loop again and process each item type
|
||||
for (let i = 0; i < sectionData.length; i++) {
|
||||
let def = sectionData[i];
|
||||
if (def.index > -1) {
|
||||
let maxIndex = statString.length
|
||||
if (sectionData[i + 1] && sectionData[i + 1].index > -1) {
|
||||
maxIndex = sectionData[i + 1].index
|
||||
}
|
||||
def.substring = statString.substring(def.index, maxIndex)
|
||||
def.substring = XRegExp.replace(def.substring, def.regDef, "")
|
||||
def.substring = XRegExp.replace(def.substring, regLine1, " ")
|
||||
// At this point, def.substring contains the items list as a string
|
||||
|
||||
// Then create a table of it in termList, with specific sub-parsing rules
|
||||
let termList = XRegExp.split(def.substring, regSep);
|
||||
//console.log("Term list identified", termList)
|
||||
let lastSkillName
|
||||
for (let name of termList) {
|
||||
let itemFound, subres, value;
|
||||
if (def.secondParse) {
|
||||
subres = XRegExp.exec(name, XRegExp(def.secondParse, 'gi'))
|
||||
//console.log("Second pars", def, name, subres)
|
||||
name = subres.name.trim().replace("\n", "").replace("\r", "")
|
||||
if (subres.value) {
|
||||
value = XRegExp.replace(subres.value, "(", "")
|
||||
value = XRegExp.replace(value, ")", "")
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
}
|
||||
name = __patchName(name)
|
||||
if (def.name == 'trait') {
|
||||
try {
|
||||
itemFound = await __findItem(name, "trait")
|
||||
}
|
||||
catch { }
|
||||
if (itemFound && value && value.length > 0) {
|
||||
let number = value.match(/\d+/g);
|
||||
if (number && (name.toLowerCase() == 'ranged' || name.toLowerCase() == 'weapon' || name.toLowerCase() == "bite" || name.toLowerCase() == "tail" ||
|
||||
name.toLowerCase() == 'arme' || name.toLowerCase() == "morsure" || name.toLowerCase() == "queue")) {
|
||||
//console.log(itemFound)
|
||||
number = number[0] // Take first number ....
|
||||
itemFound.system.specification.value = Number(number) - ((name.toLowerCase() == 'ranged') ? 0 : Math.floor(Number(model.characteristics.s.initial) / 10))
|
||||
} else {
|
||||
itemFound.system.specification.value = game.i18n.localize(value)
|
||||
}
|
||||
//itemFound.name += "(" + value + ")"
|
||||
}
|
||||
if (!itemFound)
|
||||
ui.notifications.error("Trait non trouvé, à ajouter manuellement : " + name, { permanent: true })
|
||||
|
||||
} else if (def.name == 'skill') {
|
||||
try {
|
||||
itemFound = await __findSkill(name, value);
|
||||
}
|
||||
catch { }
|
||||
let newName = name
|
||||
if (!itemFound && lastSkillName) {
|
||||
newName = lastSkillName + " (" + name + ")"
|
||||
itemFound = await __findSkill(newName, value)
|
||||
}
|
||||
if (itemFound && subres && value) {
|
||||
itemFound.system.advances.value = Number(value) - Number(model.characteristics[itemFound.system.characteristic.value].initial);
|
||||
}
|
||||
lastSkillName = undefined
|
||||
for (let keySkillGroup of skillGrouping) {
|
||||
if (newName.includes(keySkillGroup)) { // useful to handle skills grouping
|
||||
lastSkillName = keySkillGroup
|
||||
}
|
||||
}
|
||||
if (!itemFound) {
|
||||
ui.notifications.error("Compétence non trouvée, à ajouter manuellement : " + name, { permanent: true })
|
||||
}
|
||||
} else if (def.name == 'talent') {
|
||||
try {
|
||||
itemFound = await __findTalent(name);
|
||||
}
|
||||
catch { }
|
||||
if (itemFound && subres && value)
|
||||
itemFound.system.advances.value = Number(value);
|
||||
if (!itemFound) {
|
||||
ui.notifications.error("Talent non trouvé, à ajouter manuellement : " + name, { permanent: true })
|
||||
}
|
||||
|
||||
} else if (def.name == 'trapping') {
|
||||
itemFound = await __findItem(name, "trapping");
|
||||
if (!itemFound) {
|
||||
itemFound = await __findItem(name, "weapon");
|
||||
}
|
||||
if (!itemFound) {
|
||||
itemFound = await __findItem(name, "armor");
|
||||
}
|
||||
if (!itemFound && name) {
|
||||
if (name.toLowerCase().includes("armor") || name.toLowerCase().includes("armure") || name.toLowerCase().includes("armour")) {
|
||||
itemFound = new ItemWfrp4e({ img: "systems/wfrp4e/icons/blank.png", name: name, type: "armour", system: game.model.Item.armour })
|
||||
} else if (name.toLowerCase().includes("weapon") || name.toLowerCase().includes("arme")) {
|
||||
itemFound = new ItemWfrp4e({ img: "systems/wfrp4e/icons/blank.png", name: name, type: "weapon", system: game.model.Item.weapon })
|
||||
} else {
|
||||
itemFound = new ItemWfrp4e({ img: "systems/wfrp4e/icons/blank.png", name: name, type: "trapping", system: game.model.Item.trapping })
|
||||
itemFound.system.trappingType.value = "misc"
|
||||
if (name.toLowerCase().includes("cloth")) {
|
||||
itemFound.system.trappingType.value = "clothingAccessories"
|
||||
}
|
||||
}
|
||||
itemFound = itemFound.toObject()
|
||||
}
|
||||
// Searching money stuff
|
||||
for (let mondeyDef of moneyLang) {
|
||||
if (name.toLowerCase().includes(mondeyDef.name)) {
|
||||
let regMoney = XRegExp("(\\d+)\\s*" + mondeyDef.name.trim(), 'gi')
|
||||
let moneyParsed = XRegExp.exec(name.toLowerCase(), regMoney)
|
||||
console.log("Parsing money", name, moneyParsed)
|
||||
moneys[mondeyDef.key] += (moneyParsed && moneyParsed[1]) ? Number(moneyParsed[1]) : 0
|
||||
}
|
||||
}
|
||||
} else if (def.name.toLowerCase().includes('spell')) {
|
||||
console.log("Found spells section!!!!", name, def, def.lore || "NO LORE")
|
||||
// Lore management, firs pass
|
||||
if (def.lore) {
|
||||
let newName = name + " (" + def.lore + ")"
|
||||
itemFound = await __findItem(newName, "spell");
|
||||
//console.log("Trying to find ", newName, itemFound)
|
||||
}
|
||||
if (!itemFound) { // If not found with Lore, try without the lore
|
||||
itemFound = await __findItem(name, "spell");
|
||||
}
|
||||
if (!itemFound && name) { // Auto-create the spell name
|
||||
itemFound = new ItemWfrp4e({ img: "systems/wfrp4e/icons/blank.png", name: name + "(To be checked/completed)", type: "spell", system: game.model.Item.spell })
|
||||
itemFound = itemFound.toObject();
|
||||
}
|
||||
} else if (def.name == 'mutation') {
|
||||
try {
|
||||
itemFound = await __findItem(name, "mutation");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
if (itemFound)
|
||||
globalItemList.push(itemFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
let moneyItems = await game.wfrp4e.utility.allMoneyItems() || [];
|
||||
moneyItems = moneyItems.sort((a, b) => (a.system.coinValue.value > b.system.coinValue.value) ? -1 : 1);
|
||||
for (let m of moneyItems) {
|
||||
m.system.quantity.value = 0
|
||||
if (m.system.coinValue.value == 1) {
|
||||
m.system.quantity.value += moneys.bp
|
||||
}
|
||||
if (m.system.coinValue.value == 240) {
|
||||
m.system.quantity.value += moneys.gc
|
||||
}
|
||||
if (m.system.coinValue.value == 12) {
|
||||
m.system.quantity.value += moneys.ss
|
||||
}
|
||||
}
|
||||
globalItemList = globalItemList.concat(moneyItems);
|
||||
//DEBUG : console.log("My liste :", moneyItems, moneys);
|
||||
let name = pnjName;
|
||||
|
||||
let effects = globalItemList.reduce((total, globItem) => total.concat(globItem.effects), [])
|
||||
effects = effects.filter(e => !!e)
|
||||
effects = effects.filter(e => e.transfer)
|
||||
for (let e of effects) {
|
||||
for (let c of e.changes) {
|
||||
// Charac management stuff
|
||||
if (c.key?.includes("characteristics")) {
|
||||
let cKey = c.key.split(".")[2]
|
||||
model.characteristics[cKey].initial -= Number(c.value)
|
||||
}
|
||||
// Move management
|
||||
if (c.key?.includes("move")) {
|
||||
model.details.move.value -= Number(c.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
//DEBUG : console.log("EFFECTS", effects)
|
||||
|
||||
return { name, type, system: model, items: globalItemList, effects }
|
||||
}
|
||||
|
||||
// If the carac string has not been found
|
||||
ui.notifications.error("Impossible de convertir ces statitiques, les caractéristiques n'ont pas été trouvées", { permanent: true })
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,77 @@
|
||||
const __INGREDIENTS = [
|
||||
{ "name": "Pomme",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": [ "Terre agricole", "Forêt", "Prairie"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{ "name": "Haricot Noir",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": ["Prairie"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{ "name": "Coriandre",
|
||||
"type": "herb",
|
||||
"rarete": "rare",
|
||||
"milieu": ["Prairie"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{ "name": "Vêture de Renard",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": ["Forêt"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{
|
||||
"name": "Iris",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": ["Marais", "Lande"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{
|
||||
"name": "Laurier",
|
||||
"type": "herb",
|
||||
"rarete": "limite",
|
||||
"milieu": ["Forêt"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{
|
||||
"name": "Roquette",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": ["Tourbière", "Prairie"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{
|
||||
"name": "Romarin",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": ["Forêt", "Prairie"],
|
||||
"affinite": "ghur"
|
||||
},
|
||||
{
|
||||
"name": "Câpre",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": ["Marais", "Lande"],
|
||||
"affinite": "shyish"
|
||||
},
|
||||
{
|
||||
"name": "Ciboulette",
|
||||
"type": "herb",
|
||||
"rarete": "commun",
|
||||
"milieu": ["Forêt", "Prairie"],
|
||||
"affinite": "shyish"
|
||||
},
|
||||
{
|
||||
"name": "Cigüe Aquatique",
|
||||
"type": "herb",
|
||||
"rarete": "limite",
|
||||
"milieu": ["Aquatique", "Marais"],
|
||||
"affinite": "shyish"
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
const __EFFETS = [
|
||||
{
|
||||
"name": "Corpus Curator",
|
||||
"cout": 5,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": "La Potion agit comme un anticoagulant, faisant suinter le sang par les pores de la peau. Le buveur gagne 2 états Hémorragie."
|
||||
},
|
||||
{
|
||||
"name": "Renforcer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": "La Potion est imprégnée des forces de la Créature utilisée dans sa fabrication. Si un ingrédient utilisé provient d'une créature qui avait des scores plus élevés que les caractéristiques du buveur, le buveur bénéficie de la valeur de la caractéristique concernée pour 1d10 (ou 4) rounds. La caractéristique est décidée par l’Apothicaire lors de la Fabrication."
|
||||
},
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": "Cette Potion est essentiellement un distillat de Ghur. Le buveur gagne le Trait Peur (1) pour 1d10 (ou 4) rounds. De plus, tout Test de Focalisation (Ghur) fait par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Domaine des Bêtes",
|
||||
"cout": 5,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": "La Potion est infusée avec un sort du Domaine des Bêtes ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine des Bêtes, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Nature apaisante",
|
||||
"cout": 5,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": "La Potion est rendue plus agréable et moins agressive pour le buveur. Le jet de Détérioration reçoit un « malus » de -20% et le buveur gagne un bonus de +20 à son test d'Endurance pour annuler tous les effets de la Détérioration."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": "La Potion est infusée avec l’essence même de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait :<p> Amphibie, Arboricole, Armure (Valeur), Belliqueux, Bestial, Grand, Morsure (Valeur), Bond, Brutal, A Sang-Froid, Constricteur, Sang corrosif, Infravision, Dur à cuire, Perturbant, Rapide, Peur (Valeur), Vol (Valeur), Frénésie, Rage, Endurant, Cornes (Valeur), Affamé, Infecté, Parasité, Vision Nocturne, Insensible à la douleur, Taille (Petit, Moyen, Grand), Nerveux, Furtif, Foulée, Stupide, Limicole, Coriace, Pisteur, Vampirique, Venin (Difficulté), Vomissement, Arme (Valeur), Grimpant, Toile( Valeur)</p> Lorsqu’une valeur est nécessaire, il convient d’utiliser la valeur du Trait de la Créature."
|
||||
},
|
||||
{
|
||||
"name": "Exalter & Affaiblir",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": "Cet effet permet de donner à la potion des effets de bonus aux caractéristiques, en exploitant l’affinité des Vents avec les capacités physiques et mentales des créature et êtres vivants. L’Apothicaire qui applique cet effet peut octroyer de +10% (exaltation) ou -10% (affaiblissement) dans les caractéristiques suivantes, pendant 1d10 (ou 4) rounds : Capacité de Combat (CC), Force (F)."
|
||||
},
|
||||
{
|
||||
"name": "Abhorens Creaturex",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Bêtes (Ghur)",
|
||||
"description": " Le consommateur d’une telle potion devient un puissant répulsif pour les animaux et créatures ayant été utilisées comme Ingrédient. Le buveur gagne ainsi le Trait Peur (2), qui ne s’applique qu’aux créatures et animaux concernés : la règle de Peur s’applique alors, pendant 1d10 (ou 4) rounds."
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Mort (Shyish)",
|
||||
"description": "La Potion est essentiellement composée de Shyish. Le buveur gagne +2 états Exténué pour 1d10 (ou 4) rounds. De plus, toute Focalisation (Shyish) effectuée par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Arrêt du Métabolisme",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Mort (Shyish)",
|
||||
"description": "Cette Potion est infusée avec les énergies temporelles de Shyish, permettant de ralentir le temps pour le buveur. Tout les états – comme Empoisonné, Hémorragie, maladies, etc – qui agissent sur le métabolisme sont ralentis d’un facteur 10 pour 1d10 (ou 4) jours. Pour les effets qui sont subis à chaque Round de combat, ils agissent tout les 10 rounds."
|
||||
},
|
||||
{
|
||||
"name": "Domaine de la Mort",
|
||||
"cout": 5,
|
||||
"affinite": "Mort (Shyish)",
|
||||
"description": "La Potion est infusée avec un sort du Domaine de la Mort ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine de la Mort, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Corpus Curator",
|
||||
"cout": 5,
|
||||
"affinite": "Mort (Shyish)",
|
||||
"description": "La Potion donne au buveur un sentiment de peur et de malheur. Les options suivantes sont possibles : soit le buveur reçoit 2 états Brisé, soit il devient stérile pendant 30 jours, soit Le buveur subit 2 états Empoisonné lorsqu’il consomme la Potion. L’option est choisie par l’Apothicaire lors de la fabrication."
|
||||
},
|
||||
{
|
||||
"name": "Retarder",
|
||||
"cout": 5,
|
||||
"affinite": "Mort (Shyish)",
|
||||
"description": "La potion a un effet tranquillisant, et permet de ralentir son effet dans le sang. Les Effets de la Potion sont retardés de 1d10 (ou 4) rounds."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Mort (Shyish)",
|
||||
"description": "La Potion est infusée avec l’essence de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait.<p> Peur (Valeur), Immunité à la Psychologie</p>Lorsqu’une valeur est nécessaire, il convient d’utiliser la valeur du Trait de la Créature."
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Feu (Aqshy)",
|
||||
"description": "La Potion est essentiellement composée de Aqshy. Le buveur gagne +2 états En Flammes pour 1d10 (ou 4) rounds. De plus, toute Focalisation ( Aqshy) effectué par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Domaine du Feu",
|
||||
"cout": 5,
|
||||
"affinite": "Feu (Aqshy)",
|
||||
"description": "La Potion est infusée avec un sort du Domaine du Feu ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine du Feu, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Corpus Curator",
|
||||
"cout": 5,
|
||||
"affinite": "Feu (Aqshy)",
|
||||
"description": "La Potion se charge d’un feu purificateur : le buveur bénéficie d’un bonus de +20% à tout ses tests de Résistance ou de Guérison concernant les maladies pendant 1 journée."
|
||||
},
|
||||
{
|
||||
"name": "Puissance",
|
||||
"cout": 5,
|
||||
"affinite": "Feu (Aqshy)",
|
||||
"description": "La potion est renforcée, gagnant la puissance d'Aqshy. Cet effet peut être acheté pour doubler l'effet (mais pas la durée) de tout autre Effet. Ce doublement s'ajoute à toute autre augmentation effectuée, comme d’avoir acheté un effet plusieurs fois."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Feu (Aqshy)",
|
||||
"description": "La Potion est infusée avec l’essence même de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait : Souffle (Feu, Fumée), Sang corrosif, Frénésie, Rage, Immunité(Feu)"
|
||||
},
|
||||
{
|
||||
"name": "Exalter & Affaiblir",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Feu (Aqshy)",
|
||||
"description": "Cet effet permet de donner à la potion des effets de bonus aux caractéristiques, en exploitant l’affinité des Vents avec les capacités physiques et mentales des créature et êtres vivants. L’Apothicaire qui applique cet effet peut octroyer de +10% (exaltation) ou 10% (affaiblissement) dans les caractéristiques suivantes, pendant 1d10 (ou 4) rounds : Initiative (I), Force Mentale (FM)"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Cieux (Azur)",
|
||||
"description": "La Potion est essentiellement composée d’Azur. Le buveur subit un éclair qui vient de son propre cerveau, recevant des Dommages égaux à son Bonus de Force Mentale, qui ignorent le Bonus d’Endurance et les Points d’Armure. De plus, toute Focalisation (Azur) effectuée par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Gazeux",
|
||||
"cout": 5,
|
||||
"affinite": "Cieux (Azur)",
|
||||
"description": "La Potion est transformée en gaz, plutôt qu’en liquide, et peut du même coup être respirée au lieu d’être ingérée. Une fois libérée de sa fiole, elle remplit un espace de 2 mètres carrés pour 1d10 (ou 4) rounds avant de se dissiper. Des vents forts peuvent la faire disparaître prématurément également."
|
||||
},
|
||||
{
|
||||
"name": "Corpus Curator",
|
||||
"cout": 5,
|
||||
"affinite": "Cieux (Azur)",
|
||||
"description": "La Potion se charge d’une aura de bienveillance et de protection : le buveur bénéficie d’un bonus de +20% à tout ses tests de Résistance et de Calme concernant les états psychologiques (Peur, Terreur, etc.) pendant 1 journée."
|
||||
},
|
||||
{
|
||||
"name": "Domaine des Cieux",
|
||||
"cout": 5,
|
||||
"affinite": "Cieux (Azur)",
|
||||
"description": "La Potion est infusée avec un sort du Domaine des Cieux ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine des Cieux, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Cieux (Azur)",
|
||||
"description": "La Potion est infusée avec l’essence même de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait : <p>Souffle (Froid, Electricté), Etreinte glaciale , Intelligent, Sournois, Ethéré</p>. Lorsqu’une valeur est nécessaire, il convient d’utiliser la valeur du Trait de la Créature."
|
||||
},
|
||||
{
|
||||
"name": "Exalter & Affaiblir",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Cieux (Azur)",
|
||||
"description": "Cet effet permet de donner à la potion des effets de bonus aux caractéristiques, en exploitant l’affinité des Vents avec les capacités physiques et mentales des créature et êtres vivants. L’Apothicaire qui applique cet effet peut octroyer de +10% (exaltation) ou -10% (affaiblissement) dans les caractéristiques suivantes, pendant 1d10 (ou 4) rounds : Intelligence (Int), Sociabilité (Soc)"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Métal (Chamon)",
|
||||
"description": "La Potion est essentiellement composée de Chamon. Le buveur bénéficie du Trait Armure (2) sur une localisation choisie par l’Apothicaire pendant 1d10 (ou 4) Rounds. De plus, toute Focalisation (Chamon) effectuée par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Domaine du Métal",
|
||||
"cout": 5,
|
||||
"affinite": "Métal (Chamon)",
|
||||
"description": "La Potion est infusée avec un sort du Domaine du Métal ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine du Métal, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Corpus Curator",
|
||||
"cout": 5,
|
||||
"affinite": "Métal (Chamon)",
|
||||
"description": "La Potion se charge d’une aura de résistance, qui peut fournir deux effets : Soit le buveur se voit immédiatement retiré 2 états Assomé et Empétré, Soit le buveur reçoit 2 états Assommé. Le choix de l’option est fait par l’Apothicaire lors de la fabrication."
|
||||
},
|
||||
{
|
||||
"name": "Soutenir",
|
||||
"cout": 5,
|
||||
"affinite": "Métal (Chamon)",
|
||||
"description": "La Potion est épaissie et rendue plus dense, et s’accroche plus longtemps aux muscles et aux veines du buveur. La durée des Effets de la Potion est doublée."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Métal (Chamon)",
|
||||
"description": "La Potion est infusée avec l’essence même de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait : <p>Armure(Valeur), Souffle (Corrosif), Arme (Valeur)</p> Lorsqu’une valeur est nécessaire, il convient d’utiliser la valeur du Trait de la Créature."
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Vie (Ghyran)",
|
||||
"description": "La Potion est essentiellement composée de Ghyran. Le buveur enlève tout ses états Exténué et Hémorragie. De plus, toute Focalisation (Ghyran) effectuée par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Corpus Curator",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Vie (Ghyran)",
|
||||
"description": " La Potion se charge d’une aura de vitalité, qui peut fournir les effets suivants : La Potion confère le pouvoir régénérateur de la vie au buveur. Il guérit instantanément de 1d10 (ou 4) Points de Blessures, La Potion augmente les capacité des guérison naturelle du buveur, accélérant le processus de guérison par un facteur 10 pendant 1d10 (ou 4) jours. Cela inclus la guérison des maladies, des blessures et des pertes de Points de Blessures. La Potion agit comme un antidote universel, fournissant un bonus de +20% à tout les Tests liés aux Poisons pendant 1 journée. Le choix de l’option est fait par l’Apothicaire lors de la fabrication."
|
||||
},
|
||||
{
|
||||
"name": "Domaine de la Vie",
|
||||
"cout": 5,
|
||||
"affinite": "Vie (Ghyran)",
|
||||
"description": " La Potion est infusée avec un sort du Domaine de la Vie ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine de la Vie, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Débordement",
|
||||
"cout": 5,
|
||||
"affinite": "Vie (Ghyran)",
|
||||
"description": "La Potion est épaissie et double de volume sans perdre en efficacité. Une dose supplémentaire de la Potion est créée, avec les mêmes Effets que la première."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Vie (Ghyran)",
|
||||
"description": "La Potion est infusée avec l’essence même de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait : <p>Souffle (Poison), Immunité (Poison), Régénérer</p>"
|
||||
},
|
||||
{
|
||||
"name": "Exalter & Affaiblie",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Vie (Ghyran)",
|
||||
"description": "Cet effet permet de donner à la potion des effets de bonus aux caractéristiques, en exploitant l’affinité des Vents avec les capacités physiques et mentales des créature et êtres vivants. L’Apothicaire qui applique cet effet peut octroyer de +10% (exaltation) ou 10% (affaiblissement) dans les caractéristiques suivantes, pendant 1d10 (ou 4) rounds : Endurance, Agilité"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Lumière (Hysh)",
|
||||
"description": "La Potion est essentiellement composée de Hysh. Le buveur souffre d’un état Aveuglé pendant 1d10 (ou 4) rounds. De plus, toute Focalisation (Hysh) effectuée par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Domaine de la Lumière",
|
||||
"cout": 5,
|
||||
"affinite": "Lumière (Hysh)",
|
||||
"description": "La Potion est infusée avec un sort du Domaine de la Lumière ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine de la Lumière, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Purifier",
|
||||
"cout": 5,
|
||||
"affinite": "Lumière (Hysh)",
|
||||
"description": "La Potion est nettoyée de ses impuretés, laissant beaucoup moins de traces dans le corps et l’esprit. Le jet de Détérioration reçoit un « malus » de -40%."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Lumière (Hysh)",
|
||||
"description": "La Potion est infusée avec l’essence même de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait : <p>Magique, Résistance à la Magie (Valeur), Regard Pétrifiant (Valeur)</p>. Lorsqu’une valeur est nécessaire, il convient d’utiliser la valeur du Trait de la Créature."
|
||||
},
|
||||
{
|
||||
"name": "Exalter & Affaiblir",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Lumière (Hysh)",
|
||||
"description": "Cet effet permet de donner à la potion des effets de bonus aux caractéristiques, en exploitant l’affinité des Vents avec les capacités physiques et mentales des créature et êtres vivants. L’Apothicaire qui applique cet effet peut octroyer de +10% (exaltation) ou 10% (affaiblissement) dans les caractéristiques suivantes, pendant 1d10 (ou 4) rounds : Intelligence, Capacité de Tir"
|
||||
},
|
||||
{
|
||||
"name": "Abhorens Creaturex",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Lumière (Hysh)",
|
||||
"description": "Le consommateur d’une telle potion devient un puissant répulsif pour créatures ayant le Trait Morts-Vivant ou Mutation. Ces créatures subissent un malus de 10% pour toutes leurs actions envers le buveur de la potion."
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Essence",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Ombres (Uglu)",
|
||||
"description": "La Potion est essentiellement composée d’Uglu. Le buveur gagne le Trait Ethéré pendant 1d10 (ou 4) rounds. De plus, toute Focalisation (Uglu) effectuée par le buveur gagne un bonus de +20 pour la même durée. L’Apothicaire peut choisir d’annuler l’un de ses deux Effets lors de la fabrication de la Potion."
|
||||
},
|
||||
{
|
||||
"name": "Domaine des Ombres",
|
||||
"cout": 5,
|
||||
"affinite": "Ombres (Uglu)",
|
||||
"description": "La Potion est infusée avec un sort du Domaine des Ombres ou un sort d’Arcane, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine des Ombres, et coûte au lanceur le NI du sort +2 en points de Puissance (au lieu des 5 habituels)."
|
||||
},
|
||||
{
|
||||
"name": "Obscurcir",
|
||||
"cout": 5,
|
||||
"affinite": "Ombres (Uglu)",
|
||||
"description": "La Potion est modifiée - son goût, son odeur et sa texture sont atténués - , lui permettant d’être mélangée à des boissons ou de la nourriture sans être immédiatement remarquée. Si le buveur ne sait pas qu'il est sur le point d'absorber la potion, il a une pénalité de -20 à sa Perception pour la détecter."
|
||||
},
|
||||
{
|
||||
"name": "Transformer",
|
||||
"cout": 5,
|
||||
"duree10_4": true,
|
||||
"affinite": "Ombres (Uglu)",
|
||||
"description": "La Potion est infusée avec l’essence même de la Créature utilisée dans sa fabrication. Le buveur gagne les Traits suivants pendant 1d10 (ou 4) rounds, si la/les Créatures utilisées dans la fabrication de la Potion les possédait.<p>Infravision, Vision Nocturne, Nerveux, Furtif, Grimpant</p>"
|
||||
},
|
||||
{
|
||||
"name": "Camouflage",
|
||||
"cout": 5,
|
||||
"affinite": "Ombres (Uglu)",
|
||||
"description": "La Potion agit sur la présence physique du buveur, le camouflant dans les vents tourbillonnants d’Uglu. Le consommateur, tandis le serpent d’Uglu envahit son corps, reçoit un bonus de +20% pour tout ses Tests de Discrétion pendant la prochaine heure."
|
||||
},
|
||||
{
|
||||
"name": "Exalter & Affaiblir",
|
||||
"cout": 5,
|
||||
"affinite": "Ombres (Uglu)",
|
||||
"duree10_4": true,
|
||||
"description": "Cet effet permet de donner à la potion des effets de bonus aux caractéristiques, en exploitant l’affinité des Vents avec les capacités physiques et mentales des créature et êtres vivants. L’Apothicaire qui applique cet effet peut octroyer de +10% (exaltation) ou -10% (affaiblissement) dans les caractéristiques suivantes, pendant 1d10 (ou 4) rounds : Dextérité, Agilité"
|
||||
},
|
||||
{
|
||||
"name": "Domaine de la Sorcellerie",
|
||||
"cout": 5,
|
||||
"affinite": "Sorcellerie (Dhar)",
|
||||
"duree10_4": true,
|
||||
"description": "La Potion est infusée avec un sort du Domaine de la Sorcellerie, un sort d’Arcane ou un sort de Magie Mineure, que l’Apothicaire est capable d’incanter. Le sort doit avoir une cible et une portée à ‘Vous’. Cet Effet ne peut être doublé par aucun moyen. Le Sort ne bénéficie pas du bonus du Domaine de la Sorcellerie, et coûte au lanceur le NI du sort +2 en points de Puissance (ou au minimum 5 si le NI du sort est faible). Les sorts peuvent être incantés par quelqu’un d’autre (mais l’Apothicaire doit être capable de lancer au moins 1 sort) : dans ce cas, la Puissance nécessaire est doublée."
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user