HumanBreak/src/ui/book.vue
2023-08-01 12:22:05 +08:00

302 lines
7.4 KiB
Vue

<!-- 怪物手册ui -->
<template>
<div id="book">
<div id="tools">
<span id="back" class="button-text tools" @click="exit"
><left-outlined />返回游戏</span
>
</div>
<div v-if="enemy.length === 0" id="none">
<div>本层无怪物</div>
</div>
<Scroll
v-else
style="width: 100%; height: 94%; font-family: normal"
v-model:now="scroll"
v-model:drag="drag"
>
<div v-for="(e, i) of toShow" class="enemy">
<EnemyOne
:selected="i === selected"
:enemy="e"
:key="i"
@select="select(e, i)"
@hover="selected = i"
></EnemyOne>
<a-divider
dashed
style="width: 100%; border-color: #ddd4"
></a-divider>
</div>
</Scroll>
</div>
<BookDetail
v-if="detail"
:from-book="true"
@close="closeDetail()"
></BookDetail>
</template>
<script setup lang="tsx">
import { sleep } from 'mutate-animate';
import { onMounted, onUnmounted, ref } from 'vue';
import EnemyOne from '../components/enemyOne.vue';
import Scroll from '../components/scroll.vue';
import { getDamageColor, has, keycode } from '../plugin/utils';
import BookDetail from './bookDetail.vue';
import { LeftOutlined } from '@ant-design/icons-vue';
import { KeyCode } from '../plugin/keyCodes';
import { noClosePanel } from '../plugin/uiController';
import { ToShowEnemy, detailInfo } from '../plugin/ui/book';
const floorId =
// @ts-ignore
core.floorIds[core.status.event?.ui?.index] ?? core.status.floorId;
const specials = Object.fromEntries(
core.getSpecials().map(v => {
return [v[0], v.slice(1)];
})
) as Record<
string,
EnemySpecialDeclaration extends [number, ...infer F] ? F : never
>;
const enemy = core.getCurrentEnemys(floorId);
const toShow: ToShowEnemy[] = enemy.map(v => {
const cri = v.enemy.calCritical(1, 'none')[0];
const critical = core.formatBigNumber(cri[0]?.atkDelta);
const criticalDam = core.formatBigNumber(-cri[0]?.delta);
const ratio = core.status.maps[floorId].ratio;
const defDam = core.formatBigNumber(
-v.enemy.calDefDamage(ratio, 'none')[0]?.delta
);
const damage = core.formatBigNumber(
v.enemy.damage?.[0]?.damage ?? Infinity
);
const fromFunc = (
func: string | ((enemy: Enemy) => string),
enemy: Enemy
) => {
return typeof func === 'string' ? func : func(enemy);
};
const special: [string, string, string][] = v.enemy.enemy.special.map(
vv => {
const s = specials[vv];
return [
fromFunc(s[0], v.enemy.enemy),
fromFunc(s[1], v.enemy.enemy),
s[2] as string
];
}
);
const showSpecial =
special.length > 2
? special.slice(0, 2).concat(['...', '', '#fff'])
: special.slice();
const damageColor = getDamageColor(
v.enemy.damage?.[0]?.damage ?? Infinity
) as string;
return {
critical,
criticalDam,
defDam,
special,
damageColor,
showSpecial,
damage,
...v
};
});
const scroll = ref(0);
const drag = ref(false);
const detail = ref(false);
const selected = ref(0);
/**
* 选择怪物,展示详细信息
* @param enemy 选择的怪物
* @param index 选择的怪物索引
*/
function select(enemy: ToShowEnemy, index: number) {
if (drag.value) return;
const h = window.innerHeight;
const y = index * h * 0.2 - scroll.value;
detailInfo.enemy = enemy;
detailInfo.pos = y;
detail.value = true;
hide();
}
/**
* 隐藏怪物手册
*/
async function hide() {
const div = document.getElementById('book') as HTMLDivElement;
div.style.opacity = '0';
await sleep(600);
div.style.display = 'none';
}
/**
* 关闭详细信息
*/
async function closeDetail() {
show();
await sleep(600);
detail.value = false;
}
/**
* 显示怪物手册
*/
async function show() {
const div = document.getElementById('book') as HTMLDivElement;
div.style.display = 'flex';
await sleep(50);
div.style.opacity = '1';
}
/**
* 退出怪物手册
*/
async function exit() {
noClosePanel.value = true;
ancTe.plugin.ui.bookOpened.value = false;
if (ancTe.plugin.ui.transition.value) await sleep(650);
else await sleep(100);
if (core.events.recoverEvents(core.status.event.interval)) {
return;
} else if (has(core.status.event.ui)) {
core.status.boxAnimateObjs = [];
// @ts-ignore
core.ui._drawViewMaps(core.status.event.ui);
} else core.ui.closePanel();
}
function checkScroll() {
const h = window.innerHeight;
const y = selected.value * h * 0.2 - scroll.value;
if (y < 0) {
scroll.value += y - 20;
}
if (y > h * 0.655) {
scroll.value += y - h * 0.655 + 20;
}
}
/**
* 键盘松开时
*/
function keyup(e: KeyboardEvent) {
const c = keycode(e.keyCode);
if (c === KeyCode.KeyX || c === KeyCode.Escape) {
exit();
}
if (
(c === KeyCode.Enter || c === KeyCode.KeyC || c === KeyCode.Space) &&
!detail.value
) {
select(toShow[selected.value], selected.value);
}
}
/**
* 键盘按下时
*/
function keydown(e: KeyboardEvent) {
const c = keycode(e.keyCode);
if (!detail.value) {
if (c === KeyCode.DownArrow) {
if (selected.value < enemy.length - 1) {
selected.value++;
}
checkScroll();
}
if (c === KeyCode.UpArrow) {
if (selected.value > 0) {
selected.value--;
}
checkScroll();
}
// 一次移动5个怪物
if (c === KeyCode.LeftArrow || c === KeyCode.PageUp) {
if (selected.value <= 4) {
selected.value = 0;
} else {
selected.value -= 5;
}
checkScroll();
}
if (c === KeyCode.RightArrow || c === KeyCode.PageDown) {
if (selected.value >= enemy.length - 5) {
selected.value = enemy.length - 1;
} else {
selected.value += 5;
}
checkScroll();
}
}
}
onMounted(async () => {
if (ancTe.plugin.ui.transition.value) await sleep(600);
else await sleep(50);
document.addEventListener('keyup', keyup);
document.addEventListener('keydown', keydown);
});
onUnmounted(async () => {
document.removeEventListener('keyup', keyup);
document.removeEventListener('keydown', keydown);
});
</script>
<style lang="less" scoped>
#book {
user-select: none;
width: 80%;
height: 100%;
font-family: 'normal';
overflow: hidden;
transition: opacity 0.6s linear;
display: flex;
flex-direction: column;
justify-content: space-between;
}
#tools {
height: 6%;
font-size: 3.2vh;
}
#none {
width: 100%;
height: 100%;
font-size: 6vw;
display: flex;
justify-content: center;
align-items: center;
font-family: 'normal';
}
.enemy {
display: flex;
flex-direction: column;
height: 20vh;
width: 100%;
padding: 0 1% 0 1%;
}
@media screen and (max-width: 600px) {
#book {
width: 100%;
padding: 5%;
}
}
</style>