calculation stuff in ui
This commit is contained in:
parent
6b40ec0a11
commit
5fe8b2f773
181
data/calc.js
181
data/calc.js
@ -1,181 +0,0 @@
|
|||||||
// https://gamefaqs.gamespot.com/snes/563501-the-7th-saga/faqs/54038
|
|
||||||
|
|
||||||
exports.physicalAttack = (
|
|
||||||
attackerPowerInnate,
|
|
||||||
attackerPowerWeapon,
|
|
||||||
targetGuardInnate,
|
|
||||||
targetGuardArmor,
|
|
||||||
targetGuardAccessory,
|
|
||||||
options = {},
|
|
||||||
) => {
|
|
||||||
let attackPower = attackerPowerInnate;
|
|
||||||
if (options.attackerDefending) {
|
|
||||||
attackPower *= 1.5;
|
|
||||||
}
|
|
||||||
if (options.attackerPowerUp) {
|
|
||||||
attackPower *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
let weaponPower = attackerPowerWeapon;
|
|
||||||
if (options.attackerDefending) {
|
|
||||||
weaponPower *= 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
let guardPower = targetGuardInnate;
|
|
||||||
if (options.targetGuardDown) {
|
|
||||||
guardPower /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalAttackPower = attackPower + weaponPower;
|
|
||||||
const totalGuardPower = guardPower + targetGuardArmor + targetGuardAccessory;
|
|
||||||
|
|
||||||
let averageDamage = totalAttackPower - (totalGuardPower / 2);
|
|
||||||
|
|
||||||
if (options.targetGuardUp) {
|
|
||||||
averageDamage /= 2;
|
|
||||||
}
|
|
||||||
if (options.targetDefending) {
|
|
||||||
averageDamage /= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
let minDamage = averageDamage * 0.75;
|
|
||||||
let maxDamage = averageDamage * 1.25;
|
|
||||||
|
|
||||||
const absoluteMin = 1;
|
|
||||||
|
|
||||||
return {
|
|
||||||
normal: {
|
|
||||||
avg: Math.max(absoluteMin, averageDamage),
|
|
||||||
min: Math.max(absoluteMin, minDamage),
|
|
||||||
max: Math.max(absoluteMin, maxDamage),
|
|
||||||
},
|
|
||||||
critical: {
|
|
||||||
avg: Math.max(absoluteMin, averageDamage * 2),
|
|
||||||
min: Math.max(absoluteMin, minDamage * 2),
|
|
||||||
max: Math.max(absoluteMin, maxDamage * 2),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.magicalAttack = (
|
|
||||||
attackerMagicInnate,
|
|
||||||
targetMagicInnate,
|
|
||||||
targetResistanceInnate,
|
|
||||||
targetResistanceArmor,
|
|
||||||
targetResistanceAccessory,
|
|
||||||
spellPower,
|
|
||||||
options = {},
|
|
||||||
) => {
|
|
||||||
let attackerPower = attackerMagicInnate;
|
|
||||||
|
|
||||||
let targetPower = targetMagicInnate;
|
|
||||||
if (options.targetMagicUp) {
|
|
||||||
targetPower += 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetResistance = 100 - targetResistanceInnate - targetResistanceArmor - targetResistanceAccessory;
|
|
||||||
|
|
||||||
const spellBonusAttackPower = attackerPower + (options.attackerMagicUp ? 40 : 0);
|
|
||||||
const spellBonus = ((spellBonusAttackPower / 2) + spellPower) * (targetResistance / 100);
|
|
||||||
|
|
||||||
const averageDamage = attackerPower - targetPower + spellBonus;
|
|
||||||
|
|
||||||
return {
|
|
||||||
avg: Math.max(1, averageDamage),
|
|
||||||
min: Math.max(1, averageDamage * 0.75),
|
|
||||||
max: Math.max(1, averageDamage * 1.25),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.hpCatcherAttack = (
|
|
||||||
attackerMagicInnate,
|
|
||||||
attackerMaxHP,
|
|
||||||
attackerCurrentHP,
|
|
||||||
targetCurrentHP,
|
|
||||||
options = {},
|
|
||||||
) => {
|
|
||||||
let attackerPower = attackerMagicInnate;
|
|
||||||
if (options.attackerMagicUp) {
|
|
||||||
attackerPower += 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
const adjust = dmg => Math.min(attackerMaxHP - attackerCurrentHP, Math.min(Math.min(dmg, 50), targetCurrentHP));
|
|
||||||
const minDamage = attackerPower / 2;
|
|
||||||
|
|
||||||
return {
|
|
||||||
min: adjust(minDamage),
|
|
||||||
max: adjust(minDamage + 15),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.mpCatcherAttack = (
|
|
||||||
attackerMagicInnate,
|
|
||||||
attackerMaxMP,
|
|
||||||
attackerCurrentMP,
|
|
||||||
targetCurrentMP,
|
|
||||||
options = {},
|
|
||||||
) => {
|
|
||||||
let attackerPower = attackerMagicInnate;
|
|
||||||
if (options.attackerMagicUp) {
|
|
||||||
attackerPower += 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
const adjust = dmg => Math.min(attackerMaxMP - attackerCurrentMP, Math.min(Math.min(dmg, 40), targetCurrentMP));
|
|
||||||
const minDamage = attackerPower / 2;
|
|
||||||
|
|
||||||
return {
|
|
||||||
min: adjust(minDamage),
|
|
||||||
max: adjust(minDamage + 15),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.effectSpellHitRate = (
|
|
||||||
targetResistanceInnate,
|
|
||||||
targetResistanceArmor,
|
|
||||||
targetResistanceAccessory,
|
|
||||||
) => {
|
|
||||||
if (targetResistanceInnate === null) {
|
|
||||||
// e.g. "Class 1" enemies
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.max(5, 100 - targetResistanceInnate - targetResistanceArmor - targetResistanceAccessory);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.hitRate = (
|
|
||||||
attackerSpeedInnate,
|
|
||||||
targetSpeedInnate,
|
|
||||||
options = {},
|
|
||||||
) => {
|
|
||||||
let attackerSpeed = attackerSpeedInnate;
|
|
||||||
if (options.attackerSpeedUp) {
|
|
||||||
attackerSpeed += 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetSpeed = targetSpeedInnate;
|
|
||||||
if (options.targetSpeedUp) {
|
|
||||||
targetSpeed += 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hitRate = 85 + (0.8 * (attackerSpeed - targetSpeed));
|
|
||||||
return Math.max(10, Math.min(98, hitRate));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.runRate = (
|
|
||||||
attackerSpeedInnate,
|
|
||||||
targetSpeedInnate,
|
|
||||||
options = {},
|
|
||||||
) => {
|
|
||||||
let attackerSpeed = attackerSpeedInnate;
|
|
||||||
if (options.attackerSpeedUp) {
|
|
||||||
attackerSpeed += 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetSpeed = targetSpeedInnate;
|
|
||||||
if (options.targetSpeedUp) {
|
|
||||||
targetSpeed += 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
const runRate = 0.25 + (1.6 * (attackerSpeed - targetSpeed));
|
|
||||||
return Math.max(0.1, Math.min(0.8, runRate));
|
|
||||||
};
|
|
@ -1,7 +1,7 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const {spells} = require('./spells');
|
const {spells} = require('../web/static/spells');
|
||||||
|
|
||||||
const spellMap = spells.reduce((map, spell) => {
|
const spellMap = spells.reduce((map, spell) => {
|
||||||
map[spell.name] = spell;
|
map[spell.name] = spell;
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
const attackSpell = (name, mp, power, element, multiple) => {
|
|
||||||
return {
|
|
||||||
type: 'Attack',
|
|
||||||
name,
|
|
||||||
mp,
|
|
||||||
power,
|
|
||||||
element,
|
|
||||||
targets: multiple ? 'multi' : 'single',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const effectSpell = (name, mp, element, effect, multiple) => {
|
|
||||||
return {
|
|
||||||
type: 'Effect',
|
|
||||||
name,
|
|
||||||
mp,
|
|
||||||
element,
|
|
||||||
effect,
|
|
||||||
targets: multiple ? 'multi' : 'single',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const healSpell = (name, mp, healingPower, effect) => {
|
|
||||||
return {
|
|
||||||
type: 'Healing',
|
|
||||||
name,
|
|
||||||
mp,
|
|
||||||
healingPower,
|
|
||||||
effect,
|
|
||||||
targets: 'single',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const supportSpell = (name, mp, effect, locations) => {
|
|
||||||
return {
|
|
||||||
type: 'Support',
|
|
||||||
name,
|
|
||||||
mp,
|
|
||||||
effect,
|
|
||||||
locations,
|
|
||||||
targets: 'single',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.spells = [
|
|
||||||
attackSpell('Fire1', 3, 30, 'Fire'),
|
|
||||||
attackSpell('Fire2', 12, 70, 'Fire'),
|
|
||||||
attackSpell('Ice1', 3, 36, 'Ice'),
|
|
||||||
attackSpell('Ice2', 12, 80, 'Ice'),
|
|
||||||
attackSpell('Laser1', 3, 30, 'Thunder'),
|
|
||||||
attackSpell('Laser2', 10, 50, 'Thunder'),
|
|
||||||
attackSpell('Laser3', 20, 100, 'Thunder'),
|
|
||||||
attackSpell('Firebird', 14, 30, 'Fire', true),
|
|
||||||
attackSpell('Fireball', 32, 50, 'Fire', true),
|
|
||||||
attackSpell('Blizzard1', 14, 34, 'Ice', true),
|
|
||||||
attackSpell('Blizzard2', 32, 60, 'Ice', true),
|
|
||||||
attackSpell('Thunder1', 10, 40, 'Thunder', true),
|
|
||||||
attackSpell('Thunder2', 40, 75, 'Thunder', true),
|
|
||||||
|
|
||||||
effectSpell('Petrify', 10, 'Debuff', 'Petrification'),
|
|
||||||
effectSpell('Poison', 0, 'Debuff', 'Poison (monsters only)'),
|
|
||||||
effectSpell('Defense2', 5, 'Debuff', 'Halves guard'),
|
|
||||||
effectSpell('HPCatcher', 6, 'Debuff', 'Drains up to 50 HP and heals caster'),
|
|
||||||
effectSpell('MPCatcher', 8, 'Debuff', 'Drains up to 40 MP and heals caster'),
|
|
||||||
effectSpell('Vacuum1', 30, 'Vacuum', 'Instant death'),
|
|
||||||
effectSpell('Vacuum2', 60, 'Vacuum', 'Instant death', true),
|
|
||||||
|
|
||||||
healSpell('Heal1', 4, 40, 'Restores 40 HP'),
|
|
||||||
healSpell('Heal2', 18, 90, 'Restores 90 HP'),
|
|
||||||
healSpell('Heal3', 34, 'max', 'Restores all HP'),
|
|
||||||
healSpell('Elixir', 120, 'max', 'Restores all HP and MP'),
|
|
||||||
healSpell('Revive1', 40, 'max', 'Restores all HP to dead ally, fails ~50% of the time'),
|
|
||||||
healSpell('Revive2', 90, 'max', 'Restores all HP to dead ally, always succeeds'),
|
|
||||||
|
|
||||||
supportSpell('Purify', 8, 'Removes petrification and poison', [ 'Battle', 'Map' ]),
|
|
||||||
supportSpell('Defense1', 5, 'Halves physical damage from enemy', [ 'Battle' ]),
|
|
||||||
supportSpell('Power', 6, 'Doubles power', [ 'Battle' ]),
|
|
||||||
supportSpell('Agility', 3, 'Increases Speed by 30', [ 'Battle' ]),
|
|
||||||
supportSpell('F.Shield', 16, 'Nullifies next attack spell', [ 'Battle' ]),
|
|
||||||
supportSpell('Protect', 20, 'Nullifies next Vacuum spell', [ 'Battle' ]),
|
|
||||||
supportSpell('Exit', 30, 'Escape cave/dungeon immediately', [ 'Map' ]),
|
|
||||||
];
|
|
@ -2,8 +2,7 @@ const express = require('express');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const enemies = require('../data/enemies.json');
|
const enemies = require('../data/enemies.json');
|
||||||
const calc = require('../data/calc');
|
const spells = require('./static/spells');
|
||||||
const spells = require('../data/spells');
|
|
||||||
const items = require('../data/items');
|
const items = require('../data/items');
|
||||||
const exp = require('../data/exp');
|
const exp = require('../data/exp');
|
||||||
|
|
||||||
@ -15,29 +14,67 @@ app.set('cache views', false);
|
|||||||
|
|
||||||
app.use(express.static(path.join(__dirname)));
|
app.use(express.static(path.join(__dirname)));
|
||||||
|
|
||||||
|
const render = (res, view, params) => {
|
||||||
|
res.render(view, {
|
||||||
|
...params,
|
||||||
|
charSpells: spells.spells.concat([]).sort((a, b) => {
|
||||||
|
if (a.power && b.power) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!a.power && b.power) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.power === b.power) {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.power < b.power ? -1 : 1;
|
||||||
|
}),
|
||||||
|
charWeapons: items.weapons.concat([]).sort((a, b) => {
|
||||||
|
if (a.attack === b.attack) {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
return a.attack === b.attack ? 0 : (a.attack < b.attack ? -1 : 1);
|
||||||
|
}),
|
||||||
|
charArmor: items.armor.concat([]).sort((a, b) => {
|
||||||
|
if (a.defense === b.defense) {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
return a.defense === b.defense ? 0 : (a.defense < b.defense ? -1 : 1);
|
||||||
|
}),
|
||||||
|
charAccessories: items.accessories.concat([]).sort((a, b) => {
|
||||||
|
if (a.defense === b.defense) {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
return a.defense === b.defense ? 0 : (a.defense < b.defense ? -1 : 1);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
app.get([ '/', '/enemies' ], (req, res) => {
|
app.get([ '/', '/enemies' ], (req, res) => {
|
||||||
res.render('enemies', {
|
render(res, 'enemies', {
|
||||||
context: 'enemies',
|
context: 'enemies',
|
||||||
enemies,
|
enemies,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/spells', (req, res) => {
|
app.get('/spells', (req, res) => {
|
||||||
res.render('spells', {
|
render(res, 'spells', {
|
||||||
context: 'spells',
|
context: 'spells',
|
||||||
spells: spells.spells.sort((a, b) => a.name.localeCompare(b.name)),
|
spells: spells.spells.sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/exp', (req, res) => {
|
app.get('/exp', (req, res) => {
|
||||||
res.render('exp', {
|
render(res, 'exp', {
|
||||||
context: 'exp',
|
context: 'exp',
|
||||||
exp: exp.exp,
|
exp: exp.exp,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/weapons', (req, res) => {
|
app.get('/weapons', (req, res) => {
|
||||||
res.render('weapons', {
|
render(res, 'weapons', {
|
||||||
context: 'weapons',
|
context: 'weapons',
|
||||||
weapons: items.weapons.sort((a, b) => {
|
weapons: items.weapons.sort((a, b) => {
|
||||||
if (a.attack === b.attack) {
|
if (a.attack === b.attack) {
|
||||||
@ -49,7 +86,7 @@ app.get('/weapons', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get('/armor', (req, res) => {
|
app.get('/armor', (req, res) => {
|
||||||
res.render('armor', {
|
render(res, 'armor', {
|
||||||
context: 'armor',
|
context: 'armor',
|
||||||
armor: items.armor.sort((a, b) => {
|
armor: items.armor.sort((a, b) => {
|
||||||
if (a.defense === b.defense) {
|
if (a.defense === b.defense) {
|
||||||
@ -61,14 +98,14 @@ app.get('/armor', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get('/accessories', (req, res) => {
|
app.get('/accessories', (req, res) => {
|
||||||
res.render('accessories', {
|
render(res, 'accessories', {
|
||||||
context: 'accessories',
|
context: 'accessories',
|
||||||
accessories: items.accessories,
|
accessories: items.accessories,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/items', (req, res) => {
|
app.get('/items', (req, res) => {
|
||||||
res.render('items', {
|
render(res, 'items', {
|
||||||
context: 'items',
|
context: 'items',
|
||||||
items: items.items,
|
items: items.items,
|
||||||
});
|
});
|
||||||
|
@ -49,7 +49,7 @@ table.sticky-header {
|
|||||||
|
|
||||||
table.sticky-header tr.header th {
|
table.sticky-header tr.header th {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 37px;
|
top: 44px;
|
||||||
background-color: #fcfac8;
|
background-color: #fcfac8;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25);
|
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25);
|
||||||
@ -58,3 +58,7 @@ table.sticky-header tr.header th {
|
|||||||
th.sorted, td.sorted {
|
th.sorted, td.sorted {
|
||||||
background-color: #dae5f6 !important;
|
background-color: #dae5f6 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.row-clickable tbody.data td {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
184
web/static/calc.js
Normal file
184
web/static/calc.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// https://gamefaqs.gamespot.com/snes/563501-the-7th-saga/faqs/54038
|
||||||
|
|
||||||
|
(function(exports) {
|
||||||
|
exports.physicalAttack = (
|
||||||
|
attackerPowerInnate,
|
||||||
|
attackerPowerWeapon,
|
||||||
|
targetGuardInnate,
|
||||||
|
targetGuardArmor,
|
||||||
|
targetGuardAccessory,
|
||||||
|
options = {},
|
||||||
|
) => {
|
||||||
|
let attackPower = attackerPowerInnate;
|
||||||
|
if (options.attackerDefending) {
|
||||||
|
attackPower *= 1.5;
|
||||||
|
}
|
||||||
|
if (options.attackerPowerUp) {
|
||||||
|
attackPower *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let weaponPower = attackerPowerWeapon;
|
||||||
|
if (options.attackerDefending) {
|
||||||
|
weaponPower *= 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
let guardPower = targetGuardInnate;
|
||||||
|
if (options.targetGuardDown) {
|
||||||
|
guardPower /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalAttackPower = attackPower + weaponPower;
|
||||||
|
const totalGuardPower = guardPower + targetGuardArmor + targetGuardAccessory;
|
||||||
|
|
||||||
|
let averageDamage = totalAttackPower - (totalGuardPower / 2);
|
||||||
|
|
||||||
|
if (options.targetGuardUp) {
|
||||||
|
averageDamage /= 2;
|
||||||
|
}
|
||||||
|
if (options.targetDefending) {
|
||||||
|
averageDamage /= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let minDamage = averageDamage * 0.75;
|
||||||
|
let maxDamage = averageDamage * 1.25;
|
||||||
|
|
||||||
|
const absoluteMin = 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
normal: {
|
||||||
|
avg: Math.max(absoluteMin, averageDamage),
|
||||||
|
min: Math.max(absoluteMin, minDamage),
|
||||||
|
max: Math.max(absoluteMin, maxDamage),
|
||||||
|
},
|
||||||
|
critical: {
|
||||||
|
avg: Math.max(absoluteMin, averageDamage * 2),
|
||||||
|
min: Math.max(absoluteMin, minDamage * 2),
|
||||||
|
max: Math.max(absoluteMin, maxDamage * 2),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.magicalAttack = (
|
||||||
|
attackerMagicInnate,
|
||||||
|
targetMagicInnate,
|
||||||
|
targetResistanceInnate,
|
||||||
|
targetResistanceArmor,
|
||||||
|
targetResistanceAccessory,
|
||||||
|
spellPower,
|
||||||
|
options = {},
|
||||||
|
) => {
|
||||||
|
let attackerPower = attackerMagicInnate;
|
||||||
|
|
||||||
|
let targetPower = targetMagicInnate;
|
||||||
|
if (options.targetMagicUp) {
|
||||||
|
targetPower += 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetResistance = 100 - targetResistanceInnate - targetResistanceArmor - targetResistanceAccessory;
|
||||||
|
|
||||||
|
const spellBonusAttackPower = attackerPower + (options.attackerMagicUp ? 40 : 0);
|
||||||
|
const spellBonus = ((spellBonusAttackPower / 2) + spellPower) * (targetResistance / 100);
|
||||||
|
|
||||||
|
const averageDamage = attackerPower - targetPower + spellBonus;
|
||||||
|
|
||||||
|
return {
|
||||||
|
avg: Math.max(1, averageDamage),
|
||||||
|
min: Math.max(1, averageDamage * 0.75),
|
||||||
|
max: Math.max(1, averageDamage * 1.25),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.hpCatcherAttack = (
|
||||||
|
attackerMagicInnate,
|
||||||
|
attackerMaxHP,
|
||||||
|
attackerCurrentHP,
|
||||||
|
targetCurrentHP,
|
||||||
|
options = {},
|
||||||
|
) => {
|
||||||
|
let attackerPower = attackerMagicInnate;
|
||||||
|
if (options.attackerMagicUp) {
|
||||||
|
attackerPower += 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjust = dmg => Math.min(attackerMaxHP - attackerCurrentHP, Math.min(Math.min(dmg, 50), targetCurrentHP));
|
||||||
|
const minDamage = attackerPower / 2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
min: adjust(minDamage),
|
||||||
|
max: adjust(minDamage + 15),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.mpCatcherAttack = (
|
||||||
|
attackerMagicInnate,
|
||||||
|
attackerMaxMP,
|
||||||
|
attackerCurrentMP,
|
||||||
|
targetCurrentMP,
|
||||||
|
options = {},
|
||||||
|
) => {
|
||||||
|
let attackerPower = attackerMagicInnate;
|
||||||
|
if (options.attackerMagicUp) {
|
||||||
|
attackerPower += 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjust = dmg => Math.min(attackerMaxMP - attackerCurrentMP, Math.min(Math.min(dmg, 40), targetCurrentMP));
|
||||||
|
const minDamage = attackerPower / 2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
min: adjust(minDamage),
|
||||||
|
max: adjust(minDamage + 15),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.effectSpellHitRate = (
|
||||||
|
targetResistanceInnate,
|
||||||
|
targetResistanceArmor,
|
||||||
|
targetResistanceAccessory,
|
||||||
|
) => {
|
||||||
|
if (targetResistanceInnate === null) {
|
||||||
|
// e.g. "Class 1" enemies
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(5, 100 - targetResistanceInnate - targetResistanceArmor - targetResistanceAccessory);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.hitRate = (
|
||||||
|
attackerSpeedInnate,
|
||||||
|
targetSpeedInnate,
|
||||||
|
options = {},
|
||||||
|
) => {
|
||||||
|
let attackerSpeed = attackerSpeedInnate;
|
||||||
|
if (options.attackerSpeedUp) {
|
||||||
|
attackerSpeed += 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetSpeed = targetSpeedInnate;
|
||||||
|
if (options.targetSpeedUp) {
|
||||||
|
targetSpeed += 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hitRate = 85 + (0.8 * (attackerSpeed - targetSpeed));
|
||||||
|
return Math.max(10, Math.min(98, hitRate));
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.runRate = (
|
||||||
|
attackerSpeedInnate,
|
||||||
|
targetSpeedInnate,
|
||||||
|
options = {},
|
||||||
|
) => {
|
||||||
|
let attackerSpeed = attackerSpeedInnate;
|
||||||
|
if (options.attackerSpeedUp) {
|
||||||
|
attackerSpeed += 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetSpeed = targetSpeedInnate;
|
||||||
|
if (options.targetSpeedUp) {
|
||||||
|
targetSpeed += 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runRate = 0.25 + (1.6 * (attackerSpeed - targetSpeed));
|
||||||
|
return Math.max(10, Math.min(80, runRate));
|
||||||
|
};
|
||||||
|
|
||||||
|
}(typeof(module) !== 'undefined' ? module.exports : window.saga.calc));
|
BIN
web/static/images/7th-saga-apprentices.png
Normal file
BIN
web/static/images/7th-saga-apprentices.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
web/static/images/7th-saga-enemies.png
Normal file
BIN
web/static/images/7th-saga-enemies.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 KiB |
@ -3,9 +3,28 @@
|
|||||||
const $locationFilterForm = $('.location-filter-form');
|
const $locationFilterForm = $('.location-filter-form');
|
||||||
const $table = $('#main-table');
|
const $table = $('#main-table');
|
||||||
|
|
||||||
|
let charStats = {
|
||||||
|
power: null,
|
||||||
|
guard: null,
|
||||||
|
magic: null,
|
||||||
|
speed: null,
|
||||||
|
weapon: null,
|
||||||
|
armor: null,
|
||||||
|
accessory: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCharStatChange = (readFromCookie) => {
|
||||||
|
if (!readFromCookie) {
|
||||||
|
window.Cookies.set('charStats', JSON.stringify(charStats), {
|
||||||
|
sameSite: 'strict',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.saga = {
|
window.saga = {
|
||||||
sortData: () => {
|
sortData: () => {
|
||||||
console.log('sorting data');
|
|
||||||
const qs = new URLSearchParams(window.location.search);
|
const qs = new URLSearchParams(window.location.search);
|
||||||
const col = qs.get('col');
|
const col = qs.get('col');
|
||||||
let dir = qs.get('dir');
|
let dir = qs.get('dir');
|
||||||
@ -158,6 +177,33 @@
|
|||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
},
|
},
|
||||||
|
updateCharStat: (stat, value) => {
|
||||||
|
charStats[stat] = value;
|
||||||
|
onCharStatChange();
|
||||||
|
},
|
||||||
|
updateCharWeapon: (name, power) => {
|
||||||
|
charStats.weapon = {
|
||||||
|
name,
|
||||||
|
power,
|
||||||
|
};
|
||||||
|
onCharStatChange();
|
||||||
|
},
|
||||||
|
updateCharArmor: (name, defense) => {
|
||||||
|
charStats.armor = {
|
||||||
|
name,
|
||||||
|
defense,
|
||||||
|
};
|
||||||
|
onCharStatChange();
|
||||||
|
},
|
||||||
|
updateCharAccessory: (name, defense, resistance) => {
|
||||||
|
charStats.accessory = {
|
||||||
|
name,
|
||||||
|
defense,
|
||||||
|
resistance,
|
||||||
|
};
|
||||||
|
onCharStatChange();
|
||||||
|
},
|
||||||
|
calc: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkLocations = () => {
|
const checkLocations = () => {
|
||||||
@ -184,20 +230,44 @@
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkCharStats = () => {
|
||||||
|
try {
|
||||||
|
const stats = JSON.parse(window.Cookies.get('charStats'));
|
||||||
|
if (stats.power !== null) {
|
||||||
|
$('#char-power').val(stats.power);
|
||||||
|
}
|
||||||
|
if (stats.guard !== null) {
|
||||||
|
$('#char-guard').val(stats.guard);
|
||||||
|
}
|
||||||
|
if (stats.magic !== null) {
|
||||||
|
$('#char-magic').val(stats.magic);
|
||||||
|
}
|
||||||
|
if (stats.speed !== null) {
|
||||||
|
$('#char-speed').val(stats.speed);
|
||||||
|
}
|
||||||
|
if (stats.weapon !== null) {
|
||||||
|
$('#char-weapon').val(stats.weapon.name);
|
||||||
|
}
|
||||||
|
if (stats.armor !== null) {
|
||||||
|
$('#char-armor').val(stats.armor.name);
|
||||||
|
}
|
||||||
|
if (stats.accessory !== null) {
|
||||||
|
$('#char-accessory').val(stats.accessory.name);
|
||||||
|
}
|
||||||
|
charStats = stats;
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
checkLocations();
|
checkLocations();
|
||||||
checkApprentices();
|
checkApprentices();
|
||||||
|
checkCharStats();
|
||||||
window.saga.sortData();
|
window.saga.sortData();
|
||||||
window.saga.filterApprentices();
|
window.saga.filterApprentices();
|
||||||
window.saga.filterLocations();
|
window.saga.filterLocations();
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('popstate', () => {
|
window.addEventListener('popstate', update);
|
||||||
checkLocations();
|
|
||||||
checkApprentices();
|
|
||||||
window.saga.sortData();
|
|
||||||
window.saga.filterApprentices();
|
|
||||||
window.saga.filterLocations();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.sortable-links a').click(function(e) {
|
$('.sortable-links a').click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -207,4 +277,187 @@
|
|||||||
|
|
||||||
$apprenticeFilterForm.find('input[type="checkbox"]').on('change', window.saga.filterApprentices);
|
$apprenticeFilterForm.find('input[type="checkbox"]').on('change', window.saga.filterApprentices);
|
||||||
$locationFilterForm.find('input[type="checkbox"]').on('change', window.saga.filterLocations);
|
$locationFilterForm.find('input[type="checkbox"]').on('change', window.saga.filterLocations);
|
||||||
|
|
||||||
|
$('#char-stats-modal').find('input,select').on('change', (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
const stat = e.target.id.replace(/^char-/, '');
|
||||||
|
|
||||||
|
const attrToNum = (name) => Number(e.target.querySelector(`option[value="${value}"]`).getAttribute('data-' + name));
|
||||||
|
|
||||||
|
switch (stat) {
|
||||||
|
case 'power':
|
||||||
|
case 'guard':
|
||||||
|
case 'magic':
|
||||||
|
case 'speed':
|
||||||
|
window.saga.updateCharStat(stat, Number(value));
|
||||||
|
break;
|
||||||
|
case 'weapon':
|
||||||
|
window.saga.updateCharWeapon(value, attrToNum('power'));
|
||||||
|
break;
|
||||||
|
case 'armor':
|
||||||
|
window.saga.updateCharArmor(value, attrToNum('defense'));
|
||||||
|
break;
|
||||||
|
case 'accessory':
|
||||||
|
window.saga.updateCharAccessory(
|
||||||
|
value,
|
||||||
|
attrToNum('defense'),
|
||||||
|
{
|
||||||
|
fire: attrToNum('res-fire'),
|
||||||
|
ice: attrToNum('res-ice'),
|
||||||
|
thunder: attrToNum('res-thunder'),
|
||||||
|
vacuum: attrToNum('res-vacuum'),
|
||||||
|
debuff: attrToNum('res-debuff'),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
const $enemyInfoModal = $('#enemy-info-modal');
|
||||||
|
if ($enemyInfoModal.length) {
|
||||||
|
$table.find('tbody.data td').on('click', (e) => {
|
||||||
|
const rowData = $(e.target).closest('tr').data();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
$enemyInfoModal.find('[class^="physical-"]').text('n/a');
|
||||||
|
} else {
|
||||||
|
const powerInnate = charStats.power || 0;
|
||||||
|
const powerWeapon = charStats.weapon ? charStats.weapon.power : 0;
|
||||||
|
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 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-power-up').text(dmgRange(withPower));
|
||||||
|
$enemyInfoModal.find('.physical-dmg-power-up-guard-up').text(dmgRange(withPowerGuard));
|
||||||
|
}
|
||||||
|
|
||||||
|
const magicDmg = (dmg) => `${Math.floor(dmg.min)}-${Math.ceil(dmg.max)}`;
|
||||||
|
const toPer = x => Math.round(x) + '%';
|
||||||
|
|
||||||
|
if (charStats.magic === null) {
|
||||||
|
$enemyInfoModal.find('[class^="magic-"]').text('n/a');
|
||||||
|
} else {
|
||||||
|
const attackerMagic = charStats.magic;
|
||||||
|
const targetMagic = rowData.magic;
|
||||||
|
const res = {
|
||||||
|
ice: rowData.resIce,
|
||||||
|
fire: rowData.resFire,
|
||||||
|
thunder: rowData.resThunder,
|
||||||
|
vacuum: rowData.resVacuum,
|
||||||
|
debuff: rowData.resDebuff,
|
||||||
|
};
|
||||||
|
const resArmor = 0;
|
||||||
|
const resAccessory = 0;
|
||||||
|
|
||||||
|
window.saga.spells.filter(x => !!x.power).forEach((spell) => {
|
||||||
|
const elementalRes = res[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, {
|
||||||
|
attackerMagicUp: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const prefix = '.magic-dmg-' + spell.name;
|
||||||
|
$enemyInfoModal.find(prefix).text(magicDmg(def));
|
||||||
|
$enemyInfoModal.find(prefix + '-magic-up').text(magicDmg(magicUp));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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, {
|
||||||
|
attackerMagicUp: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let prefix = '.magic-dmg-HPCatcher';
|
||||||
|
$enemyInfoModal.find(prefix).text(magicDmg(def));
|
||||||
|
$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, {
|
||||||
|
attackerMagicUp: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
prefix = '.magic-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, {
|
||||||
|
attackerSpeedUp: true,
|
||||||
|
});
|
||||||
|
const runRate = window.saga.calc.runRate(charStats.speed, rowData.speed);
|
||||||
|
const runRateSpeed = window.saga.calc.runRate(charStats.speed, rowData.speed, {
|
||||||
|
attackerSpeedUp: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
$enemyInfoModal.find('.run-rate').text(toPer(runRate));
|
||||||
|
$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 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);
|
||||||
|
$enemyInfoModal.find('.debuff-rate').text(toPer(debuffRate));
|
||||||
|
$enemyInfoModal.find('.vacuum-rate').text(toPer(vacuumRate));
|
||||||
|
|
||||||
|
$enemyInfoModal.modal({
|
||||||
|
show: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
}(window));
|
}(window));
|
||||||
|
84
web/static/spells.js
Normal file
84
web/static/spells.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
(function(exports) {
|
||||||
|
const attackSpell = (name, mp, power, element, multiple) => {
|
||||||
|
return {
|
||||||
|
type: 'Attack',
|
||||||
|
name,
|
||||||
|
mp,
|
||||||
|
power,
|
||||||
|
element,
|
||||||
|
targets: multiple ? 'multi' : 'single',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const effectSpell = (name, mp, element, effect, multiple) => {
|
||||||
|
return {
|
||||||
|
type: 'Effect',
|
||||||
|
name,
|
||||||
|
mp,
|
||||||
|
element,
|
||||||
|
effect,
|
||||||
|
targets: multiple ? 'multi' : 'single',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const healSpell = (name, mp, healingPower, effect) => {
|
||||||
|
return {
|
||||||
|
type: 'Healing',
|
||||||
|
name,
|
||||||
|
mp,
|
||||||
|
healingPower,
|
||||||
|
effect,
|
||||||
|
targets: 'single',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const supportSpell = (name, mp, effect, locations) => {
|
||||||
|
return {
|
||||||
|
type: 'Support',
|
||||||
|
name,
|
||||||
|
mp,
|
||||||
|
effect,
|
||||||
|
locations,
|
||||||
|
targets: 'single',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.spells = [
|
||||||
|
attackSpell('Fire1', 3, 30, 'Fire'),
|
||||||
|
attackSpell('Fire2', 12, 70, 'Fire'),
|
||||||
|
attackSpell('Ice1', 3, 36, 'Ice'),
|
||||||
|
attackSpell('Ice2', 12, 80, 'Ice'),
|
||||||
|
attackSpell('Laser1', 3, 30, 'Thunder'),
|
||||||
|
attackSpell('Laser2', 10, 50, 'Thunder'),
|
||||||
|
attackSpell('Laser3', 20, 100, 'Thunder'),
|
||||||
|
attackSpell('Firebird', 14, 30, 'Fire', true),
|
||||||
|
attackSpell('Fireball', 32, 50, 'Fire', true),
|
||||||
|
attackSpell('Blizzard1', 14, 34, 'Ice', true),
|
||||||
|
attackSpell('Blizzard2', 32, 60, 'Ice', true),
|
||||||
|
attackSpell('Thunder1', 10, 40, 'Thunder', true),
|
||||||
|
attackSpell('Thunder2', 40, 75, 'Thunder', true),
|
||||||
|
|
||||||
|
effectSpell('Petrify', 10, 'Debuff', 'Petrification'),
|
||||||
|
effectSpell('Poison', 0, 'Debuff', 'Poison (monsters only)'),
|
||||||
|
effectSpell('Defense2', 5, 'Debuff', 'Halves guard'),
|
||||||
|
effectSpell('HPCatcher', 6, 'Debuff', 'Drains up to 50 HP and heals caster'),
|
||||||
|
effectSpell('MPCatcher', 8, 'Debuff', 'Drains up to 40 MP and heals caster'),
|
||||||
|
effectSpell('Vacuum1', 30, 'Vacuum', 'Instant death'),
|
||||||
|
effectSpell('Vacuum2', 60, 'Vacuum', 'Instant death', true),
|
||||||
|
|
||||||
|
healSpell('Heal1', 4, 40, 'Restores 40 HP'),
|
||||||
|
healSpell('Heal2', 18, 90, 'Restores 90 HP'),
|
||||||
|
healSpell('Heal3', 34, 'max', 'Restores all HP'),
|
||||||
|
healSpell('Elixir', 120, 'max', 'Restores all HP and MP'),
|
||||||
|
healSpell('Revive1', 40, 'max', 'Restores all HP to dead ally, fails ~50% of the time'),
|
||||||
|
healSpell('Revive2', 90, 'max', 'Restores all HP to dead ally, always succeeds'),
|
||||||
|
|
||||||
|
supportSpell('Purify', 8, 'Removes petrification and poison', ['Battle', 'Map']),
|
||||||
|
supportSpell('Defense1', 5, 'Halves physical damage from enemy', ['Battle']),
|
||||||
|
supportSpell('Power', 6, 'Doubles power', ['Battle']),
|
||||||
|
supportSpell('Agility', 3, 'Increases Speed by 30', ['Battle']),
|
||||||
|
supportSpell('F.Shield', 16, 'Nullifies next attack spell', ['Battle']),
|
||||||
|
supportSpell('Protect', 20, 'Nullifies next Vacuum spell', ['Battle']),
|
||||||
|
supportSpell('Exit', 30, 'Escape cave/dungeon immediately', ['Map']),
|
||||||
|
];
|
||||||
|
}(typeof(module) !== 'undefined' ? module.exports : window.saga));
|
@ -1,30 +1,31 @@
|
|||||||
extends master.pug
|
extends master.pug
|
||||||
|
|
||||||
block tab-content
|
block tab-content
|
||||||
table#main-table.table.table-sm.table-borderless.table-striped.table-hover.sticky-header
|
table#main-table.table.table-sm.table-borderless.table-striped.table-hover.sticky-header.row-clickable
|
||||||
tr.header-above
|
tr.header-above
|
||||||
th(colspan="2")
|
th(colspan="2")
|
||||||
th(colspan="3").text-center.bg-info.text-light Reward
|
th(colspan="2").text-center.bg-info.text-light Reward
|
||||||
th(colspan="7").text-center.bg-secondary.text-light Stats
|
th(colspan="6").text-center.bg-secondary.text-light Stats
|
||||||
th(colspan="5").text-center.bg-dark.text-light Resistance
|
th(colspan="5").text-center.bg-dark.text-light Resistance
|
||||||
|
th(colspan="2")
|
||||||
tr.header
|
tr.header
|
||||||
th.align-middle Img
|
th.align-middle Img
|
||||||
+sortHeader('Name', 'name')
|
+sortHeader('Name', 'name')
|
||||||
+sortHeader('Gold', 'gold')
|
+sortHeader('Gold', 'gold')
|
||||||
+sortHeader('Exp', 'exp')
|
+sortHeader('Exp', 'exp')
|
||||||
th.align-middle Drops
|
|
||||||
+sortHeader('HP', 'hp')
|
+sortHeader('HP', 'hp')
|
||||||
+sortHeader('MP', 'mp')
|
+sortHeader('MP', 'mp')
|
||||||
+sortHeader('Power', 'power')
|
+sortHeader('Power', 'power')
|
||||||
+sortHeader('Guard', 'guard')
|
+sortHeader('Guard', 'guard')
|
||||||
+sortHeader('Magic', 'magic')
|
+sortHeader('Magic', 'magic')
|
||||||
+sortHeader('Speed', 'speed')
|
+sortHeader('Speed', 'speed')
|
||||||
th.align-middle Spells
|
|
||||||
+sortHeader('Fire', 'res-fire')
|
+sortHeader('Fire', 'res-fire')
|
||||||
+sortHeader('Ice', 'res-ice')
|
+sortHeader('Ice', 'res-ice')
|
||||||
+sortHeader('Thunder', 'res-thunder')
|
+sortHeader('Thunder', 'res-thunder')
|
||||||
+sortHeader('Vacuum', 'res-vacuum')
|
+sortHeader('Vacuum', 'res-vacuum')
|
||||||
+sortHeader('Debuff', 'res-debuff')
|
+sortHeader('Debuff', 'res-debuff')
|
||||||
|
th.align-middle Spells
|
||||||
|
th.align-middle Drops
|
||||||
tbody.data: each enemy in enemies
|
tbody.data: each enemy in enemies
|
||||||
tr(
|
tr(
|
||||||
data-name=enemy.name
|
data-name=enemy.name
|
||||||
@ -39,30 +40,128 @@ block tab-content
|
|||||||
data-res-fire=enemy.resistance.fire
|
data-res-fire=enemy.resistance.fire
|
||||||
data-res-ice=enemy.resistance.ice
|
data-res-ice=enemy.resistance.ice
|
||||||
data-res-thunder=enemy.resistance.thunder
|
data-res-thunder=enemy.resistance.thunder
|
||||||
data-res-vacuum=enemy.resistance.vacuum
|
data-res-vacuum=(enemy.resistance.vacuum === null ? 100 : enemy.resistance.vacuum)
|
||||||
data-res-debuff=enemy.resistance.debuff
|
data-res-debuff=(enemy.resistance.debuff === null ? 100 : enemy.resistance.debuff)
|
||||||
)
|
)
|
||||||
td
|
td
|
||||||
td: strong= enemy.name
|
td: strong= enemy.name
|
||||||
td: code= enemy.gold
|
td.text-right: code= enemy.gold
|
||||||
td: code= enemy.exp
|
td.text-right: code= enemy.exp
|
||||||
|
td.text-right: code= enemy.hp
|
||||||
|
td.text-right: code= enemy.mp
|
||||||
|
td.text-right: code= enemy.power
|
||||||
|
td.text-right: code= enemy.guard
|
||||||
|
td.text-right: code= enemy.magic
|
||||||
|
td.text-right: code= enemy.speed
|
||||||
|
td.text-right: code= enemy.resistance.fire
|
||||||
|
td.text-right: code= enemy.resistance.ice
|
||||||
|
td.text-right: code= enemy.resistance.thunder
|
||||||
|
td.text-right: code= enemy.resistance.vacuum === null ? 100 : enemy.resistance.vacuum
|
||||||
|
td.text-right: code= enemy.resistance.debuff === null ? 100 : enemy.resistance.debuff
|
||||||
|
td
|
||||||
|
+na(enemy.spells.length)
|
||||||
|
ul.list-horizontal: each spell in enemy.spells
|
||||||
|
li= spell
|
||||||
td
|
td
|
||||||
+na(Object.keys(enemy.drops).length)
|
+na(Object.keys(enemy.drops).length)
|
||||||
ul.list-horizontal: each rate, item in enemy.drops
|
ul.list-horizontal: each rate, item in enemy.drops
|
||||||
li
|
li
|
||||||
= item + ' (' + (rate * 100).toFixed(2) + '%)'
|
= item + ' (' + (rate * 100).toFixed(2) + '%)'
|
||||||
td: code= enemy.hp
|
|
||||||
td: code= enemy.mp
|
div#enemy-info-modal.modal(tabindex="-1")
|
||||||
td: code= enemy.power
|
div.modal-dialog.modal-lg
|
||||||
td: code= enemy.guard
|
div.modal-content
|
||||||
td: code= enemy.magic
|
div.modal-header
|
||||||
td: code= enemy.speed
|
h5.modal-title
|
||||||
|
span.name
|
||||||
|
div
|
||||||
|
| #[span.gold]/#[span.exp]
|
||||||
|
div.modal-body
|
||||||
|
div.row
|
||||||
|
div.col-6
|
||||||
|
table.table.table-horizontal
|
||||||
|
tr
|
||||||
|
th(colspan="2") Stats
|
||||||
|
th(colspan="2") Resistances
|
||||||
|
tr
|
||||||
|
th HP/MP
|
||||||
td
|
td
|
||||||
+na(enemy.spells.length)
|
span.hp
|
||||||
ul.list-horizontal: each spell in enemy.spells
|
| /
|
||||||
li= spell
|
span.mp
|
||||||
td: code= enemy.resistance.fire
|
th Fire
|
||||||
td: code= enemy.resistance.ice
|
td.res-fire
|
||||||
td: code= enemy.resistance.thunder
|
tr
|
||||||
td: code= enemy.resistance.vacuum
|
th Power
|
||||||
td: code= enemy.resistance.debuff
|
td.power
|
||||||
|
th Ice
|
||||||
|
td.res-ice
|
||||||
|
tr
|
||||||
|
th Guard
|
||||||
|
td.guard
|
||||||
|
th Thunder
|
||||||
|
td.res-thunder
|
||||||
|
tr
|
||||||
|
th Magic
|
||||||
|
td.magic
|
||||||
|
th Vacuum
|
||||||
|
td.res-vacuum
|
||||||
|
tr
|
||||||
|
th Speed
|
||||||
|
td.speed
|
||||||
|
th Debuff
|
||||||
|
td.res-debuff
|
||||||
|
|
||||||
|
div.card.bg-light
|
||||||
|
div.card-body
|
||||||
|
table.table.table-sm.table-horizontal
|
||||||
|
tr
|
||||||
|
th Run rate
|
||||||
|
td: code.run-rate
|
||||||
|
th w/ speed up
|
||||||
|
td: code.run-rate-speed-up
|
||||||
|
tr
|
||||||
|
th Hit rate
|
||||||
|
td: code.hit-rate
|
||||||
|
th w/ speed up
|
||||||
|
td: code.hit-rate-speed-up
|
||||||
|
tr
|
||||||
|
th Debuff rate
|
||||||
|
td: code.debuff-rate
|
||||||
|
th
|
||||||
|
td
|
||||||
|
tr
|
||||||
|
th Vacuum rate
|
||||||
|
td: code.vacuum-rate
|
||||||
|
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'))
|
||||||
|
@ -37,7 +37,8 @@ html
|
|||||||
label.form-check-label(for=id)= location
|
label.form-check-label(for=id)= location
|
||||||
|
|
||||||
div.container-fluid
|
div.container-fluid
|
||||||
ul.nav.nav-tabs.mt-4.px-4.position-sticky(style="top: 0; background-color: white; z-index: 1")
|
div.bg-light.position-sticky.pt-2.d-flex.justify-content-between(style="top: 0; background-color: white; z-index: 1")
|
||||||
|
ul.nav.mr-auto.nav-tabs
|
||||||
li.nav-item: a.nav-link(href="/enemies" class=(context === 'enemies' ? 'active' : '')) Enemies
|
li.nav-item: a.nav-link(href="/enemies" class=(context === 'enemies' ? 'active' : '')) Enemies
|
||||||
li.nav-item: a.nav-link(href="/spells" class=(context === 'spells' ? 'active' : '')) Spells
|
li.nav-item: a.nav-link(href="/spells" class=(context === 'spells' ? 'active' : '')) Spells
|
||||||
li.nav-item: a.nav-link(href="/items" class=(context === 'items' ? 'active' : '')) Items
|
li.nav-item: a.nav-link(href="/items" class=(context === 'items' ? 'active' : '')) Items
|
||||||
@ -45,13 +46,70 @@ html
|
|||||||
li.nav-item: a.nav-link(href="/armor" class=(context === 'armor' ? 'active' : '')) Armor
|
li.nav-item: a.nav-link(href="/armor" class=(context === 'armor' ? 'active' : '')) Armor
|
||||||
li.nav-item: a.nav-link(href="/accessories" class=(context === 'accessories' ? 'active' : '')) Accessories
|
li.nav-item: a.nav-link(href="/accessories" class=(context === 'accessories' ? 'active' : '')) Accessories
|
||||||
li.nav-item: a.nav-link(href="/exp" class=(context === 'exp' ? 'active' : '')) Experience
|
li.nav-item: a.nav-link(href="/exp" class=(context === 'exp' ? 'active' : '')) Experience
|
||||||
li.nav-item: a.nav-link(href="/calc" class=(context === 'calc' ? 'active' : '')) Calculations
|
div
|
||||||
|
button.btn.btn-secondary.btn-sm(data-toggle="modal" data-target="#char-stats-modal") Character stats…
|
||||||
div.tab-content
|
div.tab-content
|
||||||
div.tab-pane.show.active.mt-2
|
div.tab-pane.show.active.mt-2
|
||||||
block tab-content
|
block tab-content
|
||||||
|
|
||||||
|
|
||||||
|
div#char-stats-modal.modal(tabindex="-1")
|
||||||
|
div.modal-dialog
|
||||||
|
div.modal-content
|
||||||
|
div.modal-header
|
||||||
|
h5.modal-title Character stats
|
||||||
|
button.close(type="button" data-dismiss="modal")
|
||||||
|
div.modal-body
|
||||||
|
div.row
|
||||||
|
div.col-6
|
||||||
|
div.form-row.form-group
|
||||||
|
label.col-form-label-sm.col-4(for="char-power") Power
|
||||||
|
div.col-8: input#char-power.form-control(type="number" min="0" step="1" max="999" autocomplete="off")
|
||||||
|
div.form-row.form-group
|
||||||
|
label.col-form-label-sm.col-4(for="char-guard") Guard
|
||||||
|
div.col-8: input#char-guard.form-control(type="number" min="0" step="1" max="999" autocomplete="off")
|
||||||
|
div.form-row.form-group
|
||||||
|
label.col-form-label-sm.col-4(for="char-magic") Magic
|
||||||
|
div.col-8: input#char-magic.form-control(type="number" min="0" step="1" max="255" autocomplete="off")
|
||||||
|
div.form-row.form-group
|
||||||
|
label.col-form-label-sm.col-4(for="char-speed") Speed
|
||||||
|
div.col-8: input#char-speed.form-control(type="number" min="0" step="1" max="255" autocomplete="off")
|
||||||
|
div.col-6
|
||||||
|
div.form-row.form-group
|
||||||
|
label.col-form-label-sm.col-4(for="char-weapon") Weapon
|
||||||
|
div.col-8: select#char-weapon.form-control(autocomplete="off")
|
||||||
|
option(value="") Choose weapon
|
||||||
|
each weapon in charWeapons
|
||||||
|
option(value=weapon.name data-power=weapon.attack)
|
||||||
|
= weapon.name + ' (' + weapon.attack + ')'
|
||||||
|
div.form-row.form-group
|
||||||
|
label.col-form-label-sm.col-4(for="char-armor") Armor
|
||||||
|
div.col-8: select#char-armor.form-control(autocomplete="off")
|
||||||
|
option(value="") Choose armor
|
||||||
|
each armor in charArmor
|
||||||
|
option(value=armor.name data-defense=armor.defense)
|
||||||
|
= armor.name + ' (' + armor.defense + ')'
|
||||||
|
div.form-row.form-group
|
||||||
|
label.col-form-label-sm.col-4(for="char-armor") Accessory
|
||||||
|
div.col-8: select#char-accessory.form-control(autocomplete="off")
|
||||||
|
option(value="") Choose accessory
|
||||||
|
each accessory in charAccessories
|
||||||
|
option(
|
||||||
|
value=accessory.name
|
||||||
|
data-defense=accessory.defense
|
||||||
|
data-res-fire=accessory.resistance.fire
|
||||||
|
data-res-ice=accessory.resistance.ice
|
||||||
|
data-res-thunder=accessory.resistance.thunder
|
||||||
|
data-res-vacuum=accessory.resistance.vacuum
|
||||||
|
data-res-debuff=accessory.resistance.debuff
|
||||||
|
)= accessory.name + ' (' + accessory.defense + ')'
|
||||||
|
div.modal-footer
|
||||||
|
button.btn.btn-primary(data-dismiss="modal") Close
|
||||||
|
|
||||||
script(src="/static/jquery.js")
|
script(src="/static/jquery.js")
|
||||||
script(src="/static/popper.js")
|
script(src="/static/popper.js")
|
||||||
script(src="/static/bootstrap.js")
|
script(src="/static/bootstrap.js")
|
||||||
script(src="/static/js.cookie.js")
|
script(src="/static/js.cookie.js")
|
||||||
script(src="/static/saga.js")
|
script(src="/static/saga.js")
|
||||||
|
script(src="/static/calc.js")
|
||||||
|
script(src="/static/spells.js")
|
||||||
|
@ -2,7 +2,7 @@ extends master.pug
|
|||||||
|
|
||||||
block tab-content
|
block tab-content
|
||||||
|
|
||||||
div.row.d-flex.justify-content-center: div.col-12.col-md-8
|
div.row: div.col-12.col-md-8
|
||||||
+apprenticeFilterForm()
|
+apprenticeFilterForm()
|
||||||
table#main-table.table.table-sm.table-borderless.table-hover.table-striped.sticky-header
|
table#main-table.table.table-sm.table-borderless.table-hover.table-striped.sticky-header
|
||||||
thead: tr.header
|
thead: tr.header
|
||||||
|
Loading…
Reference in New Issue
Block a user