Add character calculator

This commit is contained in:
I Made Setia Baruna 2020-11-03 03:33:19 +07:00
parent e2360f799f
commit 3a396169f4
59 changed files with 2442 additions and 242 deletions

View file

@ -0,0 +1,163 @@
<script>
import { createEventDispatcher } from 'svelte';
import VirtualList from './VirtualList.svelte';
import { fade } from 'svelte/transition';
import { mdiChevronDown } from '@mdi/js';
import Icon from './Icon.svelte';
import { characters as characterList } from '../data/charactersAscension';
const dispatch = createEventDispatcher();
export let placeholder = 'Select character...';
export let selected = null;
export let hoveredIndex = -1;
let label = '';
let focused = false;
let container;
let input;
let search = '';
function toggleOptions() {
focused = !focused;
if (focused) {
input.focus();
}
}
function select(val) {
selected = val;
focused = false;
hoveredIndex = -1;
dispatch('change');
}
function selectIndex(val) {
selected = filteredCharacter[val][1];
focused = false;
hoveredIndex = -1;
dispatch('change');
}
function onWindowClick(event) {
if (!container) return;
const eventTarget = event.path && event.path.length > 0 ? event.path[0] : event.target;
if (container.contains(eventTarget)) return;
focused = false;
hoveredIndex = -1;
}
function onHover(index) {
hoveredIndex = index;
}
function onInput(event) {
search = event.target.value;
}
function clearSearch() {
search = '';
filteredCharacter = characters;
maxItemRow = 6;
}
function onKeyDown(event) {
if (!focused) return;
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
hoveredIndex = hoveredIndex === filteredCharacter.length - 1 ? 0 : hoveredIndex + 1;
break;
case 'ArrowUp':
event.preventDefault();
hoveredIndex = hoveredIndex === 0 ? filteredCharacter.length - 1 : hoveredIndex - 1;
break;
case 'Enter':
event.preventDefault();
if (hoveredIndex >= 0 && hoveredIndex < filteredCharacter.length) selectIndex(hoveredIndex);
break;
case 'Escape':
event.preventDefault();
focused = false;
hoveredIndex = -1;
break;
}
}
$: characters = Object.entries(characterList).sort((a, b) => a[1].name.localeCompare(b[1].name));
$: filteredCharacter = characters.filter((e) => e[1].name.toLowerCase().includes(search));
$: maxItemRow = Math.min(6, filteredCharacter.length);
$: nothingSelected = selected === null;
$: if (!nothingSelected) {
label = selected.name;
}
$: focused, clearSearch();
$: classes = focused ? 'border-primary' : 'border-transparent';
$: iconClasses = focused ? 'transform rotate-180' : '';
</script>
<style>
.hovered {
@apply text-white !important;
@apply bg-primary;
}
.options {
max-height: calc(50vh);
overflow-y: auto;
}
</style>
<svelte:window on:click={onWindowClick} on:keydown={onKeyDown} />
<div class="select-none relative" bind:this={container}>
<div
class={`flex w-full relative items-center px-4 bg-background rounded-2xl h-14 focus-within:outline-none
focus-within:border-primary border-2 border-transparent ease-in duration-100 ${classes}`}
on:click={toggleOptions}>
{#if !nothingSelected}
<img class="w-6 h-6 mr-2" src={`/images/characters/${selected.id}.png`} alt={selected.name} />
{/if}
<input
bind:this={input}
class={`bg-transparent focus:outline-none h-full ${nothingSelected ? 'text-gray-500' : 'text-white'}`}
{placeholder}
value={nothingSelected || focused ? search : label}
on:input={onInput} />
<Icon path={mdiChevronDown} color="white" className={`absolute right-0 mr-4 duration-100 ease-in ${iconClasses}`} />
</div>
{#if focused}
<div
transition:fade={{ duration: 100 }}
class="options bg-item rounded-2xl absolute mt-2 pl-2 w-full min-h-full z-50 flex flex-col text-white shadow-xl border border-background">
{#if filteredCharacter.length}
<VirtualList
itemHeight={48}
height={`${maxItemRow * 48 + 16}px`}
items={filteredCharacter}
let:item={[id, character]}
let:index>
<span
on:click={() => select(character)}
on:mouseenter={() => onHover(index)}
class={`p-3 rounded-xl cursor-pointer flex mr-2
${index === 0 ? 'mt-2' : ''}
${index === characters.length ? 'mb-2' : ''}
${!nothingSelected && selected.id === id ? 'text-primary font-semibold' : ''}
${hoveredIndex === index ? 'hovered' : ''}`}>
<img class="w-6 h-6 mr-2" src={`/images/characters/${id}.png`} alt={character.name} />
<span class="flex-1">{character.name}</span>
</span>
</VirtualList>
{:else}<span class="p-3 rounded-xl cursor-pointer flex mr-2 my-2"> Character not found </span>{/if}
</div>
{/if}
</div>

83
src/data/characterExp.js Normal file
View file

@ -0,0 +1,83 @@
export const characterExp = [
0,
1000,
2325,
4025,
6175,
8800,
11950,
15675,
20025,
25025,
30725,
37175,
44400,
52450,
61375,
71200,
81950,
93675,
106400,
120175,
135050,
151850,
169850,
189100,
209650,
231525,
254775,
279425,
305525,
333100,
362200,
392850,
425100,
458975,
494525,
531775,
570750,
611500,
654075,
698500,
744800,
795425,
848125,
902900,
959800,
1018875,
1080150,
1143675,
1209475,
1277600,
1348075,
1424575,
1503625,
1585275,
1669550,
1756500,
1846150,
1938550,
2033725,
2131725,
2232600,
2341550,
2453600,
2568775,
2687100,
2808625,
2933400,
3061475,
3192875,
3327650,
3465825,
3614525,
3766900,
3922975,
4082800,
4246400,
4413825,
4585125,
4760350,
4939525,
5122700,
];

File diff suppressed because it is too large Load diff

View file

@ -1,32 +1,19 @@
export const itemList = {
unknown: {
id: 'unknown',
name: 'unknown',
},
unknown: { id: 'unknown', name: 'unknown' },
none: { id: 'none', name: 'none' },
fetters_of_the_dandelion_gladiator: {
id: 'fetters_of_the_dandelion_gladiator',
name: 'Fetters of the Dandelion Gladiator',
},
chaos_device: {
id: 'chaos_device',
name: 'Chaos Device',
},
divining_scroll: {
id: 'divining_scroll',
name: 'Divining Scroll',
},
chaos_device: { id: 'chaos_device', name: 'Chaos Device' },
divining_scroll: { id: 'divining_scroll', name: 'Divining Scroll' },
chains_of_the_dandelion_gladiator: {
id: 'chains_of_the_dandelion_gladiator',
name: 'Chains of the Dandelion Gladiator',
},
chaos_circuit: {
id: 'chaos_circuit',
name: 'Chaos Circuit',
},
sealed_scroll: {
id: 'sealed_scroll',
name: 'Sealed Scroll',
},
chaos_circuit: { id: 'chaos_circuit', name: 'Chaos Circuit' },
sealed_scroll: { id: 'sealed_scroll', name: 'Sealed Scroll' },
shackles_of_the_dandelion_gladiator: {
id: 'shackles_of_the_dandelion_gladiator',
name: 'Shackles of the Dandelion Gladiator',
@ -39,10 +26,7 @@ export const itemList = {
id: 'dead_ley_line_branches',
name: 'Dead Ley Line Branches',
},
slime_condensate: {
id: 'slime_condensate',
name: 'Slime Condensate',
},
slime_condensate: { id: 'slime_condensate', name: 'Slime Condensate' },
boreal_wolfs_cracked_tooth: {
id: 'boreal_wolfs_cracked_tooth',
name: "Boreal Wolf's Cracked Tooth",
@ -51,18 +35,12 @@ export const itemList = {
id: 'dead_ley_line_leaves',
name: 'Dead Ley Line Leaves',
},
slime_secretions: {
id: 'slime_secretions',
name: 'Slime Secretions',
},
slime_secretions: { id: 'slime_secretions', name: 'Slime Secretions' },
boreal_wolfs_broken_fang: {
id: 'boreal_wolfs_broken_fang',
name: "Boreal Wolf's Broken Fang",
},
ley_line_sprouts: {
id: 'ley_line_sprouts',
name: 'Ley Line Sprouts',
},
ley_line_sprouts: { id: 'ley_line_sprouts', name: 'Ley Line Sprouts' },
slime_concentrate: {
id: 'slime_concentrate',
name: 'Slime Concentrate',
@ -75,26 +53,17 @@ export const itemList = {
id: 'dead_ley_line_branch',
name: 'Dead Ley Line Branch',
},
firm_arrowhead: {
id: 'firm_arrowhead',
name: 'Firm Arrowhead',
},
firm_arrowhead: { id: 'firm_arrowhead', name: 'Firm Arrowhead' },
weathered_arrowhead: {
id: 'weathered_arrowhead',
name: 'Weathered Arrowhead',
},
chaos_core: {
id: 'chaos_core',
name: 'Chaos Core',
},
chaos_core: { id: 'chaos_core', name: 'Chaos Core' },
dream_of_the_dandelion_gladiator: {
id: 'dream_of_the_dandelion_gladiator',
name: 'Dream of the Dandelion Gladiator',
},
sharp_arrowhead: {
id: 'sharp_arrowhead',
name: 'Sharp Arrowhead',
},
sharp_arrowhead: { id: 'sharp_arrowhead', name: 'Sharp Arrowhead' },
luminous_sands_from_guyun: {
id: 'luminous_sands_from_guyun',
name: 'Luminous Sands from Guyun',
@ -119,10 +88,7 @@ export const itemList = {
id: 'sergeants_insignia',
name: "Sergeant's Insignia",
},
relic_from_guyun: {
id: 'relic_from_guyun',
name: 'Relic from Guyun',
},
relic_from_guyun: { id: 'relic_from_guyun', name: 'Relic from Guyun' },
inspectors_sacrificial_knife: {
id: 'inspectors_sacrificial_knife',
name: "Inspector's Sacrificial Knife",
@ -139,10 +105,7 @@ export const itemList = {
id: 'tile_of_decarabians_tower',
name: "Tile of Decarabian's Tower",
},
heavy_horn: {
id: 'heavy_horn',
name: 'Heavy Horn',
},
heavy_horn: { id: 'heavy_horn', name: 'Heavy Horn' },
debris_of_decarabians_city: {
id: 'debris_of_decarabians_city',
name: "Debris of Decarabian's City",
@ -179,18 +142,12 @@ export const itemList = {
id: 'mist_veiled_mercury_elixir',
name: 'Mist Veiled Mercury Elixir',
},
mist_grass: {
id: 'mist_grass',
name: 'Mist Grass',
},
mist_grass: { id: 'mist_grass', name: 'Mist Grass' },
mist_veiled_gold_elixir: {
id: 'mist_veiled_gold_elixir',
name: 'Mist Veiled Gold Elixir',
},
mist_grass_wick: {
id: 'mist_grass_wick',
name: 'Mist Grass Wick',
},
mist_grass_wick: { id: 'mist_grass_wick', name: 'Mist Grass Wick' },
mist_veiled_primo_elixir: {
id: 'mist_veiled_primo_elixir',
name: 'Mist Veiled Primo Elixir',
@ -203,10 +160,7 @@ export const itemList = {
id: 'fragile_bone_shard',
name: 'Fragile Bone Shard',
},
damaged_mask: {
id: 'damaged_mask',
name: 'Damaged Mask',
},
damaged_mask: { id: 'damaged_mask', name: 'Damaged Mask' },
piece_of_aerosiderite: {
id: 'piece_of_aerosiderite',
name: 'Piece of Aerosiderite',
@ -215,10 +169,7 @@ export const itemList = {
id: 'sturdy_bone_shard',
name: 'Sturdy Bone Shard',
},
stained_mask: {
id: 'stained_mask',
name: 'Stained Mask',
},
stained_mask: { id: 'stained_mask', name: 'Stained Mask' },
bit_of_aerosiderite: {
id: 'bit_of_aerosiderite',
name: 'Bit of Aerosiderite',
@ -227,10 +178,7 @@ export const itemList = {
id: 'fossilized_bone_shard',
name: 'Fossilized Bone Shard',
},
ominous_mask: {
id: 'ominous_mask',
name: 'Ominous Mask',
},
ominous_mask: { id: 'ominous_mask', name: 'Ominous Mask' },
chunk_of_aerosiderite: {
id: 'chunk_of_aerosiderite',
name: 'Chunk of Aerosiderite',
@ -255,18 +203,12 @@ export const itemList = {
id: 'shimmering_nectar',
name: 'Shimmering Nectar',
},
energy_nectar: {
id: 'energy_nectar',
name: 'Energy Nectar',
},
energy_nectar: { id: 'energy_nectar', name: 'Energy Nectar' },
mist_flower_pollen: {
id: 'mist_flower_pollen',
name: 'Mist Flower Pollen',
},
seal_scroll: {
id: 'seal_scroll',
name: 'Seal Scroll',
},
seal_scroll: { id: 'seal_scroll', name: 'Seal Scroll' },
black_copper_horn: {
id: 'black_copper_horn',
name: 'Black Copper Horn',
@ -275,4 +217,139 @@ export const itemList = {
id: 'historic_arrowhead',
name: 'Historic Arrowhead',
},
agnidus_agate_sliver: {
id: 'agnidus_agate_sliver',
name: 'Agnidus Agate Sliver',
},
small_lamp_grass: { id: 'small_lamp_grass', name: 'Small Lamp Grass' },
agnidus_agate_fragment: {
id: 'agnidus_agate_fragment',
name: 'Agnidus Agate Fragment',
},
everflame_seed: { id: 'everflame_seed', name: 'Everflame Seed' },
agnidus_agate_chunk: {
id: 'agnidus_agate_chunk',
name: 'Agnidus Agate Chunk',
},
agnidus_agate_gemstone: {
id: 'agnidus_agate_gemstone',
name: 'Agnidus Agate Gemstone',
},
varunada_lazurite_sliver: {
id: 'varunada_lazurite_sliver',
name: 'Varunada Lazurite Sliver',
},
philanemo_mushroom: {
id: 'philanemo_mushroom',
name: 'Philanemo Mushroom',
},
varunada_lazurite_fragment: {
id: 'varunada_lazurite_fragment',
name: 'Varunada Lazurite Fragment',
},
cleansing_heart: { id: 'cleansing_heart', name: 'Cleansing Heart' },
varunada_lazurite_chunk: {
id: 'varunada_lazurite_chunk',
name: 'Varunada Lazurite Chunk',
},
varunada_lazurite_gemstone: {
id: 'varunada_lazurite_gemstone',
name: 'Varunada Lazurite Gemstone',
},
vajrada_amethyst_sliver: {
id: 'vajrada_amethyst_sliver',
name: 'Vajrada Amethyst Sliver',
},
noctilucous_jade: { id: 'noctilucous_jade', name: 'Noctilucous Jade' },
vajrada_amethyst_fragment: {
id: 'vajrada_amethyst_fragment',
name: 'Vajrada Amethyst Fragment',
},
lightning_prism: { id: 'lightning_prism', name: 'Lightning Prism' },
vajrada_amethyst_chunk: {
id: 'vajrada_amethyst_chunk',
name: 'Vajrada Amethyst Chunk',
},
vajrada_amethyst_gemstone: {
id: 'vajrada_amethyst_gemstone',
name: 'Vajrada Amethyst Gemstone',
},
windwheel_aster: { id: 'windwheel_aster', name: 'Windwheel Aster' },
shivada_jade_sliver: {
id: 'shivada_jade_sliver',
name: 'Shivada Jade Sliver',
},
cor_lapis: { id: 'cor_lapis', name: 'Cor Lapis' },
shivada_jade_fragment: {
id: 'shivada_jade_fragment',
name: 'Shivada Jade Fragment',
},
hoarfrost_core: { id: 'hoarfrost_core', name: 'Hoarfrost Core' },
shivada_jade_chunk: {
id: 'shivada_jade_chunk',
name: 'Shivada Jade Chunk',
},
shivada_jade_gemstone: {
id: 'shivada_jade_gemstone',
name: 'Shivada Jade Gemstone',
},
vayuda_turquoise_sliver: {
id: 'vayuda_turquoise_sliver',
name: 'Vayuda Turquoise Sliver',
},
dandelion_seed: { id: 'dandelion_seed', name: 'Dandelion Seed' },
vayuda_turquoise_fragment: {
id: 'vayuda_turquoise_fragment',
name: 'Vayuda Turquoise Fragment',
},
hurricane_seed: { id: 'hurricane_seed', name: 'Hurricane Seed' },
vayuda_turquoise_chunk: {
id: 'vayuda_turquoise_chunk',
name: 'Vayuda Turquoise Chunk',
},
vayuda_turquoise_gemstone: {
id: 'vayuda_turquoise_gemstone',
name: 'Vayuda Turquoise Gemstone',
},
calla_lily: { id: 'calla_lily', name: 'Calla Lily' },
valberry: { id: 'valberry', name: 'Valberry' },
prithiva_topaz_sliver: {
id: 'prithiva_topaz_sliver',
name: 'Prithiva Topaz Sliver',
},
glaze_lily: { id: 'glaze_lily', name: 'Glaze Lily' },
prithiva_topaz_fragment: {
id: 'prithiva_topaz_fragment',
name: 'Prithiva Topaz Fragment',
},
basalt_pillar: { id: 'basalt_pillar', name: 'Basalt Pillar' },
prithiva_topaz_chunk: {
id: 'prithiva_topaz_chunk',
name: 'Prithiva Topaz Chunk',
},
prithiva_topaz_gemstone: {
id: 'prithiva_topaz_gemstone',
name: 'Prithiva Topaz Gemstone',
},
violetgrass: { id: 'violetgrass', name: 'Violetgrass' },
wolfhook: { id: 'wolfhook', name: 'Wolfhook' },
brilliant_diamond_sliver: {
id: 'brilliant_diamond_sliver',
name: 'Brilliant Diamond Sliver',
},
brilliant_diamond_fragment: {
id: 'brilliant_diamond_fragment',
name: 'Brilliant Diamond Fragment',
},
brilliant_diamond_chunk: {
id: 'brilliant_diamond_chunk',
name: 'Brilliant Diamond Chunk',
},
brilliant_diamond_gemstone: {
id: 'brilliant_diamond_gemstone',
name: 'Brilliant Diamond Gemstone',
},
cecilia: { id: 'cecilia', name: 'Cecilia' },
jueyun_chili: { id: 'jueyun_chili', name: 'Jueyun Chili' },
silk_flower: { id: 'silk_flower', name: 'Silk Flower' },
};

View file

@ -0,0 +1,395 @@
<script>
import { fade } from 'svelte/transition';
import { mdiClose, mdiInformationOutline } from '@mdi/js';
import Input from '../../components/Input.svelte';
import AscensionSelector from '../../components/AscensionSelector.svelte';
import CharacterSelect from '../../components/CharacterSelect.svelte';
import Checkbox from '../../components/Checkbox.svelte';
import Check from '../../components/Check.svelte';
import Button from '../../components/Button.svelte';
import Icon from '../../components/Icon.svelte';
import { characterExp } from '../../data/characterExp';
let resources = [
{ selected: true, disabled: false, image: '/images/items/heros_wit.png', label: "Hero's Wit", value: '20000' },
{
selected: true,
disabled: false,
image: '/images/items/adventurers_experience.png',
label: "Adventurer's Experience",
value: '5000',
},
{
selected: true,
disabled: false,
image: '/images/items/wanderes_advice.png',
label: "Wanderer's Advice",
value: '1000',
},
];
let withAscension = true;
let selectedCharacter = null;
let currentLevel = '';
let currentExp = '';
let currentAscension = 0;
let intendedAscension = 0;
let intendedLevel = '';
let minAscension = 0;
let minIntendedAscension = 0;
let ascensionResouce = {};
let unknownList = {};
let currentMax = null;
let moraNeeded = 0;
let changed = false;
let numberFormat = Intl.NumberFormat();
$: usedResource = resources.filter((e) => e.selected).sort((a, b) => b.value - a.value);
$: currentAscension, updateIntendedAscension();
$: currentLevel, updateMinAscension();
$: intendedLevel, updateMinIntendedAscension();
$: canCalculate =
(withAscension ? selectedCharacter !== null : true) &&
intendedLevel >= currentLevel &&
intendedAscension >= currentAscension &&
currentLevel !== '' &&
currentLevel > 0 &&
currentLevel <= 80 &&
intendedLevel !== '' &&
intendedLevel > 0 &&
intendedLevel <= 80;
function updateIntendedAscension() {
intendedAscension = Math.max(currentAscension, intendedAscension);
}
function updateMinAscension() {
if (currentLevel > 80) {
minAscension = 6;
} else if (currentLevel > 70) {
minAscension = 5;
} else if (currentLevel > 60) {
minAscension = 4;
} else if (currentLevel > 50) {
minAscension = 3;
} else if (currentLevel > 40) {
minAscension = 2;
} else if (currentLevel > 20) {
minAscension = 1;
} else {
minAscension = 0;
}
currentAscension = Math.max(currentAscension, minAscension);
}
function updateMinIntendedAscension() {
if (intendedLevel > 80) {
minIntendedAscension = 6;
} else if (intendedLevel > 70) {
minIntendedAscension = 5;
} else if (intendedLevel > 60) {
minIntendedAscension = 4;
} else if (intendedLevel > 50) {
minIntendedAscension = 3;
} else if (intendedLevel > 40) {
minIntendedAscension = 2;
} else if (intendedLevel > 20) {
minIntendedAscension = 1;
} else {
minIntendedAscension = 0;
}
intendedAscension = Math.max(intendedAscension, minIntendedAscension);
}
function onChange() {
changed = true;
}
function getSuffix(val) {
switch (val) {
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
case 4:
return 'th';
}
}
function calculateAscension() {
const current = Math.max(currentAscension, 0);
const target = intendedAscension;
console.log(selectedCharacter.ascension);
const result = selectedCharacter.ascension.slice(current, target).reduce(
(sum, character, index) => {
if (character.mora === 0) {
if (unknownList[index + current] === undefined) {
unknownList[index + current] = ['Mora amount'];
}
}
const mora = sum.mora + character.mora;
const items = sum.items;
for (const [i, item] of character.items.entries()) {
if (item.item.id === 'unknown') {
if (unknownList[index + current] === undefined) {
unknownList[index + current] = [];
}
unknownList[index + current].push(`${i + 1}${getSuffix(i + 1)} material`);
}
if (items[item.item.id] === undefined) {
items[item.item.id] = { ...item.item, amount: 0 };
}
items[item.item.id].amount += item.amount;
}
return { items, mora };
},
{
mora: 0,
items: {},
},
);
moraNeeded = moraNeeded + result.mora;
ascensionResouce = result.items;
console.log(ascensionResouce);
}
function calculate() {
unknownList = {};
ascensionResouce = {};
const values = resources
.filter((e) => e.selected)
.map((e) => e.value)
.sort((a, b) => b - a);
const items = values.map(() => 0);
const target = characterExp[intendedLevel - 1] - (characterExp[currentLevel - 1] + currentExp);
let current = target;
let max = [];
moraNeeded = (Math.floor(target / 1000) * 1000) / 5;
items[0] = Math.ceil(current / values[0]);
max.push({
usage: [...items],
over: current - items[0] * values[0],
});
items[0] = Math.ceil(current / values[0]);
items[0] -= 1;
items[1] = Math.ceil((current - items[0] * values[0]) / values[1]);
max.push({
usage: [...items],
over: current - (items[0] * values[0] + items[1] * values[1]),
});
function process(usage, start) {
let i = start;
if (i === values.length - 1) return;
while (usage[i] > 0) {
usage[i]--;
usage[i + 1] = 0;
let currentTotal = usage.reduce((total, e, f) => {
total += e * values[f];
return total;
}, 0);
usage[i + 1] = Math.ceil((target - currentTotal) / values[i + 1]);
currentTotal = usage.reduce((total, e, f) => {
total += e * values[f];
return total;
}, 0);
max.push({
usage: [...usage],
over: target - currentTotal,
});
if (usage[i] === 0) i++;
if (i === values.length - 1) break;
process([...usage], start + 1);
}
}
process([...items], 1);
currentMax = max.sort((a, b) => b.over - a.over)[0];
if (withAscension) {
calculateAscension();
}
changed = false;
}
</script>
<div class="bg-item rounded-xl p-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-4">
<div>
<div class="grid gap-2">
<Check on:change={onChange} bind:checked={withAscension}>Calculate Ascension Material?</Check>
{#if withAscension}
<CharacterSelect on:change={onChange} bind:selected={selectedCharacter} placeholder="Select character" />
{/if}
<div class="grid gap-2">
<p class="text-white text-center mt-3">Current Character Level, Exp, & Ascension</p>
<Input
on:change={onChange}
type="number"
min={1}
max={80}
bind:value={currentLevel}
placeholder="Input current character level..." />
<Input
on:change={onChange}
type="number"
min={0}
bind:value={currentExp}
placeholder="Input current character exp..." />
{#if withAscension}
<AscensionSelector min={minAscension} bind:value={currentAscension} on:change={onChange} />
{/if}
</div>
<div class="grid gap-2">
<p class="text-white text-center mt-3">Intended Character Level & Ascension</p>
<Input
on:change={onChange}
type="number"
min={currentLevel}
max={80}
bind:value={intendedLevel}
placeholder="Input intended character level..." />
{#if withAscension}
<AscensionSelector
min={Math.max(currentAscension, minIntendedAscension)}
bind:value={intendedAscension}
on:change={onChange} />
{/if}
</div>
</div>
</div>
<div class="flex flex-col pl-1">
<p class="text-white text-center md:text-left mb-1">Resource to Use</p>
{#each resources as res}
<div class="mb-1">
<Checkbox disabled={res.disabled} bind:checked={res.selected} on:change={onChange}>
<span class="text-white">
{#if res.image}
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={res.image} alt={res.label} />
</span>
{/if}
{res.label}
</span>
</Checkbox>
</div>
{/each}
</div>
<div class="md:col-span-2 xl:col-span-1">
<Button disabled={!canCalculate} className="block w-full md:w-auto" on:click={calculate}>Calculate</Button>
{#if currentMax !== null && !changed}
{#if Object.keys(unknownList).length > 0}
<div class="border-2 border-red-400 rounded-xl mt-2 p-4 md:inline-block">
<p class="font-bold flex items-center text-red-400">
<Icon className="mr-1 mb-1" path={mdiInformationOutline} />
There are some unknown information
</p>
{#each Object.entries(unknownList) as [title, values]}
<p class="text-red-400">Ascension level {Number(title) + 1}</p>
<ul>
{#each values as val}
<li class="pl-4 text-red-400">- {val}</li>
{/each}
</ul>
{/each}
</div>
{/if}
<div transition:fade={{ duration: 100 }} class="bg-background rounded-xl p-4 mt-2 block md:inline-block">
<table>
{#each usedResource as res, i}
{#if currentMax.usage[i] > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
{#if res.image}
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={res.image} alt={res.label} />
</span>
{/if}
{res.label}
</span>
</td>
</tr>
{/if}
{/each}
{#each Object.entries(ascensionResouce) as [id, item]}
{#if item.amount > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{item.amount}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={`/images/items/${id}.png`} alt={item.name} />
</span>
{item.name}
</span>
</td>
</tr>
{/if}
{/each}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src="/images/mora.png" alt="Mora" />
</span>
Mora (approximate)
</span>
</td>
</tr>
{#if currentMax.over < 0}
<tr>
<td />
<td class="text-red-400 py-1">{currentMax.over * -1} EXP Wasted</td>
</tr>
{/if}
</table>
</div>
{/if}
</div>
</div>
</div>

View file

@ -2,16 +2,16 @@
import { fade } from 'svelte/transition';
import { mdiStar, mdiClose, mdiInformationOutline } from '@mdi/js';
import Select from '../components/Select.svelte';
import Input from '../components/Input.svelte';
import AscensionSelector from '../components/AscensionSelector.svelte';
import WeaponSelect from '../components/WeaponSelect.svelte';
import Checkbox from '../components/Checkbox.svelte';
import Check from '../components/Check.svelte';
import Button from '../components/Button.svelte';
import Icon from '../components/Icon.svelte';
import Select from '../../components/Select.svelte';
import Input from '../../components/Input.svelte';
import AscensionSelector from '../../components/AscensionSelector.svelte';
import WeaponSelect from '../../components/WeaponSelect.svelte';
import Checkbox from '../../components/Checkbox.svelte';
import Check from '../../components/Check.svelte';
import Button from '../../components/Button.svelte';
import Icon from '../../components/Icon.svelte';
import { weaponExp } from '../data/weaponExp';
import { weaponExp } from '../../data/weaponExp';
let weaponsRarity = [
{ label: '3 Star', value: 3 },
@ -250,165 +250,158 @@
}
</script>
<svelte:head>
<title>Calculator - Paimon.moe</title>
</svelte:head>
<div class="pt-20 lg:ml-64 lg:pt-8 p-8">
<h1 class="font-display font-black text-5xl text-white mb-2">Calculator</h1>
<div class="bg-item rounded-xl p-4">
<!-- <h2 class="font-display font-bold text-2xl text-white mb-2">Weapon Level Calculator</h2> -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-4">
<div>
<div class="grid gap-2">
<Check on:change={onChange} bind:checked={withAscension}>Calculate Ascension Material?</Check>
{#if !withAscension}
<Select
on:change={onChange}
bind:selected={rarity}
icon={mdiStar}
options={weaponsRarity}
placeholder="Select weapon rarity" />
{:else}
<WeaponSelect on:change={onChange} bind:selected={selectedWeapon} placeholder="Select weapon" />
{/if}
<div class="bg-item rounded-xl p-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-4">
<div>
<div class="grid gap-2">
<Check on:change={onChange} bind:checked={withAscension}>Calculate Ascension Material?</Check>
{#if !withAscension}
<Select
on:change={onChange}
bind:selected={rarity}
icon={mdiStar}
options={weaponsRarity}
placeholder="Select weapon rarity" />
{:else}
<WeaponSelect on:change={onChange} bind:selected={selectedWeapon} placeholder="Select weapon" />
{/if}
<div class="grid gap-2">
<p class="text-white text-center mt-3">Current Weapon Level, Exp, & Ascension</p>
<Input
on:change={onChange}
type="number"
min={1}
max={80}
bind:value={currentLevel}
placeholder="Input current weapon level..." />
<Input
on:change={onChange}
type="number"
min={0}
bind:value={currentExp}
placeholder="Input current weapon exp..." />
{#if withAscension}
<AscensionSelector min={minAscension} bind:value={currentAscension} on:change={onChange} />
{/if}
</div>
<div class="grid gap-2">
<p class="text-white text-center mt-3">Intended Weapon Level & Ascension</p>
<Input
on:change={onChange}
type="number"
min={currentLevel}
max={80}
bind:value={intendedLevel}
placeholder="Input intended weapon level..." />
{#if withAscension}
<AscensionSelector
min={Math.max(currentAscension, minIntendedAscension)}
bind:value={intendedAscension}
on:change={onChange} />
{/if}
</div>
<div class="grid gap-2">
<p class="text-white text-center mt-3">Current Weapon Level, Exp, & Ascension</p>
<Input
on:change={onChange}
type="number"
min={1}
max={80}
bind:value={currentLevel}
placeholder="Input current weapon level..." />
<Input
on:change={onChange}
type="number"
min={0}
bind:value={currentExp}
placeholder="Input current weapon exp..." />
{#if withAscension}
<AscensionSelector min={minAscension} bind:value={currentAscension} on:change={onChange} />
{/if}
</div>
<div class="grid gap-2">
<p class="text-white text-center mt-3">Intended Weapon Level & Ascension</p>
<Input
on:change={onChange}
type="number"
min={currentLevel}
max={80}
bind:value={intendedLevel}
placeholder="Input intended weapon level..." />
{#if withAscension}
<AscensionSelector
min={Math.max(currentAscension, minIntendedAscension)}
bind:value={intendedAscension}
on:change={onChange} />
{/if}
</div>
</div>
<div class="flex flex-col pl-1">
<p class="text-white text-center md:text-left mb-1">Resource to Use</p>
{#each resources as res}
<div class="mb-1">
<Checkbox disabled={res.disabled} bind:checked={res.selected} on:change={onChange}>
<span class="text-white">
{#if res.image}
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={res.image} alt={res.label} />
</span>
{/if}
{res.label}
</span>
</Checkbox>
</div>
{/each}
</div>
<div class="md:col-span-2 xl:col-span-1">
<Button disabled={!canCalculate} className="block w-full md:w-auto" on:click={calculate}>Calculate</Button>
{#if currentMax !== null && !changed}
{#if Object.keys(unknownList).length > 0}
<div class="border-2 border-red-400 rounded-xl mt-2 p-4 md:inline-block">
<p class="font-bold flex items-center text-red-400">
<Icon className="mr-1 mb-1" path={mdiInformationOutline} />
There are some unknown information
</p>
{#each Object.entries(unknownList) as [title, values]}
<p class="text-red-400">Ascension level {Number(title) + 1}</p>
<ul>
{#each values as val}
<li class="pl-4 text-red-400">- {val}</li>
{/each}
</ul>
{/each}
</div>
{/if}
<div transition:fade={{ duration: 100 }} class="bg-background rounded-xl p-4 mt-2 block md:inline-block">
<table>
{#each usedResource as res, i}
{#if currentMax.usage[i] > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
{#if res.image}
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={res.image} alt={res.label} />
</span>
{/if}
{res.label}
</span>
</td>
</tr>
{/if}
{/each}
{#each Object.entries(ascensionResouce) as [id, item]}
{#if item.amount > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{item.amount}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={`/images/items/${id}.png`} alt={item.name} />
</span>
{item.name}
</span>
</td>
</tr>
{/if}
{/each}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src="/images/mora.png" alt="Mora" />
</span>
Mora (approximate ±40)
</span>
</td>
</tr>
{#if currentMax.over < 0}
<tr>
<td />
<td class="text-red-400 py-1">{currentMax.over * -1} EXP Wasted</td>
</tr>
</div>
<div class="flex flex-col pl-1">
<p class="text-white text-center md:text-left mb-1">Resource to Use</p>
{#each resources as res}
<div class="mb-1">
<Checkbox disabled={res.disabled} bind:checked={res.selected} on:change={onChange}>
<span class="text-white">
{#if res.image}
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={res.image} alt={res.label} />
</span>
{/if}
</table>
{res.label}
</span>
</Checkbox>
</div>
{/each}
</div>
<div class="md:col-span-2 xl:col-span-1">
<Button disabled={!canCalculate} className="block w-full md:w-auto" on:click={calculate}>Calculate</Button>
{#if currentMax !== null && !changed}
{#if Object.keys(unknownList).length > 0}
<div class="border-2 border-red-400 rounded-xl mt-2 p-4 md:inline-block">
<p class="font-bold flex items-center text-red-400">
<Icon className="mr-1 mb-1" path={mdiInformationOutline} />
There are some unknown information
</p>
{#each Object.entries(unknownList) as [title, values]}
<p class="text-red-400">Ascension level {Number(title) + 1}</p>
<ul>
{#each values as val}
<li class="pl-4 text-red-400">- {val}</li>
{/each}
</ul>
{/each}
</div>
{/if}
</div>
<div transition:fade={{ duration: 100 }} class="bg-background rounded-xl p-4 mt-2 block md:inline-block">
<table>
{#each usedResource as res, i}
{#if currentMax.usage[i] > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{currentMax.usage[i]}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
{#if res.image}
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={res.image} alt={res.label} />
</span>
{/if}
{res.label}
</span>
</td>
</tr>
{/if}
{/each}
{#each Object.entries(ascensionResouce) as [id, item]}
{#if item.amount > 0}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{item.amount}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src={`/images/items/${id}.png`} alt={item.name} />
</span>
{item.name}
</span>
</td>
</tr>
{/if}
{/each}
<tr>
<td class="text-right border-b border-gray-700 py-1">
<span class="text-white mr-2 whitespace-no-wrap">{numberFormat.format(moraNeeded)}
<Icon size={0.5} path={mdiClose} /></span>
</td>
<td class="border-b border-gray-700 py-1">
<span class="text-white">
<span class="w-6 inline-block">
<img class="h-6 inline-block mr-1" src="/images/mora.png" alt="Mora" />
</span>
Mora (approximate ±40)
</span>
</td>
</tr>
{#if currentMax.over < 0}
<tr>
<td />
<td class="text-red-400 py-1">{currentMax.over * -1} EXP Wasted</td>
</tr>
{/if}
</table>
</div>
{/if}
</div>
</div>
</div>

View file

@ -0,0 +1,55 @@
<script>
import { mdiArrowDown, mdiArrowUp } from '@mdi/js';
import WeaponCalculator from './_weapon.svelte';
import CharacterCalculator from './_character.svelte';
import Button from '../../components/Button.svelte';
import Icon from '../../components/Icon.svelte';
let weaponCalc;
let characterCalc;
export function scroll(type) {
const elementPosition =
type === 'character' ? characterCalc.getBoundingClientRect().top : weaponCalc.getBoundingClientRect().top;
const headerOffset = 80;
const offsetPosition = elementPosition - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth',
});
}
</script>
<svelte:head>
<title>Calculator - Paimon.moe</title>
</svelte:head>
<div class="pt-20 lg:ml-64 lg:pt-8 p-8">
<div
class="flex flex-col items-center md:flex-row-reverse md:justify-end md:items-start lg:items-center mb-2"
bind:this={weaponCalc}>
<Button on:click={() => scroll('character')}>
<Icon size={0.8} path={mdiArrowDown} />
Go To Character Calculator
</Button>
<h1
class="font-display font-black text-center mt-2 md:mt-0 md:mr-2 xl:mr-8 text-3xl lg:text-left lg:text-5xl text-white">
Weapon Calculator
</h1>
</div>
<WeaponCalculator />
<div
class="flex flex-col items-center md:flex-row-reverse md:justify-end md:items-start lg:items-center mt-8 mb-2"
bind:this={characterCalc}>
<Button on:click={() => scroll('weapon')}>
<Icon size={0.8} path={mdiArrowUp} />
Go To Weapon Calculator
</Button>
<h1
class="font-display font-black text-center mt-2 md:mt-0 md:mr-2 xl:mr-8 text-3xl lg:text-left lg:text-5xl text-white">
Character Calculator
</h1>
</div>
<CharacterCalculator />
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB