v2.7.0 : templates de drop (table BDD, proba admin, application au drop)

- db.js : table templates + seed des 19 templates du CSV (mappes vers les
  mods + nouveau champ tour), templatesAll/setTemplate/resetCategory.
- server.js : /api/reference/templates.
- mp.js : config templateChance (0-100, bornee) ; applyGearTemplates applique
  jusqu'a 3 templates (6 si 2 mains), distincts, proba par emplacement,
  fusionne les mods (dont tour) et compose le nom ; applique au spawnItem.
  Solo vanilla.
- gear.js : GEAR_MOD_LABELS += tour (affichage 'TOUR +X mn').
- admin.html : champ Proba de template / emplacement.
- test/mp.js : test des templates (nb selon mains, distincts, proba 0).

Note : effet gameplay de tour (DLA) non cable, en attente de la regle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
perco
2026-06-14 00:17:53 +02:00
co-authored by Claude Opus 4.8
parent b55a6f8ac9
commit 00a4669928
8 changed files with 116 additions and 8 deletions
+31 -2
View File
@@ -41,6 +41,7 @@ const DEFAULT_CONFIG = {
deathRespawnSec: 90, // délai de réapparition d'un troll terrassé
maxTrolls: 40,
logSize: 150,
templateChance: 0, // proba (%) qu'un emplacement de template soit rempli sur un drop d'équipement
};
const CONFIG_BOUNDS = {
@@ -50,6 +51,7 @@ const CONFIG_BOUNDS = {
monsterTarget: [0, 120], repopSec: [10, 3600],
itemTarget: [0, 120], deathRespawnSec: [0, 3600],
maxTrolls: [1, 200], logSize: [20, 500],
templateChance: [0, 100],
};
/* ---------- Tuning : valeurs du bestiaire, de l'équipement et des trésors ----------
@@ -99,6 +101,33 @@ function applyGearTuning(item) {
return item;
}
/* Templates de drop : un objet qui tombe reçoit jusqu'à 3 templates (6 pour une
* arme à 2 mains), chaque emplacement étant rempli avec la proba `chance` (%)
* par un template DISTINCT tiré au hasard en base. Les bonus (dont `tour`)
* s'ajoutent aux mods de l'objet, et les suffixes au nom (« Épée Courte de
* l'Aigle des Mages »). N'affecte que le monde partagé (drops) ; le solo reste
* vanilla. */
function applyGearTemplates(item, chance) {
if (!item || item.kind !== "gear" || !(chance > 0)) return item;
const pool = db.templatesAll();
if (!pool.length) return item;
const slots = item.twoHanded ? 6 : 3;
const picked = [];
for (let i = 0; i < slots && pool.length; i++) {
if (Math.random() * 100 >= chance) continue;
picked.push(pool.splice(Math.floor(Math.random() * pool.length), 1)[0]);
}
if (!picked.length) return item;
const mods = { ...(item.mods || {}) };
for (const t of picked) {
for (const k of db.TEMPLATE_KEYS) if (t[k]) mods[k] = (mods[k] || 0) + t[k];
}
item.mods = Object.fromEntries(Object.entries(mods).filter(([, v]) => v));
item.name = `${item.name} ${picked.map(t => t.name).join(" ")}`;
item.templates = picked.map(t => t.name);
return item;
}
/* ---------- Création du monde ---------- */
function randomFloor(world, taken = null) {
@@ -153,7 +182,7 @@ function spawnItem(world) {
const depth = world.config.worldDepth;
const item = r < 0.34 ? tunedRandomPotion()
: r < 0.48 ? tunedRandomScroll()
: r < 0.78 ? applyGearTuning(gearLib.makeRandomGear(depth))
: r < 0.78 ? applyGearTemplates(applyGearTuning(gearLib.makeRandomGear(depth)), world.config.templateChance)
: { kind: "gold", gold: g.rollDice(depth, 6).total * 10, emoji: "💰" };
if (item.kind === "gold") item.name = `${item.gold} Mountyzédons`;
world.items.push({ ...item, x: pos.x, y: pos.y });
@@ -1038,5 +1067,5 @@ module.exports = {
adminOverview, adminSetConfig, adminSetTuning, adminDefaults, adminResetWorld, adminKickTroll,
currentTuning, saveWorld, loadWorld,
spawnMonster, spawnItem, monsterAct, trollDla, worldLog,
tunedRandomPotion, tunedRandomScroll, applyGearTuning,
tunedRandomPotion, tunedRandomScroll, applyGearTuning, applyGearTemplates,
};