临界信息

This commit is contained in:
unanmed 2022-11-19 11:30:14 +08:00
parent 8517dbdabf
commit 9771247b79
24 changed files with 5022 additions and 2106 deletions

2
components.d.ts vendored
View File

@ -8,7 +8,7 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ADivider: typeof import('ant-design-vue/es')['Divider']
BookOne: typeof import('./src/components/bookOne.vue')['default']
ASlider: typeof import('ant-design-vue/es')['Slider']
BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default']
EnemyOne: typeof import('./src/components/enemyOne.vue')['default']
Scroll: typeof import('./src/components/scroll.vue')['default']

View File

@ -15,7 +15,7 @@
<meta name="x5-orientation" content="portrait">
<meta name="x5-fullscreen" content="true">
<meta name="x5-page-mode" content="app">
<link type='text/css' href='styles.css' rel='stylesheet'>
<link type='text/css' href='/styles.css' rel='stylesheet'>
</head>
<body>

View File

@ -14,6 +14,7 @@
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "^3.2.13",
"axios": "^1.1.3",
"chart.js": "^4.0.1",
"lodash": "^4.17.21",
"lz-string": "^1.4.4",
"mutate-animate": "^0.1.0",

View File

@ -12,6 +12,7 @@ specifiers:
'@vitejs/plugin-vue-jsx': ^2.1.1
ant-design-vue: ^3.2.13
axios: ^1.1.3
chart.js: ^4.0.1
compressing: ^1.6.2
fontmin: ^0.9.9
form-data: ^4.0.0
@ -32,6 +33,7 @@ dependencies:
'@ant-design/icons-vue': 6.1.0_vue@3.2.45
ant-design-vue: 3.2.15_vue@3.2.45
axios: 1.1.3
chart.js: 4.0.1
lodash: 4.17.21
lz-string: 1.4.4
mutate-animate: 0.1.0
@ -1069,6 +1071,11 @@ packages:
supports-color: 5.5.0
dev: true
/chart.js/4.0.1:
resolution: {integrity: sha512-5/8/9eBivwBZK81mKvmIwTb2Pmw4D/5h1RK9fBWZLLZ8mCJ+kfYNmV9rMrGoa5Hgy2/wVDBMLSUDudul2/9ihA==}
engines: {pnpm: ^7.0.0}
dev: false
/chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ var enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80 =
"blackSlime": {"name":"青头怪","hp":170,"atk":20,"def":8,"money":0,"exp":3,"point":0,"special":[]},
"slimelord": {"name":"粘液王","hp":200,"atk":58,"def":24,"money":0,"exp":8,"point":0,"special":[]},
"bat": {"name":"小蝙蝠","hp":60,"atk":15,"def":0,"money":0,"exp":2,"point":0,"special":[4]},
"bigBat": {"name":"大蝙蝠","hp":150,"atk":17,"def":5,"money":0,"exp":4,"point":0,"special":[4]},
"bigBat": {"name":"大蝙蝠","hp":150,"atk":17,"def":5,"money":0,"exp":4,"point":0,"special":[4,5,6],"crit":0,"charge":0,"courage":0,"together":0,"hungry":0,"value":100,"n":1000},
"redBat": {"name":"恐怖蝙蝠","hp":1200,"atk":260,"def":110,"money":1,"exp":32,"point":0,"special":[5]},
"vampire": {"name":"冥灵魔王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},
"skeleton": {"name":"骷髅人","hp":300,"atk":80,"def":10,"money":0,"exp":9,"point":0,"special":[1],"crit":300},

View File

@ -9311,5 +9311,10 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
}
return res;
}
},
"uiChange": function () {
ui.prototype.drawBook = function () {
return core.plugin.bookOpened.value = true;
}
}
};

View File

