diff --git a/web/static/saga.js b/web/static/saga.js index 078c340..f8484d8 100644 --- a/web/static/saga.js +++ b/web/static/saga.js @@ -317,15 +317,40 @@ const $enemyInfoModal = $('#enemy-info-modal'); if ($enemyInfoModal.length) { - $table.find('tbody.data td').on('click', (e) => { - const rowData = $(e.target).closest('tr').data(); + let rowData; + let shouldApplyRunes = false; + $enemyInfoModal.find('.apply-runes-form input[type="checkbox"]').on('change', (e) => { + shouldApplyRunes = e.target.checked; + + if (rowData) { + if (shouldApplyRunes) { + rowData.guard /= 2; + rowData.power /= 2; + rowData.speed /= 2; + rowData.hp /= 2; + rowData.magic /= 2; + } else { + rowData.guard *= 2; + rowData.power *= 2; + rowData.speed *= 2; + rowData.hp *= 2; + rowData.magic *= 2; + } + + refreshModal(); + } + }); + + const refreshModal = () => { + rowData.spells = Array.isArray(rowData.spells) ? rowData.spells : rowData.spells.split(','); + const calc = window.saga.calc; Object.keys(rowData).forEach((key) => { const cls = key.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase()); $enemyInfoModal.find('.' + cls).text(rowData[key]); }); - if (charStats.power === null && charStats.weapon === null) { + if (charStats.power === null && charStats.weapon === null && charStats.guard === null && !charStats.armor && !charStats.accessory) { $enemyInfoModal.find('[class^="physical-"]').text('n/a'); } else { const powerInnate = charStats.power || 0; @@ -333,51 +358,66 @@ const guardInnate = rowData.guard; const guardArmor = 0; const guardAccessory = 0; - const def = window.saga.calc.physicalAttack( - powerInnate, - powerWeapon, - guardInnate, - guardArmor, - guardAccessory, - ); - const withGuard = window.saga.calc.physicalAttack( - powerInnate, - powerWeapon, - guardInnate, - guardArmor, - guardAccessory, - { - targetGuardUp: true, - } - ); - const withPower = window.saga.calc.physicalAttack( - powerInnate, - powerWeapon, - guardInnate, - guardArmor, - guardAccessory, - { - attackerPowerUp: true, - } - ); - const withPowerGuard = window.saga.calc.physicalAttack( - powerInnate, - powerWeapon, - guardInnate, - guardArmor, - guardAccessory, - { - attackerPowerUp: true, - targetGuardUp: true, - } - ); + const def = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory); + const withDefend = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory, { + attackerDefending: true, + }); + const withGuard = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory, { + targetGuardUp: true, + }); + const withDefendGuard = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory, { + targetGuardUp: true, + attackerDefending: true, + }); + const withPower = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory, { + attackerPowerUp: true, + }); + const withPowerDefend = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory, { + attackerDefending: true, + attackerPowerUp: true, + }); + const withPowerGuard = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory, { + attackerPowerUp: true, + targetGuardUp: true, + }); + const withPowerGuardDefend = calc.physicalAttack(powerInnate, powerWeapon, guardInnate, guardArmor, guardAccessory, { + attackerDefending: true, + attackerPowerUp: true, + targetGuardUp: true, + }); const dmgRange = (dmg) => `${Math.floor(dmg.normal.min)}-${Math.ceil(dmg.normal.max)}`; $enemyInfoModal.find('.physical-dmg').text(dmgRange(def)); $enemyInfoModal.find('.physical-dmg-guard-up').text(dmgRange(withGuard)); + $enemyInfoModal.find('.physical-dmg-defend').text(dmgRange(withDefend)); + $enemyInfoModal.find('.physical-dmg-defend-guard-up').text(dmgRange(withDefendGuard)); $enemyInfoModal.find('.physical-dmg-power-up').text(dmgRange(withPower)); $enemyInfoModal.find('.physical-dmg-power-up-guard-up').text(dmgRange(withPowerGuard)); + $enemyInfoModal.find('.physical-dmg-defend-power-up').text(dmgRange(withPowerDefend)); + $enemyInfoModal.find('.physical-dmg-defend-power-up-guard-up').text(dmgRange(withPowerGuardDefend)); + + // enemy attack + const enemyPower = rowData.power; + const charGuard = charStats.guard || 0; + const charArmor = charStats.armor ? charStats.armor.defense : 0; + const charAccessory = charStats.accessory ? charStats.accessory.defense : 0; + const enemyDef = calc.physicalAttack(enemyPower, 0, charGuard, charArmor, charAccessory,); + const enemyDefDefend = calc.physicalAttack(enemyPower, 0, charGuard, charArmor, charAccessory, { + targetDefending: true, + }); + const enemyDefDefendGuardUp = calc.physicalAttack(enemyPower, 0, charGuard, charArmor, charAccessory, { + targetDefending: true, + targetGuardUp: true, + }); + const enemyDefGuardUp = calc.physicalAttack(enemyPower, 0, charGuard, charArmor, charAccessory, { + targetGuardUp: true, + }); + + $enemyInfoModal.find('.physical-enemy-dmg').text(dmgRange(enemyDef)); + $enemyInfoModal.find('.physical-enemy-dmg-guard-up').text(dmgRange(enemyDefGuardUp)); + $enemyInfoModal.find('.physical-enemy-dmg-defend').text(dmgRange(enemyDefDefend)); + $enemyInfoModal.find('.physical-enemy-dmg-defend-guard-up').text(dmgRange(enemyDefDefendGuardUp)); } const magicDmg = (dmg) => `${Math.floor(dmg.min)}-${Math.ceil(dmg.max)}`; @@ -386,9 +426,9 @@ if (charStats.magic === null) { $enemyInfoModal.find('[class^="magic-"]').text('n/a'); } else { - const attackerMagic = charStats.magic; - const targetMagic = rowData.magic; - const res = { + const charMagic = charStats.magic; + const enemyMagic = rowData.magic; + const enemyRes = { ice: rowData.resIce, fire: rowData.resFire, thunder: rowData.resThunder, @@ -399,10 +439,10 @@ const resAccessory = 0; window.saga.spells.filter(x => !!x.power).forEach((spell) => { - const elementalRes = res[spell.element.toLowerCase()]; + const elementalRes = enemyRes[spell.element.toLowerCase()]; - const def = window.saga.calc.magicalAttack(attackerMagic, targetMagic, elementalRes, resArmor, resAccessory, spell.power); - const magicUp = window.saga.calc.magicalAttack(attackerMagic, targetMagic, elementalRes, resArmor, resAccessory, spell.power, { + const def = calc.magicalAttack(charMagic, enemyMagic, elementalRes, resArmor, resAccessory, spell.power); + const magicUp = calc.magicalAttack(charMagic, enemyMagic, elementalRes, resArmor, resAccessory, spell.power, { attackerMagicUp: true, }); @@ -412,8 +452,8 @@ }); // hpcatcher - let def = window.saga.calc.hpCatcherAttack(charStats.magic, 999, 0, rowData.hp); - let magicUp = window.saga.calc.hpCatcherAttack(charStats.magic, 999, 0, rowData.hp, { + let def = calc.hpCatcherAttack(charStats.magic, 999, 0, rowData.hp); + let magicUp = calc.hpCatcherAttack(charStats.magic, 999, 0, rowData.hp, { attackerMagicUp: true, }); @@ -422,23 +462,63 @@ $enemyInfoModal.find(prefix + '-magic-up').text(magicDmg(magicUp)); // mpcatcher - def = window.saga.calc.mpCatcherAttack(charStats.magic, 999, 0, rowData.mp); - magicUp = window.saga.calc.mpCatcherAttack(charStats.magic, 999, 0, rowData.mp, { + def = calc.mpCatcherAttack(charStats.magic, 999, 0, rowData.mp); + magicUp = calc.mpCatcherAttack(charStats.magic, 999, 0, rowData.mp, { attackerMagicUp: true, }); prefix = '.magic-dmg-MPCatcher'; $enemyInfoModal.find(prefix).text(magicDmg(def)); $enemyInfoModal.find(prefix + '-magic-up').text(magicDmg(magicUp)); + + $enemyInfoModal.find('.enemy-spells tbody tr') + .hide() + .filter((i, row) => { + return rowData.spells.includes(row.getAttribute('data-name')); + }) + .show(); + + window.saga.spells.filter(x => !!x.power && rowData.spells.includes(x.name)).forEach((spell) => { + const elementalRes = ''; + const resArmor = 0; + const resAccessory = charStats.accessor ? charStats.accessor.resistance[spell.element.toLowerCase()] : 0; + let def = calc.magicalAttack(enemyMagic, charMagic, elementalRes, resArmor, resAccessory, spell.power); + let magicUp = calc.magicalAttack(enemyMagic, charMagic, elementalRes, resArmor, resAccessory, spell.power, { + targetMagicUp: true, + }); + + $enemyInfoModal.find(`.enemy-spells .magic-enemy-dmg-${spell.name}`).text(magicDmg(def)); + $enemyInfoModal.find(`.enemy-spells .magic-enemy-dmg-${spell.name}-magic-up`).text(magicDmg(magicUp)); + + // hpcatcher + def = calc.hpCatcherAttack(rowData.magic, 999, 0, rowData.hp); + magicUp = calc.hpCatcherAttack(rowData.magic, 999, 0, rowData.hp, { + targetMagicUp: true, + }); + + let prefix = '.magic-enemy-dmg-HPCatcher'; + $enemyInfoModal.find(prefix).text(magicDmg(def)); + $enemyInfoModal.find(prefix + '-magic-up').text(magicDmg(magicUp)); + + // mpcatcher + def = calc.mpCatcherAttack(rowData.magic, 999, 0, rowData.mp); + magicUp = calc.mpCatcherAttack(rowData.magic, 999, 0, rowData.mp, { + attackerMagicUp: true, + }); + + prefix = '.magic-enemy-dmg-MPCatcher'; + $enemyInfoModal.find(prefix).text(magicDmg(def)); + $enemyInfoModal.find(prefix + '-magic-up').text(magicDmg(magicUp)); + }); } if (charStats.speed !== null) { - const hitRate = window.saga.calc.hitRate(charStats.speed, rowData.speed); - const hitRateSpeed = window.saga.calc.hitRate(charStats.speed, rowData.speed, { + const hitRate = calc.hitRate(charStats.speed, rowData.speed); + const hitRateSpeed = calc.hitRate(charStats.speed, rowData.speed, { attackerSpeedUp: true, }); - const runRate = window.saga.calc.runRate(charStats.speed, rowData.speed); - const runRateSpeed = window.saga.calc.runRate(charStats.speed, rowData.speed, { + const runRate = rowData.cls === 1 ? 0 : calc.runRate(charStats.speed, rowData.speed); + const runRateSpeed = rowData.cls === 1 ? 0 : calc.runRate(charStats.speed, rowData.speed, { attackerSpeedUp: true, }); @@ -446,18 +526,45 @@ $enemyInfoModal.find('.run-rate-speed-up').text(toPer(runRateSpeed)); $enemyInfoModal.find('.hit-rate').text(toPer(hitRate)); $enemyInfoModal.find('.hit-rate-speed-up').text(toPer(hitRateSpeed)); + + const enemyHitRate = calc.hitRate(rowData.speed, charStats.speed); + const enemyHitRateSpeed = calc.hitRate(rowData.speed, charStats.speed, { + attackerSpeedUp: true, + }); + const enemyHitRateCharSpeed = calc.hitRate(rowData.speed, charStats.speed, { + targetSpeedUp: true, + }); + const enemyHitRateCharSpeedSpeed = calc.hitRate(rowData.speed, charStats.speed, { + attackerSpeedUp: true, + targetSpeedUp: true, + }); + + $enemyInfoModal.find('.hit-rate-enemy').text(toPer(enemyHitRate)); + $enemyInfoModal.find('.hit-rate-enemy-speed-up').text(toPer(enemyHitRateSpeed)); + $enemyInfoModal.find('.hit-rate-enemy-char-speed-up').text(toPer(enemyHitRateCharSpeed)); + $enemyInfoModal.find('.hit-rate-enemy-char-speed-up-speed-up').text(toPer(enemyHitRateCharSpeedSpeed)); } - const debuffRate = window.saga.calc.effectSpellHitRate(rowData.resDebuff === 100 ? null : rowData.resDebuff, 0, 0); - const vacuumRate = window.saga.calc.effectSpellHitRate(rowData.resVacuum === 100 ? null : rowData.resVacuum, 0, 0); + const debuffRate = calc.effectSpellHitRate(rowData.resDebuff === 100 ? null : rowData.resDebuff, 0, 0); + const vacuumRate = calc.effectSpellHitRate(rowData.resVacuum === 100 ? null : rowData.resVacuum, 0, 0); $enemyInfoModal.find('.debuff-rate').text(toPer(debuffRate)); $enemyInfoModal.find('.vacuum-rate').text(toPer(vacuumRate)); + }; + + $table.find('tbody.data td').on('click', (e) => { + rowData = rowData = { ...$(e.target).closest('tr').data() }; + + if (rowData.name === 'Gorsia') { + $enemyInfoModal.find('.apply-runes-form').show().prop('checked', false); + } else { + $enemyInfoModal.find('.apply-runes-form').hide().find('input').prop('checked', false); + } + + refreshModal(); $enemyInfoModal.modal({ show: true, }); }); - } else { - } }(window)); diff --git a/web/views/enemies.pug b/web/views/enemies.pug index 0f4195f..9ebc473 100644 --- a/web/views/enemies.pug +++ b/web/views/enemies.pug @@ -42,6 +42,8 @@ block tab-content data-res-thunder=enemy.resistance.thunder data-res-vacuum=(enemy.resistance.vacuum === null ? 100 : enemy.resistance.vacuum) data-res-debuff=(enemy.resistance.debuff === null ? 100 : enemy.resistance.debuff) + data-spells=((enemy.spells || []).join(',')) + data-cls=enemy.cls ) td td: strong= enemy.name @@ -69,17 +71,20 @@ block tab-content = item + ' (' + (rate * 100).toFixed(2) + '%)' div#enemy-info-modal.modal(tabindex="-1") - div.modal-dialog.modal-lg + div.modal-dialog.modal-xl div.modal-content div.modal-header h5.modal-title span.name + div.form-check-inline.ml-4.apply-runes-form(style="font-size: 75%") + input.form-check-input#apply-runes(type="checkbox" autocomplete="off") + label.form-check-label(for="apply-runes") Apply runes div | #[span.gold]/#[span.exp] - div.modal-body + div.modal-body(style="font-size: 85%") div.row - div.col-6 - table.table.table-horizontal + div.col-4 + table.table.table-horizontal.table-sm tr th(colspan="2") Stats th(colspan="2") Resistances @@ -113,6 +118,76 @@ block tab-content td.res-debuff div.card.bg-light + div.card-header Enemy attack + div.card-body + table.table.table-horizontal.table-sm + tr + th Default + th Guard up + th Defend + th Defend+Guard + tr + td: code.physical-enemy-dmg + td: code.physical-enemy-dmg-defend + td: code.physical-enemy-dmg-guard-up + td: code.physical-enemy-dmg-defend-guard-up + + h6 Hit rate + table.table.table-horizontal.table-sm.mt-2 + thead: tr + th + th Normal + th Speed up + tr + th Default + td: code.hit-rate-enemy + td: code.hit-rate-enemy-speed-up + tr + th Char. speed up + td: code.hit-rate-enemy-char-speed-up + td: code.hit-rate-enemy-char-speed-up-speed-up + + div.card.bg-light.mt-2 + div.card-header Enemy magic + div.card-body: table.table.table-horizontal.table-sm.enemy-spells + thead: tr + th + th Default + th Magic up + tbody + each spell in charSpells.filter(x => (!!x.power || x.name === 'HPCatcher' || x.name === 'MPCatcher')) + tr(data-name=spell.name class=("enemy-spell-" + spell.name)) + th= spell.name + td: code(class=("magic-enemy-dmg-" + spell.name)) + td: code(class=("magic-enemy-dmg-" + spell.name + '-magic-up')) + + div.col-4 + div.card.bg-light + div.card-header Character attack + div.card-body: table.table.table-horizontal.table-sm + tr + th + th Default + th Guard up + tr + th Normal + td: code.physical-dmg + td: code.physical-dmg-guard-up + tr + th Defend + td: code.physical-dmg-defend + td: code.physical-dmg-defend-guard-up + tr + th Power up + td: code.physical-dmg-power-up + td: code.physical-dmg-power-up-guard-up + tr + th Defend+Power + td: code.physical-dmg-defend-power-up + td: code.physical-dmg-defend-power-up-guard-up + + div.card.bg-light.mt-2 + div.card-header Character rates div.card-body table.table.table-sm.table-horizontal tr @@ -136,32 +211,15 @@ block tab-content th td - div.col-6 - div.card.bg-light - div.card-body - table.table.table-horizontal.table-sm - tr - th - th - th Default - th Guard up - tr - th(rowspan="2").align-middle Physical - th Normal - td: code.physical-dmg - td: code.physical-dmg-guard-up - tr - th Power up - td: code.physical-dmg-power-up - td: code.physical-dmg-power-up-guard-up - tr - th - th - th Default - th Magic up - each spell in charSpells.filter(x => !!x.power || x.name === 'HPCatcher' || x.name === 'MPCatcher') - tr - th= spell.name - th - td: code(class=("magic-dmg-" + spell.name)) - td: code(class=("magic-dmg-" + spell.name + '-magic-up')) + div.col-4: div.card.bg-light + div.card-header Character magic + div.card-body: table.table.table-horizontal.table-sm + tr + th + th Default + th Magic up + each spell in charSpells.filter(x => !!x.power || x.name === 'HPCatcher' || x.name === 'MPCatcher') + tr + th= spell.name + td: code(class=("magic-dmg-" + spell.name)) + td: code(class=("magic-dmg-" + spell.name + '-magic-up'))