Outils pour utilisateurs

Outils du site


simulateur

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Prochaine révision
Révision précédente
simulateur [2026/04/29 20:30] – créée nanakisimulateur [2026/05/20 16:46] (Version actuelle) nanaki
Ligne 1: Ligne 1:
-<!DOCTYPE html>+Voici un simulateur en version beta, n'hésitez pas à faire vos remarques sur le discord ou le forum. 
 + 
 <html> <html>
 <head> <head>
Ligne 9: Ligne 11:
   font-family: Arial;   font-family: Arial;
   background:#0b1a13;   background:#0b1a13;
-  color:#e0d3a3;+  color:#7dd3fc;
   padding:10px;   padding:10px;
 } }
- 
 h1,h2 { color:gold;} h1,h2 { color:gold;}
- +.box { border:1px solid gold; padding:10px; margin-bottom:10px; }
-.box { +
-  border:1px solid gold; +
-  padding:10px; +
-  margin-bottom:10px; +
-} +
 select { margin:3px; max-width:150px; } select { margin:3px; max-width:150px; }
-input { margin:3px; width:50px; } +input { margin:3px; width:60px; } 
- +.statLine { display:flex; justify-content:space-between; max-width:260px;
-.statLine { +.result { background:#000; color:#00ffcc; padding:10px; white-space:pre-wrap; } 
-  display:flex; +.small { font-size:12px; color:#aaa; }
-  justify-content:space-between; +
-  max-width:260px; +
-} +
- +
-.result { +
-  background:#111; +
-  padding:10px; +
-}+
 </style> </style>
 </head> </head>
Ligne 39: Ligne 26:
 <body> <body>
  
-<h1>Simulateur</h1>+<h1>🎲 Simulateur</h1>
  
 <div class="box"> <div class="box">
 Race : Race :
 <select id="race"></select> <select id="race"></select>
- 
 <h3>Stats de base</h3> <h3>Stats de base</h3>
 <pre id="baseStats"></pre> <pre id="baseStats"></pre>
Ligne 57: Ligne 43:
 <div class="box"> <div class="box">
 <h3>Équipement (3 max + 1 anneau)</h3> <h3>Équipement (3 max + 1 anneau)</h3>
- 
 Tête <select id="head"></select> <span id="headStats"></span><br> Tête <select id="head"></select> <span id="headStats"></span><br>
 Cou <select id="neck"></select> <span id="neckStats"></span><br> Cou <select id="neck"></select> <span id="neckStats"></span><br>
Ligne 66: Ligne 51:
 Anneau <select id="ring"></select> <span id="ringStats"></span><br> Anneau <select id="ring"></select> <span id="ringStats"></span><br>
 Pieds <select id="feet"></select> <span id="feetStats"></span> Pieds <select id="feet"></select> <span id="feetStats"></span>
- 
 <div id="limitWarn" style="color:red;"></div> <div id="limitWarn" style="color:red;"></div>
 </div> </div>
Ligne 75: Ligne 59:
 </div> </div>
  
-<button onclick="calculate()">Calculer</button>+<div class="box"> 
 +<h3>Sorts actifs</h3> 
 +<div id="spells"></div> 
 +<div id="spellBonus" class="small"></div> 
 +</div> 
 + 
 + 
 + 
 +<!-- ================= POUSSEE ================= --> 
 +<div class="box"> 
 +<h3>Simulateur de poussée</h3> 
 + 
 +Force assaillant <input id="pushF" type="number" value="10"> 
 +Renforcement <select id="pushBuff"><option value="0">0</option><option value="6">+6</option></select><br> 
 + 
 +Endurance <input id="pushE" type="number" value="5"> 
 +Agilité <input id="pushAgi" type="number" value="5"><br> 
 + 
 +PV actuels <input id="pushPV" type="number" value="50"><br> 
 + 
 +Instabilité <select id="pushDebuff"><option value="0">0</option><option value="-6">-6</option></select> 
 +Stabilité <select id="pushStab"><option value="0">0</option><option value="6">+6</option><option value="12">+12</option><option value="18">+18</option></select> 
 + 
 +<pre id="pushResult" class="result"></pre> 
 +</div> 
 + 
 +<button onclick="resetAll()">Reset</button> 
 + 
 +<button onclick="exportBuild()">📤 Export</button> 
 +<button onclick="importBuild()">📥 Import</button> 
 +<button onclick="copyLink()">🔗 Lien</button>
  
 <pre id="result" class="result"></pre> <pre id="result" class="result"></pre>
Ligne 83: Ligne 97:
 // ===== RACES ===== // ===== RACES =====
 const races = { const races = {
-Elfe:{cc:9,ct:10,f:9,e:4,agi:9,p:5,mvt:5,pv:55,pm:30,r:4,rm:6,fm:8,m:6}, +Elfe:{a:2,cc:9,ct:10,f:9,e:4,agi:9,p:5,mvt:5,pv:55,pm:30,r:4,rm:6,fm:8,m:6}, 
-Nain:{cc:11,ct:8,f:11,e:6,agi:6,p:4,mvt:4,pv:50,pm:15,r:5,rm:4,fm:10,m:3}, +Nain:{a:2,cc:11,ct:8,f:11,e:6,agi:6,p:4,mvt:4,pv:50,pm:15,r:5,rm:4,fm:10,m:3}, 
-Géant:{cc:9,ct:10,f:12,e:4,agi:7,p:4,mvt:5,pv:65,pm:20,r:5,rm:5,fm:6,m:5}, +Géant:{a:2,cc:9,ct:10,f:12,e:4,agi:7,p:4,mvt:5,pv:65,pm:20,r:5,rm:5,fm:6,m:5}, 
-Olympien:{cc:10,ct:9,f:10,e:5,agi:8,p:4,mvt:5,pv:60,pm:30,r:5,rm:6,fm:7,m:5}, +Olympien:{a:2,cc:10,ct:9,f:10,e:5,agi:8,p:4,mvt:5,pv:60,pm:30,r:5,rm:6,fm:7,m:5}, 
-HS:{cc:8,ct:9,f:7,e:3,agi:11,p:5,mvt:6,pv:45,pm:40,r:3,rm:7,fm:9,m:5}+HS:{a:2,cc:8,ct:9,f:7,e:3,agi:11,p:5,mvt:6,pv:45,pm:40,r:3,rm:7,fm:9,m:5}
 }; };
  
 // ===== COST ===== // ===== COST =====
 const costTable = { const costTable = {
 +a:[800,200,100],
 cc:[100,50,30], ct:[110,50,30], cc:[100,50,30], ct:[110,50,30],
 f:[120,55,30], e:[120,55,30], f:[120,55,30], e:[120,55,30],
Ligne 107: Ligne 122:
 // ===== PASSIFS ===== // ===== PASSIFS =====
 const skills = [ const skills = [
-"Fulgurance","Duelliste","Encaisser", +{name:"Fulgurance",desc:"+1 toucher au cac + 1 esquive tous les 5 mouvements"}, 
-"Anguille","Couverture","Inépuisable","Maitre bretteur"+{name:"Couverture",desc:"85% CC / 15% AGI"}
 +{name:"Réflexes fulgurants",desc:"85% AGI / 15% CC"}, 
 +{name:"Berserker",desc:"+1 toucher au cac tous les 10 pv perdus"}, 
 +{name:"Griffes",desc:"+3 dégâts au poings"}, 
 +{name:"Anguille",desc:"Avantage esquive"}
 ]; ];
  
-// ===== ÉQUIPEMENTS =====+// ===== SORTS ===== 
 +const spells = [ 
 +{name:"Peau de granit",dex:0,prot:2}, 
 +{name:"Coup précis",dex:2,prot:0}, 
 + 
 +{name:"Maladresse",dex:-2,prot:0}, 
 +{name:"Vulnérabilité",dex:0,prot:-2}, 
 + 
 +{name:"Puissance de la nature",dex:2,prot:2}, 
 + 
 +{name:"Aide",dex:4,prot:0}, 
 +{name:"Malchance",dex:-4,prot:0}, 
 + 
 +{name:"Fatigue",dex:0,prot:-4}, 
 +{name:"Réflexes accrus",dex:0,prot:4}, 
 + 
 +{name:"Bénédiction",dex:4,prot:4}, 
 + 
 +{name:"Puissance du lutin capricieux",dex:-4,prot:-4}, 
 + 
 +{name:"Exténuation",dex:0,prot:-8}, 
 +{name:"Sauvegarde",dex:0,prot:8}, 
 +{name:"Guigne",dex:-8,prot:0}, 
 +{name:"Virtuose",dex:8,prot:0} 
 +]; 
 + 
 + 
 const equipments = [ const equipments = [
  
Ligne 133: Ligne 179:
 {name:"Arc long",race:"all",slot:"weapon2h",stats:{ct:2,f:-1,cc:-2}}, {name:"Arc long",race:"all",slot:"weapon2h",stats:{ct:2,f:-1,cc:-2}},
 {name:"Targe renforcée",race:"all",slot:"offhand",stats:{cc:1,pv:10}}, {name:"Targe renforcée",race:"all",slot:"offhand",stats:{cc:1,pv:10}},
 +
 +/* ===== NOUVEAUX COMMUNS ===== */
 +
 +{name:"Torche",race:"all",slot:"weapon",stats:{cc:-2,f:-2}},
 +{name:"Hache de jet",race:"all",slot:"weapon",stats:{ct:1}},
 +{name:"Pierre Noire",race:"all",slot:"weapon",stats:{f:1,cc:-1}},
 +{name:"Main-gauche offensive",race:"all",slot:"offhand",stats:{cc:2,f:-1}},
  
 /* ================= ELFES ================= */ /* ================= ELFES ================= */
  
-{name:"Bâton du Pèlerin",race:"Elfe",slot:"weapon",stats:{mvt:1,fm:1}}, +{name:"Bâton du Pèlerin",race:"all",slot:"weapon",stats:{mvt:1,fm:1}}, 
-{name:"Bottes Tâlroval",race:"Elfe",slot:"feet",stats:{mvt:2}}, +{name:"Bottes Tâlroval",race:"all",slot:"feet",stats:{mvt:2}}, 
-{name:"Cuirasse",race:"Elfe",slot:"body",stats:{e:1,r:2}}, +{name:"Cuirasse",race:"all",slot:"body",stats:{e:1,r:2}}, 
-{name:"Lance de garde Sylvestre",race:"Elfe",slot:"weapon",stats:{cc:1,f:1}}, +{name:"Lance de garde Sylvestre",race:"all",slot:"weapon",stats:{cc:1,f:1}}, 
-{name:"Sceptre de mage",race:"Elfe",slot:"weapon2h",stats:{m:1,fm:1}}, +{name:"Sceptre de mage",race:"all",slot:"weapon2h",stats:{m:1,fm:1}}, 
-{name:"Arc Ensorcelé",race:"Elfe",slot:"weapon2h",stats:{ct:1,rm:2,cc:-1}}, +{name:"Arc Ensorcelé",race:"all",slot:"weapon2h",stats:{ct:1,rm:2,cc:-1}}, 
-{name:"Armure de garde Sylvestre",race:"Elfe",slot:"body",stats:{e:2,mvt:-1},magicBlock:true}, +{name:"Armure de garde Sylvestre",race:"all",slot:"body",stats:{e:2,mvt:-1},magicBlock:true}, 
-{name:"Toge de feuillage",race:"Elfe",slot:"body",stats:{fm:1,agi:1,pv:5}}, +{name:"Toge de feuillage",race:"all",slot:"body",stats:{fm:1,agi:1,pv:5}}, 
-{name:"Marque Forestière",race:"Elfe",slot:"neck",stats:{pm:10,r:1}}, +{name:"Marque Forestière",race:"all",slot:"neck",stats:{pm:10,r:1}}, 
-{name:"Capuche du chasseur",race:"Elfe",slot:"head",stats:{ct:1,r:1}},+{name:"Capuche du chasseur",race:"all",slot:"head",stats:{ct:1,r:1}}, 
 + 
 +// spécifique elfe 
 +{name:"Arc Elfique",race:"Elfe",slot:"weapon2h",stats:{ct:2,cc:-1}},
  
 /* ================= NAINS ================= */ /* ================= NAINS ================= */
  
-{name:"Cotte de mailles",race:"Nain",slot:"body",stats:{e:2,agi:-4}}, +{name:"Cotte de mailles",race:"all",slot:"body",stats:{e:2,agi:-4}}, 
-{name:"Labrys",race:"Nain",slot:"weapon",stats:{cc:1,f:1}}, +{name:"Labrys",race:"all",slot:"weapon",stats:{cc:1,f:1}}, 
-{name:"Marteau de guerre",race:"Nain",slot:"weapon2h",stats:{f:2,cc:-1}}, +{name:"Marteau de guerre",race:"all",slot:"weapon2h",stats:{f:2,cc:-1}}, 
-{name:"Targe renforcée naine",race:"Nain",slot:"offhand",stats:{cc:1,pv:10}}, +{name:"Targe renforcée naine",race:"all",slot:"offhand",stats:{cc:1,pv:10}}, 
-{name:"Armure runique",race:"Nain",slot:"body",stats:{e:1,pm:8}}, +{name:"Armure runique",race:"all",slot:"body",stats:{e:1,pm:8}}, 
-{name:"Cape dorée",race:"Nain",slot:"cape",stats:{m:1,fm:1},magicBlock:true}, +{name:"Cape dorée",race:"all",slot:"cape",stats:{m:1,fm:1},magicBlock:true}, 
-{name:"Solerets",race:"Nain",slot:"feet",stats:{mvt:1,pm:5,pv:5}}, +{name:"Solerets",race:"all",slot:"feet",stats:{mvt:1,pm:5,pv:5}}, 
-{name:"Casque grossissant",race:"Nain",slot:"head",stats:{ct:2,p:-1}},+{name:"Casque grossissant",race:"all",slot:"head",stats:{ct:2,p:-1}}, 
 + 
 +// spécifiques nains 
 +{name:"Bâton d'archimage",race:"Nain",slot:"weapon2h",stats:{m:2,fm:-1}}, 
 +{name:"Hache runique",race:"Nain",slot:"weapon",stats:{cc:1,f:2}},
  
 /* ================= GÉANTS ================= */ /* ================= GÉANTS ================= */
  
-{name:"Bouclier Clipeus",race:"Géant",slot:"offhand",stats:{e:1,pv:10}}, +{name:"Bouclier Clipeus",race:"all",slot:"offhand",stats:{e:1,pv:10}}, 
-{name:"Masse d'arme",race:"Géant",slot:"weapon",stats:{cc:1,f:1}}, +{name:"Masse d'arme",race:"all",slot:"weapon",stats:{cc:1,f:1}}, 
-{name:"Hallebarde de pierre",race:"Géant",slot:"weapon",stats:{cc:2,f:-1}},+{name:"Hallebarde de pierre",race:"all",slot:"weapon",stats:{cc:2,f:-1}},
 {name:"Bâton de shaman",race:"Géant",slot:"weapon2h",stats:{fm:2,pv:-5}}, {name:"Bâton de shaman",race:"Géant",slot:"weapon2h",stats:{fm:2,pv:-5}},
-{name:"Javelot lourd",race:"Géant",slot:"weapon",stats:{f:2,ct:-1,cc:-1}}, +{name:"Javelot lourd",race:"all",slot:"weapon",stats:{f:2,ct:-1,cc:-1}}, 
-{name:"Broigne",race:"Géant",slot:"body",stats:{e:2,f:-1}}, +{name:"Broigne",race:"all",slot:"body",stats:{e:2,f:-1}}, 
-{name:"Fétiche de shaman",race:"Géant",slot:"offhand",stats:{r:2,pm:5}}, +{name:"Fétiche de shaman",race:"all",slot:"offhand",stats:{r:2,pm:5}}, 
-{name:"Bottes incandescentes",race:"Géant",slot:"feet",stats:{mvt:1,r:1,pv:5}}, +{name:"Bottes incandescentes",race:"all",slot:"feet",stats:{mvt:1,r:1,pv:5}}, 
-{name:"Médaillon hanté",race:"Géant",slot:"neck",stats:{cc:1,ct:1}},+{name:"Médaillon hanté",race:"all",slot:"neck",stats:{cc:1,ct:1}}, 
 + 
 +// spécifique géant 
 +{name:"Peau de granit manifiée",race:"Géant",slot:"body",stats:{m:1,rm:2,agi:-2}},
  
 /* ================= HS ================= */ /* ================= HS ================= */
  
-{name:"Bâton de sage",race:"HS",slot:"weapon2h",stats:{m:1,agi:1}}, +{name:"Bâton de sage",race:"all",slot:"weapon2h",stats:{m:1,agi:1}}, 
-{name:"Orbe de mana",race:"HS",slot:"weapon2h",stats:{m:1,rm:1}}, +{name:"Orbe de mana",race:"all",slot:"weapon2h",stats:{m:1,rm:1}}, 
-{name:"Cape de mage",race:"HS",slot:"cape",stats:{fm:1,rm:1}}, +{name:"Cape de mage",race:"all",slot:"cape",stats:{fm:1,rm:1}}, 
-{name:"Armure de fourrure",race:"HS",slot:"body",stats:{e:2,rm:-3}}, +{name:"Armure de fourrure",race:"all",slot:"body",stats:{e:2,rm:-3}}, 
-{name:"Ceste",race:"HS",slot:"weapon",stats:{f:1,cc:1}}, +{name:"Ceste",race:"all",slot:"weapon",stats:{f:1,cc:1}}, 
-{name:"Collier d'apaisement",race:"HS",slot:"neck",stats:{pv:10,pm:5}}, +{name:"Collier d'apaisement",race:"all",slot:"neck",stats:{pv:10,pm:5}}, 
-{name:"Bottes de voleur",race:"HS",slot:"feet",stats:{mvt:1,agi:1}}, +{name:"Bottes de voleur",race:"all",slot:"feet",stats:{mvt:1,agi:1}}, 
-{name:"Sarbacane",race:"HS",slot:"weapon",stats:{ct:1,f:10,cc:-1}}, +{name:"Sarbacane",race:"all",slot:"weapon",stats:{ct:1,f:10,cc:-1}}, 
-{name:"Bouclier en lianes",race:"HS",slot:"offhand",stats:{cc:1,agi:1}},+{name:"Bouclier en lianes",race:"all",slot:"offhand",stats:{cc:1,agi:1}}, 
 + 
 +// spécifiques HS 
 +{name:"Manteau de feuillage",race:"HS",slot:"body",stats:{e:1,agi:2}}, 
 +{name:"Bottes griffues",race:"HS",slot:"feet",stats:{cc:1,f:1,pv:5}},
  
 /* ================= OLYMPIENS ================= */ /* ================= OLYMPIENS ================= */
  
-{name:"Armure hoplitique",race:"Olympien",slot:"body",stats:{e:1,m:1}}, +{name:"Armure hoplitique",race:"all",slot:"body",stats:{e:1,m:1}}, 
-{name:"Bouclier Ancile",race:"Olympien",slot:"offhand",stats:{e:1}}, +{name:"Bouclier Ancile",race:"all",slot:"offhand",stats:{e:1}}, 
-{name:"Diadème",race:"Olympien",slot:"head",stats:{r:1,rm:2}}, +{name:"Diadème",race:"all",slot:"head",stats:{r:1,rm:2}}, 
-{name:"Pilum",race:"Olympien",slot:"weapon",stats:{ct:1,f:1,cc:-1}}, +{name:"Pilum",race:"all",slot:"weapon",stats:{ct:1,f:1,cc:-1}}, 
-{name:"Spatha",race:"Olympien",slot:"weapon",stats:{cc:1,f:1}}, +{name:"Spatha",race:"all",slot:"weapon",stats:{cc:1,f:1}}, 
-{name:"Sceptre de puissance",race:"Olympien",slot:"weapon",stats:{m:1,pm:8}}, +{name:"Sceptre de puissance",race:"all",slot:"weapon",stats:{m:1,pm:8}}, 
-{name:"Lame Sainte",race:"Olympien",slot:"weapon",stats:{cc:1,m:1}}, +{name:"Lame Sainte",race:"all",slot:"weapon",stats:{cc:1,m:1}}, 
-{name:"Tiare d'Oracle",race:"Olympien",slot:"head",stats:{fm:1,p:1}}, +{name:"Tiare d'Oracle",race:"all",slot:"head",stats:{fm:1,p:1}}, 
-{name:"Cnémides",race:"Olympien",slot:"feet",stats:{mvt:1,rm:1,r:1}},+{name:"Cnémides",race:"all",slot:"feet",stats:{mvt:1,rm:1,r:1}}, 
 + 
 +// spécifique olympien 
 +{name:"Lorica",race:"Olympien",slot:"body",stats:{e:2,agi:-2}},
  
 /* ================= ANNEAUX ================= */ /* ================= ANNEAUX ================= */
Ligne 206: Ligne 276:
  
 ]; ];
- 
- 
- 
  
  
Ligne 216: Ligne 283:
 const baseDiv = document.getElementById("baseStats"); const baseDiv = document.getElementById("baseStats");
  
-// races 
 for(let r in races){ for(let r in races){
 raceSelect.innerHTML += `<option>${r}</option>`; raceSelect.innerHTML += `<option>${r}</option>`;
Ligne 222: Ligne 288:
  
 // stats UI // stats UI
-const statOrder = ["cc","ct","f","e","agi","m","fm","p","pv","pm","mvt","r","rm"]; +for(let in costTable){
-const statNames = { +
-cc:"CC", ct:"CT", f:"Force", e:"Endurance", +
-agi:"Agilité", m:"Magie", fm:"FM", +
-p:"Perception", pv:"PV", pm:"PM", +
-mvt:"Mvt", r:"Récup", rm:"RM" +
-}; +
- +
-statOrder.forEach(s=>{+
 statsDiv.innerHTML += ` statsDiv.innerHTML += `
 <div class="statLine"> <div class="statLine">
-<span>${statNames[s]}</span>+<span>${s}</span>
 <input type="number" id="${s}" value="0" min="0"> <input type="number" id="${s}" value="0" min="0">
 </div>`; </div>`;
-});+}
  
 // passifs // passifs
 skills.forEach(s=>{ skills.forEach(s=>{
-document.getElementById("skills").innerHTML += `<label><input type="checkbox"> ${s}</label><br>`;+document.getElementById("skills").innerHTML += ` 
 +<label> 
 +<input type="checkbox" class="skill" data-name="${s.name}"
 +<b>${s.name}</b><br> 
 +<span class="small">${s.desc}</span> 
 +</label><br>`; 
 +}); 
 + 
 + 
 +// affichage sorts 
 +spells.forEach(s=>
 +document.getElementById("spells").innerHTML += ` 
 +<label> 
 +<input type="checkbox" class="spell" data-dex="${s.dex}" data-prot="${s.prot}"> 
 +<b>${s.name}</b>  
 +<span class="small">(Dex ${s.dex>=0?"+":""}${s.dex} | Prot ${s.prot>=0?"+":""}${s.prot})</span> 
 +</label><br>`;
 }); });
  
Ligne 247: Ligne 321:
 baseDiv.textContent = JSON.stringify(races[raceSelect.value],null,2); baseDiv.textContent = JSON.stringify(races[raceSelect.value],null,2);
 } }
- 
 raceSelect.addEventListener("change", displayBase); raceSelect.addEventListener("change", displayBase);
 displayBase(); displayBase();
Ligne 253: Ligne 326:
 // ===== COST ===== // ===== COST =====
 function calcCost(b,m1,m2,n){ function calcCost(b,m1,m2,n){
-let total=0, last=b;+let total=0,last=b;
 for(let i=1;i<=n;i++){ for(let i=1;i<=n;i++){
 if(i===1) last=b; if(i===1) last=b;
Ligne 261: Ligne 334:
 } }
 return total; return total;
 +}
 +
 +function updateCost(){
 +let total=0;
 +for(let s in costTable){
 +let v=+document.getElementById(s).value||0;
 +let [b,m1,m2]=costTable[s];
 +total+=calcCost(b,m1,m2,v);
 +}
 +document.getElementById("cost").innerHTML="💰 PI : "+total;
 } }
  
 // ===== SLOTS ===== // ===== SLOTS =====
-const head document.getElementById("head"); +const slots 
-const neck = document.getElementById("neck"); +head:head,neck:neck,cape:cape,rightHand:rightHand, 
-const cape = document.getElementById("cape"); +leftHand:leftHand,body:body,ring:ring,feet:feet 
-const rightHand = document.getElementById("rightHand"); +};
-const leftHand = document.getElementById("leftHand"); +
-const body = document.getElementById("body"); +
-const ring = document.getElementById("ring"); +
-const feet = document.getElementById("feet");+
  
-// reset slots 
 function resetSlots(){ function resetSlots(){
-[head,neck,cape,rightHand,leftHand,body,ring,feet].forEach(sel=>+Object.values(slots).forEach(s=>s.innerHTML="<option value=''>--</option>");
-sel.innerHTML = `<option value="">--</option>`; +
-});+
 } }
  
-// remplir équipements 
 function fillEquip(){ function fillEquip(){
-let r = raceSelect.value;+let r=raceSelect.value;
  
 equipments.forEach((e,i)=>{ equipments.forEach((e,i)=>{
 +if(e.race!=="all"&&e.race!==r) return;
  
-if(e.race !== "all&& e.race !== rreturn;+if(e.slot==="weapon"||e.slot==="weapon2h") rightHand.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +if(e.slot==="offhand") leftHand.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +if(e.slot==="body") body.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +if(e.slot==="head") head.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +if(e.slot==="neck") neck.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +if(e.slot==="cape") cape.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +if(e.slot==="feet") feet.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +if(e.slot==="ring") ring.innerHTML+=`<option value="${i}">${e.name}</option>`; 
 +}); 
 +}
  
-// main droite +raceSelect.addEventListener("change",()=>{ 
-if(e.slot==="weapon" || e.slot==="weapon2h") +resetSlots(); 
-rightHand.innerHTML +`<option value="${i}">${e.name}</option>`;+fillEquip(); 
 +autoCalculate(); 
 +});
  
-// main gauche +resetSlots(); 
-if(e.slot==="offhand"+fillEquip();
-leftHand.innerHTML += `<option value="${i}">${e.name}</option>`;+
  
-// corps +// ===== BONUS ===== 
-if(e.slot==="body") +function displayStats(select,id){ 
-body.innerHTML += `<option value="${i}">${e.name}</option>`;+let v=select.value; 
 +let el=document.getElementById(id); 
 +if(v===""){el.innerHTML="";return;
 +let e=equipments[v]; 
 +let txt=""
 +for(let s in e.stats){ 
 +let val=e.stats[s]; 
 +txt+=val>0?`+${val} ${s} `:`${val} ${s} `; 
 +
 +el.innerHTML=txt; 
 +}
  
-// tête +const slotToStatId 
-if(e.slot==="head") +head:"headStats", 
-head.innerHTML += `<option value="${i}">${e.name}</option>`;+neck:"neckStats"
 +cape:"capeStats", 
 +rightHand:"rightStats", 
 +leftHand:"leftStats", 
 +body:"bodyStats", 
 +ring:"ringStats", 
 +feet:"feetStats" 
 +};
  
-// cou +Object.entries(slots).forEach(([k,s])=>{ 
-if(e.slot==="neck"+s.addEventListener("change",()=>{ 
-neck.innerHTML += `<option value="${i}">${e.name}</option>`;+displayStats(s, slotToStatId[k]); 
 +checkLimit(); 
 +autoCalculate(); 
 +}); 
 +});
  
-// dos +// ===== LIMIT ===== 
-if(e.slot==="cape"+function checkLimit(){ 
-cape.innerHTML += `<option value="${i}">${e.name}</option>`;+let count=0; 
 +Object.entries(slots).forEach(([k,s])=>{ 
 +if(k!=="ring"&&s.value!=="") count++; 
 +});
  
-// pieds +if(count>=3){ 
-if(e.slot==="feet") +limitWarn.innerText="Limite atteinte"; 
-feet.innerHTML += `<option value="${i}">${e.name}</option>`;+Object.entries(slots).forEach(([k,s])=>{ 
 +if(k!=="ring"&&s.value===""s.disabled=true; 
 +}); 
 +}else{ 
 +limitWarn.innerText=""
 +Object.values(slots).forEach(s=>s.disabled=false); 
 +
 +}
  
-// anneau +// ===== CALCUL =====
-if(e.slot==="ring"+
-ring.innerHTML +`<option value="${i}">${e.name}</option>`;+
  
 +function calculate(){
 +
 +let activeSpells = [...document.querySelectorAll(".spell:checked")];
 +
 +let bestDex = 0;
 +let bestProt = 0;
 +
 +activeSpells.forEach(s=>{
 +let d = +s.dataset.dex;
 +let p = +s.dataset.prot;
 +
 +if(Math.abs(d) > Math.abs(bestDex)) bestDex = d;
 +if(Math.abs(p) > Math.abs(bestProt)) bestProt = p;
 }); });
 +
 +let char={...races[raceSelect.value]};
 +
 +// stats investies
 +for(let s in costTable){
 +let v=+document.getElementById(s).value||0;
 +char[s]=(char[s]||0)+v;
 } }
  
-head.addEventListener("change", ()=> displayStats(head,"headStats")); +// equip 
-neck.addEventListener("change", ()=> displayStats(neck,"neckStats")); +Object.values(slots).forEach(sel=>
-cape.addEventListener("change", ()=> displayStats(cape,"capeStats"))+if(sel.value===""return
-rightHand.addEventListener("change", ()=> displayStats(rightHand,"rightStats"))+let e=equipments[sel.value]
-leftHand.addEventListener("change", ()=> displayStats(leftHand,"leftStats")); +for(let s in e.stats){ 
-body.addEventListener("change", ()=> displayStats(body,"bodyStats")); +char[s]=(char[s]||0)+e.stats[s]
-ring.addEventListener("change", ()=> displayStats(ring,"ringStats"))+
-feet.addEventListener("change", ()=> displayStats(feet,"feetStats"));+});
  
-// update race +// ===== DÉS ===== 
-raceSelect.addEventListener("change", ()=>{ +let ccDice = char.cc * 2; 
-resetSlots(); +let ctDice = char.ct * 2; 
-fillEquip();+let fmDice = char.fm * 2; 
 +let agiDice = char.agi * 2; 
 + 
 +// ===== VALEURS FINALES ===== 
 +let finalCC = ccDice + bestDex; 
 +let finalCT = ctDice + bestDex; 
 +let finalFM = fmDice + bestDex; 
 + 
 + 
 +// ===== CT DISTANCE ===== 
 +function getCTDistance(baseCT, distance){ 
 +if(distance <= 2) return baseCT; 
 +return baseCT - (distance - 2) * 3; 
 +
 + 
 +let ct1 = getCTDistance(finalCT,1); 
 +let ct2 = getCTDistance(finalCT,2); 
 +let ct3 = getCTDistance(finalCT,3); 
 +let ct4 = getCTDistance(finalCT,4); 
 + 
 + 
 +// ===== ESQUIVE ===== 
 + 
 +let checked = [...document.querySelectorAll(".skill:checked")].map(e=>e.dataset.name); 
 + 
 +// ===== ESQUIVE CAC ===== 
 +// règle : meilleur entre CC et AGI (dés)puis conversion jet + protection 
 + 
 +let esquiveCAC = Math.max(ccDice, agiDice)
 + 
 +// conversion en jet + protection 
 +esquiveCAC = esquiveCAC + bestProt; 
 + 
 + 
 +// ===== ESQUIVE TIR ===== 
 +// règle : 
 +// max entre : 
 +// (3/4 CC + 1/4 AGI) 
 +// (1/4 CC + 3/4 AGI) 
 +// arrondi inférieur → dés → ×2 → + protection 
 + 
 +let esquiveA = Math.floor(char.cc * 0.75 + char.agi * 0.25); 
 +let esquiveB = Math.floor(char.cc * 0.25 + char.agi * 0.75); 
 + 
 +// passifs qui remplacent le calcul 
 +let esquiveStat; 
 + 
 +if(checked.includes("Réflexes fulgurants")){ 
 +    esquiveStat = Math.floor(char.agi * 0.85 + char.cc * 0.15); 
 +
 +else if(checked.includes("Couverture")){ 
 +    esquiveStat = Math.floor(char.cc * 0.85 + char.agi * 0.15); 
 +
 +else{ 
 +    esquiveStat = Math.max(esquiveA, esquiveB); 
 +
 + 
 +// conversion en jet 
 +let esquiveTir = esquiveStat * 2; 
 + 
 +// protection (UNE seule fois) 
 +esquiveTir += bestProt; 
 + 
 +// bonus passifs 
 +if(checked.includes("Fulgurance")) 
 +    esquiveTir += 1; 
 + 
 + 
 +// ===== ESQUIVE MAGIQUE ===== 
 +// règle : FM en jet + protection 
 + 
 +let esquiveFM = fmDice + bestProt; 
 + 
 +// ===== MAGIE ===== 
 +let jetFM = finalFM; 
 + 
 +let sorts = ""; 
 +for(let lvl=1; lvl<=5; lvl++){ 
 +    let seuil = 6 + 6 * lvl; 
 +    let reussite jetFM >= seuil ? "✅" : "❌"; 
 + 
 +    sorts += `Niveau ${lvl} → Seuil ${seuil} | Jet ${jetFM} ${reussite}\n`; 
 +
 + 
 +// ===== AFFICHAGE ===== 
 +document.getElementById("spellBonus").innerText = 
 +"Bonus actifs → Dex: "+bestDex+" | Protection: "+bestProt
 + 
 +result.textContent = 
 + 
 +"===== PERSONNAGE =====\n"
 +"Race : "+raceSelect.value+ 
 + 
 +"\n\n===== STATS =====\n"
 +JSON.stringify(char,null,2)+ 
 + 
 +"\n\n===== JET OFFENSIF =====\n"
 +"\n⚔ CC : jet moyen "+ccDice+" ("+finalCC+")"
 +"\n🎯 CT : jet moyen "+ctDice+" ("+finalCT+")"
 +"\n✨ FM : jet moyen "+fmDice+" ("+finalFM+")"
 + 
 +"\n\n===== JET DEFENSIF =====\n"
 +"\n🛡 Esquive CAC : "+esquiveCAC+ 
 +"\n🏹 Esquive tir : "+esquiveTir+ 
 +"\n✨ Esquive FM : "+esquiveFM+ 
 + 
 +"\n\n===== JET DISTANCE =====\n"
 +"\n🎯 CT distances :"+ 
 +"\n1 case : "+ct1+ 
 +"\n2 cases : "+ct2+ 
 +"\n3 cases : "+ct3+ 
 +"\n4 cases : "+ct4+ 
 + 
 +"\n\n===== MAGIE =====\n"
 +"Jet FM : "+jetFM+"\n\n"
 +sorts; 
 +
 + 
 + 
 +// ===== AUTO ===== 
 +function autoCalculate(){ 
 +updateCost(); 
 +calculate(); 
 +
 + 
 +// events stats 
 +document.querySelectorAll("#stats input").forEach(i=>
 +i.addEventListener("input", autoCalculate);
 }); });
  
-// init+document.querySelectorAll(".spell").forEach(s=>
 +s.addEventListener("change", autoCalculate); 
 +}); 
 + 
 + 
 +// passifs 
 +document.querySelectorAll(".skill").forEach(s=>
 +s.addEventListener("change", autoCalculate); 
 +}); 
 + 
 + 
 + 
 +// ===== RESET ===== 
 +function resetAll(){ 
 + 
 +document.querySelectorAll("#stats input").forEach(i=>i.value=0); 
 +document.querySelectorAll(".spell").forEach(s=>s.checked=false); 
 + 
 +Object.values(slots).forEach(s=>
 +s.value=""; 
 +s.disabled=false; 
 +}); 
 + 
 +document.querySelectorAll(".skill").forEach(s=>s.checked=false); 
 resetSlots(); resetSlots();
 fillEquip(); fillEquip();
 +displayBase();
 +checkLimit();
 +autoCalculate();
 +}
  
 +// init
 +autoCalculate();
  
-// ===== AFFICHAGE BONUS ÉQUIPEMENT ===== 
-function displayStats(select, targetId){ 
-  let id = select.value; 
-  let target = document.getElementById(targetId); 
  
-  if(id===""){ 
-    target.innerHTML = ""; 
-    return; 
-  } 
  
-  let item equipments[id];+// ===== POUSSEE ===== 
 +function calculatePush(){
  
-  let txt "<span style='color:lightgreen'>";+let F=+pushF.value; 
 +let buff=+pushBuff.value;
  
-  for(let stat in item.stats){ +let E=+pushE.value; 
-    let val item.stats[stat];+let agi=+pushAgi.value; 
 +let pv=+pushPV.value;
  
-    if(val > 0)+let instability=+pushDebuff.value; 
-      txt += `+${val${stat.toUpperCase()} `+let stability=+pushStab.value; 
-    } else + 
-      txt +`<span style="color:#ff6b6b">${val${stat.toUpperCase()}</span> `;+let attaque=F+buff; 
 +let defense=Math.max(E+4,agi)+(pv/10)+instability+stability; 
 + 
 +pushResult.textContent= 
 +"Attaque: "+attaque+ 
 +"\nDéfense: "+defense.toFixed(1)+ 
 +"\n"+(attaque>=defense?"✅ PUSH":"❌ FAIL"); 
 +} 
 + 
 +// ===== EVENTS ===== 
 +document.querySelectorAll("input").forEach(i=>i.addEventListener("input",()=>{ 
 +updateCost(); calculate(); calculatePush(); 
 +})); 
 + 
 +document.querySelectorAll("select").forEach(i=>i.addEventListener("change",()=>
 +updateCost(); calculate(); calculatePush(); 
 +})); 
 + 
 + 
 +// ===== INIT ===== 
 +updateCost(); 
 +calculate(); 
 +calculatePush(); 
 + 
 + 
 +// ===================================== 
 +// ENCODE / DECODE 
 +// ===================================== 
 + 
 +function encodeBuild(data){ 
 +    return encodeURIComponent(JSON.stringify(data)); 
 +} 
 + 
 +function decodeBuild(code)
 +    return JSON.parse(decodeURIComponent(code)); 
 +
 + 
 +// ===================================== 
 +// BUILD DATA 
 +// ===================================== 
 + 
 +function getBuildData(){ 
 + 
 +    let data = { 
 +        race: raceSelect.value, 
 +        stats: {}
 +        equip: {}, 
 +        skills: [], 
 +        spells: [] 
 +    }; 
 + 
 +    // stats 
 +    for(let s in costTable){ 
 +        data.stats[s] = +document.getElementById(s).value || 0;
     }     }
-  } 
  
-  txt += "</span>";+    // équipements 
 +    Object.entries(slots).forEach(([k,s])=>
 +        data.equip[k] = s.value; 
 +    });
  
-  if(item.magicBlock){ +    // passifs 
-    txt += <span style='color:orange'>❗ No Magic</span>"; +    document.querySelectorAll(".skill:checked").forEach(s=>
-  }+        data.skills.push(s.dataset.name)
 +    });
  
-  target.innerHTML txt;+    // sorts 
 +    document.querySelectorAll(".spell:checked").forEach(s=>{ 
 +        data.spells.push({ 
 +            dex: +s.dataset.dex, 
 +            prot: +s.dataset.prot 
 +        }); 
 +    }); 
 + 
 +    return data;
 } }
  
 +// =====================================
 +// APPLY BUILD
 +// =====================================
  
-// ===== CALCUL ===== +function applyBuild(data){
-function calculate(){+
  
-let char = {...races[raceSelect.value]}; +    // race 
-let totalPI 0;+    raceSelect.value = data.race || "Elfe";
  
-// stats +    // recharge équipements race 
-for(let s in costTable){ +    resetSlots(); 
-let v = +document.getElementById(s).value || 0; +    fillEquip(); 
-char[s]+=v+ 
-let [b,m1,m2]=costTable[s]; +    // stats 
-totalPI += calcCost(b,m1,m2,v);+    for(let s in costTable){ 
 +        document.getElementById(s).value 
 +            data.stats?.[s] || 0; 
 +    } 
 +     
 +    // équipements 
 +    Object.entries(slots).forEach(([k,s])=>{ 
 +        s.value = data.equip?.[k] || ""
 +    }); 
 + 
 +    // refresh affichage bonus équipements 
 +    Object.entries(slots).forEach(([k,s])=>{ 
 +        displayStats(s, slotToStatId[k])
 +    }); 
 +     
 + 
 +    // passifs 
 +    document.querySelectorAll(".skill").forEach(s=>
 +        s.checked = 
 +            data.skills?.includes(s.dataset.name); 
 +    }); 
 + 
 +    // sorts 
 +    document.querySelectorAll(".spell").forEach(s=>
 + 
 +        s.checked = data.spells?.some(sp => 
 +            sp.dex == +s.dataset.dex && 
 +            sp.prot == +s.dataset.prot 
 +        ); 
 + 
 +    }); 
 + 
 +    displayBase(); 
 +    checkLimit(); 
 +    autoCalculate();
 } }
  
