Les classifications de Pralinor #609

Merged
uberwald merged 11 commits from VincentVk/foundryvtt-reve-de-dragon:v10 into v10 2023-01-14 08:54:28 +01:00
14 changed files with 464 additions and 55 deletions
Showing only changes of commit 162a6a04b8 - Show all commits

View File

@ -2,13 +2,7 @@ import { SYSTEM_RDD } from "./constants.js";
import { Grammar } from "./grammar.js"; import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
import { CompendiumTableHelpers, CompendiumTable } from "./settings/system-compendiums.js"; import { CompendiumTableHelpers, CompendiumTable } from "./settings/system-compendiums.js";
import { RdDRaretes } from "./tirage/raretes.js";
const RARETES = [
{ name: 'Commune', frequence: 54, min: 27, max: 108 },
{ name: 'Frequente', frequence: 18, min: 9, max: 36 },
{ name: 'Rare', frequence: 6, min: 3, max: 12 },
{ name: 'Rarissime', frequence: 2, min: 1, max: 4 }]
const DEFAULT_RARETE = 1;
const SETTINGS_LISTE_MILIEUX = "liste-milieux"; const SETTINGS_LISTE_MILIEUX = "liste-milieux";
const MILIEUX = [ const MILIEUX = [
@ -44,15 +38,6 @@ export class Environnement {
this.table = new CompendiumTable('faune-flore-mineraux', 'Item', ITEM_ENVIRONNEMENT_TYPES) this.table = new CompendiumTable('faune-flore-mineraux', 'Item', ITEM_ENVIRONNEMENT_TYPES)
} }
static getRarete(name = undefined) {
return RARETES.find(it => it.name == name) ?? RARETES[DEFAULT_RARETE];
}
static getFrequenceRarete(rarete, field = undefined) {
const selected = this.getRarete(rarete);
return selected[field];
}
async milieux() { async milieux() {
return Object.values(await this.mapMilieux()); return Object.values(await this.mapMilieux());
} }
@ -106,26 +91,20 @@ export class Environnement {
return await CompendiumTableHelpers.getRandom(table, 'Item', ITEM_ENVIRONNEMENT_TYPES, undefined, typeName); return await CompendiumTableHelpers.getRandom(table, 'Item', ITEM_ENVIRONNEMENT_TYPES, undefined, typeName);
} }
async buildEnvironnementTable(milieux) { async buildEnvironnementTable(milieux, filter) {
const filterMilieux = item => item.system?.environnement.filter(env => milieux.includes(env.milieu)); if (!milieux){
const itemRareteEnMilieu = item => { milieux = await this.milieux()
const raretes = filterMilieux(item);
const frequenceMax = Math.max(raretes.map(env => env.frequence));
return raretes.find(env => env.frequence == frequenceMax);
} }
const itemFrequenceEnMilieu = item => itemRareteEnMilieu(item)?.frequence ?? 0; const frequence = item => item.getRarete(milieux)?.frequence ?? 0;
const isPresentEnMilieu = item => itemFrequenceEnMilieu(item) > 0; return await this.table.buildTable(frequence, it => frequence(it) > 0 && filter(it));
return await this.table.buildTable(itemFrequenceEnMilieu, isPresentEnMilieu);
} }
async getElements(itemFrequence, filter) { async getElements(itemFrequence, filter) {
return await this.table.getContent(itemFrequence, filter); return await this.table.getContent(itemFrequence, filter);
} }
} }
export class EnvironmentSheetHelper { export class EnvironmentSheetHelper {
static defaultOptions(defaultOptions) { static defaultOptions(defaultOptions) {
return mergeObject(defaultOptions, { return mergeObject(defaultOptions, {
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "informations" }] tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "informations" }]
@ -170,12 +149,9 @@ export class EnvironmentSheetHelper {
static $changeRarete(sheet, event, updated) { static $changeRarete(sheet, event, updated) {
const name = sheet.html.find(event.currentTarget).val(); const name = sheet.html.find(event.currentTarget).val();
const rarete = Environnement.getRarete(name); const rarete = RdDRaretes.getRarete(name);
updated.rarete = rarete.name; updated.rarete = rarete.code;
updated.frequence = rarete.frequence; updated.frequence = rarete.frequence;
// updated.frequence = Math.min(
// Math.max(rarete.min, updated.frequence ?? rarete.frequence),
// rarete.max);
} }
static async onAddMilieu(sheet, event) { static async onAddMilieu(sheet, event) {
@ -190,8 +166,8 @@ export class EnvironmentSheetHelper {
ui.notifications.warn(`${sheet.item.name} a déjà une rareté ${exists.rarete} en ${milieu} (fréquence: ${exists.frequence})`); ui.notifications.warn(`${sheet.item.name} a déjà une rareté ${exists.rarete} en ${milieu} (fréquence: ${exists.frequence})`);
return return
} }
const rarete = Environnement.getRarete(); const rarete = RdDRaretes.getRarete();
const newList = [...list, { milieu, rarete: rarete.name, frequence: rarete.frequence }].sort(Misc.ascending(it => it.milieu)) const newList = [...list, { milieu, rarete: rarete.code, frequence: rarete.frequence }].sort(Misc.ascending(it => it.milieu))
await sheet.item.update({ 'system.environnement': newList }) await sheet.item.update({ 'system.environnement': newList })
} }

View File

@ -5,6 +5,7 @@ import { RdDHerbes } from "./rdd-herbes.js";
import { RdDTimestamp } from "./rdd-timestamp.js"; import { RdDTimestamp } from "./rdd-timestamp.js";
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
import { SystemCompendiums } from "./settings/system-compendiums.js"; import { SystemCompendiums } from "./settings/system-compendiums.js";
import { RdDRaretes } from "./tirage/raretes.js";
const typesInventaireMateriel = [ const typesInventaireMateriel = [
"arme", "arme",
@ -32,6 +33,7 @@ const typesObjetsConnaissance = ["meditation", "recettealchimique", "sort"]
const typesObjetsEffet = ["possession", "poison", "maladie"] const typesObjetsEffet = ["possession", "poison", "maladie"]
const typesObjetsCompetence = ["competence", "competencecreature"] const typesObjetsCompetence = ["competence", "competencecreature"]
const typesObjetsTemporels = ["poison", "maladie", "queue", "ombre", "souffle", "signedraconique", "rencontre"] const typesObjetsTemporels = ["poison", "maladie", "queue", "ombre", "souffle", "signedraconique", "rencontre"]
const typesEnvironnement = ['herbe', 'faune', 'ingredient'];
const encBrin = 0.00005; // un brin = 1 décigramme = 1/10g = 1/10000kg = 1/20000 enc const encBrin = 0.00005; // un brin = 1 décigramme = 1/10g = 1/10000kg = 1/20000 enc
const encPepin = 0.0007; /* un pépin de gemme = 1/10 cm3 = 1/1000 l = 3.5/1000 kg = 7/2000 kg = 7/1000 enc const encPepin = 0.0007; /* un pépin de gemme = 1/10 cm3 = 1/1000 l = 3.5/1000 kg = 7/2000 kg = 7/1000 enc
densité 3.5 (~2.3 à 4, parfois plus) -- https://www.juwelo.fr/guide-des-pierres/faits-et-chiffres/ densité 3.5 (~2.3 à 4, parfois plus) -- https://www.juwelo.fr/guide-des-pierres/faits-et-chiffres/
@ -107,6 +109,12 @@ export class RdDItem extends Item {
static getItemTypesInventaire(mode = 'materiel') { static getItemTypesInventaire(mode = 'materiel') {
return typesInventaire[mode ?? 'materiel'] return typesInventaire[mode ?? 'materiel']
} }
static getItemTypesDraconiques() {
return typesObjetsDraconiques;
}
static getItemTypesEnvironnement() {
return typesEnvironnement;
}
static getTypesOeuvres() { static getTypesOeuvres() {
return typesObjetsOeuvres return typesObjetsOeuvres
@ -159,13 +167,46 @@ export class RdDItem extends Item {
isCompetence() { return typesObjetsCompetence.includes(this.type) } isCompetence() { return typesObjetsCompetence.includes(this.type) }
isTemporel() { return typesObjetsTemporels.includes(this.type) } isTemporel() { return typesObjetsTemporels.includes(this.type) }
isOeuvre() { return typesObjetsOeuvres.includes(this.type) } isOeuvre() { return typesObjetsOeuvres.includes(this.type) }
isDraconique() { return typesObjetsDraconiques.includes(this.type) } isDraconique() { return RdDItem.getItemTypesDraconiques().includes(this.type) }
isEffet() { return typesObjetsEffet.includes(this.type) } isEffet() { return typesObjetsEffet.includes(this.type) }
isConnaissance() { return typesObjetsConnaissance.includes(this.type) } isConnaissance() { return typesObjetsConnaissance.includes(this.type) }
isInventaire(mode = 'materiel') { return RdDItem.getItemTypesInventaire(mode).includes(this.type); } isInventaire(mode = 'materiel') { return RdDItem.getItemTypesInventaire(mode).includes(this.type); }
isAlcool() { return this.isNourritureBoisson() && this.system.boisson && this.system.alcoolise; } isAlcool() { return this.isNourritureBoisson() && this.system.boisson && this.system.alcoolise; }
isHerbeAPotion() { return this.type == 'herbe' && (this.system.categorie == 'Soin' || this.system.categorie == 'Repos'); } isHerbeAPotion() { return this.type == 'herbe' && (this.system.categorie == 'Soin' || this.system.categorie == 'Repos'); }
isEnvironnement() { return RdDItem.getItemTypesEnvironnement().includes(this.type) }
isPresentDansMilieux(milieux) {
return this.getEnvironnements(milieux).length > 0
}
getEnvironnements(milieux = undefined) {
return this.isEnvironnement()
? this.system?.environnement.filter(env => !milieux || milieux.includes(env.milieu))
: []
}
getEnvRarete(milieux = undefined) {
if (this.isEnvironnement()) {
const list = this.getEnvironnements(milieux);
const frequenceMax = Math.max(...list.map(env => env.frequence));
return list.find(env => env.frequence == frequenceMax);
}
return {}
}
getRarete(milieux = undefined) {
if (this.isEnvironnement()) {
const env = this.getEnvRarete(milieux);
return RdDRaretes.getRarete(env.rarete);
}
if (this.isInventaire()) {
return RdDRaretes.rareteEquipement(this)
}
return RdDRaretes.getRareteFrequente();
}
getFrequence(milieux = undefined) {
const frequence = this.getRarete(milieux)?.frequence;
return frequence == undefined ? 1 : frequence;
}
getItemGroup() { getItemGroup() {
if (this.isInventaire()) return "equipement"; if (this.isInventaire()) return "equipement";

View File

@ -5,6 +5,7 @@ import { Grammar } from "./grammar.js";
import { Monnaie } from "./item-monnaie.js"; import { Monnaie } from "./item-monnaie.js";
import { RdDItem } from "./item.js"; import { RdDItem } from "./item.js";
import { RdDTimestamp } from "./rdd-timestamp.js"; import { RdDTimestamp } from "./rdd-timestamp.js";
import { RdDRaretes } from "./tirage/raretes.js";
class Migration { class Migration {
get code() { return "sample"; } get code() { return "sample"; }
@ -288,10 +289,11 @@ class _10_3_0_FrequenceEnvironnement extends Migration {
} }
_updatesFrequences(it) { _updatesFrequences(it) {
const rarete = RdDRaretes.getRarete(it.system.rarete);
return { return {
_id: it.id, _id: it.id,
'system.rarete': undefined, 'system.rarete': undefined,
'system.environnement': [{ milieu: it.system.milieu, rarete: it.system.rarete, frequence: Environnement.getFrequenceRarete(it.system.rarete, 'frequence') }] 'system.environnement': [{ milieu: it.system.milieu, rarete: rarete.code, frequence: rarete.frequence }]
} }
} }
} }

View File

@ -14,6 +14,7 @@ import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js";
import { RdDRollTables } from "./rdd-rolltables.js"; import { RdDRollTables } from "./rdd-rolltables.js";
import { RdDUtility } from "./rdd-utility.js"; import { RdDUtility } from "./rdd-utility.js";
import { CompendiumTableHelpers } from "./settings/system-compendiums.js"; import { CompendiumTableHelpers } from "./settings/system-compendiums.js";
import { FenetreRechercheTirage } from "./tirage/fenetre-recherche-tirage.js";
import { TMRUtility } from "./tmr-utility.js"; import { TMRUtility } from "./tmr-utility.js";
const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/; const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/;
@ -23,6 +24,7 @@ export class RdDCommands {
static init() { static init() {
const rddCommands = new RdDCommands(); const rddCommands = new RdDCommands();
game.system.rdd.commands = rddCommands;
Hooks.on("chatMessage", (html, content, msg) => { Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') { if (content[0] == '/') {
@ -35,7 +37,6 @@ export class RdDCommands {
return true; return true;
}); });
game.system.rdd.commands = rddCommands;
} }
constructor() { constructor() {
@ -75,7 +76,8 @@ export class RdDCommands {
this.registerCommand({ path: ["/tirer", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe('chat'), descr: "Tire une Idée fixe" }); this.registerCommand({ path: ["/tirer", "ideefixe"], func: (content, msg, params) => RdDRollTables.getIdeeFixe('chat'), descr: "Tire une Idée fixe" });
this.registerCommand({ path: ["/tirer", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant('chat'), descr: "Tire un Désir Lancinant" }); this.registerCommand({ path: ["/tirer", "desir"], func: (content, msg, params) => RdDRollTables.getDesirLancinant('chat'), descr: "Tire un Désir Lancinant" });
this.registerCommand({ path: ["/tirer", "rencontre"], func: (content, msg, params) => this.getRencontreTMR(params), descr: `Détermine une rencontre dans les TMR (synonyme de "/tmrr")` }); this.registerCommand({ path: ["/tirer", "rencontre"], func: (content, msg, params) => this.getRencontreTMR(params), descr: `Détermine une rencontre dans les TMR (synonyme de "/tmrr")` });
this.registerCommand({ path: ["/tirer", "milieu"], func: (content, msg, params) => this.tableMilieu(msg, params, 'chat'), descr: "Effectue un tirage dans la table desressource naturelles pour un milieu donné" }); this.registerCommand({ path: ["/tirer", "milieu"], func: (content, msg, params) => this.tableMilieu(msg, params, 'chat'), descr: "Effectue un tirage dans la table des ressource naturelles pour un milieu donné" });
this.registerCommand({ path: ["/tirage"], func: (content, msg, params) => this.tirage(), descr: "Ouvre la fenêtre de recherche et tirage" });
this.registerCommand({ path: ["/meteo"], func: (content, msg, params) => this.getMeteo(msg, params), descr: "Propose une météo marine" }); this.registerCommand({ path: ["/meteo"], func: (content, msg, params) => this.getMeteo(msg, params), descr: "Propose une météo marine" });
this.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" }); this.registerCommand({ path: ["/nom"], func: (content, msg, params) => RdDNameGen.getName(msg, params), descr: "Génère un nom aléatoire" });
@ -254,10 +256,7 @@ export class RdDCommands {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async help(msg) { async help(msg, table = undefined) {
this.help(msg, undefined);
}
async help(msg, table) {
let commands = [] let commands = []
this._buildSubTableHelp(commands, table ?? this.commandsTable); this._buildSubTableHelp(commands, table ?? this.commandsTable);
@ -511,5 +510,8 @@ export class RdDCommands {
return await RdDMeteo.getMeteo(); return await RdDMeteo.getMeteo();
} }
async tirage() {
new FenetreRechercheTirage({}).render(true);
}
} }

View File

@ -16,6 +16,7 @@ import { Environnement } from "./environnement.js";
import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemCompetence } from "./item-competence.js";
import { RdDResolutionTable } from "./rdd-resolution-table.js"; import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDTimestamp } from "./rdd-timestamp.js"; import { RdDTimestamp } from "./rdd-timestamp.js";
import { RdDRaretes } from "./tirage/raretes.js";
/* -------------------------------------------- */ /* -------------------------------------------- */
// This table starts at 0 -> niveau -10 // This table starts at 0 -> niveau -10
@ -202,6 +203,7 @@ export class RdDUtility {
'systems/foundryvtt-reve-de-dragon/templates/enum-periode.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-periode.html',
'systems/foundryvtt-reve-de-dragon/templates/enum-tmr-effet.html', 'systems/foundryvtt-reve-de-dragon/templates/enum-tmr-effet.html',
// Partials // Partials
'systems/foundryvtt-reve-de-dragon/templates/tirage/liste-resultats.hbs',
'systems/foundryvtt-reve-de-dragon/templates/common/timestamp.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/timestamp.hbs',
'systems/foundryvtt-reve-de-dragon/templates/common/periodicite.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/periodicite.hbs',
'systems/foundryvtt-reve-de-dragon/templates/common/enum-duree.hbs', 'systems/foundryvtt-reve-de-dragon/templates/common/enum-duree.hbs',
@ -298,7 +300,7 @@ export class RdDUtility {
Handlebars.registerHelper('linkCompendium', (pack, id, name) => RdDUtility.linkCompendium(pack, id, name)); Handlebars.registerHelper('linkCompendium', (pack, id, name) => RdDUtility.linkCompendium(pack, id, name));
Handlebars.registerHelper('uniteQuantite', (itemId, actorId) => RdDUtility.getItem(itemId, actorId)?.getUniteQuantite()); Handlebars.registerHelper('uniteQuantite', (itemId, actorId) => RdDUtility.getItem(itemId, actorId)?.getUniteQuantite());
Handlebars.registerHelper('isFieldInventaireModifiable', (type, field) => RdDItem.isFieldInventaireModifiable(type, field)); Handlebars.registerHelper('isFieldInventaireModifiable', (type, field) => RdDItem.isFieldInventaireModifiable(type, field));
Handlebars.registerHelper('getFrequenceRarete', (rarete, field) => Environnement.getFrequenceRarete(rarete, field)); Handlebars.registerHelper('rarete-getChamp', (rarete, field) => RdDRaretes.getChamp(rarete, field));
return loadTemplates(templatePaths); return loadTemplates(templatePaths);
} }

View File

@ -177,7 +177,7 @@ export class SystemCompendiums extends FormApplication {
*/ */
export class CompendiumTable { export class CompendiumTable {
constructor(compendium, type, subTypes, sorting = undefined) { constructor(compendium, type, subTypes = undefined, sorting = undefined) {
this.compendium = compendium; this.compendium = compendium;
this.type = type; this.type = type;
this.subTypes = subTypes; this.subTypes = subTypes;
@ -187,13 +187,13 @@ export class CompendiumTable {
async getContent(itemFrequence = it => it.system.frequence, filter = it => true) { async getContent(itemFrequence = it => it.system.frequence, filter = it => true) {
return await SystemCompendiums.getContent(this.compendium, return await SystemCompendiums.getContent(this.compendium,
this.type, this.type,
it => this.subTypes.includes(it.type) && filter(it), it => (!this.subTypes || this.subTypes.includes(it.type)) && filter(it),
itemFrequence, itemFrequence,
this.sorting); this.sorting);
} }
async buildTable(itemFrequence = it => it.system.frequence, filter = it => true) { async buildTable(itemFrequence = it => it.system.frequence, filter = it => true) {
const elements = await this.getContent(filter, itemFrequence); const elements = await this.getContent(itemFrequence, filter);
return CompendiumTableHelpers.buildTable(elements, itemFrequence); return CompendiumTableHelpers.buildTable(elements, itemFrequence);
} }
@ -225,9 +225,23 @@ export class CompendiumTableHelpers {
}); });
} }
static async getRandom(table, type, subTypes, forcedRoll = undefined, localisation = undefined) { static concatTables(...tables) {
const rows = tables.reduce((a, b) => a.concat(b));
let max = 0;
const total = rows.map(it => it.frequence).reduce(Misc.sum(), 0);
return rows.map(row => {
const frequence = row.frequence;
row.min = max + 1;
row.max = max + frequence;
row.total = total
max += frequence;
return row;
})
}
static async getRandom(table, type, subTypes = ['objet'], forcedRoll = undefined, localisation = undefined) {
if (table.length == 0) { if (table.length == 0) {
ui.notifications.warn(`Aucun ${Misc.typeName(type, subTypes[0])} trouvé dans ${localisation ?? ' les compendiums'}`); const typeName = Misc.typeName(type, subTypes[0]);
ui.notifications.warn(`Aucun ${typeName} trouvé dans ${localisation ?? ' les compendiums'}`);
return undefined; return undefined;
} }
return await CompendiumTableHelpers.selectRow(table, forcedRoll); return await CompendiumTableHelpers.selectRow(table, forcedRoll);
@ -260,7 +274,7 @@ export class CompendiumTableHelpers {
roll: row.roll, roll: row.roll,
document: row.document, document: row.document,
percentages, percentages,
typeName: Misc.typeName(type, row.document.type), typeName: Misc.typeName(type, row.document?.type ?? 'objet'),
isGM: game.user.isGM, isGM: game.user.isGM,
}); });
const messageData = { const messageData = {

View File

@ -0,0 +1,217 @@
import { RdDItem } from '../item.js';
import { HtmlUtility } from '../html-utility.js';
import { Misc } from "../misc.js";
import { CompendiumTable, CompendiumTableHelpers, SystemCompendiums } from '../settings/system-compendiums.js';
import { RdDRaretes } from './raretes.js';
const FILTER_GROUPS = [
{ group: 'type', label: "Type d'objet" },
{ group: 'comestible', label: 'Alimentaire' },
{ group: 'categorie', label: 'Utilisation' },
{ group: 'milieu', label: 'Milieu' },
{ group: 'rarete', label: 'Rarete' },
{ group: 'qualite', label: 'Qualité' },
{ group: 'enc', label: 'Encombrement' },
{ group: 'prix', label: 'Prix' },
]
const FILTERS = [
{ group: 'comestible', code: 'pret', label: 'Comestible', check: item => item.isComestible() == 'pret' },
{ group: 'comestible', code: 'alcool', label: 'Alcool', check: item => item.isAlcool() },
{ group: 'comestible', code: 'boisson', label: 'Boisson', check: item => item.isNourritureBoisson() && item.system.boisson },
{ group: 'comestible', code: 'brut', label: 'A préparer', check: item => item.isComestible() == 'brut' },
{ group: 'comestible', code: 'poison', label: 'Toxique', check: item => item.isComestible() == 'poison' },
{ group: 'comestible', code: 'non', label: 'Immangeable', check: item => !item.isComestible() || item.isComestible() == '' },
{ group: 'categorie', code: 'alchimie', label: 'Alchimique', check: item => item.isEnvironnement() && item.system.categorie == 'Alchimie' },
{ group: 'categorie', code: 'cuisine', label: 'Cuisine', check: item => item.isEnvironnement() && item.system.categorie == 'Cuisine' },
{ group: 'categorie', code: 'repos', label: 'Repos', check: item => item.isEnvironnement() && item.system.categorie == 'Repos' },
{ group: 'categorie', code: 'soins', label: 'Soins', check: item => item.isEnvironnement() && item.system.categorie == 'Soin' },
{ group: 'categorie', code: 'autres', label: 'Autres', check: item => !item.isEnvironnement() || ['', 'Autre'].includes(item.system.categorie) },
{ group: "qualite", code: "mauvaise", label: "Mauvaise (négative)", check: item => item.isInventaire() && item.system.qualite < 0 },
{ group: "qualite", code: "quelconque", label: "Quelconque (0)", check: item => item.isInventaire() && item.system.qualite == 0 },
{ group: "qualite", code: "correcte", label: "Correcte (1-3)", check: item => item.isInventaire() && 1 <= item.system.qualite && item.system.qualite <= 3 },
{ group: "qualite", code: "bonne", label: "Bonne (4-6)", check: item => item.isInventaire() && 4 <= item.system.qualite && item.system.qualite <= 6 },
{ group: "qualite", code: "excellente", label: "Excellente (7-9)", check: item => item.isInventaire() && 7 <= item.system.qualite && item.system.qualite <= 9 },
{ group: "qualite", code: "mythique", label: "Mythique (10+)", check: item => item.isInventaire() && 10 <= item.system.qualite },
{ group: "enc", code: "negligeable", label: "Négligeable (jusqu'à 0.1)", check: item => item.isInventaire() && item.system.encombrement <= 0.1 },
{ group: "enc", code: "leger", label: "Léger (0.1 à 0.5)", check: item => item.isInventaire() && 0.1 < item.system.encombrement && item.system.encombrement <= 0.5 },
{ group: "enc", code: "moyen", label: "Moyen (0.5 à 1.5)", check: item => item.isInventaire() && 0.5 < item.system.encombrement && item.system.encombrement <= 1.5 },
{ group: "enc", code: "lourd", label: "Lourd (1.5 à 3)", check: item => item.isInventaire() && 1.5 < item.system.encombrement && item.system.encombrement <= 3 },
{ group: "enc", code: "massif", label: "Massif (3 à 10)", check: item => item.isInventaire() && 3 < item.system.encombrement && item.system.encombrement <= 10 },
{ group: "enc", code: "anemort", label: "Un âne mort (plus de 10)", check: item => item.isInventaire() && 10 < item.system.encombrement },
{ group: "prix", code: "gratuit", label: "Gratuit", check: item => item.isInventaire() && item.system.cout == 0 },
{ group: "prix", code: "deniers", label: "Deniers (étain) 1-9", check: item => item.isInventaire() && 0 < item.system.cout && item.system.cout < 0.1 },
{ group: "prix", code: "bronze", label: "Bronzes 1-9", check: item => item.isInventaire() && 0.1 <= item.system.cout && item.system.cout < 1 },
{ group: "prix", code: "sols", label: "Sols (argent) 1-9", check: item => item.isInventaire() && 1 <= item.system.cout && item.system.cout < 10 },
{ group: "prix", code: "dragons", label: "Dragons (or) 1+ ", check: item => item.isInventaire() && 10 <= item.system.cout },
]
export class FenetreRechercheTirage extends Application {
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
template: "systems/foundryvtt-reve-de-dragon/templates/tirage/fenetre-recherche-tirage.hbs",
title: `Recherches et tirages`,
width: 600,
height: 600,
popOut: true,
resizable: true
});
}
static async $filterMilieux() {
const milieux = await game.system.rdd.environnement.milieux();
return milieux.map(m => { return { group: 'milieu', code: m, label: m, check: item => item.isPresentDansMilieux(m) } })
}
static $filterRarete() {
return RdDRaretes.raretes()
.map(r => { return { group: 'rarete', code: r.code, label: r.label, check: item => item.getRarete()?.code == r.code }; });
}
static $filterTypes() {
return [
{ group: 'type', code: 'inventaire', label: 'Inventaire', check: item => item.isInventaire() && !item.isEnvironnement() },
]
.concat(['arme', 'armure'].map(it => FenetreRechercheTirage.$typeToFilter(it)))
.concat([{ group: 'type', code: 'environement', label: 'Faune, Flore, Ingrédients', check: item => item.isEnvironnement() }])
.concat(RdDItem.getItemTypesEnvironnement().map(it => FenetreRechercheTirage.$typeToFilter(it)))
}
static $typeToFilter(type) { return { group: 'type', code: type, label: Misc.typeName('Item', type), check: item => item.type == type }; }
static async create(tirage = {}) {
new FenetreRechercheTirage(tirage).render(true);
}
constructor(tirage) {
super();
this.tirage = tirage;
this.compendiums = [
SystemCompendiums.getCompendium('faune-flore-mineraux'),
SystemCompendiums.getCompendium('equipement')
]
}
async getData() {
const filterGroups = duplicate(FILTER_GROUPS);
FILTERS
.concat(FenetreRechercheTirage.$filterTypes())
.concat(await FenetreRechercheTirage.$filterMilieux())
.concat(FenetreRechercheTirage.$filterRarete())
.forEach(f => addFilterToGroup(filterGroups, f))
mergeObject(this.tirage,
{
filterGroups: filterGroups.filter(it => it.group)
})
let formData = super.getData();
mergeObject(formData, this.tirage)
return formData;
function addFilterToGroup(filterGroups, filter) {
if (filter.group && filter.code && filter.label) {
let fg = filterGroups.find(g => g.group == filter.group);
if (fg == undefined) {
filterGroups.push({ group: filter.group, label: filter.group, filters: [filter] })
}
else if (fg.filters == undefined) {
fg.filters = [filter];
}
else {
fg.filters.push(filter);
}
}
else {
console.warn("Filtre incorrect, pas de groupe/code/label", filter);
}
}
}
activateListeners(html) {
super.activateListeners(html);
this.html = html;
HtmlUtility.showControlWhen(this.html.find('div.group-filters'), false);
HtmlUtility.showControlWhen(this.html.find('i.filter-group-hide'), false);
HtmlUtility.showControlWhen(this.html.find('i.filter-group-show'), true);
this.html.find("a.filter-group-toggle").click(event => {
const groupDiv = this.html.find(event.currentTarget)?.parents('div.filter-group').first();
const visible = groupDiv.find('div.group-filters').first().is(":visible");
this.showFilterGroup(groupDiv, !visible)
});
this.html.find("input.activate-filter").change(event => this.changeListeFiltresActifs())
this.html.find("a.supprimer-filtres").click(async event => this.html.find('input.activate-filter:checked').prop("checked", false))
this.html.find("a.recherche-filtres").click(async event => {
const table = await this.buildTable();
this.html.find('div.liste-resultats').html(await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/tirage/liste-resultats.hbs`, { resultats: table }));
})
this.html.find("a.tirage-filtres").click(async event => {
const table = await this.buildTable();
const row = await CompendiumTableHelpers.getRandom(table, 'Item')
await CompendiumTableHelpers.tableRowToChatMessage(row, 'Item');
})
}
async buildTable() {
const filter = this.getSelectedItemsFilter();
const equipementCompendiumTable = new CompendiumTable('equipement', 'Item');
const equipements = await equipementCompendiumTable.buildTable(it => it.getFrequence(), filter)
const environnements = await game.system.rdd.environnement.buildEnvironnementTable(undefined, filter);
return CompendiumTableHelpers.concatTables(environnements, equipements);
}
getSelectedItemsFilter() {
const byGroup = this.getSelectedFiltersByGroup();
const groupSummaries = Object.entries(byGroup).map(([key, list]) => {
const group = this.tirage.filterGroups.find(g => key == g.group);
const filters = list.map(it => it.code).map(code => group.filters.find(f => code == f.code))
return filters
.map(f => f.check)
.reduce((a, b) => { return it => a(it) || b(it) });;
});
if (groupSummaries.length == 0) {
return it => true;
}
return groupSummaries.reduce((a, b) => { return it => a(it) && b(it) })
}
showFilterGroup(groupDiv, show) {
if (groupDiv) {
HtmlUtility.showControlWhen(groupDiv.find('div.group-filters'), show);
HtmlUtility.showControlWhen(groupDiv.find('i.filter-group-hide'), show);
HtmlUtility.showControlWhen(groupDiv.find('i.filter-group-show'), !show);
}
}
changeListeFiltresActifs() {
const byGroup = this.getSelectedFiltersByGroup();
const groupSummaries = Object.entries(byGroup).map(([key, list]) => {
const group = this.tirage.filterGroups.find(g => key == g.group);
const filters = list.map(it => it.code).map(code => group.filters.find(f => code == f.code))
return group.label + ': ' + filters
.map(f => f.label)
.reduce(Misc.joining(', '));;
});
const fullText = groupSummaries.length == 0 ? "" : groupSummaries.reduce(Misc.joining(' - '));
this.html.find('span.liste-filtres-actifs').text(fullText);
}
getSelectedFiltersByGroup() {
const selectedFilters = jQuery.map(this.html.find('input.activate-filter:checked'), it => {
const element = this.html.find(it);
return { group: element.data('group'), code: element.data('code') };
});
const byGroup = Misc.classify(selectedFilters, it => it.group);
return byGroup;
}
}

45
module/tirage/raretes.js Normal file
View File

@ -0,0 +1,45 @@
const RARETES = [
{ code: 'Commune', label: 'Commune', frequence: 54, min: 27, max: 108 },
{ code: 'Frequente', label: 'Fréquente', frequence: 18, min: 9, max: 36 },
{ code: 'Rare', label: 'Rare', frequence: 6, min: 3, max: 12 },
{ code: 'Rarissime', label: 'Rarissime', frequence: 2, min: 1, max: 4 }]
const DEFAULT_RARETE = 1;
export class RdDRaretes {
static getRarete(code = undefined) {
return RARETES.find(it => it.code == code) ?? RARETES[DEFAULT_RARETE];
}
static getChamp(rarete, field = undefined) {
const selected = this.getRarete(rarete);
return field ? selected[field] : selected[frequence];
}
static getRareteFrequente() {
return RARETES[DEFAULT_RARETE];
}
static raretes() {
return RARETES;
}
static frequenceEquipement(item) {
return RdDRaretes.rareteEquipement(item).frequence
}
static rareteEquipement(item) {
const qualite = item.system.qualite ?? 0;
if (qualite <= 0) {
return RARETES[0]
}
if (qualite <= 3) {
return RARETES[1]
}
if (qualite <= 6) {
return RARETES[2]
}
return RARETES[3]
}
}

View File

@ -7,5 +7,6 @@
{"name":"Appel à la chance","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/commodities/flowers/clover.webp","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.rollAppelChance();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671233849101,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"UzAWljmFq5sY702w"} {"name":"Appel à la chance","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/commodities/flowers/clover.webp","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.rollAppelChance();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671233849101,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"UzAWljmFq5sY702w"}
{"name":"Encaissement","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/svg/bones.svg","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.encaisser();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671234017623,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"WD6T8AdRbX2Ylxqe"} {"name":"Encaissement","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/svg/bones.svg","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.encaisser();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671234017623,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"WD6T8AdRbX2Ylxqe"}
{"name":"Jet quelconque","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/sundries/gaming/dice-runed-tan.webp","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.roll();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671233500655,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"bnJnbKDHpbqY8Pr9"} {"name":"Jet quelconque","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/sundries/gaming/dice-runed-tan.webp","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.roll();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671233500655,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"bnJnbKDHpbqY8Pr9"}
{"name":"Recherche et tirage","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/tools/scribal/magnifying-glass.webp","command":"game.system.rdd.commands.tirage()","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.ZFWPNdQBjQs9z0YW"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.5.4","coreVersion":"10.291","createdTime":1673472449426,"modifiedTime":1673655461651,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"iVZnxOxhCMpkvYh3"}
{"name":"Jet d'éthylisme","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/consumables/drinks/alcohol-beer-stein-wooden-metal-brown.webp","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.jetEthylisme();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671233646086,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"mvub1dRHNFmWjRr7"} {"name":"Jet d'éthylisme","type":"script","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"icons/consumables/drinks/alcohol-beer-stein-wooden-metal-brown.webp","command":"const selected = game.system.rdd.RdDUtility.getSelectedActor();\nif (selected) {\n selected.jetEthylisme();\n}\nelse {\n ui.notifications.info('Pas de personnage sélectionné');\n}","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.XHNbjnGKXaCiCadq"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1671220038331,"modifiedTime":1671233646086,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"mvub1dRHNFmWjRr7"}
{"name":"Tirer le tarot","type":"chat","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"systems/foundryvtt-reve-de-dragon/icons/tarots/dos-tarot.webp","command":"/tirer tarot","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.HBZSKR9OHCQbLcTC"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1669469547231,"modifiedTime":1671237401618,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"vTfJTFYYiRu8X5LM"} {"name":"Tirer le tarot","type":"chat","scope":"global","author":"Hp9ImM4o9YRTSdfu","img":"systems/foundryvtt-reve-de-dragon/icons/tarots/dos-tarot.webp","command":"/tirer tarot","ownership":{"default":0,"Hp9ImM4o9YRTSdfu":3},"flags":{"core":{"sourceId":"Macro.HBZSKR9OHCQbLcTC"}},"_stats":{"systemId":"foundryvtt-reve-de-dragon","systemVersion":"10.3.15","coreVersion":"10.291","createdTime":1669469547231,"modifiedTime":1671237401618,"lastModifiedBy":"Hp9ImM4o9YRTSdfu"},"folder":null,"sort":0,"_id":"vTfJTFYYiRu8X5LM"}

View File

@ -469,6 +469,16 @@ input:is(.blessure-premiers_soins, .blessure-soins_complets) {
.dice-img { .dice-img {
border-width: 0; border-width: 0;
} }
.in-text-img {
max-width: 1.2em;
max-height: 1.2em;
flex-grow: 0;
margin-right: 0.2rem;
vertical-align: bottom;
border: none;
padding: 0.1rem;
}
.button-img { .button-img {
vertical-align: baseline; vertical-align: baseline;
max-width: 32px; max-width: 32px;
@ -677,10 +687,11 @@ div.dimmed .img-signe-heure {
.competence-list .item-controls.hidden-controls { .competence-list .item-controls.hidden-controls {
display: none !important; display: none !important;
} }
.item-controls i:is(.fas,.far) { .item-controls i:is(.fas, .fa, .fa-solid) {
font-size: 0.8em;
color: var(--color-controls); color: var(--color-controls);
} }
.item-controls i:is(.fas,.far):hover { .item-controls i:is(.fas, .far, .fa-solid):hover {
opacity: 0.7 ; opacity: 0.7 ;
} }
@ -1929,3 +1940,37 @@ display: inline-flex;
width: 80px; width: 80px;
height: 68px; height: 68px;
} }
div.vl {
border: 1px solid ;
border-color: hsla(0, 0%, 0%, 0.5);
height: inherit;
min-height: max-content;
margin-left: 0;
margin-right: 0;
width: 1px;
flex-grow: 0;
}
div.fenetre-recherche div.recherche {
display: flex;
}
div.fenetre-recherche div.recherche div.filtres {
width: fit-content;
min-width: 200px;
float: left;
}
div.fenetre-recherche div.titre-fenetre-recherche {
flex-basis: 0;
max-height: fit-content;
}
div.fenetre-recherche div.liste-resultats {
display: flex;
flex: auto;
flex-flow: row wrap;
flex-direction: row;
align-content: flex-start ;
}
div.fenetre-recherche div.liste-resultats div.resultat {
width: fit-content;
margin: 0.2rem 0.5rem;
}

View File

@ -5,10 +5,16 @@
data-pack="{{pack}}" data-pack="{{pack}}"
{{#if doctype}}data-doctype="{{doctype}}"{{/if}} {{#if doctype}}data-doctype="{{doctype}}"{{/if}}
data-id="{{id}}" data-id="{{id}}"
><i class="fas fa-suitcase"></i>{{name}}</a> >
{{else}} {{else}}
<a class="rdd-world-content-link" <a class="rdd-world-content-link"
{{#if doctype}}data-doctype="{{doctype}}"{{/if}} {{#if doctype}}data-doctype="{{doctype}}"{{/if}}
data-id="{{id}}" data-id="{{id}}"
><i class="fas fa-suitcase"></i>{{name}}</a> >
{{/if}} {{/if}}
{{#if img}}
<img class="in-text-img" src="{{img}}" alt="{{name}}" />
{{else}}
<i class="fas fa-suitcase"></i>
{{/if}}
{{name}}</a>

View File

@ -29,8 +29,8 @@
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-rarete.html"}} {{>"systems/foundryvtt-reve-de-dragon/templates/enum-rarete.html"}}
{{/select}} {{/select}}
</select> </select>
{{rangePicker name="milieu-{{key}}-frequence" value=env.frequence min=(getFrequenceRarete env.rarete 'min') max=(getFrequenceRarete env.rarete 'max') step=1}} {{rangePicker name="milieu-{{key}}-frequence" value=env.frequence min=(rarete-getChamp env.rarete 'min') max=(rarete-getChamp env.rarete 'max') step=1}}
<label>[{{getFrequenceRarete env.rarete 'min'}}-{{getFrequenceRarete env.rarete 'max'}}]</label> <label>[{{rarete-getChamp env.rarete 'min'}}-{{rarete-getChamp env.rarete 'max'}}]</label>
</span> </span>
</div> </div>
{{/each}} {{/each}}

View File

@ -0,0 +1,49 @@
<div class="fenetre-recherche">
<div>
<span><a class="supprimer-filtres chat-card-button">Tout</a></span>
<span><a class="recherche-filtres chat-card-button">Appliquer les filtres</a></span>
<span><a class="tirage-filtres chat-card-button">Tirage aléatoire</a></span>
<span class="liste-filtres-actifs"></span>
</div>
<hr>
<div class="recherche">
<div class="filtres">
<div class="titre-fenetre-recherche">
<h3>Filtres</h3>
</div>
{{#each filterGroups as |group|}}
<div class="filter-group" data-group="{{group.group}}" >
<h4><a class="filter-group-toggle" >{{group.label}}
<span class="item-controls">
<i class="filter-group-show fa-solid fa-chevrons-down"></i>
<i class="filter-group-hide fa-solid fa-chevrons-up"></i>
</span>
</a>
</h4>
<div class="flexcol group-filters">
{{#each group.filters as |filter|}}
<div class="flexrow">
<input type="checkbox" class="activate-filter" name="{{filter.group}}-{{filter.code}}"
data-group="{{filter.group}}" data-code="{{filter.code}}"/>
<label for="{{filter.group}}-{{filter.code}}">{{filter.label}}</label>
</div>
{{/each}}
</div>
</div>
{{/each}}
<div class="flex-grow"></div>
</div>
<div class="vl"></div>
<div class="resultats">
<div class="titre-fenetre-recherche">
<h3>Résultat de recherche</h3>
</div>
<div class="liste-resultats">
</div>
</div>
</div>
{{!--
--}}
</form>

View File

@ -0,0 +1,9 @@
{{#each resultats as |row|}}
<div class="resultat">
{{>'systems/foundryvtt-reve-de-dragon/templates/common/compendium-link.hbs'
pack=row.document.pack
id=row.document.id
name=row.document.name
img=row.document.img}}
</div>
{{/each}}