Fréquences par milieu pour l'environnement

Les herbes et les ingrédients peuvent être cherchées/tirées
This commit is contained in:
Vincent Vandemeulebrouck 2022-11-28 11:31:19 +01:00
parent b7a8b0c08d
commit b7a0e5d034
23 changed files with 702 additions and 158 deletions

222
module/environnement.js Normal file
View File

@ -0,0 +1,222 @@
import { SYSTEM_RDD } from "./constants.js";
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
import { CompendiumTableHelpers, SystemCompendiums, CompendiumTable } from "./settings/system-compendiums.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 SETTINGS_LISTE_MILIEUX = "liste-milieux";
const MILIEUX = [
"Collines",
"Déserts",
"Désolations",
"Forêts",
"Forêts Tropicales",
"Marais",
"Milieux Aquatiques",
"Milieux Maritimes",
"Montagnes",
"Plaines",
"Sous-Sols",
]
const ITEM_ENVIRONNEMENT_TYPES = [
'herbe', 'ingredient'
]
const COMPENDIUMS_ENVIRONNEMENT = [
{ type: 'Item', subType: 'herbe', compendium: 'botanique' },
{ type: 'Item', subType: 'ingredient', compendium: 'botanique' },
{ type: 'Item', subType: 'ingredient', compendium: 'equipement' },
]
export class Environnement {
static init() {
game.settings.register(SYSTEM_RDD, SETTINGS_LISTE_MILIEUX, {
name: "Liste des milieux proposés",
hint: "Liste des noms de milieux proposés, séparés par des virgules",
scope: "world",
config: true,
default: MILIEUX.reduce(Misc.joining(',')),
type: String
});
game.system.rdd.environnement = new Environnement();
}
constructor() {
this.table = new CompendiumTable('faune-flore-mineraux', 'Item', ITEM_ENVIRONNEMENT_TYPES)
}
static getRarete(name = undefined) {
return RARETES.find(it => it.name == name) ?? RARETES[0];
}
static getFrequenceRarete(rarete, field = undefined) {
const selected = this.getRarete(rarete);
return selected[field];
}
async milieux() {
const milieux = new Set(this.getMilieuxSettings());
const elements = await this.getElements(it => 1, it => ITEM_ENVIRONNEMENT_TYPES.includes(it.type));
elements.forEach(it => it.system.environnement.forEach(env => milieux.add(env.milieu)))
return [...milieux];
}
getMilieuxSettings() {
return game.settings.get(SYSTEM_RDD, SETTINGS_LISTE_MILIEUX).split(',');
}
async findEnvironnementsLike(search) {
return (await this.milieux()).filter(it => Grammar.includesLowerCaseNoAccent(it, search));
}
async searchToChatMessage(search) {
const table = await this.buildEnvironnementTable(search);
await CompendiumTableHelpers.tableToChatMessage(table, 'Item', 'herbe', 'Ressource naturelle');
return true
}
async getRandom(search) {
const table = await this.buildEnvironnementTable(search);
const row = await CompendiumTableHelpers.getRandom(table, 'Item', 'herbe', undefined, 'Ressource naturelle');
return row
}
async buildEnvironnementTable(search) {
const itemRareteEnMilieu = item => item.system?.environnement.find(env => Grammar.includesLowerCaseNoAccent(env.milieu, search));
const itemFrequenceEnMilieu = item => itemRareteEnMilieu(item)?.frequence ?? 0;
const isPresentEnMilieu = item => itemFrequenceEnMilieu(item) > 0;
let elements = await this.getElements(itemFrequenceEnMilieu, isPresentEnMilieu);
return CompendiumTableHelpers.buildTable(elements, itemFrequenceEnMilieu);
}
async getElements(itemFrequence, filter) {
let elements = [];
for (let c of COMPENDIUMS_ENVIRONNEMENT) {
const fromCompendium = await c.table.getContent(itemFrequence, filter);
elements = elements.concat(fromCompendium);
}
return elements;
}
}
export class EnvironmentSheetHelper {
static defaultOptions(defaultOptions, type) {
return mergeObject(defaultOptions, {
classes: ["rdd", "sheet", "item"],
template: `systems/foundryvtt-reve-de-dragon/templates/item-${type}-sheet.html`,
width: 500,
height: 600,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "informations" }]
});
}
/* -------------------------------------------- */
static getHeaderButtons(sheet, buttons) {
buttons.unshift({ class: "post", icon: "fas fa-comment", onclick: ev => sheet.item.postItem() });
return buttons;
}
static setPosition(sheet, superPosition) {
const position = superPosition;
const sheetHeader = sheet.element.find(".sheet-header");
const sheetBody = sheet.element.find(".sheet-body");
sheetBody.css("height", position.height - sheetHeader[0].clientHeight)
return position;
}
/* -------------------------------------------- */
static async getData(sheet) {
const formData = duplicate(sheet.item);
const milieux = await game.system.rdd.environnement.milieux();
const milieuxDisponibles = milieux.filter(it => !sheet.item.system.environnement.find(e => e.milieu == it));
mergeObject(formData, {
title: formData.name,
isGM: game.user.isGM,
owner: sheet.actor?.isOwner,
isOwned: sheet.actor ? true : false,
actorId: sheet.actor?.id,
editable: sheet.isEditable,
cssClass: sheet.isEditable ? "editable" : "locked",
milieux: milieuxDisponibles
});
return formData;
}
static activateListeners(sheet, html) {
if (!sheet.options.editable) return;
html.find("a.milieu-add").click(event => EnvironmentSheetHelper.onAddMilieu(sheet, event));
html.find("div.environnement-milieu a.milieu-delete").click(event => EnvironmentSheetHelper.onDeleteMilieu(sheet, event));
html.find("div.environnement-milieu select.environnement-rarete").change(event => EnvironmentSheetHelper.onChange(sheet, event,
(updated) => {
const name = $(event.currentTarget).val();
const rarete = Environnement.getRarete(name);
updated.rarete = rarete.name;
updated.frequence = Math.min(
Math.max(rarete.min, updated.frequence ?? rarete.frequence),
rarete.max);
}));
html.find("div.environnement-milieu input[name='environnement-frequence']").change(event => EnvironmentSheetHelper.onChange(sheet, event,
(updated) => {
updated.frequence = Number($(event.currentTarget).val())
}));
}
static async onAddMilieu(sheet, event) {
const milieu = $("input.input-selection-milieu").val();
if (!milieu) {
ui.notifications.warn(`Choisissez le milieu dans lequel se trouve le/la ${sheet.item.name}`);
return
}
const list = sheet.item.system.environnement;
const exists = list.find(it => it.milieu == milieu);
if (exists) {
ui.notifications.warn(`${sheet.item.name} a déjà une rareté ${exists.rarete} en ${milieu} (fréquence: ${exists.frequence})`);
return
}
const rarete = Environnement.getRarete();
const newList = [...list, { milieu, rarete: rarete.name, frequence: rarete.frequence }].sort(Misc.ascending(it => it.milieu))
await sheet.item.update({ 'system.environnement': newList })
}
static async onDeleteMilieu(sheet, event) {
const milieu = EnvironmentSheetHelper.$getEventMilieu(event);
if (milieu) {
const newList = sheet.item.system.environnement.filter(it => it.milieu != milieu)
.sort(Misc.ascending(it => it.milieu));
await sheet.item.update({ 'system.environnement': newList });
}
}
static async onChange(sheet, event, doMutation) {
const list = sheet.item.system.environnement;
const milieu = EnvironmentSheetHelper.$getEventMilieu(event);
const updated = list.find(it => it.milieu == milieu);
if (updated) {
doMutation(updated);
const newList = [...list.filter(it => it.milieu != milieu), updated]
.sort(Misc.ascending(it => it.milieu));
await sheet.item.update({ 'system.environnement': newList });
}
}
static $getEventMilieu(event) {
return $(event.currentTarget)?.parents("div.environnement-milieu").data("milieu");
}
static template(itemType) {
/* -------------------------------------------- */
return `systems/foundryvtt-reve-de-dragon/templates/item-${itemType}-sheet.html`;
}
static title(item) {
return Misc.typeName('Item', item.type) + ': ' + item.name;
}
}