-// résumé +// ===================================== 
-let esquive Math.round(char.agi*0.75 + char.cc*0.25);+// EXPORT 
 +// =====================================
  
-result.textContent = +function exportBuild(){ 
-JSON.stringify(char,null,2+ 
-+ "\n\n💰 PI : "+totalPI +    let data = getBuildData()
-+ "\n⚔ CC : "+char.cc + 
-+ "\n🎯 CT 1-4 : "+char.ct+"/"+(char.ct-1)+"/"+(char.ct-2)+"/"+(char.ct-3) +    let code = encodeBuild(data);
-+ "\n✨ FM : "+char.fm +
-+ "\n🛡 Esquive tir : "+esquive;+
  
 +    prompt("Copie ton build :", code);
 } }
  
-</script>+// ===================================== 
 +// IMPORT 
 +// =====================================
  
 +function importBuild(codeInput){
 +
 +    let code = codeInput;
 +
 +    if(!code){
 +        code = prompt("Colle le code du build :");
 +    }
 +
 +    if(!code) return;
 +
 +    try{
 +
 +        code = code.trim();
 +
 +        let data = decodeBuild(code);
 +
 +        applyBuild(data);
 +
 +        alert("✅ Build importé");
 +
 +    }catch(e){
 +
 +        console.log(e);
 +
 +        alert("❌ Code invalide");
 +    }
 +}
 +
 +// =====================================
 +// LIEN PARTAGE
 +// =====================================
 +
 +function copyLink(){
 +
 +    let data = getBuildData();
 +
 +    let code = encodeBuild(data);
 +
 +    let url =
 +        window.location.origin +
 +        window.location.pathname +
 +        "?id=simulateur&build=" + code;
 +
 +    navigator.clipboard.writeText(url).then(()=>{
 +
 +        alert("🔗 Lien copié !");
 +
 +    }).catch(()=>{
 +
 +        prompt("Copie ce lien :", url);
 +
 +    });
 +}
 +
 +// =====================================
 +// AUTO LOAD URL
 +// =====================================
 +
 +(function(){
 +
 +    try{
 +
 +        let params =
 +            new URLSearchParams(window.location.search);
 +
 +        let code = params.get("build");
 +
 +        if(!code) return;
 +
 +        let data = decodeBuild(code);
 +
 +        applyBuild(data);
 +
 +        console.log("✅ Build URL chargé");
 +
 +    }catch(e){
 +
 +        console.log("Erreur chargement URL", e);
 +
 +    }
 +
 +})();
 +
 +</script>
 </body> </body>
 </html> </html>
simulateur.1777487437.txt.gz · Dernière modification : 2026/04/29 20:30 de nanaki