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

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
simulateur [2026/04/30 10:47] nanakisimulateur [2026/05/20 16:46] (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> <head>
Ligne 8: 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:#000; +
-  color:#00ffcc; +
-  padding:10px; +
-  white-space:pre-wrap; +
-} +
- +
-.small { +
-  font-size:12px; +
-  color:#aaa; +
-}+
 </style> </style>
 </head> </head>
Ligne 45: 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 63: 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 72: 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 79: Ligne 57:
 <h3>Passifs</h3> <h3>Passifs</h3>
 <div id="skills"></div> <div id="skills"></div>
 +</div>
 +
 +<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> </div>
  
 <button onclick="resetAll()">Reset</button> <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 89: 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 113: Ligne 122:
 // ===== PASSIFS ===== // ===== PASSIFS =====
 const skills = [ const skills = [
-{name:"Fulgurance",desc:"+1 esquive"},+{name:"Fulgurance",desc:"+1 toucher au cac + 1 esquive tous les 5 mouvements"},
 {name:"Couverture",desc:"85% CC / 15% AGI"}, {name:"Couverture",desc:"85% CC / 15% AGI"},
 {name:"Réflexes fulgurants",desc:"85% AGI / 15% CC"}, {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"} {name:"Anguille",desc:"Avantage esquive"}
 ]; ];
 +
 +// ===== 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 237: Ligne 276:
  
 ]; ];
 +
  
 // ===== INIT ===== // ===== INIT =====
Ligne 263: Ligne 303:
 <b>${s.name}</b><br> <b>${s.name}</b><br>
 <span class="small">${s.desc}</span> <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>`; </label><br>`;
 }); });
Ligne 383: Ligne 434:
  
 // ===== CALCUL ===== // ===== CALCUL =====
 +
 function calculate(){ 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]}; let char={...races[raceSelect.value]};
Ligne 402: Ligne 467:
 }); });
  
-// esquive +// ===== DÉS ===== 
-let esquive=Math.round(char.agi*0.75+char.cc*0.25);+let ccDice = char.cc 2; 
 +let ctDice = char.ct * 2; 
 +let fmDice = char.fm 2; 
 +let agiDice = char.agi * 2;
  
-let checked=[...document.querySelectorAll(".skill:checked")].map(e=>e.dataset.name);+// ===== VALEURS FINALES ===== 
 +let finalCC ccDice + bestDex; 
 +let finalCT = ctDice + bestDex; 
 +let finalFM fmDice + bestDex;
  
-if(checked.includes("Réflexes fulgurants")) 
-esquive=Math.round(char.agi*0.85+char.cc*0.15); 
  
-if(checked.includes("Couverture")+// ===== CT DISTANCE ===== 
-esquive=Math.round(char.cc*0.85+char.agi*0.15);+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")) if(checked.includes("Fulgurance"))
-esquive+=1;+    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;
  
-// affichage 
 result.textContent = result.textContent =
-"===== STATS =====\n"++ 
 +"===== PERSONNAGE =====\n"
 +"Race : "+raceSelect.value+ 
 + 
 +"\n\n===== STATS =====\n"+
 JSON.stringify(char,null,2)+ JSON.stringify(char,null,2)+
-"\n\n⚔ CC : "+char.cc+ 
-"\n🎯 CT : "+char.ct+"\n\n===== JET OFFENSIF =====\n"
-"\n✨ FM : "+char.fm+"\n⚔ CC : jet moyen "+ccDice+" ("+finalCC+")"
-"\n🛡 Esquive : "+esquive;+"\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 ===== // ===== AUTO =====
Ligne 436: Ligne 599:
 i.addEventListener("input", autoCalculate); i.addEventListener("input", autoCalculate);
 }); });
 +
 +document.querySelectorAll(".spell").forEach(s=>{
 +s.addEventListener("change", autoCalculate);
 +});
 +
  
 // passifs // passifs
Ligne 441: Ligne 609:
 s.addEventListener("change", autoCalculate); s.addEventListener("change", autoCalculate);
 }); });
 +
 +
  
 // ===== RESET ===== // ===== RESET =====
Ligne 446: Ligne 616:
  
 document.querySelectorAll("#stats input").forEach(i=>i.value=0); document.querySelectorAll("#stats input").forEach(i=>i.value=0);
 +document.querySelectorAll(".spell").forEach(s=>s.checked=false);
  
 Object.values(slots).forEach(s=>{ Object.values(slots).forEach(s=>{
Ligne 463: Ligne 634:
 // init // init
 autoCalculate(); 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] || "";
 +    });
 +
 +    // 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();
 +}
 +
 +// =====================================
 +// 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> </script>
 </body> </body>
 </html> </html>
simulateur.1777538856.txt.gz · Dernière modification : 2026/04/30 10:47 de nanaki