diff --git a/README.md b/README.md index 2b8aacf..bf1e087 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Known limits: boss mechanics, movement, deaths and crowd control are not simulat Protection Warrior, Protection Paladin, Blood Death Knight, Guardian Druid, Brewmaster Monk and Vengeance Demon Hunter are detected on import. Every mode (Quick Sim, Enchant Lab, Gear Compare, Upgrade Finder, Crest Planner, Great Vault and Talent Search) then ranks results on DPS and survival. -**Tank Sim** has its own place in the menu: the imported tank against a boss and its adds, one simulation per fight you tick (the boss alone, the boss with 2 adds, the boss with 4 adds), with the numbers a tank cares about: damage taken per second and as a share of your health, healing and absorbs, how much of the fight you survive, how often you die, and your DPS. It needs a tank specialization. +**Tank Sim** has its own place in the menu: the imported tank against a boss and its adds, one simulation per fight you tick (the boss alone, the boss with 2 adds, the boss with 4 adds), with the numbers a tank cares about: damage taken per second and as a share of your health, healing and absorbs, how much of the fight you survive, how often you die, and your DPS. It needs a tank specialization. Tank Sim can also **find gear upgrades** (on by default): Upgrade Finder's sources appear in the same mode, and every item from them is simulated in its slot in each fight you ticked and the best ones again at full precision, ranked on the survival and damage score. Each fight in the result shows what your current gear takes per second, how much of the fight it survives and how often it dies, then the upgrades for that fight. The same upgrades can be run from Upgrade Finder itself with the same number of targets. SimC's built-in tank dummy uses level-70 damage values and never threatens a Midnight tank, and SimC gives players infinite health by default. SimC Lab replaces it with a calibrated boss, tuned once per job against the imported gear and then shared by every variant and scenario: diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 2dc17bc..6c248fc 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -1,12 +1,12 @@ { "name": "simc-lab", - "version": "1.42.0", + "version": "1.43.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simc-lab", - "version": "1.42.0", + "version": "1.43.0", "license": "GPL-3.0-or-later", "dependencies": { "electron-updater": "^6.8.9" diff --git a/desktop/package.json b/desktop/package.json index 5d3b116..ace1f02 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -2,7 +2,7 @@ "name": "simc-lab", "productName": "SimC Lab", "desktopName": "simc-lab.desktop", - "version": "1.42.0", + "version": "1.43.0", "description": "Local SimulationCraft workbench with upgrade finder and tank simulation.", "author": "Roburmaster", "private": true, diff --git a/package-lock.json b/package-lock.json index 131404d..26fabda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simc-lab", - "version": "1.42.0", + "version": "1.43.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simc-lab", - "version": "1.42.0", + "version": "1.43.0", "license": "GPL-3.0-or-later", "devDependencies": { "luaparse": "^0.3.1", diff --git a/package.json b/package.json index 22bec17..2a6cb51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simc-lab", - "version": "1.42.0", + "version": "1.43.0", "private": true, "type": "module", "scripts": { diff --git a/public/app.js b/public/app.js index f88d096..12629e8 100644 --- a/public/app.js +++ b/public/app.js @@ -30,7 +30,7 @@ let variants=[{name:'Alternative 1',text:''}]; async function api(url,data){const res=await fetch(url,data===undefined?{}:{method:'POST',headers:{'Content-Type':'application/json','X-SimC-Token':token},body:JSON.stringify(data)});const result=await res.json();if(!res.ok)throw new Error(result.error||'The request failed.');return result;} function notice(message){$('#notice').hidden=!message;$('#notice').textContent=message||'';} function safeStore(key,value){try{localStorage.setItem(key,value);}catch{}} -async function setMode(value){mode=value;$$('.nav').forEach(b=>b.classList.toggle('active',b.dataset.mode===value));$('#page-title').innerHTML=`${esc(modes[value][0])}.`;$('#breadcrumb').textContent=modes[value][0];$('#page-description').textContent=modes[value][1];$('#workspace').hidden=['history','wow','logs'].includes(value);$('#history').hidden=value!=='history';$('#logs-panel').hidden=value!=='logs';if(value==='logs')logs.show();$('#wow-panel').hidden=value!=='wow';$('#enchant-panel').hidden=value!=='enchants';$('#compare-panel').hidden=value!=='compare';$('#quick-info').hidden=value!=='quick';$('#talent-panel').hidden=value!=='talents';$('#upgrade-panel').hidden=value!=='upgrades';$('#tanksim-panel').hidden=value!=='tank';$('#crest-panel').hidden=value!=='crests';$('#vault-panel').hidden=value!=='vault';$('#weapon-panel').hidden=value!=='weapons';$('#trinket-panel').hidden=value!=='trinkets'; +async function setMode(value){mode=value;$$('.nav').forEach(b=>b.classList.toggle('active',b.dataset.mode===value));$('#page-title').innerHTML=`${esc(modes[value][0])}.`;$('#breadcrumb').textContent=modes[value][0];$('#page-description').textContent=modes[value][1];$('#workspace').hidden=['history','wow','logs'].includes(value);$('#history').hidden=value!=='history';$('#logs-panel').hidden=value!=='logs';if(value==='logs')logs.show();$('#wow-panel').hidden=value!=='wow';$('#enchant-panel').hidden=value!=='enchants';$('#compare-panel').hidden=value!=='compare';$('#quick-info').hidden=value!=='quick';$('#talent-panel').hidden=value!=='talents';$('#tanksim-panel').hidden=value!=='tank';syncUpgradePanel(value);$('#crest-panel').hidden=value!=='crests';$('#vault-panel').hidden=value!=='vault';$('#weapon-panel').hidden=value!=='weapons';$('#trinket-panel').hidden=value!=='trinkets'; // Weapon Lab and Trinket Lab run on SimC's reference profiles, so they hide the character panel and always offer tank settings. const lab=['weapons','trinkets'].includes(value); // Trinket Lab carries every setting it uses in its own panel, so the rest of the app's settings are out of sight. @@ -61,8 +61,11 @@ function renderVariants(){$('#variants').innerHTML=variants.map((v,i)=>`
Your tank against a boss and its adds. Everything that stands next to the boss swings at you for the whole fight, healers top you up on a rhythm, and you can die. Pick the fights to run; each is one simulation. The boss is tuned to your own gear once, under Tank survival below, where you also set how hard the adds hit.
To compare gear in the same fights, use Gear Compare or Upgrade Finder with the same number of targets: every target beyond the first is a mob that hits you there as well. Dungeon Slice, Hectic Add Cleave, the Mythic+ route and the dummies have no mobs that attack a tank, so they are not offered for tanks.
Your tank against a boss and its adds. Everything that stands next to the boss swings at you for the whole fight, healers top you up on a rhythm, and you can die. Pick the fights to run; each is one simulation. The boss is tuned to your own gear once, under Tank survival below, where you also set how hard the adds hit.
Dungeon Slice, Hectic Add Cleave, the Mythic+ route and the dummies have no mobs that attack a tank, so they are not offered for tanks.
Current gear: ${number(base.dps)} DPS${base.error95!==null?` ± ${number(base.error95)}`:''}
`; + html+=`Current gear: ${number(base.dps)} DPS${base.error95!==null?` ± ${number(base.error95)}`:''}${base.tank?` · takes ${number(base.tank.dtps)} damage per second${base.tank.health?` (${(100*base.tank.dtps/base.tank.health).toFixed(1)}% of your health)`:''}, survives ${(100*base.tank.alive).toFixed(0)}% of the fight${base.tank.deaths!==undefined?`, dies in ${(100*base.tank.deaths).toFixed(0)}% of fights`:''}`:''}
`; html+=slotFilterBar(measured); if(slotFilter!=='all')html+=`${esc(familyNames[slotFilter]||slotFilter)}: every item measured for this slot, best first.${rows.length?'':' Nothing was measured for it in this job.'}
`; const stageName=n=>n===1?'Screening':n===3?'Embellishment pairs':'Final round';