mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-09-03 05:41:47 +08:00
639 lines
15 KiB
Vue
639 lines
15 KiB
Vue
<template>
|
|
<div id="fly">
|
|
<div id="tools">
|
|
<span class="button-text" @click="exit"
|
|
><left-outlined /> 返回游戏</span
|
|
>
|
|
</div>
|
|
<div id="fly-settings">
|
|
<div id="fly-border">
|
|
<span>无边框模式</span>
|
|
<Switch
|
|
class="fly-settings"
|
|
v-model:checked="noBorder"
|
|
checked-children="ON"
|
|
un-checked-children="OFF"
|
|
></Switch>
|
|
</div>
|
|
<div v-if="!isMobile" id="fly-tradition">
|
|
<span>传统按键模式</span>
|
|
<Switch
|
|
class="fly-settings"
|
|
v-model:checked="tradition"
|
|
checked-children="ON"
|
|
un-checked-children="OFF"
|
|
></Switch>
|
|
</div>
|
|
<span
|
|
v-if="!isMobile"
|
|
class="button-text"
|
|
id="fly-download"
|
|
@click="download"
|
|
>下载地图图片</span
|
|
>
|
|
</div>
|
|
<div id="fly-main">
|
|
<div id="fly-left">
|
|
<Scroll id="fly-area"
|
|
><div id="area-list">
|
|
<span
|
|
v-for="(v, k) in area"
|
|
:selected="nowArea === k"
|
|
class="selectable"
|
|
@click="nowArea = k"
|
|
>{{ k }}</span
|
|
>
|
|
</div></Scroll
|
|
>
|
|
<Divider type="vertical" dashed id="divider-left"></Divider>
|
|
<div id="fly-map-div">
|
|
<canvas id="fly-map" @click="click"></canvas>
|
|
</div>
|
|
</div>
|
|
<Divider
|
|
id="divider-right"
|
|
dashed
|
|
:type="isMobile ? 'horizontal' : 'vertical'"
|
|
></Divider>
|
|
<div id="fly-right">
|
|
<canvas id="fly-thumbnail" @click="fly" @wheel="wheel"></canvas>
|
|
<div id="fly-tools">
|
|
<double-left-outlined
|
|
@click="changeFloorByDelta(-10)"
|
|
class="button-text fly-button"
|
|
/>
|
|
<left-outlined
|
|
@click="changeFloorByDelta(-1)"
|
|
class="button-text fly-button"
|
|
/>
|
|
<span
|
|
class="changable"
|
|
id="fly-now"
|
|
:change="titleChange"
|
|
>{{ title }}</span
|
|
>
|
|
<right-outlined
|
|
@click="changeFloorByDelta(1)"
|
|
class="button-text fly-button"
|
|
/>
|
|
<double-right-outlined
|
|
@click="changeFloorByDelta(10)"
|
|
class="button-text fly-button"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
import Scroll from '../components/scroll.vue';
|
|
import { getArea, getMapData, MinimapDrawer } from '../tools/fly';
|
|
import { cancelGlobalDrag, isMobile, useDrag, useWheel } from '../use';
|
|
import {
|
|
LeftOutlined,
|
|
DoubleLeftOutlined,
|
|
RightOutlined,
|
|
DoubleRightOutlined
|
|
} from '@ant-design/icons-vue';
|
|
import { debounce } from 'lodash-es';
|
|
import { tip } from '../use';
|
|
import { gameKey } from '@motajs/system-action';
|
|
import { createChangable } from '../tools/common';
|
|
import { mainSetting } from '../preset/settingIns';
|
|
import { GameStorage } from '@motajs/legacy-system';
|
|
import { IMountedVBind } from '../interface';
|
|
import { Divider, Switch } from 'ant-design-vue';
|
|
|
|
const props = defineProps<IMountedVBind>();
|
|
|
|
type Loc2 = [number, number, number, number];
|
|
|
|
const area = getArea();
|
|
const nowArea = ref(
|
|
Object.keys(area).find(v => area[v].includes(core.status.floorId)) ?? ''
|
|
);
|
|
const nowFloor = ref(core.status.floorId);
|
|
const noBorder = ref(true);
|
|
const tradition = ref(false);
|
|
const settingScale = mainSetting.getValue('ui.mapScale', 100) / 100;
|
|
let scale = (isMobile ? 1.5 : 3) * settingScale;
|
|
|
|
const storage = GameStorage.for(GameStorage.fromAuthor('AncTe', 'flyConfig'));
|
|
|
|
noBorder.value = storage.getValue('noBorder', true);
|
|
tradition.value = storage.getValue('flyTradition', false);
|
|
|
|
const floor = computed(() => {
|
|
return core.status.maps[nowFloor.value];
|
|
});
|
|
|
|
watch(nowFloor, n => {
|
|
drawer.nowFloor = n;
|
|
draw();
|
|
});
|
|
watch(nowArea, n => {
|
|
scale = 3 * settingScale;
|
|
lastScale = 3 * settingScale;
|
|
drawer.nowArea = n;
|
|
drawer.scale = 3 * settingScale;
|
|
drawer.ox = 0;
|
|
drawer.oy = 0;
|
|
if (area[n] && !area[n].includes(nowFloor.value))
|
|
nowFloor.value =
|
|
area[n].find(v => v === core.status.floorId) ?? area[n][0];
|
|
});
|
|
watch(noBorder, n => {
|
|
storage.setValue('noBorder', n);
|
|
drawer.noBorder = n;
|
|
drawer.drawedThumbnail = {};
|
|
drawer.drawMap();
|
|
});
|
|
watch(tradition, n => {
|
|
storage.setValue('flyTradition', n);
|
|
});
|
|
|
|
let map: HTMLCanvasElement;
|
|
let thumb: HTMLCanvasElement;
|
|
let thumbCtx: CanvasRenderingContext2D;
|
|
|
|
function exit() {
|
|
props.controller.close(props.num);
|
|
}
|
|
|
|
const title = computed(() => {
|
|
return core.status.maps[nowFloor.value].title;
|
|
});
|
|
const titleChange = createChangable(title).change;
|
|
|
|
let drawer: MinimapDrawer;
|
|
|
|
function drawRight() {
|
|
let w = thumb.width;
|
|
let h = thumb.height;
|
|
let x = 0;
|
|
let y = 0;
|
|
const ratio = floor.value.width / floor.value.height;
|
|
if (ratio > 1) {
|
|
h = w / ratio;
|
|
y = thumb.height / 2 - h / 2;
|
|
}
|
|
if (ratio < 1) {
|
|
w = h * ratio;
|
|
x = thumb.width / 2 - w / 2;
|
|
}
|
|
thumbCtx.fillStyle = '#000';
|
|
thumbCtx.fillRect(0, 0, thumb.width, thumb.height);
|
|
core.drawThumbnail(nowFloor.value, void 0, {
|
|
ctx: thumbCtx,
|
|
all: true,
|
|
damage: true,
|
|
inFlyMap: true,
|
|
x,
|
|
y,
|
|
w,
|
|
h
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 绘制所有内容
|
|
*/
|
|
function draw() {
|
|
drawer.clearCache();
|
|
drawer.drawMap();
|
|
drawRight();
|
|
}
|
|
|
|
function download() {
|
|
drawer.download();
|
|
draw();
|
|
}
|
|
|
|
function fly() {
|
|
if (core.flyTo(nowFloor.value)) exit();
|
|
else tip('error', `无法飞往${floor.value.title}`);
|
|
}
|
|
|
|
let lastScale = scale;
|
|
const changeScale = debounce((s: number) => {
|
|
map.style.transform = '';
|
|
drawer.drawedThumbnail = {};
|
|
drawer.scale = s;
|
|
drawer.drawMap();
|
|
lastScale = s;
|
|
}, 200);
|
|
|
|
function resize(delta: number) {
|
|
drawer.ox *= delta;
|
|
drawer.oy *= delta;
|
|
scale = delta * scale;
|
|
changeScale(scale);
|
|
map.style.transform = `scale(${scale / lastScale})`;
|
|
drawer.thumbnailLoc = {};
|
|
}
|
|
|
|
let lastX = 0;
|
|
let lastY = 0;
|
|
|
|
let moved = false;
|
|
let startX = 0;
|
|
let startY = 0;
|
|
|
|
// -------------------- 点击事件
|
|
|
|
function drag(x: number, y: number) {
|
|
if (touchScale) return;
|
|
const dx = x - lastX;
|
|
const dy = y - lastY;
|
|
drawer.ox += dx;
|
|
drawer.oy += dy;
|
|
lastX = x;
|
|
lastY = y;
|
|
drawer.checkMoveThumbnail();
|
|
drawer.drawToTarget();
|
|
if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) moved = true;
|
|
}
|
|
|
|
function click(e: MouseEvent) {
|
|
if (moved) return;
|
|
|
|
const x = e.offsetX * devicePixelRatio;
|
|
const y = e.offsetY * devicePixelRatio;
|
|
for (const [id, [left, top, right, bottom]] of Object.entries(
|
|
drawer.thumbnailLoc
|
|
) as [FloorIds, Loc2][]) {
|
|
if (x >= left && x <= right && y >= top && y <= bottom) {
|
|
if (id === nowFloor.value) {
|
|
fly();
|
|
} else {
|
|
nowFloor.value = id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function wheel(ev: WheelEvent) {
|
|
changeFloorByDelta(-Math.sign(ev.deltaY));
|
|
}
|
|
|
|
function changeAreaByFloor(id: FloorIds) {
|
|
nowArea.value = Object.keys(area).find(v => area[v].includes(id))!;
|
|
}
|
|
|
|
function changeFloorByDelta(delta: number) {
|
|
const now = core.floorIds.indexOf(nowFloor.value);
|
|
let to = now + delta;
|
|
if (to < 0) to = 0;
|
|
if (to >= core.floorIds.length) to = core.floorIds.length - 1;
|
|
const floor = core.status.maps[core.floorIds[to]];
|
|
if (floor.deleted || floor.forceDelete) {
|
|
while (to !== now) {
|
|
to += Math.sign(delta);
|
|
const floor = core.status.maps[core.floorIds[to]];
|
|
if (floor.cannotViewMap) continue;
|
|
if (!floor.deleted && !floor.forceDelete) break;
|
|
if (to < 0 || to >= core.floorIds.length) break;
|
|
}
|
|
}
|
|
nowFloor.value = core.floorIds[to];
|
|
changeAreaByFloor(nowFloor.value);
|
|
drawer.locateMap(nowFloor.value);
|
|
}
|
|
|
|
function changeFloorByDir(dir: Dir) {
|
|
const data = getMapData(nowFloor.value);
|
|
|
|
for (const [from, to] of Object.entries(data.link)) {
|
|
if (!from.startsWith(nowFloor.value)) continue;
|
|
const d = from.split(',')[3] as Dir;
|
|
|
|
if (d === dir) {
|
|
const target = to.split(',')[0] as FloorIds;
|
|
drawer.locateMap(target);
|
|
nowFloor.value = target;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------- 键盘事件
|
|
|
|
gameKey.use(props.ui.symbol);
|
|
gameKey
|
|
.realize('@fly_left', () => {
|
|
if (!tradition.value) changeFloorByDir('left');
|
|
})
|
|
.realize('@fly_right', () => {
|
|
if (!tradition.value) changeFloorByDir('right');
|
|
})
|
|
.realize('@fly_up', () => {
|
|
if (!tradition.value) changeFloorByDir('up');
|
|
})
|
|
.realize('@fly_down', () => {
|
|
if (!tradition.value) changeFloorByDir('down');
|
|
})
|
|
.realize('@fly_last', () => {
|
|
if (!tradition.value) changeFloorByDelta(-1);
|
|
})
|
|
.realize('@fly_next', () => {
|
|
if (!tradition.value) changeFloorByDelta(1);
|
|
})
|
|
.realize('@fly_down_t', () => {
|
|
if (tradition.value) changeFloorByDelta(-1);
|
|
})
|
|
.realize('@fly_up_t', () => {
|
|
if (tradition.value) changeFloorByDelta(1);
|
|
})
|
|
.realize('@fly_left_t', () => {
|
|
if (tradition.value) changeFloorByDelta(-10);
|
|
})
|
|
.realize('@fly_right_t', () => {
|
|
if (tradition.value) changeFloorByDelta(10);
|
|
})
|
|
.realize('exit', () => {
|
|
exit();
|
|
})
|
|
.realize('confirm', () => {
|
|
fly();
|
|
})
|
|
.realize('fly', () => {
|
|
exit();
|
|
});
|
|
|
|
// -------------------- 触摸事件
|
|
|
|
let touchScale = false;
|
|
let lastDis = 0;
|
|
function touchdown(e: TouchEvent) {
|
|
if (e.touches.length >= 2) {
|
|
touchScale = true;
|
|
lastDis = Math.sqrt(
|
|
(e.touches[0].clientX - e.touches[1].clientX) ** 2 +
|
|
(e.touches[0].clientY - e.touches[1].clientY) ** 2
|
|
);
|
|
}
|
|
}
|
|
|
|
function touchup(e: TouchEvent) {
|
|
if (e.touches.length < 2) touchScale = false;
|
|
}
|
|
|
|
function touchmove(e: TouchEvent) {
|
|
if (!touchScale) return;
|
|
const dis = Math.sqrt(
|
|
(e.touches[0].clientX - e.touches[1].clientX) ** 2 +
|
|
(e.touches[0].clientY - e.touches[1].clientY) ** 2
|
|
);
|
|
resize(dis / lastDis);
|
|
lastDis = dis;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
map = document.getElementById('fly-map') as HTMLCanvasElement;
|
|
thumb = document.getElementById('fly-thumbnail') as HTMLCanvasElement;
|
|
thumbCtx = thumb.getContext('2d')!;
|
|
|
|
drawer = new MinimapDrawer(map);
|
|
drawer.scale = scale;
|
|
drawer.noBorder = noBorder.value;
|
|
drawer.showInfo = true;
|
|
drawer.nowArea = nowArea.value;
|
|
|
|
const mapStyle = getComputedStyle(map);
|
|
const thumbStyle = getComputedStyle(thumb);
|
|
map.width = parseFloat(mapStyle.width) * devicePixelRatio;
|
|
map.height = parseFloat(mapStyle.height) * devicePixelRatio;
|
|
thumb.width = parseFloat(thumbStyle.width) * devicePixelRatio;
|
|
thumb.height = parseFloat(thumbStyle.width) * devicePixelRatio;
|
|
|
|
Array.from(document.getElementsByClassName('fly-settings')).forEach(v => {
|
|
v.addEventListener('click', e => (v as HTMLElement).blur());
|
|
});
|
|
|
|
drawer.locateMap(nowFloor.value);
|
|
draw();
|
|
|
|
useDrag(
|
|
map,
|
|
drag,
|
|
(x, y) => {
|
|
lastX = x;
|
|
lastY = y;
|
|
startX = x;
|
|
startY = y;
|
|
},
|
|
() => {
|
|
setTimeout(() => {
|
|
moved = false;
|
|
}, 50);
|
|
},
|
|
true
|
|
);
|
|
|
|
useWheel(map, (x, y) => {
|
|
const delta = -Math.sign(y) * 0.1 + 1;
|
|
resize(delta);
|
|
});
|
|
|
|
map.addEventListener('touchstart', touchdown);
|
|
map.addEventListener('touchend', touchup);
|
|
map.addEventListener('touchmove', touchmove);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
cancelGlobalDrag(drag);
|
|
gameKey.dispose(props.ui.symbol);
|
|
});
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
#fly {
|
|
width: 100%;
|
|
height: 100%;
|
|
font-size: 150%;
|
|
font-family: 'normal';
|
|
display: flex;
|
|
align-items: center;
|
|
user-select: none;
|
|
}
|
|
|
|
#tools {
|
|
width: 100%;
|
|
font-family: 'normal';
|
|
font-size: 3.2vh;
|
|
height: 5vh;
|
|
position: fixed;
|
|
left: 5vw;
|
|
top: 5vh;
|
|
}
|
|
|
|
#fly-main {
|
|
display: flex;
|
|
height: 80%;
|
|
width: 100%;
|
|
flex-direction: row;
|
|
}
|
|
|
|
#fly-left {
|
|
width: 50vw;
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
#fly-area {
|
|
height: 100%;
|
|
width: 15vw;
|
|
}
|
|
|
|
#area-list {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
#divider-left {
|
|
margin: 0;
|
|
height: 100%;
|
|
border-color: #ddd4;
|
|
}
|
|
|
|
#fly-map-div,
|
|
#fly-map {
|
|
width: 35vw;
|
|
height: 72vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
#divider-right {
|
|
height: 100%;
|
|
border-color: #ddd4;
|
|
margin: 0;
|
|
}
|
|
|
|
#fly-right {
|
|
width: 40vw;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: space-around;
|
|
position: relative;
|
|
}
|
|
|
|
#fly-tools {
|
|
margin: 0;
|
|
width: 80%;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: 100%;
|
|
background-color: #0004;
|
|
}
|
|
|
|
#fly-thumbnail {
|
|
width: 35vw;
|
|
height: 35vw;
|
|
max-height: 75vh;
|
|
max-width: 75vh;
|
|
border: 0.1vw solid #ddd4;
|
|
}
|
|
|
|
#fly-settings {
|
|
position: fixed;
|
|
bottom: 5vh;
|
|
left: 10vw;
|
|
width: 80vw;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
|
|
div {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
span {
|
|
margin-right: 5vw;
|
|
}
|
|
}
|
|
}
|
|
|
|
.fly-settings[aria-checked='false'] {
|
|
background-color: #ddd4;
|
|
}
|
|
|
|
.fly-button {
|
|
padding: 3%;
|
|
border-radius: 8px;
|
|
background-color: rgba(0, 0, 0, 0.301);
|
|
border: 1px dashed #ddda;
|
|
filter: drop-shadow(0px 0px 16px black);
|
|
}
|
|
|
|
#fly-now {
|
|
text-wrap: nowrap;
|
|
white-space: nowrap;
|
|
max-width: 50%;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
text-shadow:
|
|
1px 1px 1px black,
|
|
1px -1px 1px black,
|
|
-1px 1px 1px black,
|
|
-1px -1px 1px black;
|
|
}
|
|
|
|
@media screen and (max-width: 600px) {
|
|
#fly {
|
|
font-size: 225%;
|
|
}
|
|
|
|
#fly-main {
|
|
flex-direction: column;
|
|
height: 90%;
|
|
}
|
|
|
|
#fly-map-div,
|
|
#fly-map {
|
|
width: 60vw;
|
|
height: 30vh;
|
|
}
|
|
|
|
#fly-area {
|
|
width: 30vw;
|
|
height: 30vh;
|
|
}
|
|
|
|
#fly-left {
|
|
width: 90vw;
|
|
}
|
|
|
|
#divider-right {
|
|
height: 0;
|
|
}
|
|
|
|
#fly-right {
|
|
width: 90vw;
|
|
height: 60vh;
|
|
}
|
|
|
|
#fly-thumbnail {
|
|
width: 80vw;
|
|
height: 80vw;
|
|
}
|
|
|
|
#tools {
|
|
top: 2vh;
|
|
}
|
|
|
|
#fly-settings {
|
|
bottom: 2%;
|
|
}
|
|
}
|
|
</style>
|