Update weapon calculator
29
src/components/AscensionSelector.svelte
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let value = 1;
|
||||
export let min = 0;
|
||||
|
||||
const dispath = createEventDispatcher();
|
||||
|
||||
function setValue(val) {
|
||||
if (value === val) {
|
||||
value = Math.max(min, val - 1);
|
||||
} else {
|
||||
value = Math.max(min, val);
|
||||
}
|
||||
|
||||
dispath('change');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 justify-center items-center bg-background rounded-2xl h-14">
|
||||
{#each { length: 6 } as _, i}
|
||||
<span on:click={() => setValue(i + 1)} class="cursor-pointer">
|
||||
<img
|
||||
class={`w-10 h-10 transition duration-100 ${i > value - 1 ? 'opacity-25' : ''}`}
|
||||
src="/images/ascension_star.png"
|
||||
alt="level" />
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
|
@ -1,8 +1,10 @@
|
|||
<script>
|
||||
export let className;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
{disabled}
|
||||
class={`text-white border-2 border-white border-opacity-25 rounded-xl px-4 py-2 transition duration-100
|
||||
hover:border-primary focus:outline-none focus:border-primary ${className}`}
|
||||
hover:border-primary focus:outline-none focus:border-primary disabled:opacity-50 disabled:border-gray-600 ${className}`}
|
||||
on:click><slot /></button>
|
||||
|
|
36
src/components/Check.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script>
|
||||
export let checked = false;
|
||||
export let disabled;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.checkbox {
|
||||
fill: none;
|
||||
|
||||
@apply text-primary;
|
||||
@apply stroke-current;
|
||||
stroke-dasharray: 100;
|
||||
stroke-dashoffset: 100;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 0.85rem;
|
||||
|
||||
transition: background 0.1s, stroke-dashoffset 0.15s ease-in;
|
||||
}
|
||||
|
||||
.checkbox-wrapper:hover .checkbox {
|
||||
@apply bg-background-secondary;
|
||||
}
|
||||
|
||||
input:checked + .checkbox {
|
||||
@apply bg-item !important;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<label class="checkbox-wrapper flex flex-1 pl-4 items-center bg-background rounded-2xl h-14 cursor-pointer">
|
||||
<span class="flex-1 text-white"><slot /></span>
|
||||
<input {disabled} class="w-0 h-0 opacity-0" on:change bind:checked type="checkbox" />
|
||||
<svg class="checkbox border-4 border-item w-10 h-10 rounded-xl mr-2" viewBox="0 0 100 100">
|
||||
<polyline points="20,50 42,70 80,30" />
|
||||
</svg>
|
||||
</label>
|
9
src/components/Checkbox.svelte
Normal file
|
@ -0,0 +1,9 @@
|
|||
<script>
|
||||
export let checked = false;
|
||||
export let disabled;
|
||||
</script>
|
||||
|
||||
<label class="flex items-center">
|
||||
<input {disabled} class="mr-2 w-4 h-4" on:change bind:checked type="checkbox" />
|
||||
<slot />
|
||||
</label>
|
|
@ -1,19 +1,35 @@
|
|||
<script>
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
export let icon;
|
||||
export let icon = null;
|
||||
export let placeholder;
|
||||
export let type = 'text';
|
||||
export let min;
|
||||
export let max;
|
||||
|
||||
export let value = '';
|
||||
|
||||
const handleInput = (event) => {
|
||||
if (type === 'number') {
|
||||
value = Number(event.target.value);
|
||||
} else {
|
||||
value = event.target.value;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex flex-1 relative items-center bg-item rounded-2xl h-14 focus-within:border-primary border-2 border-transparent ease-in duration-100">
|
||||
class="flex flex-1 relative items-center bg-background rounded-2xl h-14 focus-within:border-primary border-2 border-transparent ease-in duration-100">
|
||||
{#if icon}
|
||||
<Icon path={icon} color="white" className="absolute ml-4 w-6 h-6" />
|
||||
{/if}
|
||||
<input
|
||||
{placeholder}
|
||||
bind:value
|
||||
class="w-full pl-12 pr-4 text-white placeholder-gray-500 leading-none bg-transparent border-none focus:outline-none" />
|
||||
{type}
|
||||
{value}
|
||||
{min}
|
||||
{max}
|
||||
on:change
|
||||
on:input={handleInput}
|
||||
class={`w-full ${icon ? 'pl-12' : 'pl-4'} pr-4 text-white placeholder-gray-500 leading-none bg-transparent border-none focus:outline-none`} />
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import Icon from './Icon.svelte';
|
||||
import { mdiChevronDown, mdiCheck } from '@mdi/js';
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let icon = null;
|
||||
export let options;
|
||||
|
@ -41,6 +44,8 @@
|
|||
selected = options[index];
|
||||
focused = false;
|
||||
hoveredIndex = -1;
|
||||
|
||||
dispatch('change');
|
||||
}
|
||||
|
||||
function onWindowClick(event) {
|
||||
|
@ -104,9 +109,9 @@
|
|||
|
||||
<svelte:window on:click={onWindowClick} on:keydown={onKeyDown} />
|
||||
|
||||
<div class="select-none relative flex-1" bind:this={container}>
|
||||
<div class="select-none relative" bind:this={container}>
|
||||
<button
|
||||
class={`flex w-full relative items-center px-4 bg-item rounded-2xl h-14 focus:outline-none focus:border-primary border-2 border-transparent ease-in duration-100 ${classes}`}
|
||||
class={`flex w-full relative items-center px-4 bg-background rounded-2xl h-14 focus:outline-none focus:border-primary border-2 border-transparent ease-in duration-100 ${classes}`}
|
||||
on:click={toggleOptions}>
|
||||
{#if icon}
|
||||
<Icon path={icon} color="white" className="mr-3" />
|
||||
|
@ -117,11 +122,11 @@
|
|||
{#if focused}
|
||||
<div
|
||||
transition:fade={{ duration: 100 }}
|
||||
class="bg-item rounded-2xl absolute mt-2 p-2 w-full z-50 flex flex-col text-white shadow-select border border-background">
|
||||
class="bg-item rounded-2xl absolute mt-2 p-2 w-full z-50 flex flex-col text-white shadow-xl border border-background">
|
||||
{#each options as option, index}
|
||||
<span
|
||||
on:click={() => select(index)}
|
||||
on:mouseenter={() => onHover(index)}
|
||||
on:click={() => !option.disabled && select(index)}
|
||||
on:mouseenter={() => !option.disabled && onHover(index)}
|
||||
class={`p-3 rounded-xl cursor-pointer flex
|
||||
${selectedIndex === index || selectedMulti.has(index) ? 'text-primary font-semibold' : ''}
|
||||
${hoveredIndex === index ? 'hovered' : ''}`}>
|
||||
|
|
|
@ -56,4 +56,10 @@
|
|||
image="/images/wish.png"
|
||||
label="Wish Counter"
|
||||
href="/wish" />
|
||||
<SidebarItem
|
||||
on:clicked={close}
|
||||
active={segment === 'calculator'}
|
||||
image="/images/calculator.png"
|
||||
label="Calculator"
|
||||
href="/calculator" />
|
||||
</div>
|
||||
|
|
139
src/components/VirtualList.svelte
Normal file
|
@ -0,0 +1,139 @@
|
|||
<!-- Copyright (c) 2018 Rich Harris
|
||||
|
||||
Permission is hereby granted by the authors of this software, to any person, to use the software for any purpose, free of charge, including the rights to run, read, copy, change, distribute and sell it, and including usage rights to any patents the authors may hold on it, subject to the following conditions:
|
||||
|
||||
This license, or a link to its text, must be included with all copies of the software and any derivative works.
|
||||
|
||||
Any modification to the software submitted to the authors may be incorporated into the software under the terms of this license.
|
||||
|
||||
The software is provided "as is", without warranty of any kind, including but not limited to the warranties of title, fitness, merchantability and non-infringement. The authors have no obligation to provide support or updates for the software, and may not be held liable for any damages, claims or other liability arising from its use. -->
|
||||
<script>
|
||||
import { onMount, tick } from 'svelte';
|
||||
// props
|
||||
export let items;
|
||||
export let height = '100%';
|
||||
export let itemHeight = undefined;
|
||||
let foo;
|
||||
// read-only, but visible to consumers via bind:start
|
||||
export let start = 0;
|
||||
export let end = 0;
|
||||
// local state
|
||||
let height_map = [];
|
||||
let rows;
|
||||
let viewport;
|
||||
let contents;
|
||||
let viewport_height = 0;
|
||||
let visible;
|
||||
let mounted;
|
||||
let top = 0;
|
||||
let bottom = 0;
|
||||
let average_height;
|
||||
$: visible = items.slice(start, end).map((data, i) => {
|
||||
return { index: i + start, data };
|
||||
});
|
||||
// whenever `items` changes, invalidate the current heightmap
|
||||
$: if (mounted) refresh(items, viewport_height, itemHeight);
|
||||
async function refresh(items, viewport_height, itemHeight) {
|
||||
const { scrollTop } = viewport;
|
||||
await tick(); // wait until the DOM is up to date
|
||||
let content_height = top - scrollTop;
|
||||
let i = start;
|
||||
while (content_height < viewport_height && i < items.length) {
|
||||
let row = rows[i - start];
|
||||
if (!row) {
|
||||
end = i + 1;
|
||||
await tick(); // render the newly visible row
|
||||
row = rows[i - start];
|
||||
}
|
||||
const row_height = (height_map[i] = itemHeight || row.offsetHeight);
|
||||
content_height += row_height;
|
||||
i += 1;
|
||||
}
|
||||
end = i;
|
||||
const remaining = items.length - end;
|
||||
average_height = (top + content_height) / end;
|
||||
bottom = remaining * average_height;
|
||||
height_map.length = items.length;
|
||||
}
|
||||
async function handle_scroll() {
|
||||
const { scrollTop } = viewport;
|
||||
const old_start = start;
|
||||
for (let v = 0; v < rows.length; v += 1) {
|
||||
height_map[start + v] = itemHeight || rows[v].offsetHeight;
|
||||
}
|
||||
let i = 0;
|
||||
let y = 0;
|
||||
while (i < items.length) {
|
||||
const row_height = height_map[i] || average_height;
|
||||
if (y + row_height > scrollTop) {
|
||||
start = i;
|
||||
top = y;
|
||||
break;
|
||||
}
|
||||
y += row_height;
|
||||
i += 1;
|
||||
}
|
||||
while (i < items.length) {
|
||||
y += height_map[i] || average_height;
|
||||
i += 1;
|
||||
if (y > scrollTop + viewport_height) break;
|
||||
}
|
||||
end = i;
|
||||
const remaining = items.length - end;
|
||||
average_height = y / end;
|
||||
while (i < items.length) height_map[i++] = average_height;
|
||||
bottom = remaining * average_height;
|
||||
// prevent jumping if we scrolled up into unknown territory
|
||||
if (start < old_start) {
|
||||
await tick();
|
||||
let expected_height = 0;
|
||||
let actual_height = 0;
|
||||
for (let i = start; i < old_start; i += 1) {
|
||||
if (rows[i - start]) {
|
||||
expected_height += height_map[i];
|
||||
actual_height += itemHeight || rows[i - start].offsetHeight;
|
||||
}
|
||||
}
|
||||
const d = actual_height - expected_height;
|
||||
viewport.scrollTo(0, scrollTop + d);
|
||||
}
|
||||
// TODO if we overestimated the space these
|
||||
// rows would occupy we may need to add some
|
||||
// more. maybe we can just call handle_scroll again?
|
||||
}
|
||||
// trigger initial refresh
|
||||
onMount(() => {
|
||||
rows = contents.getElementsByTagName('svelte-virtual-list-row');
|
||||
mounted = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
svelte-virtual-list-viewport {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
display: block;
|
||||
}
|
||||
svelte-virtual-list-contents,
|
||||
svelte-virtual-list-row {
|
||||
display: block;
|
||||
}
|
||||
svelte-virtual-list-row {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte-virtual-list-viewport
|
||||
bind:this={viewport}
|
||||
bind:offsetHeight={viewport_height}
|
||||
on:scroll={handle_scroll}
|
||||
style="height: {height};">
|
||||
<svelte-virtual-list-contents bind:this={contents} style="padding-top: {top}px; padding-bottom: {bottom}px;">
|
||||
{#each visible as row (row.index)}
|
||||
<svelte-virtual-list-row>
|
||||
<slot item={row.data} index={row.index}>Missing template</slot>
|
||||
</svelte-virtual-list-row>
|
||||
{/each}
|
||||
</svelte-virtual-list-contents>
|
||||
</svelte-virtual-list-viewport>
|
165
src/components/WeaponSelect.svelte
Normal file
|
@ -0,0 +1,165 @@
|
|||
<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 { weaponList } from '../data/weaponList';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let placeholder = 'Select weapon...';
|
||||
|
||||
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 = filteredWeapons[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 = '';
|
||||
filteredWeapons = weapons;
|
||||
maxItemRow = 6;
|
||||
}
|
||||
|
||||
function onKeyDown(event) {
|
||||
if (!focused) return;
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
hoveredIndex = hoveredIndex === filteredWeapons.length - 1 ? 0 : hoveredIndex + 1;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
hoveredIndex = hoveredIndex === 0 ? filteredWeapons.length - 1 : hoveredIndex - 1;
|
||||
break;
|
||||
case 'Enter':
|
||||
event.preventDefault();
|
||||
if (hoveredIndex >= 0 && hoveredIndex < filteredWeapons.length) selectIndex(hoveredIndex);
|
||||
break;
|
||||
case 'Escape':
|
||||
event.preventDefault();
|
||||
focused = false;
|
||||
hoveredIndex = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$: weapons = Object.entries(weaponList)
|
||||
.filter((e) => e[1].rarity >= 3)
|
||||
.sort((a, b) => a[1].name.localeCompare(b[1].name));
|
||||
|
||||
$: filteredWeapons = weapons.filter((e) => e[1].name.toLowerCase().includes(search));
|
||||
$: maxItemRow = Math.min(6, filteredWeapons.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/weapons/${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 filteredWeapons.length}
|
||||
<VirtualList
|
||||
itemHeight={48}
|
||||
height={`${maxItemRow * 48 + 16}px`}
|
||||
items={filteredWeapons}
|
||||
let:item={[id, weapon]}
|
||||
let:index>
|
||||
<span
|
||||
on:click={() => select(weapon)}
|
||||
on:mouseenter={() => onHover(index)}
|
||||
class={`p-3 rounded-xl cursor-pointer flex mr-2
|
||||
${index === 0 ? 'mt-2' : ''}
|
||||
${index === weapons.length ? 'mb-2' : ''}
|
||||
${!nothingSelected && selected.id === id ? 'text-primary font-semibold' : ''}
|
||||
${hoveredIndex === index ? 'hovered' : ''}`}>
|
||||
<img class="w-6 h-6 mr-2" src={`/images/weapons/${id}.png`} alt={weapon.name} />
|
||||
<span class="flex-1">{weapon.name}</span>
|
||||
</span>
|
||||
</VirtualList>
|
||||
{:else}<span class="p-3 rounded-xl cursor-pointer flex mr-2 my-2"> Weapon not found </span>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
278
src/data/itemList.js
Normal file
|
@ -0,0 +1,278 @@
|
|||
export const itemList = {
|
||||
unknown: {
|
||||
id: 'unknown',
|
||||
name: 'unknown',
|
||||
},
|
||||
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',
|
||||
},
|
||||
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',
|
||||
},
|
||||
shackles_of_the_dandelion_gladiator: {
|
||||
id: 'shackles_of_the_dandelion_gladiator',
|
||||
name: 'Shackles of the Dandelion Gladiator',
|
||||
},
|
||||
boreal_wolfs_milk_tooth: {
|
||||
id: 'boreal_wolfs_milk_tooth',
|
||||
name: "Boreal Wolf's Milk Tooth",
|
||||
},
|
||||
dead_ley_line_branches: {
|
||||
id: 'dead_ley_line_branches',
|
||||
name: 'Dead Ley Line Branches',
|
||||
},
|
||||
slime_condensate: {
|
||||
id: 'slime_condensate',
|
||||
name: 'Slime Condensate',
|
||||
},
|
||||
boreal_wolfs_cracked_tooth: {
|
||||
id: 'boreal_wolfs_cracked_tooth',
|
||||
name: "Boreal Wolf's Cracked Tooth",
|
||||
},
|
||||
dead_ley_line_leaves: {
|
||||
id: 'dead_ley_line_leaves',
|
||||
name: 'Dead Ley Line Leaves',
|
||||
},
|
||||
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',
|
||||
},
|
||||
slime_concentrate: {
|
||||
id: 'slime_concentrate',
|
||||
name: 'Slime Concentrate',
|
||||
},
|
||||
boreal_wolfs_nostalgia: {
|
||||
id: 'boreal_wolfs_nostalgia',
|
||||
name: "Boreal Wolf's Nostalgia",
|
||||
},
|
||||
dead_ley_line_branch: {
|
||||
id: 'dead_ley_line_branch',
|
||||
name: 'Dead Ley Line Branch',
|
||||
},
|
||||
firm_arrowhead: {
|
||||
id: 'firm_arrowhead',
|
||||
name: 'Firm Arrowhead',
|
||||
},
|
||||
weathered_arrowhead: {
|
||||
id: 'weathered_arrowhead',
|
||||
name: 'Weathered Arrowhead',
|
||||
},
|
||||
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',
|
||||
},
|
||||
luminous_sands_from_guyun: {
|
||||
id: 'luminous_sands_from_guyun',
|
||||
name: 'Luminous Sands from Guyun',
|
||||
},
|
||||
hunters_sacrificial_knife: {
|
||||
id: 'hunters_sacrificial_knife',
|
||||
name: "Hunter's Sacrificial Knife",
|
||||
},
|
||||
recruits_insignia: {
|
||||
id: 'recruits_insignia',
|
||||
name: "Recruit's Insignia",
|
||||
},
|
||||
lustrous_stone_from_guyun: {
|
||||
id: 'lustrous_stone_from_guyun',
|
||||
name: 'Lustrous Stone from Guyun',
|
||||
},
|
||||
agents_sacrificial_knife: {
|
||||
id: 'agents_sacrificial_knife',
|
||||
name: "Agent's Sacrificial Knife",
|
||||
},
|
||||
sergeants_insignia: {
|
||||
id: 'sergeants_insignia',
|
||||
name: "Sergeant's Insignia",
|
||||
},
|
||||
relic_from_guyun: {
|
||||
id: 'relic_from_guyun',
|
||||
name: 'Relic from Guyun',
|
||||
},
|
||||
inspectors_sacrificial_knife: {
|
||||
id: 'inspectors_sacrificial_knife',
|
||||
name: "Inspector's Sacrificial Knife",
|
||||
},
|
||||
lieutenants_insignia: {
|
||||
id: 'lieutenants_insignia',
|
||||
name: "Lieutenant's Insignia",
|
||||
},
|
||||
divine_body_from_guyun: {
|
||||
id: 'divine_body_from_guyun',
|
||||
name: 'Divine Body from Guyun',
|
||||
},
|
||||
tile_of_decarabians_tower: {
|
||||
id: 'tile_of_decarabians_tower',
|
||||
name: "Tile of Decarabian's Tower",
|
||||
},
|
||||
heavy_horn: {
|
||||
id: 'heavy_horn',
|
||||
name: 'Heavy Horn',
|
||||
},
|
||||
debris_of_decarabians_city: {
|
||||
id: 'debris_of_decarabians_city',
|
||||
name: "Debris of Decarabian's City",
|
||||
},
|
||||
black_bronze_horn: {
|
||||
id: 'black_bronze_horn',
|
||||
name: 'Black Bronze Horn',
|
||||
},
|
||||
fragment_of_decarabians_epic: {
|
||||
id: 'fragment_of_decarabians_epic',
|
||||
name: "Fragment of Decarabian's Epic",
|
||||
},
|
||||
black_crystal_horn: {
|
||||
id: 'black_crystal_horn',
|
||||
name: 'Black Crystal Horn',
|
||||
},
|
||||
scattered_piece_of_decarabians_dream: {
|
||||
id: 'scattered_piece_of_decarabians_dream',
|
||||
name: "Scattered Piece of Decarabian's Dream",
|
||||
},
|
||||
forbidden_curse_scroll: {
|
||||
id: 'forbidden_curse_scroll',
|
||||
name: 'Forbidden Curse Scroll',
|
||||
},
|
||||
mist_veiled_lead_elixir: {
|
||||
id: 'mist_veiled_lead_elixir',
|
||||
name: 'Mist Veiled Lead Elixir',
|
||||
},
|
||||
mist_grass_pollen: {
|
||||
id: 'mist_grass_pollen',
|
||||
name: 'Mist Grass Pollen',
|
||||
},
|
||||
mist_veiled_mercury_elixir: {
|
||||
id: 'mist_veiled_mercury_elixir',
|
||||
name: 'Mist Veiled Mercury Elixir',
|
||||
},
|
||||
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_veiled_primo_elixir: {
|
||||
id: 'mist_veiled_primo_elixir',
|
||||
name: 'Mist Veiled Primo Elixir',
|
||||
},
|
||||
grain_of_aerosiderite: {
|
||||
id: 'grain_of_aerosiderite',
|
||||
name: 'Grain of Aerosiderite',
|
||||
},
|
||||
fragile_bone_shard: {
|
||||
id: 'fragile_bone_shard',
|
||||
name: 'Fragile Bone Shard',
|
||||
},
|
||||
damaged_mask: {
|
||||
id: 'damaged_mask',
|
||||
name: 'Damaged Mask',
|
||||
},
|
||||
piece_of_aerosiderite: {
|
||||
id: 'piece_of_aerosiderite',
|
||||
name: 'Piece of Aerosiderite',
|
||||
},
|
||||
sturdy_bone_shard: {
|
||||
id: 'sturdy_bone_shard',
|
||||
name: 'Sturdy Bone Shard',
|
||||
},
|
||||
stained_mask: {
|
||||
id: 'stained_mask',
|
||||
name: 'Stained Mask',
|
||||
},
|
||||
bit_of_aerosiderite: {
|
||||
id: 'bit_of_aerosiderite',
|
||||
name: 'Bit of Aerosiderite',
|
||||
},
|
||||
fossilized_bone_shard: {
|
||||
id: 'fossilized_bone_shard',
|
||||
name: 'Fossilized Bone Shard',
|
||||
},
|
||||
ominous_mask: {
|
||||
id: 'ominous_mask',
|
||||
name: 'Ominous Mask',
|
||||
},
|
||||
chunk_of_aerosiderite: {
|
||||
id: 'chunk_of_aerosiderite',
|
||||
name: 'Chunk of Aerosiderite',
|
||||
},
|
||||
treasure_hoarder_insignia: {
|
||||
id: 'treasure_hoarder_insignia',
|
||||
name: 'Treasure Hoarder Insignia',
|
||||
},
|
||||
silver_raven_insignia: {
|
||||
id: 'silver_raven_insignia',
|
||||
name: 'Silver Raven Insignia',
|
||||
},
|
||||
golden_raven_insignia: {
|
||||
id: 'golden_raven_insignia',
|
||||
name: 'Golden Raven Insignia',
|
||||
},
|
||||
whopperflower_nectar: {
|
||||
id: 'whopperflower_nectar',
|
||||
name: 'Whopperflower Nectar',
|
||||
},
|
||||
shimmering_nectar: {
|
||||
id: 'shimmering_nectar',
|
||||
name: 'Shimmering 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',
|
||||
},
|
||||
black_copper_horn: {
|
||||
id: 'black_copper_horn',
|
||||
name: 'Black Copper Horn',
|
||||
},
|
||||
historic_arrowhead: {
|
||||
id: 'historic_arrowhead',
|
||||
name: 'Historic Arrowhead',
|
||||
},
|
||||
};
|
248
src/data/weaponExp.js
Normal file
|
@ -0,0 +1,248 @@
|
|||
export const weaponExp = [
|
||||
[
|
||||
0,
|
||||
275,
|
||||
700,
|
||||
1300,
|
||||
2100,
|
||||
3125,
|
||||
4400,
|
||||
5950,
|
||||
7800,
|
||||
9975,
|
||||
12475,
|
||||
15350,
|
||||
18600,
|
||||
22250,
|
||||
26300,
|
||||
30800,
|
||||
35750,
|
||||
41150,
|
||||
47050,
|
||||
53475,
|
||||
60400,
|
||||
68250,
|
||||
76675,
|
||||
85725,
|
||||
95400,
|
||||
105725,
|
||||
116700,
|
||||
128350,
|
||||
140700,
|
||||
153750,
|
||||
167550,
|
||||
182075,
|
||||
197375,
|
||||
213475,
|
||||
230375,
|
||||
248075,
|
||||
266625,
|
||||
286025,
|
||||
306300,
|
||||
327475,
|
||||
349525,
|
||||
373675,
|
||||
398800,
|
||||
424925,
|
||||
452075,
|
||||
480275,
|
||||
509525,
|
||||
539850,
|
||||
571275,
|
||||
603825,
|
||||
637475,
|
||||
674025,
|
||||
711800,
|
||||
750800,
|
||||
791075,
|
||||
832625,
|
||||
875475,
|
||||
919625,
|
||||
965125,
|
||||
1011975,
|
||||
1060200,
|
||||
1112275,
|
||||
1165825,
|
||||
1220875,
|
||||
1277425,
|
||||
1335525,
|
||||
1395175,
|
||||
1456400,
|
||||
1519200,
|
||||
1583600,
|
||||
1649625,
|
||||
1720700,
|
||||
1793525,
|
||||
1868100,
|
||||
1944450,
|
||||
2022600,
|
||||
2102600,
|
||||
2184450,
|
||||
2268150,
|
||||
2353725,
|
||||
],
|
||||
[
|
||||
0,
|
||||
400,
|
||||
1025,
|
||||
1925,
|
||||
3125,
|
||||
4675,
|
||||
6625,
|
||||
8975,
|
||||
11775,
|
||||
15075,
|
||||
18875,
|
||||
23225,
|
||||
28150,
|
||||
33675,
|
||||
39825,
|
||||
46625,
|
||||
54125,
|
||||
62325,
|
||||
71275,
|
||||
81000,
|
||||
91500,
|
||||
103400,
|
||||
116175,
|
||||
129875,
|
||||
144525,
|
||||
160150,
|
||||
176775,
|
||||
194425,
|
||||
213125,
|
||||
232900,
|
||||
253800,
|
||||
275825,
|
||||
299025,
|
||||
323400,
|
||||
349000,
|
||||
375825,
|
||||
403925,
|
||||
433325,
|
||||
464050,
|
||||
496125,
|
||||
529550,
|
||||
566125,
|
||||
604200,
|
||||
643800,
|
||||
684950,
|
||||
727675,
|
||||
772000,
|
||||
817950,
|
||||
865550,
|
||||
914850,
|
||||
965850,
|
||||
1021225,
|
||||
1078450,
|
||||
1137550,
|
||||
1198575,
|
||||
1261525,
|
||||
1326450,
|
||||
1393350,
|
||||
1462275,
|
||||
1533250,
|
||||
1606300,
|
||||
1685200,
|
||||
1766325,
|
||||
1849725,
|
||||
1935425,
|
||||
2023450,
|
||||
2113825,
|
||||
2206575,
|
||||
2301725,
|
||||
2399300,
|
||||
2499350,
|
||||
2607025,
|
||||
2717350,
|
||||
2830350,
|
||||
2946050,
|
||||
3064475,
|
||||
3185675,
|
||||
3309675,
|
||||
3436500,
|
||||
3566175,
|
||||
],
|
||||
[
|
||||
0,
|
||||
600,
|
||||
1550,
|
||||
2900,
|
||||
4700,
|
||||
7025,
|
||||
9950,
|
||||
13475,
|
||||
17675,
|
||||
22625,
|
||||
28325,
|
||||
34850,
|
||||
42250,
|
||||
50550,
|
||||
59775,
|
||||
69975,
|
||||
81225,
|
||||
93525,
|
||||
106950,
|
||||
121550,
|
||||
137300,
|
||||
155150,
|
||||
174325,
|
||||
194875,
|
||||
216850,
|
||||
240300,
|
||||
265250,
|
||||
291725,
|
||||
319775,
|
||||
349450,
|
||||
381800,
|
||||
414850,
|
||||
449650,
|
||||
486225,
|
||||
524625,
|
||||
564875,
|
||||
607025,
|
||||
651125,
|
||||
697225,
|
||||
745350,
|
||||
795500,
|
||||
850375,
|
||||
907500,
|
||||
966900,
|
||||
1028625,
|
||||
1092725,
|
||||
1159225,
|
||||
1228150,
|
||||
1299550,
|
||||
1373500,
|
||||
1450000,
|
||||
1533075,
|
||||
1618925,
|
||||
1707575,
|
||||
1799125,
|
||||
1893550,
|
||||
1990950,
|
||||
2091300,
|
||||
2194700,
|
||||
2301175,
|
||||
2410750,
|
||||
2529100,
|
||||
2650800,
|
||||
2775900,
|
||||
2904450,
|
||||
3036500,
|
||||
3172075,
|
||||
3311200,
|
||||
3453925,
|
||||
3600300,
|
||||
3750375,
|
||||
3911900,
|
||||
4077400,
|
||||
4246900,
|
||||
4420450,
|
||||
4598100,
|
||||
4779900,
|
||||
4965900,
|
||||
5156150,
|
||||
5350675,
|
||||
],
|
||||
];
|
10852
src/data/weaponList.js
Normal file
414
src/routes/calculator.svelte
Normal file
|
@ -0,0 +1,414 @@
|
|||
<script>
|
||||
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 { weaponExp } from '../data/weaponExp';
|
||||
|
||||
let weaponsRarity = [
|
||||
{ label: '3 Star', value: 3 },
|
||||
{ label: '4 Star', value: 4 },
|
||||
{ label: '5 Star', value: 5 },
|
||||
];
|
||||
|
||||
let resources = [
|
||||
{
|
||||
selected: true,
|
||||
disabled: true,
|
||||
image: '/images/crystal_3.png',
|
||||
label: 'Mystic Enhancement Ore',
|
||||
value: '10000',
|
||||
},
|
||||
{ selected: true, disabled: false, image: '/images/crystal_2.png', label: 'Fine Enhancement Ore', value: '2000' },
|
||||
{ selected: true, disabled: false, image: '/images/crystal_1.png', label: 'Enhancement Ore', value: '400' },
|
||||
{ selected: true, disabled: false, image: '/images/weapons/sword.png', label: '1 Star Weapon', value: '600' },
|
||||
{ selected: true, disabled: false, image: '/images/weapons/sword.png', label: '2 Star Weapon', value: '1200' },
|
||||
{ selected: true, disabled: false, image: '/images/weapons/sword.png', label: '3 Star Weapon', value: '1800' },
|
||||
];
|
||||
|
||||
let withAscension = true;
|
||||
|
||||
let rarity = null;
|
||||
|
||||
let selectedWeapon = 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 ? selectedWeapon !== null : rarity !== null) &&
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
function calculateAscension() {
|
||||
const current = Math.max(currentAscension, 0);
|
||||
const target = intendedAscension;
|
||||
|
||||
const result = selectedWeapon.ascension.slice(current, target).reduce(
|
||||
(sum, weapon, index) => {
|
||||
if (weapon.mora === 0) {
|
||||
if (unknownList[index + current] === undefined) {
|
||||
unknownList[index + current] = ['Mora amount'];
|
||||
}
|
||||
}
|
||||
|
||||
const mora = sum.mora + weapon.mora;
|
||||
const items = sum.items;
|
||||
|
||||
for (const [i, item] of weapon.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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (withAscension) {
|
||||
rarity = weaponsRarity[-3 + selectedWeapon.rarity];
|
||||
}
|
||||
|
||||
const target =
|
||||
weaponExp[rarity.value - 3][intendedLevel - 1] - (weaponExp[rarity.value - 3][currentLevel - 1] + currentExp);
|
||||
let current = target;
|
||||
let max = [];
|
||||
|
||||
moraNeeded = Math.ceil(target / 10 / 20) * 20;
|
||||
|
||||
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>
|
||||
|
||||
<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="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>
|
||||
<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>
|
||||
{/if}
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
BIN
static/images/ascension_star.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
static/images/calculator.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
static/images/crystal_1.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
static/images/crystal_2.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
static/images/crystal_3.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
static/images/items/agents_sacrificial_knife.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
static/images/items/bit_of_aerosiderite.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
static/images/items/black_bronze_horn.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/images/items/black_copper_horn.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/images/items/black_crystal_horn.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
static/images/items/boreal_wolfs_broken_fang.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
static/images/items/boreal_wolfs_cracked_tooth.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
static/images/items/boreal_wolfs_milk_tooth.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
static/images/items/boreal_wolfs_nostalgia.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
static/images/items/chains_of_the_dandelion_gladiator.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
static/images/items/chaos_circuit.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
static/images/items/chaos_core.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
static/images/items/chaos_device.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
static/images/items/chunk_of_aerosiderite.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
static/images/items/damaged_mask.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
static/images/items/dead_ley_line_branch.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
static/images/items/dead_ley_line_branches.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
static/images/items/dead_ley_line_leaves.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
static/images/items/debris_of_decarabians_city.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
static/images/items/divine_body_from_guyun.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
static/images/items/divining_scroll.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
static/images/items/dream_of_the_dandelion_gladiator.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
static/images/items/energy_nectar.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
static/images/items/fetters_of_the_dandelion_gladiator.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
static/images/items/firm_arrowhead.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
static/images/items/forbidden_curse_scroll.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
static/images/items/fossilized_bone_shard.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
static/images/items/fragile_bone_shard.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
static/images/items/fragment_of_decarabians_epic.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
static/images/items/golden_raven_insignia.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
static/images/items/grain_of_aerosiderite.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
static/images/items/heavy_horn.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
static/images/items/historic_arrowhead.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/images/items/hunters_sacrificial_knife.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
static/images/items/inspectors_sacrificial_knife.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
static/images/items/ley_line_sprouts.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
static/images/items/lieutenants_insignia.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
static/images/items/luminous_sands_from_guyun.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
static/images/items/lustrous_stone_from_guyun.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
static/images/items/mist_flower_pollen.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
static/images/items/mist_grass.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
static/images/items/mist_grass_pollen.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
static/images/items/mist_grass_wick.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
static/images/items/mist_veiled_gold_elixir.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
static/images/items/mist_veiled_lead_elixir.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
static/images/items/mist_veiled_mercury_elixir.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
static/images/items/mist_veiled_primo_elixir.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
static/images/items/ominous_mask.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
static/images/items/piece_of_aerosiderite.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
static/images/items/recruits_insignia.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
static/images/items/relic_from_guyun.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
static/images/items/scattered_piece_of_decarabians_dream.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
static/images/items/seal_scroll.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
static/images/items/sealed_scroll.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
static/images/items/sergeants_insignia.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
static/images/items/shackles_of_the_dandelion_gladiator.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
static/images/items/sharp_arrowhead.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/images/items/shimmering_nectar.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
static/images/items/silver_raven_insignia.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
static/images/items/slime_concentrate.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
static/images/items/slime_condensate.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
static/images/items/slime_secretions.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
static/images/items/stained_mask.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
static/images/items/sturdy_bone_shard.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
static/images/items/tile_of_decarabians_tower.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
static/images/items/treasure_hoarder_insignia.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
static/images/items/weathered_arrowhead.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/images/items/whopperflower_nectar.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
static/images/mora.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
static/images/weapons/alley_hunter.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
static/images/weapons/amber_catalyst.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
static/images/weapons/amos_bow.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
static/images/weapons/apprentices_notes.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
static/images/weapons/aquila_favonia.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
static/images/weapons/beginners_protector.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
static/images/weapons/black_tassel.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
static/images/weapons/blackcliff_amulet.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
static/images/weapons/blackcliff_longsword.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
static/images/weapons/blackcliff_pole.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
static/images/weapons/blackcliff_slasher.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
static/images/weapons/blackcliff_warbow.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
static/images/weapons/bloodtainted_greatsword.png
Normal file
After Width: | Height: | Size: 44 KiB |