View File

@ -0,0 +1,45 @@
import { SYSTEM_RDD } from "./constants.js";
import { EnvironmentSheetHelper as EnvironmentItemSheet } from "./environnement.js";
import { Misc } from "./misc.js";
const ITEM_TYPE = 'herbe';
export class RdDHerbeItemSheet extends ItemSheet {
static register() {
Items.registerSheet(SYSTEM_RDD, RdDHerbeItemSheet, {
label: Misc.typeName('Item', ITEM_TYPE),
types: [ITEM_TYPE],
makeDefault: true
});
}
static get defaultOptions() {
return EnvironmentItemSheet.defaultOptions(super.defaultOptions, ITEM_TYPE);
}
_getHeaderButtons() {
return EnvironmentItemSheet.getHeaderButtons(this, super._getHeaderButtons());
}
setPosition(options = {}) {
return EnvironmentItemSheet.setPosition(this, super.setPosition(options));
}
async getData() {
return await EnvironmentItemSheet.getData(this);
}
activateListeners(html) {
super.activateListeners(html);
EnvironmentItemSheet.activateListeners(this, html);
}
get template() {
return EnvironmentItemSheet.template(this.item.type);
}
get title() {
return EnvironmentItemSheet.title(this.item);
}
}

View File

@ -0,0 +1,44 @@
import { SYSTEM_RDD } from "./constants.js";
import { EnvironmentSheetHelper } from "./environnement.js";
import { Misc } from "./misc.js";
const ITEM_TYPE = 'ingredient';
export class RdDIngredientItemSheet extends ItemSheet {
static register() {
Items.registerSheet(SYSTEM_RDD, RdDIngredientItemSheet, {
label: Misc.typeName('Item', ITEM_TYPE),
types: [ITEM_TYPE],
makeDefault: true
});
}
static get defaultOptions() {
return EnvironmentSheetHelper.defaultOptions(super.defaultOptions, ITEM_TYPE);
}
_getHeaderButtons() {
return EnvironmentSheetHelper.getHeaderButtons(this, super._getHeaderButtons());
}
setPosition(options = {}) {
return EnvironmentSheetHelper.setPosition(this, super.setPosition(options));
}
async getData() {
return await EnvironmentSheetHelper.getData(this);
}
activateListeners(html) {
super.activateListeners(html);
EnvironmentSheetHelper.activateListeners(this, html);
}
get template() {
return EnvironmentSheetHelper.template(this.item.type);
}
get title() {
return EnvironmentSheetHelper.title(this.item);
}
}

View File

@ -1,6 +1,6 @@
import { LOG_HEAD, SYSTEM_RDD } from "./constants.js";
import { Environnement } from "./environnement.js";
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
class Migration {
get code() { return "sample"; }
@ -179,6 +179,7 @@ class _10_2_5_ArmesTirLancer extends Migration {
return Grammar.toLowerCaseNoAccent(it.system.competence);
}
}
class _10_2_10_DesirLancinant_IdeeFixe extends Migration {
get code() { return "desir-lancinat-idee-fixe"; }
get version() { return "10.2.10"; }
@ -240,6 +241,24 @@ class _10_3_0_Inventaire extends Migration {
}
}
class _10_3_0_FrequenceEnvironnement extends Migration {
get code() { return "migration-frequence-resources"; }
get version() { return "10.3.0"; }
async migrate() {
await this.applyItemsUpdates(items => items.filter(it => ['herbe', 'ingredient'].includes(it.type))
.map(it => this._updatesFrequences(it)));
}
_updatesFrequences(it) {
return {
_id: it.id,
'system.rarete': undefined,
'system.environnement': [{ milieu: it.system.milieu, rarete: it.system.rarete, frequence: Environnement.getFrequenceRarete(it.system.rarete, 'frequence') }]
}
}
}
export class Migrations {
static getMigrations() {
return [
@ -249,7 +268,8 @@ export class Migrations {
new _10_0_33_MigrationNomsDraconic(),
new _10_2_5_ArmesTirLancer(),
new _10_2_10_DesirLancinant_IdeeFixe(),
new _10_3_0_Inventaire()
new _10_3_0_Inventaire(),
new _10_3_0_FrequenceEnvironnement()
];
}
@ -264,11 +284,9 @@ export class Migrations {
}
migrate() {
const currentVersion = game.settings.get(
SYSTEM_RDD,
"systemMigrationVersion"
);
if (isNewerVersion(game.system.version, currentVersion)) {
const currentVersion = game.settings.get(SYSTEM_RDD,"systemMigrationVersion");
//if (isNewerVersion(game.system.version, currentVersion)) {
if (true) { /* comment previous and uncomment here to test before upgrade */
const migrations = Migrations.getMigrations().filter(m => isNewerVersion(m.version, currentVersion));
if (migrations.length > 0) {
migrations.sort((a, b) =>

View File

@ -13,6 +13,7 @@ import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDRollResolutionTable } from "./rdd-roll-resolution-table.js";
import { RdDRollTables } from "./rdd-rolltables.js";
import { RdDUtility } from "./rdd-utility.js";
import { CompendiumTableHelpers } from "./settings/system-compendiums.js";
import { TMRUtility } from "./tmr-utility.js";
const rddRollNumeric = /^(\d+)\s*([\+\-]?\d+)?\s*(s)?/;
@ -49,7 +50,9 @@ export class RdDCommands {
<br><strong>/table rencontre deso</strong> affiche la table des rencontres en Désolation
<br><strong>/table rencontre mauvaise</strong> affiche la table des mauvaises rencontres`
});
this.registerCommand({ path: ["/table", "milieu"], func: (content, msg, params) => this.tableMilieu(msg, params,'liste'), descr: "Affiche la table des trouvailles dans un milieu donné" });
this.registerCommand({ path: ["/tirer", "milieu"], func: (content, msg, params) => this.tableMilieu(msg, params, 'chat'), descr: "Affiche la table des trouvailles dans un milieu donné" });
this.registerCommand({ path: ["/tirer", "comp"], func: (content, msg, params) => RdDRollTables.getCompetence('chat'), descr: "Tire une compétence au hasard" });
this.registerCommand({ path: ["/tirer", "queue"], func: (content, msg, params) => RdDRollTables.getQueue('chat'), descr: "Tire une Queue de Dragon" });
this.registerCommand({ path: ["/tirer", "ombre"], func: (content, msg, params) => RdDRollTables.getOmbre('chat'), descr: "Tire une Ombre de Thanatos" });
@ -363,6 +366,7 @@ export class RdDCommands {
}
return false;
}
async tableRencontres(msg, params) {
if (params && params.length > 0) {
const search = Misc.join(params, ' ');
@ -370,11 +374,29 @@ export class RdDCommands {
if (solvedTerrain == undefined) {
return RdDCommands._chatAnswer(msg, 'Aucune TMR correspondant à ' + search);
}
return game.system.rdd.rencontresTMR.chatTable(solvedTerrain);
return await game.system.rdd.rencontresTMR.chatTable(solvedTerrain);
}
return false;
}
async tableMilieu(msg, params, toChat) {
if (params && params.length > 0) {
const search = Misc.join(params, ' ');
const searches = game.system.rdd.environnement.findEnvironnementsLike(search);
if (searches.length == 0) {
return RdDCommands._chatAnswer(msg, 'Aucun milieu correspondant à ' + search);
}
if (toChat == 'liste') {
return await game.system.rdd.environnement.searchToChatMessage(search);
}
else {
const row = await game.system.rdd.environnement.getRandom(search);
await CompendiumTableHelpers.tableRowToChatMessage(row, 'Item');
return true;
}
}
return false;
}
/* -------------------------------------------- */
getCoutXpComp(msg, params) {
if (params && (params.length == 1 || params.length == 2)) {

View File

@ -39,6 +39,9 @@ import { DialogChronologie } from "./dialog-chronologie.js";
import { SystemCompendiums } from "./settings/system-compendiums.js";
import { RdDRencontreItemSheet } from "./item-rencontre-sheet.js";
import { TMRRencontres } from "./tmr-rencontres.js";
import { RdDHerbeItemSheet } from "./item-herbe-sheet.js";
import { Environnement } from "./environnement.js";
import { RdDIngredientItemSheet } from "./item-ingredient-sheet.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
@ -194,11 +197,13 @@ Hooks.once("init", async function () {
types: ["rencontre"],
makeDefault: true
});
RdDHerbeItemSheet.register();
RdDIngredientItemSheet.register();
Items.registerSheet(SYSTEM_RDD, RdDItemSheet, {
types: [
"competence", "competencecreature",
"recettealchimique", "musique", "chant", "danse", "jeu", "recettecuisine", "oeuvre",
"objet", "arme", "armure", "conteneur", "herbe", "ingredient", "livre", "potion", "munition",
"objet", "arme", "armure", "conteneur", "livre", "potion", "munition",
"monnaie", "nourritureboisson", "gemme",
"meditation", "queue", "ombre", "souffle", "tete", "casetmr", "sort", "sortreserve",
"nombreastral", "tache", "maladie", "poison", "possession",
@ -224,6 +229,7 @@ Hooks.once("init", async function () {
RdDHotbar.initDropbar();
RdDPossession.init();
TMRRencontres.init();
Environnement.init();
});
/* -------------------------------------------- */

View File

@ -1,5 +1,5 @@
import { Grammar } from "./grammar.js";
import { SystemCompendiums } from "./settings/system-compendiums.js";
import { CompendiumTable, CompendiumTableHelpers, SystemCompendiums } from "./settings/system-compendiums.js";
export class RdDRollTables {
@ -37,7 +37,7 @@ export class RdDRollTables {
/* -------------------------------------------- */
static async getCompetence(toChat = false) {
if (toChat == 'liste') {
return await SystemCompendiums.chatTableItems('competences', 'Item', 'competence', it => 1);
return await RdDRollTables.listOrRoll('competences', 'Item', 'competence', toChat, it => 1);
}
else {
return await RdDRollTables.drawItemFromRollTable("Détermination aléatoire de compétence", toChat);
@ -55,13 +55,15 @@ export class RdDRollTables {
}
static async getDesirLancinant(toChat = false) {
return await RdDRollTables.listOrRoll('queues-de-dragon', 'Item', 'queue', toChat, it => it.system.frequence,
it => it.system.categorie == 'lancinant' );
return await RdDRollTables.listOrRoll('queues-de-dragon', 'Item', 'queue', toChat,
it => it.system.frequence,
it => it.system.categorie == 'lancinant');
}
static async getIdeeFixe(toChat = false) {
return await RdDRollTables.listOrRoll('queues-de-dragon', 'Item', 'queue', toChat, it => it.system.frequence,
it => it.system.categorie == 'ideefixe' );
return await RdDRollTables.listOrRoll('queues-de-dragon', 'Item', 'queue', toChat,
it => it.system.frequence,
it => it.system.categorie == 'ideefixe');
}
/* -------------------------------------------- */
@ -86,10 +88,15 @@ export class RdDRollTables {
/* -------------------------------------------- */
static async listOrRoll(compendium, type, subType, toChat, itemFrequence = it => it.system.frequence, filter = it => true) {
const table = new CompendiumTable(compendium, type, subType);
if (toChat == 'liste') {
return await SystemCompendiums.chatTableItems(compendium, type, subType, itemFrequence, filter);
return await table.toChatMessage(itemFrequence, filter);
}
return await SystemCompendiums.getRandom(compendium, type, subType, toChat, itemFrequence, filter);
const row = await table.getRandom(itemFrequence, filter);
if (row) {
await CompendiumTableHelpers.tableRowToChatMessage(row, type);
}
return row;
}
/* -------------------------------------------- */

View File

@ -15,6 +15,7 @@ import { RdDNameGen } from "./rdd-namegen.js";
import { RdDConfirm } from "./rdd-confirm.js";
import { RdDCalendrier } from "./rdd-calendrier.js";
import { RdDCarac } from "./rdd-carac.js";
import { Environnement } from "./environnement.js";
/* -------------------------------------------- */
// This table starts at 0 -> niveau -10
@ -169,7 +170,11 @@ export class RdDUtility {
'systems/foundryvtt-reve-de-dragon/templates/actor/liens-suivants.html',
'systems/foundryvtt-reve-de-dragon/templates/actor/liens-vehicules.html',
//Items
'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete-script.hbs',
'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete.hbs',
'systems/foundryvtt-reve-de-dragon/templates/item/partial-inventaire.html',
'systems/foundryvtt-reve-de-dragon/templates/item/partial-environnement.html',
'systems/foundryvtt-reve-de-dragon/templates/item/partial-tab-environnement.html',
'systems/foundryvtt-reve-de-dragon/templates/header-item.html',
'systems/foundryvtt-reve-de-dragon/templates/item-competence-sheet.html',
'systems/foundryvtt-reve-de-dragon/templates/item-competencecreature-sheet.html',
@ -326,7 +331,8 @@ export class RdDUtility {
Handlebars.registerHelper('linkCompendium', (compendium, id, name) => `@Compendium[${compendium}.${id}]{${name}}`);
Handlebars.registerHelper('uniteQuantite', (type) => RdDItem.getUniteQuantite(type));
Handlebars.registerHelper('isEquipementFieldEditable', (type, field) => RdDItem.isEquipementFieldEditable(type, field));
Handlebars.registerHelper('getFrequenceRarete', (rarete, field) => Environnement.getFrequenceRarete(rarete, field));
Handlebars.registerHelper('either', (a, b) => a ?? b);
return loadTemplates(templatePaths);
}

View File

@ -16,8 +16,13 @@ const CONFIGURABLE_COMPENDIUMS = {
'rencontres': { label: "Rencontres dans les TMR", type: "Item" },
'tetes-de-dragon-pour-haut-revants': { label: "Têtes de dragons (haut-rêvant)", type: "Item" },
'tetes-de-dragon-pour-tous-personnages': { label: "Têtes de dragons (tous)", type: "Item" },
'botanique': { label: "Herbes & plantes", type: "Item" },
'equipement': { label: "Equipements", type: "Item" },
}
/**
* ======= Gestion des accès aux compendiums systèmes (ou surchargés) =======
*/
export class SystemCompendiums extends FormApplication {
static init() {
Object.keys(CONFIGURABLE_COMPENDIUMS).forEach(compendium => {
@ -50,7 +55,7 @@ export class SystemCompendiums extends FormApplication {
return game.packs.get(SystemCompendiums.getCompendium(compendium));
}
static async getContent(compendium, docType) {
static async getPackContent(compendium, docType) {
const pack = SystemCompendiums.getPack(compendium);
if (pack.metadata.type == docType) {
return await pack.getDocuments();
@ -83,35 +88,17 @@ export class SystemCompendiums extends FormApplication {
}
static async getItems(compendium, itemType = undefined) {
const items = await SystemCompendiums.getContent(compendium, 'Item');
const items = await SystemCompendiums.getPackContent(compendium, 'Item');
return (itemType ? items.filter(it => it.type == itemType) : items);
}
static async buildTable(compendium, itemFrequence, filter, type = 'Item', sorting = undefined) {
let elements = await SystemCompendiums.getContent(compendium, type);
elements = elements.filter(filter).filter(it => itemFrequence(it) > 0)
static async getContent(compendium, type, filter, itemFrequence, sorting) {
let elements = await SystemCompendiums.getPackContent(compendium, type);
elements = elements.filter(filter).filter(it => itemFrequence(it) > 0);
if (sorting) {
elements = elements.sort(sorting);
}
let max = 0;
const table = elements
.map(it => {
const frequence = itemFrequence(it)
let row = { document: it, frequence: frequence, min: max + 1, max: max + frequence }
max += frequence;
return row;
});
table.forEach(it => it.total = max);
return table;
}
static async getRandom(compendium, type, subType, toChat = true, itemFrequence = it => it.system.frequence, filter = it => true) {
const table = new SystemCompendiumTable(compendium, type, subType);
return await table.getRandom(toChat, itemFrequence, filter);
}
static async chatTableItems(compendium, type, subType, itemFrequence = it => it.system.frequence, filter = it => true) {
const table = new SystemCompendiumTable(compendium, type, subType, itemFrequence);
await table.chatTable(itemFrequence, filter);
return elements;
}
static async getDefaultItems(compendium) {
@ -180,48 +167,69 @@ export class SystemCompendiums extends FormApplication {
}
}
export class SystemCompendiumTable {
/**
* ======= Gestion de jets dans une table correspondant à un compendium =======
*/
export class CompendiumTable {
constructor(compendium, type, subType, sorting = undefined) {
this.compendium = compendium;
this.type = type;
this.subType = subType;
this.compendium = compendium;
this.sourceCompendium = SystemCompendiums.getCompendium(compendium);
this.sorting = sorting
this.sorting = sorting ?? Misc.ascending(it => it.name);
}
typeName() {
return Misc.typeName(this.type, this.subType);
}
applyType(filter) {
return it => it.type == this.subType && filter(it);
async getContent(itemFrequence = it => it.system.frequence, filter = it => true) {
return await SystemCompendiums.getContent(this.compendium,
this.type,
it => this.subType == it.type && filter(it),
itemFrequence,
this.sorting);
}
async getRandom(toChat = true, itemFrequence = it => it.system.frequence, filter = it => true, forcedRoll = undefined) {
const table = await this.$buildTable(itemFrequence, filter);
async buildTable(itemFrequence = it => it.system.frequence, filter = it => true) {
const elements = await this.getContent(filter, itemFrequence);
return CompendiumTableHelpers.buildTable(elements, itemFrequence);
}
async getRandom(itemFrequence = it => it.system.frequence, filter = it => true, forcedRoll = undefined) {
const table = await this.buildTable(itemFrequence, filter);
return await CompendiumTableHelpers.getRandom(table, this.type, this.subType, forcedRoll, SystemCompendiums.getCompendium(compendium));
}
async toChatMessage(itemFrequence = it => it.system.frequence, filter = it => true, typeName = undefined) {
const table = await this.buildTable(itemFrequence, filter);
await CompendiumTableHelpers.tableToChatMessage(table, this.type, this.subType, typeName);
return true;
}
}
/**
* ======= Gestion de tables correspondant à un compendium =======
*/
export class CompendiumTableHelpers {
static buildTable(elements, itemFrequence) {
let max = 0;
const total = elements.map(it => itemFrequence(it)).reduce(Misc.sum(), 0);
return elements.map(it => {
const frequence = itemFrequence(it);
let row = { document: it, frequence: frequence, min: max + 1, max: max + frequence, total: total };
max += frequence;
return row;
});
}
static async getRandom(table, type, subType, forcedRoll = undefined, localisation = undefined) {
if (table.length == 0) {
ui.notifications.warn(`Aucun ${this.typeName()} dans ${this.sourceCompendium}`);
ui.notifications.warn(`Aucun ${Misc.typeName(type, subType)} trouvé dans ${localisation ?? ' les compendiums'}`);
return undefined;
}
const row = await this.$selectRow(table, forcedRoll);
if (row && toChat) {
await this.$chatRolledResult(row);
}
return row;
}
async chatTable(itemFrequence = it => it.system.frequence, filter = it => true, typeName = undefined) {
const table = await this.$buildTable(itemFrequence, filter);
await this.$chatSystemCompendiumTable(table, typeName);
}
async $buildTable(itemFrequence, filter) {
return await SystemCompendiums.buildTable(this.compendium, itemFrequence, this.applyType(filter), this.type, this.sorting);
return await CompendiumTableHelpers.selectRow(table, forcedRoll);
}
/* -------------------------------------------- */
async $selectRow(table, forcedRoll = undefined) {
static async selectRow(table, forcedRoll = undefined) {
if (table.length == 0) {
return undefined
}
@ -238,14 +246,16 @@ export class SystemCompendiumTable {
}
/* -------------------------------------------- */
async $chatRolledResult(row) {
static async tableRowToChatMessage(row, type = 'Item') {
if (!row) {
return;
}
const percentages = (row.total == 100) ? '%' : ''
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table-roll.html', {
roll: row.roll,
document: row?.document,
document: row.document,
percentages,
typeName: this.typeName(),
sourceCompendium: this.sourceCompendium,
typeName: Misc.typeName(type, row.document.type),
isGM: game.user.isGM,
});
const messageData = {
@ -260,11 +270,10 @@ export class SystemCompendiumTable {
}
/* -------------------------------------------- */
async $chatSystemCompendiumTable(table, typeName) {
static async tableToChatMessage(table, type, subType, typeName = undefined) {
const flavorContent = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-compendium-table.html', {
img: RdDItem.getDefaultImg(this.subType),
typeName: typeName ?? this.typeName(),
sourceCompendium: this.sourceCompendium,
img: RdDItem.getDefaultImg(subType),
typeName: typeName ?? Misc.typeName(type, subType),
table,
isGM: game.user.isGM,
});
@ -275,5 +284,4 @@ export class SystemCompendiumTable {
}, { rollMode: "gmroll" });
}
}
}

View File

@ -1,7 +1,7 @@
import { Grammar } from "./grammar.js";
import { Misc } from "./misc.js";
import { RdDDice } from "./rdd-dice.js";
import { SystemCompendiums, SystemCompendiumTable } from "./settings/system-compendiums.js";
import { SystemCompendiums, CompendiumTable, CompendiumTableHelpers } from "./settings/system-compendiums.js";
import { TMRUtility } from "./tmr-utility.js";
@ -15,7 +15,7 @@ export class TMRRencontres {
}
constructor(){
this.table = new SystemCompendiumTable('rencontres', 'Item', 'rencontre', Misc.ascending(it => it.system.ordreTri));
this.table = new CompendiumTable('rencontres', 'Item', 'rencontre', Misc.ascending(it => it.system.ordreTri));
}
/* -------------------------------------------- */
@ -36,9 +36,12 @@ export class TMRRencontres {
const codeTerrain = Grammar.toLowerCaseNoAccent(terrain)
const filtreMauvaise = codeTerrain == 'mauvaise' ? it => it.system.mauvaiseRencontre : it => !it.system.mauvaiseRencontre;
const frequence = it => it.system.frequence[codeTerrain];
const random = await this.table.getRandom(true, frequence, filtreMauvaise, forcedRoll);
const row = await this.table.getRandom(frequence, filtreMauvaise, forcedRoll);
if (row) {
await CompendiumTableHelpers.tableRowToChatMessage(row);
}
return random?.document;
return row?.document;
}
async chatTable(terrain) {
@ -47,9 +50,9 @@ export class TMRRencontres {
const filtreMauvaise = isMauvaise ? it => it.system.mauvaiseRencontre : it => !it.system.mauvaiseRencontre;
const frequence = it => it.system.frequence[codeTerrain];
const typeName = isMauvaise ? 'Mauvaises rencontres' : `Rencontres en ${Misc.upperFirst(terrain)}`;
await this.table.chatTable(frequence, filtreMauvaise, typeName);
return true
return await this.table.toChatMessage(frequence, filtreMauvaise, typeName);
}
/* -------------------------------------------- */
async createRencontre(rencontre, tmr = undefined) {
return rencontre.clone({
@ -95,7 +98,7 @@ export class TMRRencontres {
const filtreMauvaise = codeTerrain == 'mauvaise' ? it => it.system.mauvaiseRencontre : it => !it.system.mauvaiseRencontre;
const frequence = it => it.system.frequence[codeTerrain];
const row = await this.table.getRandom(false, frequence, filtreMauvaise);
const row = await this.table.getRandom(frequence, filtreMauvaise);
if (row) {
row.document = this.createRencontre(row.document, tmr);
await this.$chatRolledRencontre(row, tmr);

View File

@ -1738,7 +1738,41 @@ display: inline-flex;
/* Change color of dropdown links on hover */
.button-dropdown-content a:hover {background-color: #ddd;}
/* ======== autocomplete ======= */
.autocomplete {
position: relative;
display: inline-block;
}
.autocomplete-items {
position: absolute;
border: 1px solid var(--color-border-dark);
border-bottom: none;
border-top: none;
z-index: 99;
/*position the autocomplete items to be the same width as the container:*/
top: 100%;
left: 0;
right: 0;
}
.autocomplete-items div {
padding: 10px;
cursor: pointer;
background-color: #fff;
border-bottom: 1px solid #d4d4d4;
}
/*when hovering an item:*/
.autocomplete-items div:hover {
background-color: #e9e9e9;
}
/*when navigating through the items using the arrow keys:*/
.autocomplete-active {
background-color: DodgerBlue !important;
color: #ffffff;
}
/*************************************************************/
#pause

View File

@ -591,6 +591,10 @@
"quantite": 1,
"qualite": 0,
"cout": 0
},
"environnement": {
"milieu": "",
"environnement": []
}
},
"competence": {
@ -704,19 +708,15 @@
"exotisme": 0
},
"herbe": {
"templates": [ "description", "inventaire" ],
"templates": [ "description", "inventaire", "environnement"],
"niveau": 0,
"base": 0,
"milieu": "",
"rarete": "",
"categorie": ""
},
"ingredient": {
"templates": [ "description", "inventaire" ],
"templates": [ "description", "inventaire", "environnement" ],
"niveau": 0,
"base": 0,
"milieu": "",
"rarete": "",
"categorie": ""
},
"livre": {

View File

@ -1,11 +1,11 @@
<img class="chat-icon" src="{{rencontre.img}}" alt="{{rencontre.name}}" />
<h4>{{#if mauvaise}}Mauvaise rencontre{{else}}Rencontre{{/if}} en {{typeTmr-name tmr.type}}</h4>
<div>{{sourceCompendium}}</div>
<div>{{rencontre.pack}}</div>
<div>Jet: {{roll.formula}} : {{roll.total}}{{percentages}}</div>
<hr>
<div>
<p>{{rencontre.name}} {{rencontre.system.force}} ({{rencontre.system.formule}})</p>
<p>{{linkCompendium sourceCompendium rencontre.id rencontre.name}}</p>
<p>{{linkCompendium rencontre.pack rencontre.id rencontre.name}}</p>
{{#if rencontre.system.description}}
<div class="poesie-extrait">
{{{rencontre.system.description}}}

View File

@ -1,10 +1,10 @@
<h4>Tirage aléatoire: {{typeName}}</h4>
<div>{{sourceCompendium}}</div>
<div>{{document.pack}}</div>
<div>Jet {{roll.formula}} : {{roll.total}}{{percentages}}</div>
<hr>
<div>
<img class="chat-icon" src="{{document.img}}" alt="{{document.name}}" />
<p>{{linkCompendium @root.sourceCompendium document.id document.name}}</p>
<p>{{linkCompendium document.pack document.id document.name}}</p>
{{#if document.system.description}}
<div class="poesie-extrait">
{{{document.system.description}}}

View File

@ -1,7 +1,11 @@
<div>
<img class="chat-icon" src="{{img}}" alt="{{typeName}}" />
<h4>Table aléatoire: {{typeName}}</h4>
<div>{{sourceCompendium}}</div>
{{#with (lookup table 0) as |row|}}
<div>
{{row.document.pack}}
</div>
{{/with}}
<br>
</div>
<div>
@ -9,7 +13,7 @@
{{#each table as |row|}}
<li class="select-target item list-item" >
<span>{{row.min}}{{#unless (eq row.min row.max)}}-{{row.max}}{{/unless}} : &nbsp;</span>
<span>{{linkCompendium @root.sourceCompendium row.document.id row.document.name}}</span>
<span>{{linkCompendium row.document.pack row.document.id row.document.name}}</span>
</li>
{{/each}}
</ul>

View File

@ -1,4 +1,3 @@
<option value=""></option>
<option value="Commune">Commune</option>
<option value="Frequente">Fréquente</option>
<option value="Rare">Rare</option>

View File

@ -11,38 +11,31 @@
</div>
</header>
{{!-- Sheet Body --}}
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="informations">Informations</a>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-tab-environnement.html"}}
</nav>
<section class="sheet-body">
<div class="flexcol">
<div class="form-group">
<label>Niveau (si applicable)</label>
<input class="attribute-value" type="text" name="system.niveau" value="{{system.niveau}}" data-dtype="Number" />
<div class="tab items" data-group="primary" data-tab="informations">
<div class="flexcol">
<div class="form-group">
<label>Niveau (si applicable)</label>
<input class="attribute-value" type="text" name="system.niveau" value="{{system.niveau}}" data-dtype="Number" />
</div>
<div class="form-group">
<label>Catégorie</label>
<select name="system.categorie" class="categorie" data-dtype="String">
{{#select system.categorie}}
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-categorie-ingredient.html"}}
{{/select}}
</select>
</div>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-inventaire.html"}}
{{>"systems/foundryvtt-reve-de-dragon/templates/partial-item-description.html"}}
</div>
<div class="form-group">
<label>Milieu</label>
<input class="attribute-value" type="text" name="system.milieu" value="{{system.milieu}}" data-dtype="String" />
</div>
<div class="form-group">
<label>Fréquence</label>
<select name="system.rarete" class="rarete" data-dtype="String">
{{#select system.rarete}}
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-rarete.html"}}
{{/select}}
</select>
</div>
<div class="form-group">
<label>Catégorie</label>
<select name="system.categorie" class="categorie" data-dtype="String">
{{#select system.categorie}}
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-categorie-ingredient.html"}}
{{/select}}
</select>
</div>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-inventaire.html"}}
{{>"systems/foundryvtt-reve-de-dragon/templates/partial-item-description.html"}}
</div>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-environnement.html"}}
</section>
</form>

View File

@ -1,34 +1,28 @@
<form class="{{cssClass}}" autocomplete="off">
{{>"systems/foundryvtt-reve-de-dragon/templates/header-item.html"}}
{{!-- Sheet Body --}}
<section class="sheet-body">
<div class="form-group">
<label>Niveau (si applicable) </label>
<input class="attribute-value" type="text" name="system.niveau" value="{{system.niveau}}" data-dtype="Number" />
</div>
<div class="form-group">
<label>Milieu</label>
<input class="attribute-value" type="text" name="system.milieu" value="{{system.milieu}}" data-dtype="String" />
</div>
<div class="form-group">
<label>Fréquence</label>
<select name="system.rarete" class="rarete" data-dtype="String">
{{#select system.rarete}}
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-rarete.html"}}
{{/select}}
</select>
</div>
<div class="form-group">
<label>Catégorie</label>
<select name="system.categorie" class="categorie" data-dtype="String">
{{#select system.categorie}}
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-categorie-ingredient.html"}}
{{/select}}
</select>
</div>
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="informations">Informations</a>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-tab-environnement.html"}}
</nav>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-inventaire.html"}}
{{>"systems/foundryvtt-reve-de-dragon/templates/partial-item-description.html"}}
<section class="sheet-body">
<div class="tab items" data-group="primary" data-tab="informations">
<div class="form-group">
<label>Niveau (si applicable) </label>
<input class="attribute-value" type="text" name="system.niveau" value="{{system.niveau}}" data-dtype="Number" />
</div>
<div class="form-group">
<label>Catégorie</label>
<select name="system.categorie" class="categorie" data-dtype="String">
{{#select system.categorie}}
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-categorie-ingredient.html"}}
{{/select}}
</select>
</div>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-inventaire.html"}}
{{>"systems/foundryvtt-reve-de-dragon/templates/partial-item-description.html"}}
</div>
{{>"systems/foundryvtt-reve-de-dragon/templates/item/partial-environnement.html"}}
</section>
</form>

View File

@ -17,6 +17,7 @@
<label>Fréquence</label>
<select name="system.rarete" class="rarete" data-dtype="String">
{{#select system.rarete}}
<option value=""></option>
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-rarete.html"}}
{{/select}}
</select>

View File

@ -0,0 +1,36 @@
{{>'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete-script.hbs'}}
<div class="tab items" data-group="primary" data-tab="environnement">
<div class="form-group">
<label>Description du milieu</label>
<input class="attribute-value" type="text" name="system.milieu" value="{{system.milieu}}" data-dtype="String" />
</div>
<hr>
<div class="form-group">
<label>Ajouter un fréquence</label>
<div class="flexrow">
<div class="autocomplete">
<input type="text" class="input-selection-milieu" placeholder="Milieu" data-dtype="String"/>
</div>
{{>'systems/foundryvtt-reve-de-dragon/templates/scripts/autocomplete.hbs' proposals=milieux className='input-selection-milieu'}}
<a class="milieu-add"><i class="fas fa-plus-circle"></i></a>
</div>
</div>
{{#each system.environnement as |env|}}
<div class="form-group environnement-milieu" data-milieu="{{env.milieu}}">
<label>
{{env.milieu}}
<a class="milieu-delete" title="Supprimer {{env.milieu}}"><i class="fas fa-trash"></i></a>
</label>
<div class="flexrow">
<select class="environnement-rarete" class="flex-shrink" data-dtype="String">
{{#select env.rarete}}
{{>"systems/foundryvtt-reve-de-dragon/templates/enum-rarete.html"}}
{{/select}}
</select>
{{rangePicker name="environnement-frequence" value=env.frequence min=(getFrequenceRarete env.rarete 'min') max=(getFrequenceRarete env.rarete 'max') step=1}}
<label>[{{getFrequenceRarete env.rarete 'min'}}-{{getFrequenceRarete env.rarete 'max'}}]</label>
</div>
</div>
{{/each}}
</div>

View File

@ -0,0 +1 @@
<a class="item" data-tab="environnement">Environnement</a>

View File

@ -0,0 +1,96 @@
<script>
function autocomplete(input, proposals) {
var currentFocus;
function createCompletionContainer(id) {
let div = document.createElement("DIV");
div.setAttribute("id", id + "autocomplete-list");
div.setAttribute("class", "autocomplete-items");
return div;
}
function createCompletionProposal(inputElement, incomplete, complete) {
let div = document.createElement("DIV");
/*make the matching letters bold:*/
const start = complete.toUpperCase().indexOf(incomplete.toUpperCase());
div.innerHTML = complete.substr(0, start)
+ "<strong>" + complete.substr(start, incomplete.length) + "</strong>"
+ complete.substr(start+incomplete.length)
/*insert a input field that will hold the current array item's value:*/
+ "<input type='hidden' value='" + complete + "'>";
/*execute a function when someone clicks on the item value (DIV element):*/
div.addEventListener("click", function(e) {
/*insert the value for the autocomplete text field:*/
inputElement.value = this.getElementsByTagName("input")[0].value;
/*close the list of autocompleted values, or any other open lists of autocompleted values:*/
closeAllOpenedLists();
});
return div;
}
function addActive(x) {
if (!x) return false;
removeActive(x);
if (currentFocus >= x.length) currentFocus = 0;
if (currentFocus < 0) currentFocus = (x.length - 1);
x[currentFocus].classList.add("autocomplete-active");
}
function removeActive(x) {
for (var i = 0; i < x.length; i++) {
x[i].classList.remove("autocomplete-active");
}
}
function closeAllOpenedLists(elmnt) {
/*close all autocomplete lists in the document, except the one passed as an argument:*/
var x = document.getElementsByClassName("autocomplete-items");
for (var i = 0; i < x.length; i++) {
if (elmnt != x[i] && elmnt != input) {
x[i].parentNode.removeChild(x[i]);
}
}
}
/*execute a function when someone writes in the text field:*/
input.addEventListener("input", function(e) {
const incomplete = this.value;
closeAllOpenedLists();
if (!incomplete) { return false; }
currentFocus = -1;
const container = createCompletionContainer(this.id)
this.parentNode.appendChild(container);
/*for each item in the array...*/
for (let i = 0; i < proposals.length; i++) {
/*check if the item starts with the same letters as the text field value:*/
if (proposals[i].toUpperCase().includes(incomplete.toUpperCase())) {
/*create a DIV element for each matching element:*/
container.appendChild(createCompletionProposal(input, incomplete, proposals[i]));
}
}
});
/*execute a function presses a key on the keyboard:*/
input.addEventListener("keydown", function(e) {
var container = document.getElementById(this.id + "autocomplete-list")?.getElementsByTagName("div");
if (e.keyCode == 40) {
currentFocus++;
addActive(container);
} else if (e.keyCode == 38) { //up
currentFocus--;
addActive(container);
} else if (e.keyCode == 13) {
e.preventDefault();
if (currentFocus > -1 && container) {
container[currentFocus].click();
}
}
});
/*execute a function when someone clicks in the document:*/
document.addEventListener("click", function (e) {
closeAllOpenedLists(e.target);
});
}
</script>

View File

@ -0,0 +1,5 @@
<script>
var proposals = [ {{#each proposals as |val|}}"{{val}}",{{/each}} ];
Array.from(document.getElementsByClassName('{{className}}'))
.forEach(element => autocomplete(element, proposals))
</script>