@ -33,12 +33,13 @@ onMounted(() => {
c.height *= scale;
ctx.scale(scale, scale);
const fn = (time: number) => {
const fn = () => {
core.clearMap(ctx);
const frame = core.status.globalAnimateStatus % frames;
core.drawIcon(ctx, props.id, 0, 0, props.width, props.height, frame);
};
fn();
addAnimate(fn);
onUnmounted(() => {

View File

@ -1,5 +1,5 @@
<template>
<div class="enemy-container">
<div class="enemy-container" @click="select">
<div class="info">
<div class="leftbar">
<span class="name">{{ enemy.name }}</span>
@ -24,7 +24,7 @@
<a-divider
type="vertical"
dashed
style="height: 100%; margin: 0 3% 0 3%; border-color: #ddd4"
style="height: 100%; margin: 0 3% 0 1%; border-color: #ddd4"
></a-divider>
<div class="rightbar">
<div class="detail">
@ -66,9 +66,9 @@
<div class="detail-info">
<span style="color: cyan"
>{{
core.status.thisMap.ratio
core.formatBigNumber(core.status.thisMap.ratio)
}}&nbsp;&nbsp;&nbsp;&nbsp;{{
core.formatBigNumber(enemy.money)
core.formatBigNumber(enemy.defDamage)
}}</span
>
</div>
@ -120,7 +120,18 @@ const props = defineProps<{
enemy: Enemy & DetailedEnemy;
}>();
const emits = defineEmits<{
(e: 'select'): void;
}>();
const core = window.core;
/**
* 选择这个怪物时
*/
function select(e: MouseEvent) {
emits('select');
}
</script>
<style lang="less" scoped>
@ -146,13 +157,14 @@ const core = window.core;
}
.leftbar {
width: 10%;
width: 15%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 1.3vw;
font-size: 2vh;
padding-left: 1%;
}
.name {
@ -168,11 +180,14 @@ const core = window.core;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
display: flex;
flex-direction: row;
align-items: center;
justify-items: space-between;
}
.rightbar {
font-size: 1.3vw;
font-size: 2.5vh;
width: 100%;
height: 100%;
padding: 1.5vh 0 1.5vh 0;
@ -184,7 +199,7 @@ const core = window.core;
height: 100%;
.detail-info {
flex-basis: 33%;
flex-basis: 33.3%;
line-height: 0;
display: flex;
flex-direction: column;
@ -192,4 +207,14 @@ const core = window.core;
}
}
}
@media screen and (max-width: 600px) {
.rightbar {
font-size: 4vw;
}
.leftbar {
font-size: 2vw;
}
}
</style>

View File

@ -11,10 +11,17 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, onUpdated } from 'vue';
import { useDrag, useWheel } from '../plugin/use';
import { cancelGlobalDrag, isMobile, useDrag, useWheel } from '../plugin/use';
const props = defineProps<{
now?: number;
type?: 'vertical' | 'horizontal';
drag?: boolean;
}>();
const emits = defineEmits<{
(e: 'update:now', value: number): void;
(e: 'update:drag', value: boolean): void;
}>();
let now = 0;
@ -44,6 +51,7 @@ function draw() {
} else if (now < 0) {
now = 0;
}
emits('update:now', now);
const length =
Math.min(ctx.canvas[canvasAttr] / total / scale, 1) *
ctx.canvas[canvasAttr];
@ -52,12 +60,12 @@ function draw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
if (props.type === 'horizontal') {
ctx.moveTo(Math.max(py + 5, 5), 20 * scale);
ctx.lineTo(Math.min(py + length - 5, ctx.canvas.width - 5), 20 * scale);
ctx.moveTo(Math.max(py + 5, 5), 10 * scale);
ctx.lineTo(Math.min(py + length - 5, ctx.canvas.width - 5), 10 * scale);
} else {
ctx.moveTo(20 * scale, Math.max(py + 5, 5));
ctx.moveTo(10 * scale, Math.max(py + 5, 5));
ctx.lineTo(
20 * scale,
10 * scale,
Math.min(py + length - 5, ctx.canvas.height - 5)
);
}
@ -81,10 +89,35 @@ function scroll() {
}
onUpdated(() => {
now = props.now ?? now;
calHeight();
draw();
});
let last: number;
let contentLast: number;
function canvasDrag(x: number, y: number) {
emits('update:drag', true);
const d = props.type === 'horizontal' ? x : y;
const dy = d - last;
last = d;
if (ctx.canvas[canvasAttr] < total * scale)
now += ((dy * total) / ctx.canvas[canvasAttr]) * scale;
content.style.transition = '';
scroll();
}
function contentDrag(x: number, y: number) {
emits('update:drag', true);
const d = props.type === 'horizontal' ? x : y;
const dy = d - contentLast;
contentLast = d;
if (ctx.canvas[canvasAttr] < total * scale) now -= dy;
content.style.transition = '';
scroll();
}
onMounted(() => {
const div = document.getElementById(`scroll-div-${id}`) as HTMLDivElement;
const canvas = document.getElementById(`scroll-${id}`) as HTMLCanvasElement;
@ -96,7 +129,7 @@ onMounted(() => {
content.addEventListener('resize', resize);
const style = getComputedStyle(canvas);
canvas.width = 40 * scale;
canvas.width = 20 * scale;
canvas.height = parseFloat(style.height) * scale;
if (props.type === 'horizontal') {
@ -105,59 +138,44 @@ onMounted(() => {
canvas.style.width = '98%';
canvas.style.margin = '0 1% 0 1%';
canvas.width = parseFloat(style.width) * scale;
canvas.height = 40 * scale;
canvas.height = 20 * scale;
}
draw();
let last: number;
let contentLast: number;
//
useDrag(
canvas,
(x, y) => {
const d = props.type === 'horizontal' ? x : y;
const dy = d - last;
last = d;
if (canvas[canvasAttr] < total * scale)
now += ((dy * total) / canvas[canvasAttr]) * scale;
content.style.transition = '';
scroll();
},
canvasDrag,
(x, y) => {
last = props.type === 'horizontal' ? x : y;
},
() => {
setTimeout(() => emits('update:drag', false));
},
true
);
//
useDrag(
content,
(x, y) => {
const d = props.type === 'horizontal' ? x : y;
const dy = d - contentLast;
contentLast = d;
if (canvas[canvasAttr] < total * scale) now -= dy;
content.style.transition = '';
scroll();
},
contentDrag,
(x, y) => {
contentLast = props.type === 'horizontal' ? x : y;
},
() => {
setTimeout(() => emits('update:drag', false));
},
true
);
useWheel(content, (x, y) => {
const d = x !== 0 ? x : y;
if (!core.domStyle.isVertical) {
if (Math.abs(d) > 50) {
content.style.transition = `${cssTarget} 0.2s ease-out`;
}
if (Math.abs(d) > 50) {
content.style.transition = `${cssTarget} 0.2s ease-out`;
} else {
content.style.transition = '';
}
now += d;
scroll();
});
@ -165,16 +183,15 @@ onMounted(() => {
onUnmounted(() => {
content.removeEventListener('resize', resize);
cancelGlobalDrag(canvasDrag);
cancelGlobalDrag(contentDrag);
});
</script>
<style lang="less" scoped>
.scroll {
opacity: 0.2;
width: 40px;
height: 98%;
margin-top: 1%;
margin-bottom: 1%;
width: 20px;
transition: opacity 0.2s linear;
}

193
src/panel/enemyCritical.vue Normal file
View File

@ -0,0 +1,193 @@
<template>
<div id="critical-main">
<div id="critical">
<div class="des">加攻伤害</div>
<canvas ref="critical" class="chart"></canvas>
<div class="slider-div">
<span>加攻数值&nbsp;&nbsp;&nbsp;&nbsp;{{ addAtk }}</span>
<a-slider
class="slider"
v-model:value="addAtk"
:max="(originCri.at(-1)?.[0] ?? 2) - 1"
></a-slider>
<span
>最大值&nbsp;&nbsp;&nbsp;&nbsp;{{
(originCri.at(-1)?.[0] ?? 2) - 1
}}</span
>
</div>
</div>
<a-divider
dashed
style="width: 100%; border-color: #ddd4; margin: 1vh 0 1vh 0"
></a-divider>
<div id="def">
<div class="des">加防伤害</div>
<canvas ref="def" class="chart"></canvas>
<div class="slider-div">
<span>加防数值&nbsp;&nbsp;&nbsp;&nbsp;{{ addDef }}</span>
<a-slider
class="slider"
v-model:value="addDef"
:max="(originDef.at(-1)?.[0] ?? 2) - 1"
></a-slider>
<span
>最大值&nbsp;&nbsp;&nbsp;&nbsp;{{
(originDef.at(-1)?.[0] ?? 2) - 1
}}</span
>
</div>
</div>
<div id="now-damage">
<span
>当前加攻&nbsp;&nbsp;&nbsp;&nbsp;{{
format(addAtk * ratio)
}}</span
>
<span
>当前加防&nbsp;&nbsp;&nbsp;&nbsp;{{
format(addDef * ratio)
}}</span
>
<span
>当前减伤&nbsp;&nbsp;&nbsp;&nbsp;{{
format(nowDamage[0], false, nowDamage[0] < 0)
}}</span
>
<span
>当前伤害&nbsp;&nbsp;&nbsp;&nbsp;{{
format(nowDamage[1])
}}</span
>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue';
import { getCriticalDamage, getDefDamage } from '../plugin/book';
import Chart, { ChartConfiguration } from 'chart.js/auto';
import { has, setCanvasSize } from '../plugin/utils';
const critical = ref<HTMLCanvasElement>();
const def = ref<HTMLCanvasElement>();
const enemy = core.plugin.bookDetailEnemy;
const originCri = getCriticalDamage(enemy);
const originDef = getDefDamage(enemy);
//
const allCri = ref(originCri);
const allDef = ref(originDef);
//
const addAtk = ref(0);
const addDef = ref(0);
const originDamage = core.getDamageInfo(enemy);
// core
const format = core.formatBigNumber;
const ratio = core.status.thisMap.ratio;
const nowDamage = computed(() => {
const dam = core.getDamageInfo(enemy, {
atk: core.status.hero.atk + addAtk.value,
def: core.status.hero.def + addDef.value
});
if (!has(dam)) return ['???', '???'];
if (!has(originDamage)) return [-dam.damage, dam.damage];
return [originDamage.damage - dam.damage, dam.damage];
});
function generateChart(ele: HTMLCanvasElement, data: [number, number][]) {
const config: ChartConfiguration = {
type: 'line',
data: generateData(data)
};
return new Chart(ele, config);
}
function generateData(data: [number, number][]) {
return {
datasets: [
{
label: '怪物伤害',
data: data.map(v => v[1])
}
],
labels: data.map(v => v[0])
};
}
function update(atk: Chart, def: Chart) {
allCri.value = getCriticalDamage(enemy, addAtk.value, addDef.value);
allDef.value = getDefDamage(enemy, addDef.value, addAtk.value);
atk.data = generateData(allCri.value);
def.data = generateData(allDef.value);
atk.update('resize');
def.update('resize');
}
onMounted(() => {
const div = document.getElementById('critical-main') as HTMLDivElement;
const style = getComputedStyle(div);
const width = parseFloat(style.width);
const height = window.innerHeight / 5;
const c = critical.value!;
const d = def.value!;
setCanvasSize(c, width, height);
setCanvasSize(d, width, height);
const criChart = generateChart(c, allCri.value);
const defChart = generateChart(d, allDef.value);
watch(addAtk, n => {
update(criChart, defChart);
});
watch(addDef, n => {
update(criChart, defChart);
});
});
</script>
<style lang="less" scoped>
#critical-main {
width: 100%;
height: 50vh;
user-select: none;
}
.des {
width: 100%;
text-align: center;
font-size: 2.5vh;
}
.slider-div {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 1.1vw;
line-height: 1;
}
}
.slider {
width: 80%;
}
#now-damage {
display: flex;
flex-direction: row;
justify-content: space-around;
font-size: 3vh;
}
</style>

View File

@ -0,0 +1,94 @@
<template>
<div id="special-main">
<Scroll id="special-scroll">
<div id="special">
<component :is="info"></component>
</div>
</Scroll>
<a-divider
dashed
style="margin: 2vh 0 2vh 0; border-color: #ddd4"
></a-divider>
<div id="critical">
<div style="font-size: 2.5vh; width: 100%; text-align: center">
临界表
</div>
<div id="critical-main">
<div id="critical-des">
<span>加攻</span>
<span>减伤</span>
</div>
<div v-for="[atk, dam] of criticals" class="critical">
<span class="critical-atk">{{ format(atk) }}</span>
<span>{{
dam < 0 ? `->&nbsp;${format(-dam)}` : format(dam)
}}</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { isMobile } from '../plugin/use';
import { getSpecialHint } from '../plugin/book';
const enemy = core.plugin.bookDetailEnemy;
const info = getSpecialHint(enemy);
const criticals = core.nextCriticals(enemy, isMobile ? 4 : 8);
const format = core.formatBigNumber;
</script>
<style lang="less" scoped>
#special-main {
width: 100%;
user-select: none;
font-size: 2em;
position: absolute;
top: 20vh;
}
#critical-main {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#critical-des,
.critical {
font-size: 1.6vw;
display: flex;
flex-direction: column;
}
.critical-atk {
border-bottom: 1px solid #ddd4;
}
.critical {
border-left: 1px solid #ddd4;
padding-left: 1%;
}
#special-scroll {
height: 40vh;
}
@media screen and (max-width: 600px) {
#detail-main {
font-size: 1.2em;
}
#special-scroll {
height: 50vh;
}
#critical-des,
.critical {
font-size: 3.6vw;
}
}
</style>

121
src/plugin/book.tsx Normal file
View File

@ -0,0 +1,121 @@
import { has } from './utils';
/**
*
* @param enemy
*/
export function getSpecialHint(enemy: Enemy & DetailedEnemy) {
const all = core
.getSpecials()
.filter(v => enemy.special.includes(v[0]))
.sort((a, b) => a[0] - b[0]);
const des = all.map(v => {
const des = v[2];
if (des instanceof Function) {
return des(enemy);
}
return des;
});
const name = all.map(v => {
const name = v[1];
if (name instanceof Function) {
return name(enemy);
}
return name;
});
return (
<div>
{all.map((v, i) => {
return (
<div class="special">
<span style={{ color: core.arrayToRGBA(v[3]) }}>
&nbsp;&nbsp;&nbsp;&nbsp;{name[i]}
</span>
<span innerHTML={des[i]}></span>
</div>
);
})}
</div>
);
}
/**
* 100
* @param enemy
*/
export function getDefDamage(
enemy: Enemy & DetailedEnemy,
addDef: number = 0,
addAtk: number = 0
) {
const ratio = core.status.thisMap.ratio;
const res: [number, number][] = [];
let origin: number | undefined;
let last = 0;
for (let i = 0; i <= 100; i++) {
const dam = core.getDamageInfo(enemy, {
def: core.status.hero.def + ratio * i + addDef,
atk: core.status.hero.atk + addAtk
});
if (i === 0) {
origin = dam?.damage;
if (has(origin)) {
res.push([addDef, origin]);
last = origin;
}
continue;
}
if (!has(dam)) continue;
if (dam.damage === origin) continue;
if (dam.damage === res.at(-1)?.[1]) continue;
if (last <= 0) break;
last = dam.damage;
res.push([ratio * i + addDef, dam.damage]);
}
return res;
}
/**
*
* @param enemy
*/
export function getCriticalDamage(
enemy: Enemy & DetailedEnemy,
addAtk: number = 0,
addDef: number = 0
): [number, number][] {
const ratio = core.status.thisMap.ratio;
const res: [number, number][] = [];
let origin: number | undefined;
let last = 0;
for (let i = 0; i <= 100; i++) {
const dam = core.getDamageInfo(enemy, {
atk: core.status.hero.atk + ratio * i + addAtk,
def: core.status.hero.def + addDef
});
if (i === 0) {
origin = dam?.damage;
if (has(origin)) {
res.push([addAtk, origin]);
last = origin;
}
continue;
}
if (!has(dam)) continue;
if (dam.damage === origin) continue;
if (dam.damage === res.at(-1)?.[1]) continue;
if (dam.damage <= 0) break;
last = dam.damage;
res.push([ratio * i + addAtk, dam.damage]);
}
return res;
}

View File

@ -1,17 +1,13 @@
import { sleep } from 'mutate-animate';
import { Component, markRaw, ref, Ref, watch } from 'vue';
import Book from '../ui/book.vue';
import BookDetail from '../ui/bookDetail.vue';
export const bookOpened = ref(false);
export const bookDetail = ref(false);
let app: HTMLDivElement;
/** ui声明列表 */
const UI_LIST: [Ref<boolean>, Component][] = [
[bookOpened, Book],
[bookDetail, BookDetail]
];
const UI_LIST: [Ref<boolean>, Component][] = [[bookOpened, Book]];
/** ui栈 */
export const uiStack = ref<Component[]>([]);
@ -32,15 +28,19 @@ export default function init() {
}
});
});
return { uiStack, bookDetail, bookOpened };
return { uiStack, bookOpened };
}
function showApp() {
async function showApp() {
app.style.display = 'flex';
await sleep(50);
app.style.opacity = '1';
core.lockControl();
}
function hideApp() {
async function hideApp() {
app.style.opacity = '0';
await sleep(600);
app.style.display = 'none';
core.unlockControl();
}

View File

@ -1,7 +1,23 @@
export default function init() {
return { useDrag, useWheel, isMobile };
return { useDrag, useWheel, useUp, isMobile };
}
type DragFn = (x: number, y: number, e: MouseEvent | TouchEvent) => void;
type DragMap = [
(e: MouseEvent) => void,
(e: TouchEvent) => void,
((e: MouseEvent) => void)?,
((e: TouchEvent) => void)?
];
const dragFnMap = new Map<DragFn, DragMap>();
/**
*
*/
export const isMobile = matchMedia('(max-width: 600px)').matches;
/**
*
* @param ele
@ -11,8 +27,9 @@ export default function init() {
*/
export function useDrag(
ele: HTMLElement,
fn: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
ondown?: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
fn: DragFn,
ondown?: DragFn,
onUp?: (e: MouseEvent | TouchEvent) => void,
global: boolean = false
) {
let down = false;
@ -25,21 +42,66 @@ export function useDrag(
if (ondown) ondown(e.touches[0].clientX, e.touches[0].clientY, e);
});
document.addEventListener('mouseup', () => (down = false));
document.addEventListener('touchend', () => (down = false));
const target = global ? document : ele;
target.addEventListener('mousemove', e => {
const mouseFn = (e: MouseEvent) => {
if (!down) return;
const ee = e as MouseEvent;
fn(ee.clientX, ee.clientY, ee);
});
target.addEventListener('touchmove', e => {
fn(e.clientX, e.clientY, e);
};
const touchFn = (e: TouchEvent) => {
if (!down) return;
const ee = e as TouchEvent;
fn(ee.touches[0].clientX, ee.touches[0].clientY, ee);
});
fn(e.touches[0].clientX, e.touches[0].clientY, e);
};
const mouseUp = (e: MouseEvent) => {
if (!down) return;
onUp && onUp(e);
down = false;
};
const touchUp = (e: TouchEvent) => {
if (!down) return;
onUp && onUp(e);
down = false;
};
target.addEventListener(
'mouseup',
mouseUp as EventListenerOrEventListenerObject
);
target.addEventListener(
'touchend',
touchUp as EventListenerOrEventListenerObject
);
dragFnMap.set(fn, [mouseFn, touchFn, mouseUp, touchUp]);
target.addEventListener(
'mousemove',
mouseFn as EventListenerOrEventListenerObject
);
target.addEventListener(
'touchmove',
touchFn as EventListenerOrEventListenerObject
);
}
/**
*
* @param fn
*/
export function cancelGlobalDrag(fn: DragFn): void {
const fns = dragFnMap.get(fn);
dragFnMap.delete(fn);
if (!fns)
throw new ReferenceError(
'The drag function to be canceled does not exist.'
);
document.removeEventListener('mousemove', fns[0]);
document.removeEventListener('touchmove', fns[1]);
document.removeEventListener('mouseup', fns[0]);
document.removeEventListener('touchend', fns[1]);
}
/**
@ -57,6 +119,15 @@ export function useWheel(
}
/**
*
*
* @param ele
* @param fn
*/
export const isMobile = matchMedia('(max-width: 600px)').matches;
export function useUp(ele: HTMLElement, fn: DragFn): void {
ele.addEventListener('mouseup', e => {
fn(e.clientX, e.clientY, e);
});
ele.addEventListener('touchend', e => {
fn(e.touches[0].clientX, e.touches[0].clientY, e);
});
}

View File

@ -25,3 +25,20 @@ export function getDamageColor(damage: number): string {
if (damage < core.status.hero.hp) return '#f22';
return '#f00';
}
/**
*
* @param canvas
* @param w
* @param h
*/
export function setCanvasSize(
canvas: HTMLCanvasElement,
w: number,
h: number
): void {
canvas.width = w;
canvas.height = h;
canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`;
}

View File

@ -7,4 +7,7 @@
justify-content: center;
align-items: center;
overflow: hidden;
transition: all 0.6s linear;
opacity: 0;
background-color: #000d;
}

View File

@ -105,7 +105,7 @@ declare class enemys {
x?: number,
y?: number,
floorId?: string
): number[][];
): [atk: number, dam: number][];
/**
* 使
@ -198,5 +198,5 @@ declare class enemys {
turn: number;
damage: number;
[x: string]: any;
};
} | null;
}

29
src/types/plugin.d.ts vendored
View File

@ -4,6 +4,8 @@ type Ref<T> = {
value: T;
};
type DragFn = (x: number, y: number, e: MouseEvent | TouchEvent) => void;
interface PluginDeclaration extends PluginUtils {
/**
* 使core.addPop或core.plugin.addPop调用
@ -16,12 +18,15 @@ interface PluginDeclaration extends PluginUtils {
/** 添加变量 例所有的正在弹出的文字像这个就可以使用core.plugin.pop获取 */
pop: any[];
/** 怪物手册的怪物详细信息的初始位置 */
bookDetailPos: number;
/** 怪物手册详细信息展示的怪物 */
bookDetailEnemy: Enemy & DetailedEnemy;
/** 手册是否打开 */
readonly bookOpened: Ref<boolean>;
/** 手册详细信息 */
readonly bookDetail: Ref<boolean>;
/** ui栈 */
readonly uiStack: Ref<Component[]>;
@ -37,11 +42,18 @@ interface PluginDeclaration extends PluginUtils {
*/
useDrag(
ele: HTMLElement,
fn: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
ondown?: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
fn: DragFn,
ondown?: DragFn,
onUp?: (e: MouseEvent | TouchEvent) => void,
global: boolean = false
): void;
/**
*
* @param fn
*/
cancelGlobalDrag(fn: DragFn): void;
/**
*
* @param ele
@ -52,6 +64,13 @@ interface PluginDeclaration extends PluginUtils {
fn: (x: number, y: number, z: number, e: WheelEvent) => void
): void;
/**
*
* @param ele
* @param fn
*/
useUp(ele: HTMLElement, fn: DragFn): void;
/**
*
* @param fn

8
src/types/util.d.ts vendored
View File

@ -100,7 +100,11 @@ declare class utils {
* @param onMap true表示用于地图显伤56
* @returns
*/
formatBigNumber(x: number, onMap?: boolean, onCritical?: boolean): string;
formatBigNumber(
x: number | string,
onMap?: boolean,
onCritical?: boolean
): string;
/** 变速移动 */
applyEasing(mode?: string): (number) => number;
@ -119,7 +123,7 @@ declare class utils {
* @param color 255011
* @returns
*/
arrayToRGBA(color: [number, number, number, number]): string;
arrayToRGBA(color: [number, number, number, number?] | string): string;
/**
* base64压缩

View File

@ -4,9 +4,14 @@
<div v-if="enemy.length === 0" id="none">
<div>本层无怪物</div>
</div>
<Scroll v-else style="width: 100%; height: 100%; font-family: normal">
<div v-for="e of enemy" class="enemy">
<EnemyOne :enemy="e"></EnemyOne>
<Scroll
v-else
style="width: 100%; height: 100%; font-family: normal"
v-model:now="scroll"
v-model:drag="drag"
>
<div v-for="(e, i) of enemy" class="enemy">
<EnemyOne :enemy="e" @select="select(e, i)"></EnemyOne>
<a-divider
dashed
style="width: 100%; border-color: #ddd4"
@ -14,18 +19,25 @@
</div>
</Scroll>
</div>
<BookDetail v-if="detail" @close="closeDetail()"></BookDetail>
</template>
<script setup lang="tsx">
import { cloneDeep } from 'lodash';
import { onMounted } from 'vue';
import { sleep } from 'mutate-animate';
import { onMounted, ref } from 'vue';
import EnemyOne from '../components/enemyOne.vue';
import Scroll from '../components/scroll.vue';
import { getDamageColor } from '../plugin/utils';
import BookDetail from './bookDetail.vue';
const floorId = core.floorIds[core.status.event?.ui] ?? core.status.floorId;
const enemy = core.getCurrentEnemys(floorId);
const scroll = ref(0);
const drag = ref(false);
const detail = ref(false);
//
enemy.forEach(v => {
const l = v.specialText.length;
@ -46,18 +58,52 @@ onMounted(() => {
const div = document.getElementById('book') as HTMLDivElement;
div.style.opacity = '1';
});
/**
* 选择怪物展示详细信息
* @param enemy 选择的怪物
* @param index 选择的怪物索引
*/
function select(enemy: Enemy & DetailedEnemy, index: number) {
if (drag.value) return;
const h = window.innerHeight;
const y = index * h * 0.2 - scroll.value;
core.plugin.bookDetailEnemy = enemy;
core.plugin.bookDetailPos = 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 = 'block';
await sleep(50);
div.style.opacity = '1';
}
</script>
<style lang="less" scoped>
#book {
opacity: 0;
background-color: #000d;
transition: opacity 0.6s linear;
user-select: none;
width: 80%;
height: 100%;
font-family: 'normal';
overflow: hidden;
transition: opacity 0.6s linear;
}
@media screen and (max-width: 600px) {

View File

@ -1,7 +1,175 @@
<!-- 怪物详细信息 -->
<template>
<div id="detail">
<div id="info" :style="{ top: `${top}px` }">
<EnemyOne :enemy="enemy"></EnemyOne>
<a-divider
dashed
style="margin: 2vh 0 2vh 0; border-color: #ddd4; width: 100%"
></a-divider>
</div>
<Transition name="detail">
<EnemySpecial v-if="panel === 'special'"></EnemySpecial>
<EnemyCritical v-else-if="panel === 'critical'"></EnemyCritical>
</Transition>
<div id="detail-more">
<Transition name="detail">
<div
id="special-more"
class="detial-more"
v-if="panel === 'special'"
>
<span id="enemy-pos" class="more"
><left-outlined /> 怪物分布情况</span
>
<span
id="critical-more"
class="more"
@click="changePanel($event, 'critical')"
>详细临界信息 <right-outlined
/></span>
</div>
<div
id="special-more"
class="detial-more"
v-else-if="panel === 'critical'"
>
<span
id="enemy-pos"
class="more"
@click="changePanel($event, 'special')"
><left-outlined /> 怪物特殊属性</span
>
</div>
</Transition>
</div>
</div>
</template>
<template></template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import EnemyOne from '../components/enemyOne.vue';
import { useDrag } from '../plugin/use';
import EnemySpecial from '../panel/enemySpecial.vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import EnemyCritical from '../panel/enemyCritical.vue';
<script lang="tsx" setup></script>
const enemy = core.plugin.bookDetailEnemy;
const top = ref(core.plugin.bookDetailPos);
const panel = ref('special');
<style lang="less" scoped></style>
const emits = defineEmits<{
(e: 'close'): void;
}>();
function changePanel(e: MouseEvent, to: string) {
e.stopPropagation();
panel.value = to;
}
onMounted(() => {
top.value = 0;
const div = document.getElementById('detail') as HTMLDivElement;
div.style.opacity = '1';
const style = getComputedStyle(div);
let moved = false;
useDrag(
div,
() => {
moved = true;
},
(x, y) => {
if (y > (parseFloat(style.height) * 4) / 5) moved = true;
},
() => {
if (moved === false) {
top.value = core.plugin.bookDetailPos;
div.style.opacity = '0';
emits('close');
}
moved = false;
}
);
});
</script>
<style lang="less" scoped>
#info {
width: 100%;
position: relative;
transition: all 0.6s ease;
height: 20vh;
padding: 0 1% 0 1%;
display: flex;
flex-direction: column;
}
#detail {
opacity: 0;
position: absolute;
left: 14%;
font-family: 'normal';
display: flex;
flex-direction: column;
align-items: center;
width: 72%;
height: 90%;
transition: all 0.6s ease;
}
#detail-more {
position: absolute;
margin-top: 3%;
width: 100%;
font-size: 3vh;
bottom: 0;
}
.detial-more {
position: absolute;
width: 100%;
bottom: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.more {
user-select: none;
cursor: pointer;
transition: color 0.2s linear;
}
.more:hover {
color: aqua;
}
.more:active {
color: aquamarine;
}
.detail-enter-active,
.detail-leave-active {
transition: all 0.6s ease;
}
.detail-enter-from,
.detail-leave-to {
opacity: 0;
}
@media screen and(max-width:600px) {
#detail {
width: 95%;
height: 100%;
padding: 5% 0 5% 5%;
left: 0%;
}
#detail-more {
font-size: 4vw;
}
}
</style>

View File

@ -26,7 +26,7 @@ export default defineConfig({
output: {
manualChunks: {
antdv: ['ant-design-vue', '@ant-design/icons-vue'],
common: ['lodash', 'axios', 'lz-string', 'vue']
common: ['lodash', 'axios', 'lz-string']
}
}
}