临界信息

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' { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
ADivider: typeof import('ant-design-vue/es')['Divider'] 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'] BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default']
EnemyOne: typeof import('./src/components/enemyOne.vue')['default'] EnemyOne: typeof import('./src/components/enemyOne.vue')['default']
Scroll: typeof import('./src/components/scroll.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-orientation" content="portrait">
<meta name="x5-fullscreen" content="true"> <meta name="x5-fullscreen" content="true">
<meta name="x5-page-mode" content="app"> <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> </head>
<body> <body>

View File

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

View File

@ -12,6 +12,7 @@ specifiers:
'@vitejs/plugin-vue-jsx': ^2.1.1 '@vitejs/plugin-vue-jsx': ^2.1.1
ant-design-vue: ^3.2.13 ant-design-vue: ^3.2.13
axios: ^1.1.3 axios: ^1.1.3
chart.js: ^4.0.1
compressing: ^1.6.2 compressing: ^1.6.2
fontmin: ^0.9.9 fontmin: ^0.9.9
form-data: ^4.0.0 form-data: ^4.0.0
@ -32,6 +33,7 @@ dependencies:
'@ant-design/icons-vue': 6.1.0_vue@3.2.45 '@ant-design/icons-vue': 6.1.0_vue@3.2.45
ant-design-vue: 3.2.15_vue@3.2.45 ant-design-vue: 3.2.15_vue@3.2.45
axios: 1.1.3 axios: 1.1.3
chart.js: 4.0.1
lodash: 4.17.21 lodash: 4.17.21
lz-string: 1.4.4 lz-string: 1.4.4
mutate-animate: 0.1.0 mutate-animate: 0.1.0
@ -1069,6 +1071,11 @@ packages:
supports-color: 5.5.0 supports-color: 5.5.0
dev: true 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: /chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'} 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":[]}, "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":[]}, "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]}, "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]}, "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":[]}, "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}, "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; return res;
} }
},
"uiChange": function () {
ui.prototype.drawBook = function () {
return core.plugin.bookOpened.value = true;
}
} }
}; };

View File

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

View File

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

View File

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

View File

@ -1,7 +1,23 @@
export default function init() { 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 * @param ele
@ -11,8 +27,9 @@ export default function init() {
*/ */
export function useDrag( export function useDrag(
ele: HTMLElement, ele: HTMLElement,
fn: (x: number, y: number, e: MouseEvent | TouchEvent) => void, fn: DragFn,
ondown?: (x: number, y: number, e: MouseEvent | TouchEvent) => void, ondown?: DragFn,
onUp?: (e: MouseEvent | TouchEvent) => void,
global: boolean = false global: boolean = false
) { ) {
let down = false; let down = false;
@ -25,21 +42,66 @@ export function useDrag(
if (ondown) ondown(e.touches[0].clientX, e.touches[0].clientY, e); 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; const target = global ? document : ele;
target.addEventListener('mousemove', e => { const mouseFn = (e: MouseEvent) => {
if (!down) return; if (!down) return;
const ee = e as MouseEvent; fn(e.clientX, e.clientY, e);
fn(ee.clientX, ee.clientY, ee); };
});
target.addEventListener('touchmove', e => { const touchFn = (e: TouchEvent) => {
if (!down) return; if (!down) return;
const ee = e as TouchEvent; fn(e.touches[0].clientX, e.touches[0].clientY, e);
fn(ee.touches[0].clientX, ee.touches[0].clientY, ee); };
});
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'; if (damage < core.status.hero.hp) return '#f22';
return '#f00'; 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; justify-content: center;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
transition: all 0.6s linear;
opacity: 0;
background-color: #000d;
} }

View File

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

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

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

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

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

View File

@ -4,9 +4,14 @@
<div v-if="enemy.length === 0" id="none"> <div v-if="enemy.length === 0" id="none">
<div>本层无怪物</div> <div>本层无怪物</div>
</div> </div>
<Scroll v-else style="width: 100%; height: 100%; font-family: normal"> <Scroll
<div v-for="e of enemy" class="enemy"> v-else
<EnemyOne :enemy="e"></EnemyOne> 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 <a-divider
dashed dashed
style="width: 100%; border-color: #ddd4" style="width: 100%; border-color: #ddd4"
@ -14,18 +19,25 @@
</div> </div>
</Scroll> </Scroll>
</div> </div>
<BookDetail v-if="detail" @close="closeDetail()"></BookDetail>
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import { cloneDeep } from 'lodash'; 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 EnemyOne from '../components/enemyOne.vue';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import { getDamageColor } from '../plugin/utils'; import { getDamageColor } from '../plugin/utils';
import BookDetail from './bookDetail.vue';
const floorId = core.floorIds[core.status.event?.ui] ?? core.status.floorId; const floorId = core.floorIds[core.status.event?.ui] ?? core.status.floorId;
const enemy = core.getCurrentEnemys(floorId); const enemy = core.getCurrentEnemys(floorId);
const scroll = ref(0);
const drag = ref(false);
const detail = ref(false);
// //
enemy.forEach(v => { enemy.forEach(v => {
const l = v.specialText.length; const l = v.specialText.length;
@ -46,18 +58,52 @@ onMounted(() => {
const div = document.getElementById('book') as HTMLDivElement; const div = document.getElementById('book') as HTMLDivElement;
div.style.opacity = '1'; 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> </script>
<style lang="less" scoped> <style lang="less" scoped>
#book { #book {
opacity: 0; opacity: 0;
background-color: #000d;
transition: opacity 0.6s linear;
user-select: none; user-select: none;
width: 80%; width: 80%;
height: 100%; height: 100%;
font-family: 'normal'; font-family: 'normal';
overflow: hidden; overflow: hidden;
transition: opacity 0.6s linear;
} }
@media screen and (max-width: 600px) { @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: { output: {
manualChunks: { manualChunks: {
antdv: ['ant-design-vue', '@ant-design/icons-vue'], antdv: ['ant-design-vue', '@ant-design/icons-vue'],
common: ['lodash', 'axios', 'lz-string', 'vue'] common: ['lodash', 'axios', 'lz-string']
} }
} }
} }