Update weapon calculator

pull/1/head
I Made Setia Baruna 2020-10-30 06:44:51 +07:00
parent 0c8b564bc9
commit e6e5020b8f
184 changed files with 12212 additions and 13 deletions

View 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>

View File

@ -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>

View 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>

View 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>

View File

@ -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>

View File

@ -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' : ''}`}>

View File

@ -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>

View 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>

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/images/crystal_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
static/images/crystal_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/images/crystal_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 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: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/images/mora.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Some files were not shown because too many files have changed in this diff Show More