Outils pour utilisateurs

Outils du site


brouillon3

Différences

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

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
brouillon3 [2026/04/21 17:20] nanakibrouillon3 [2026/05/12 13:53] (Version actuelle) nanaki
Ligne 1: Ligne 1:
 +Voici un simulateur en version beta, n'hésitez pas à faire vos remarques sur le discord ou le forum.
 +
 +
 <html> <html>
 +<head>
 +<meta charset="UTF-8">
 +<title>Simulateur RPG</title>
  
 <style> <style>
-body { font-family: Arial, sans-serif; margin: 10px; }+body { 
 +  font-family: Arial
 +  background:#0b1a13; 
 +  color:#7dd3fc; 
 +  padding:10px; 
 +
 +h1,h2 { color:gold;
 +.box { border:1px solid gold; padding:10px; margin-bottom:10px
 +select { margin:3px; max-width:150px;
 +input { margin:3px; width:60px; } 
 +.statLine { display:flex; justify-content:space-between; max-width:260px;
 +.result { background:#000; color:#00ffcc; padding:10px; white-space:pre-wrap;
 +.small { font-size:12px; color:#aaa; } 
 +</style> 
 +</head>
  
-.controls { display:flex; gap:10px; margin-bottom:10px; flex-wrap:wrap; }+<body>
  
-#pointsBox { font-weight:bold; margin-bottom:10px; }+<h1>🎲 Simulateur</h1>
  
-table { width:100%; border-collapse:collapse; } +<div class="box"> 
-th, td { border:1px solid #ccc; padding:6px; text-align:left; } +Race 
-th { background:#f2f2f2; cursor:pointer; } +<select id="race"></select> 
-tr:nth-child(even){ background:#fafafa; }+<h3>Stats de base</h3> 
 +<pre id="baseStats"></pre> 
 +</div>
  
-.offensif { background:#ffe5e5; } +<div class="box"> 
-.defensif { background:#e5f0ff; } +<h3>Améliorations</h3> 
-.utilitaire { background:#e5ffe5; }+<div id="stats"></div> 
 +<div id="cost"></div> 
 +</div>
  
-.lvl { padding:2px 6px; border-radius:4px; } +<div class="box"> 
-.lvl-{ background:#ddd; } +<h3>Équipement (3 max + anneau)</h3> 
-.lvl-2 { background:#cce5ff} +Tête <select id="head"></select> <span id="headStats"></span><br> 
-</style>+Cou <select id="neck"></select> <span id="neckStats"></span><br> 
 +Dos <select id="cape"></select> <span id="capeStats"></span><br> 
 +Main droite <select id="rightHand"></select> <span id="rightStats"></span><br> 
 +Main gauche <select id="leftHand"></select> <span id="leftStats"></span><br> 
 +Corps <select id="body"></select> <span id="bodyStats"></span><br> 
 +Anneau <select id="ring"></select> <span id="ringStats"></span><br> 
 +Pieds <select id="feet"></select> <span id="feetStats"></span> 
 +<div id="limitWarn" style="color:red;"></div> 
 +</div>
  
-<div id="pointsBox"> +<div class="box"> 
-Points utilisés : <span id="points">0</span/ 15+<h3>Passifs</h3> 
 +<div id="skills"></div>
 </div> </div>
  
-<div class="controls"> +<div class="box"> 
-<select id="raceFilter"+<h3>Sorts actifs</h3
-<option value="Toutes">Toutes</option+<div id="spells"></div
-<option value="Communs">Communs</option+<div id="spellBonus" class="small"></div
-</select>+</div>
  
-<select id="typeFilter"> 
-<option value="Tous">Tous</option> 
-<option value="CAC">CAC</option> 
-<option value="Distance">Distance</option> 
-<option value="Sort">Sort</option> 
-</select> 
  
-<input type="text" id="search" placeholder="Rechercher..."> 
-</div> 
  
-<table id="table"> +<!-- ================= POUSSEE ================= --
-<thead> +<div class="box"> 
-<tr> +<h3>Simulateur de poussée</h3>
-<th onclick="sortTable(0)">Choix</th> +
-<th onclick="sortTable(1)">Race</th> +
-<th onclick="sortTable(2)">Nom</th> +
-<th onclick="sortTable(3)">Type</th> +
-<th>Effet</th+
-<th onclick="sortTable(5)">Niveau</th+
-</tr> +
-</thead>+
  
-<tbody>+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>
  
-<tr class="Communs offensifdata-type="CACdata-level="1"> +Endurance <input id="pushE" type="numbervalue="5"> 
-<td><input type="checkboxclass="skill"></td> +Agilité <input id="pushAgitype="numbervalue="5"><br>
-<td>Communs</td> +
-<td>Attaque précise</td> +
-<td>CAC</td> +
-<td>+4 toucher, -3 dégâts</td> +
-<td><span class="lvl lvl-1">1</span></td> +
-</tr>+
  
-<tr class="Communs offensifdata-type="Distancedata-level="1"> +PV actuels <input id="pushPV" type="numbervalue="50"><br>
-<td><input type="checkbox" class="skill"></td> +
-<td>Communs</td> +
-<td>Arme improvisée</td> +
-<td>Distance</td> +
-<td>-4 toucher, -2 dégâts</td> +
-<td><span class="lvl lvl-1">1</span></td> +
-</tr>+
  
-<tr class="Communs utilitairedata-type="Sort" data-level="1"> +Instabilité <select id="pushDebuff"><option value="0">0</option><option value="-6">-6</option></select
-<td><input type="checkbox" class="skill"></td> +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>
-<td>Communs</td+
-<td>Arcane ajustée</td> +
-<td>Sort</td> +
-<td>Avantage au toucher</td> +
-<td><span class="lvl lvl-1">1</span></td> +
-</tr>+
  
-</tbody+<pre id="pushResult" class="result"></pre
-</table>+</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>
  
 <script> <script>
-const maxPoints = 15; 
  
-const raceFilter document.getElementById("raceFilter"); +// ===== RACES ===== 
-const typeFilter document.getElementById("typeFilter"); +const races 
-const search document.getElementById("search"); +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}, 
-const rows document.querySelectorAll("#table tbody tr"); +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}, 
-const checkboxes document.querySelectorAll(".skill"); +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}, 
-const pointsDisplay document.getElementById("points");+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:{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} 
 +};
  
-function updatePoints(e){ +// ===== COST ===== 
-  let total 0;+const costTable 
 +a:[800,200,100], 
 +cc:[100,50,30], ct:[110,50,30], 
 +f:[120,55,30], e:[120,55,30], 
 +agi:[95,45,25], 
 +m:[110,55,35], 
 +fm:[100,50,30], 
 +p:[110,85,78], 
 +pv:[4,2,1], 
 +pm:[5,3,1], 
 +mvt:[100,50,30], 
 +r:[40,30,15], 
 +rm:[50,40,20] 
 +};
  
-  checkboxes.forEach(cb =+// ===== PASSIFS ===== 
-    if(cb.checked) total++; +const skills = [ 
-  });+{name:"Fulgurance",desc:"+1 toucher au cac + 1 esquive tous les 5 mouvements"}, 
 +{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"} 
 +];
  
-  if(total > maxPoints){ +// ===== SORTS ===== 
-    e.target.checked false; +const spells [ 
-    return; +{name:"Peau de granit",dex:0,prot:2}, 
-  }+{name:"Coup précis",dex:2,prot:0},
  
-  pointsDisplay.textContent = total;+{name:"Maladresse",dex:-2,prot:0}, 
 +{name:"Vulnérabilité",dex:0,prot:-2},
  
-  if(total >maxPoints)+{name:"Puissance de la nature",dex:2,prot:2}, 
-    checkboxes.forEach(cb =+ 
-      if(!cb.checked) cb.disabled = true; +{name:"Aide",dex:4,prot:0}, 
-    }); +{name:"Malchance",dex:-4,prot:0}, 
-  else + 
-    checkboxes.forEach(cb => cb.disabled false); +{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 
 + 
 +/* ================= COMMUNS ================= */ 
 + 
 +{name:"Arc",race:"all",slot:"weapon2h",stats:{cc:-1,ct:1}}, 
 +{name:"Armure matelassée",race:"all",slot:"body",stats:{e:1,agi:-2}}, 
 +{name:"Bâton de marche",race:"all",slot:"weapon",stats:{mvt:1}}, 
 +{name:"Bottes de marche",race:"all",slot:"feet",stats:{mvt:1}}, 
 +{name:"Bouclier Parma",race:"all",slot:"offhand",stats:{e:1,agi:-2}}, 
 +{name:"Fustibale",race:"all",slot:"weapon",stats:{cc:-2,ct:1}}, 
 +{name:"Gladius",race:"all",slot:"weapon",stats:{cc:1}}, 
 +{name:"Sceptre",race:"all",slot:"weapon",stats:{fm:1}}, 
 +{name:"Lance",race:"all",slot:"weapon",stats:{ct:1,cc:-1}}, 
 +{name:"Targe",race:"all",slot:"offhand",stats:{cc:1,agi:-1}}, 
 +{name:"Visière",race:"all",slot:"head",stats:{p:1,cc:-1,fm:-1}}, 
 +{name:"Cape de chasse",race:"all",slot:"cape",stats:{ct:1}}, 
 +{name:"Collier tressé",race:"all",slot:"neck",stats:{pv:5,pm:5}}, 
 +{name:"Couronne d'Adonis",race:"all",slot:"head",stats:{rm:1}}, 
 +{name:"Ceinture",race:"all",slot:"body",stats:{r:1}}, 
 +{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}}, 
 + 
 +/* ===== 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 ================= */ 
 + 
 +{name:"Bâton du Pèlerin",race:"all",slot:"weapon",stats:{mvt:1,fm:1}}, 
 +{name:"Bottes Tâlroval",race:"all",slot:"feet",stats:{mvt:2}}, 
 +{name:"Cuirasse",race:"all",slot:"body",stats:{e:1,r:2}}, 
 +{name:"Lance de garde Sylvestre",race:"all",slot:"weapon",stats:{cc:1,f:1}}, 
 +{name:"Sceptre de mage",race:"all",slot:"weapon2h",stats:{m:1,fm:1}}, 
 +{name:"Arc Ensorcelé",race:"all",slot:"weapon2h",stats:{ct:1,rm:2,cc:-1}}, 
 +{name:"Armure de garde Sylvestre",race:"all",slot:"body",stats:{e:2,mvt:-1},magicBlock:true}, 
 +{name:"Toge de feuillage",race:"all",slot:"body",stats:{fm:1,agi:1,pv:5}}, 
 +{name:"Marque Forestière",race:"all",slot:"neck",stats:{pm:10,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 ================= */ 
 + 
 +{name:"Cotte de mailles",race:"all",slot:"body",stats:{e:2,agi:-4}}, 
 +{name:"Labrys",race:"all",slot:"weapon",stats:{cc:1,f:1}}, 
 +{name:"Marteau de guerre",race:"all",slot:"weapon2h",stats:{f:2,cc:-1}}, 
 +{name:"Targe renforcée naine",race:"all",slot:"offhand",stats:{cc:1,pv:10}}, 
 +{name:"Armure runique",race:"all",slot:"body",stats:{e:1,pm:8}}, 
 +{name:"Cape dorée",race:"all",slot:"cape",stats:{m:1,fm:1},magicBlock:true}, 
 +{name:"Solerets",race:"all",slot:"feet",stats:{mvt:1,pm:5,pv:5}}, 
 +{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 ================= */ 
 + 
 +{name:"Bouclier Clipeus",race:"all",slot:"offhand",stats:{e:1,pv:10}}, 
 +{name:"Masse d'arme",race:"all",slot:"weapon",stats:{cc:1,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:"Javelot lourd",race:"all",slot:"weapon",stats:{f:2,ct:-1,cc:-1}}, 
 +{name:"Broigne",race:"all",slot:"body",stats:{e:2,f:-1}}, 
 +{name:"Fétiche de shaman",race:"all",slot:"offhand",stats:{r:2,pm:5}}, 
 +{name:"Bottes incandescentes",race:"all",slot:"feet",stats:{mvt:1,r:1,pv:5}}, 
 +{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 ================= */ 
 + 
 +{name:"Bâton de sage",race:"all",slot:"weapon2h",stats:{m:1,agi:1}}, 
 +{name:"Orbe de mana",race:"all",slot:"weapon2h",stats:{m:1,rm:1}}, 
 +{name:"Cape de mage",race:"all",slot:"cape",stats:{fm:1,rm:1}}, 
 +{name:"Armure de fourrure",race:"all",slot:"body",stats:{e:2,rm:-3}}, 
 +{name:"Ceste",race:"all",slot:"weapon",stats:{f:1,cc:1}}, 
 +{name:"Collier d'apaisement",race:"all",slot:"neck",stats:{pv:10,pm:5}}, 
 +{name:"Bottes de voleur",race:"all",slot:"feet",stats:{mvt:1,agi:1}}, 
 +{name:"Sarbacane",race:"all",slot:"weapon",stats:{ct:1,f:10,cc:-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 ================= */ 
 + 
 +{name:"Armure hoplitique",race:"all",slot:"body",stats:{e:1,m:1}}, 
 +{name:"Bouclier Ancile",race:"all",slot:"offhand",stats:{e:1}}, 
 +{name:"Diadème",race:"all",slot:"head",stats:{r:1,rm:2}}, 
 +{name:"Pilum",race:"all",slot:"weapon",stats:{ct:1,f:1,cc:-1}}, 
 +{name:"Spatha",race:"all",slot:"weapon",stats:{cc:1,f:1}}, 
 +{name:"Sceptre de puissance",race:"all",slot:"weapon",stats:{m:1,pm:8}}, 
 +{name:"Lame Sainte",race:"all",slot:"weapon",stats:{cc:1,m:1}}, 
 +{name:"Tiare d'Oracle",race:"all",slot:"head",stats:{fm:1,p: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 ================= */ 
 + 
 +{name:"Anneau Caprice",race:"all",slot:"ring",stats:{fm:1}}, 
 +{name:"Anneau Férocité",race:"all",slot:"ring",stats:{f:1}}, 
 +{name:"Anneau Horizon",race:"all",slot:"ring",stats:{p:1}}, 
 +{name:"Anneau Prétention",race:"all",slot:"ring",stats:{ct:1}}, 
 +{name:"Anneau Souplesse",race:"all",slot:"ring",stats:{agi:1}}, 
 +{name:"Anneau Tenacité",race:"all",slot:"ring",stats:{e:1}}, 
 +{name:"Anneau Puissance",race:"all",slot:"ring",stats:{m:1}}, 
 +{name:"Anneau Finesse",race:"all",slot:"ring",stats:{cc:1}} 
 + 
 +]; 
 + 
 + 
 +// ===== INIT ===== 
 +const raceSelect = document.getElementById("race"); 
 +const statsDiv document.getElementById("stats"); 
 +const baseDiv document.getElementById("baseStats"); 
 + 
 +for(let r in races){ 
 +raceSelect.innerHTML += `<option>${r}</option>`; 
 +
 + 
 +// stats UI 
 +for(let s in costTable){ 
 +statsDiv.innerHTML += ` 
 +<div class="statLine"> 
 +<span>${s}</span> 
 +<input type="number" id="${s}" value="0" min="0"> 
 +</div>`;
 } }
  
-checkboxes.forEach(cb => { +// passifs 
-  cb.addEventListener("change", updatePoints);+skills.forEach(s=>{ 
 +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>`;
 }); });
  
-function filter(){ 
-  const term = search.value.toLowerCase(); 
  
-  rows.forEach(row=>{ +// affichage sorts 
-    const raceOk = raceFilter.value==="Toutes|| row.classList.contains(raceFilter.value); +spells.forEach(s=>{ 
-    const typeOk typeFilter.value==="Tous" || row.dataset.type===typeFilter.value+document.getElementById("spells").innerHTML +
-    const textOk = row.innerText.toLowerCase().includes(term);+<label> 
 +<input type="checkbox" class="spelldata-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>`
 +});
  
-    row.style.display = (raceOk && typeOk && textOk? "" : "none"; +// base stats 
-  });+function displayBase(){ 
 +baseDiv.textContent = JSON.stringify(races[raceSelect.value],null,2);
 } }
 +raceSelect.addEventListener("change", displayBase);
 +displayBase();
  
-raceFilter.onchange filter+// ===== COST ===== 
-typeFilter.onchange filter+function calcCost(b,m1,m2,n){ 
-search.oninput filter;+let total=0,last=b
 +for(let i=1;i<=n;i++){ 
 +if(i===1) last=b; 
 +else if(i<=3) last+=m1; 
 +else last+=m2; 
 +total+=last; 
 +
 +return total; 
 +}
  
-function sortTable(col){ +function updateCost(){ 
-  const tbody = document.querySelector("#table tbody"); +let total=0; 
-  const rowsArr Array.from(tbody.rows);+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; 
 +}
  
-  rowsArr.sort((a,b)=>a.cells[col].innerText.localeCompare( +// ===== SLOTS ====
-    b.cells[col].innerText, +const slots = { 
-    undefined+head:head,neck:neck,cape:cape,rightHand:rightHand
-    {numeric:true} +leftHand:leftHand,body:body,ring:ring,feet:feet 
-  ));+};
  
-  rowsArr.forEach(r=>tbody.appendChild(r));+function resetSlots(){ 
 +Object.values(slots).forEach(s=>s.innerHTML="<option value=''>--</option>");
 } }
-</script> 
  
-</html>+function fillEquip(){ 
 +let r=raceSelect.value;
  
 +equipments.forEach((e,i)=>{
 +if(e.race!=="all"&&e.race!==r) return;
  
 +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>`;
 +});
 +}
  
 +raceSelect.addEventListener("change",()=>{
 +resetSlots();
 +fillEquip();
 +autoCalculate();
 +});
  
-<html>+resetSlots(); 
 +fillEquip();
  
-<style> +// ===== BONUS ===== 
-table width:100%border-collapse:collapse; } +function displayStats(select,id){ 
-th, td border:1px solid #cccpadding:6px; }+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; 
 +}
  
-tr[data-effect="offensif"] { background:#ffe5e5; } +const slotToStatId 
-tr[data-effect="defensif"] { background:#e5f0ff; } +head:"headStats"
-tr[data-effect="malediction"] { background:#eadcff; }+neck:"neckStats", 
 +cape:"capeStats"
 +rightHand:"rightStats", 
 +leftHand:"leftStats"
 +body:"bodyStats", 
 +ring:"ringStats", 
 +feet:"feetStats" 
 +};
  
-th background:#f2f2f2; } +Object.entries(slots).forEach(([k,s])=>{ 
-</style>+s.addEventListener("change",()=>
 +displayStats(s, slotToStatId[k]); 
 +checkLimit(); 
 +autoCalculate(); 
 +}); 
 +});
  
-<table>+// ===== LIMIT ===== 
 +function checkLimit(){ 
 +let count=0; 
 +Object.entries(slots).forEach(([k,s])=>
 +if(k!=="ring"&&s.value!=="") count++; 
 +});
  
-<thead+if(count>=3){ 
-<tr> +limitWarn.innerText="Limite atteinte"; 
-<th>Race</th+Object.entries(slots).forEach(([k,s])=>{ 
-<th>Nom</th> +if(k!=="ring"&&s.value==="") s.disabled=true; 
-<th>Type</th> +}); 
-<th>Coût</th> +}else{ 
-<th>Effet</th> +limitWarn.innerText=""; 
-<th>Niveau</th+Object.values(slots).forEach(s=>s.disabled=false); 
-</tr> +} 
-</thead>+}
  
-<tbody>+// ===== CALCUL =====
  
-<!-- 🔥 OFFENSIF --> +function calculate(){
-<tr class="Communs" data-type="Sort" data-effect="offensif"> +
-<td>Communs</td> +
-<td>Boule de feu</td> +
-<td>Sort</td> +
-<td>6 PM</td> +
-<td>Dégâts de feu + brûlure</td> +
-<td>1</td> +
-</tr>+
  
-<!-- 🛡 DÉFENSIF --> +let activeSpells [...document.querySelectorAll(".spell:checked")];
-<tr class="Communsdata-type="Sort" data-effect="defensif"> +
-<td>Communs</td> +
-<td>Entraide</td> +
-<td>Sort</td> +
-<td>5 PM</td> +
-<td>Soigne un allié et lui donne un bonus défensif</td> +
-<td>1</td> +
-</tr>+
  
-<!-- ☠️ MALÉDICTION --> +let bestDex = 0; 
-<tr class="Communs" data-type="Sort" data-effect="malediction"> +let bestProt 0;
-<td>Communs</td> +
-<td>Maudit</td> +
-<td>Sort</td> +
-<td>6 PM</td> +
-<td>Réduit les capacités de la cible</td> +
-<td>1</td> +
-</tr>+
  
-</tbody>+activeSpells.forEach(s=>
 +let d = +s.dataset.dex; 
 +let p = +s.dataset.prot;
  
-</table>+if(Math.abs(d) Math.abs(bestDex)) bestDex = d; 
 +if(Math.abs(p) > Math.abs(bestProt)) bestProt = p; 
 +});
  
-</html>+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;
 +}
 +
 +// equip
 +Object.values(slots).forEach(sel=>{
 +if(sel.value==="") return;
 +let e=equipments[sel.value];
 +for(let s in e.stats){
 +char[s]=(char[s]||0)+e.stats[s];
 +}
 +});
 +
 +// ===== DÉS =====
 +let ccDice = char.cc * 2;
 +let ctDice = char.ct * 2;
 +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);
 +});
 +
 +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();
 +fillEquip();
 +displayBase();
 +checkLimit();
 +autoCalculate();
 +}
 +
 +// init
 +autoCalculate();
 +
 +
 +
 +// ===== POUSSEE =====
 +function calculatePush(){
 +
 +let F=+pushF.value;
 +let buff=+pushBuff.value;
 +
 +let E=+pushE.value;
 +let agi=+pushAgi.value;
 +let pv=+pushPV.value;
 +
 +let instability=+pushDebuff.value;
 +let stability=+pushStab.value;
 +
 +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;
 +    }
 +
 +    // équipements
 +    Object.entries(slots).forEach(([k,s])=>{
 +        data.equip[k] = s.value;
 +    });
 +
 +    // passifs
 +    document.querySelectorAll(".skill:checked").forEach(s=>{
 +        data.skills.push(s.dataset.name);
 +    });
 +
 +    // sorts
 +    document.querySelectorAll(".spell:checked").forEach(s=>{
 +        data.spells.push({
 +            dex: +s.dataset.dex,
 +            prot: +s.dataset.prot
 +        });
 +    });
 +
 +    return data;
 +}
 +
 +// =====================================
 +// APPLY BUILD
 +// =====================================
 +
 +function applyBuild(data){
 +
 +    // race
 +    raceSelect.value = data.race || "Elfe";
 +
 +    // recharge équipements race
 +    resetSlots();
 +    fillEquip();
 +
 +    // stats
 +    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] || "";
 +    });
 +
 +    // 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();
 +}
 +
 +// =====================================
 +// EXPORT
 +// =====================================
 +
 +function exportBuild(){
 +
 +    let data = getBuildData();
 +
 +    let code = encodeBuild(data);
 +
 +    prompt("Copie ton build :", code);
 +}
 +
 +// =====================================
 +// 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>
 +</html>
brouillon3.1776784831.txt.gz · Dernière modification : 2026/04/21 17:20 de nanaki