This commit is contained in:
unanmed 2023-01-01 22:02:58 +08:00
parent 71a4551004
commit b3602d3f21
18 changed files with 1040 additions and 36 deletions

1
components.d.ts vendored
View File

@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASlider: typeof import('ant-design-vue/es')['Slider']
ASwitch: typeof import('ant-design-vue/es')['Switch']
Box: typeof import('./src/components/box.vue')['default']
BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default']
Colomn: typeof import('./src/components/colomn.vue')['default']

View File

@ -2685,8 +2685,8 @@ maps.prototype._drawThumbnail_drawToTarget = function (floorId, options) {
y = options.y || 0,
size = options.size || 1;
// size的含义改为(0,1]范围的系数以适配长方形默认为1楼传为3/4SL界面为0.3
var w = Math.ceil(size * core._PX_),
h = Math.ceil(size * core._PY_);
var w = size * core._PX_,
h = size * core._PY_;
// 特判是否为编辑器编辑器中长宽均采用core.js的遗留正方形像素边长以保证下面的绘制正常
if (main.mode == 'editor') w = h = size * core.__PIXELS__;
var width = core.floors[floorId].width,
@ -2697,6 +2697,21 @@ maps.prototype._drawThumbnail_drawToTarget = function (floorId, options) {
if (centerY == null) centerY = Math.floor(height / 2);
var tempCanvas = core.bigmap.tempCanvas;
if (options.inFlyMap) {
ctx.drawImage(
tempCanvas.canvas,
0,
0,
tempCanvas.canvas.width,
tempCanvas.canvas.height,
options.x,
options.y,
options.w,
options.h
);
return;
}
const scale = core.domStyle.scale * devicePixelRatio;
if (options.all) {
var tempWidth = tempCanvas.canvas.width,

View File

@ -52,6 +52,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
// 隐藏右下角的音乐按钮
core.dom.musicBtn.style.display = 'none';
core.dom.enlargeBtn.style.display = 'none';
core.splitArea();
},
win: function (reason, norank, noexit) {
// 游戏获胜事件

View File

@ -325,7 +325,7 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a =
"text": "可以自由往来去过的楼层",
"hideInReplay": true,
"hideInToolbox": true,
"useItemEffect": "core.ui.drawFly(core.floorIds.indexOf(core.status.floorId));",
"useItemEffect": "core.ui.drawFly();",
"canUseItemEffect": "(function () {\n\treturn core.status.maps[core.status.floorId].canFlyFrom;\n})();"
},
"coin": {

View File

@ -5445,6 +5445,11 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
return (core.plugin.equipOpened.value = true);
};
ui.prototype.drawFly = function () {
if (!core.isReplaying())
return (core.plugin.flyOpened.value = true);
};
control.prototype.updateStatusBar_update = function () {
if (!core.isPlaying() || core.hasFlag('__statistics__')) return;
core.control.controldata.updateStatusBar();

View File

@ -44,12 +44,12 @@ body {
}
#startPanel {
width: 100%;
width: 150%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-color: #fff;
left: -25%;
background-color: #000;
overflow: hidden;
z-index: 300;
}
@ -106,6 +106,7 @@ body {
width: auto;
transform: translate(-50%, -50%);
z-index: 260;
object-fit: cover;
}
#startLogo {
@ -143,7 +144,7 @@ body {
}
#startButtonGroup {
width: 33%;
width: 25%;
position: absolute;
text-align: center;
font-size: 1.4em;

View File

@ -1,7 +1,7 @@
import { has } from '../utils';
export default function init() {
return { splitArea, getMapData };
return { splitArea, getMapDrawData };
}
type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
@ -12,7 +12,20 @@ interface MapBFSResult {
link: Record<BFSFromString, BFSToString>;
}
interface MapDrawData {
locs: Partial<Record<FloorIds, LocArr>>;
line: [number, number, number, number][];
width: number;
height: number;
}
let area: Record<string, FloorIds[]> = {};
const bfsCache: Partial<Record<FloorIds, MapBFSResult>> = {};
/**
* FloorIds,interval,border
*/
const drawCache: Record<string, MapDrawData> = {};
const arrow: Partial<Record<AllIds, Dir>> = {
leftPortal: 'left',
@ -21,9 +34,125 @@ const arrow: Partial<Record<AllIds, Dir>> = {
downPortal: 'down'
};
export function splitArea() {}
/**
*
*/
export function splitArea() {
const used: FloorIds[] = [];
for (const id of core.floorIds) {
if (used.includes(id) || core.status.maps[id].deleted) continue;
const data = getMapData(id, true);
used.push(...data.maps);
if (data.maps.length > 0) {
const title = core.status.maps[id].title;
area[title] = data.maps;
}
}
}
export function getMapDrawData(floorId: FloorIds) {}
export function getArea() {
return area;
}
/**
*
* @param floorId
* @param interval
* @param border
* @param noCache 使
*/
export function getMapDrawData(
floorId: FloorIds,
interval: number = 5,
border: number = 1,
noCache: boolean = false
): MapDrawData {
const id = `${floorId},${interval},${border}`;
if (drawCache[id] && !noCache) return drawCache[id];
const { link, maps } = getMapData(floorId, noCache);
const locs: Partial<Record<FloorIds, LocArr>> = {};
const line: [number, number, number, number][] = [];
const center = core.status.maps[floorId];
let left = -center.width / 2,
right = center.width / 2,
top = -center.height / 2,
bottom = center.height / 2;
for (const [from, to] of Object.entries(link)) {
const [fromId, fxs, fys, dir] = from.split(',') as [
FloorIds,
string,
string,
Dir
];
const [toId, txs, tys] = to.split(',') as [FloorIds, string, string];
const fromMap = core.status.maps[fromId];
const toMap = core.status.maps[toId];
const fx = parseInt(fxs),
fy = parseInt(fys),
tx = parseInt(txs),
ty = parseInt(tys);
const fw = fromMap.width,
fh = fromMap.height;
const tw = toMap.width,
th = toMap.height;
locs[fromId] ??= [0, 0];
const [fromX, fromY] = locs[fromId]!;
if (!locs[toId]) {
const dx = core.utils.scan[dir].x,
dy = core.utils.scan[dir].y;
const toX =
fromX +
(fx - fw / 2) -
(tx - tw / 2) +
(border * 2 + interval) * dx,
toY =
fromY +
(fy - fh / 2) -
(ty - th / 2) +
(border * 2 + interval) * dy;
// 地图位置和连线位置
locs[toId] = [toX, toY];
}
const [toX, toY] = locs[toId]!;
line.push([
fromX + (fx - fw / 2 + 0.5),
fromY + (fy - fh / 2 + 0.5),
toX + (tx - tw / 2 + 0.5),
toY + (ty - th / 2 + 0.5)
]);
// 计算地图总长宽
const l = toX - tw / 2,
r = toX + tw / 2,
t = toY - th / 2,
b = toY + th / 2;
if (l < left) left = l;
if (r > right) right = r;
if (t < top) top = t;
if (b > bottom) bottom = b;
}
// 移动位置,居中
Object.values(locs).forEach(v => {
v[0] -= left;
v[1] -= top;
});
line.forEach(v => {
v[0] -= left;
v[2] -= left;
v[1] -= top;
v[3] -= top;
});
left -= 5;
right += 5;
top -= 5;
bottom += 5;
const res = { locs, line, width: right - left, height: bottom - top };
return (drawCache[id] = res);
}
/**
* 广
@ -53,12 +182,12 @@ export function getMapData(
const block = blocks[loc as LocString];
const id = block.event.id;
if (id in arrow) {
if (!used[target]) {
const from = `${now},${loc},${arrow[id]}` as BFSFromString;
const to = `${target},${ev.loc![0]},${
ev.loc![1]
}` as BFSToString;
link[from] = to;
if (!used[target]) {
queue.push(target);
floors.push(target);
}
@ -67,8 +196,74 @@ export function getMapData(
used[now] = true;
}
return {
const res = {
maps: floors,
link
};
return (bfsCache[floorId] = res);
}
/**
*
* @param ctx
* @param floorId
* @param interval
* @param border
* @param noCache 使
*/
export function drawFlyMap(
ctx: CanvasRenderingContext2D,
floorId: FloorIds,
offset: [number, number],
size: [number, number],
scale: number = 3,
interval: number = 5,
border: number = 1,
noCache: boolean = false
) {
const data = getMapDrawData(floorId, interval, border, noCache);
const [ox, oy] = offset;
const [width, height] = size;
const canvas = ctx.canvas;
canvas.width = data.width * devicePixelRatio * scale;
canvas.height = data.height * devicePixelRatio * scale;
ctx.lineWidth = border * devicePixelRatio * scale;
ctx.strokeStyle = '#fff';
ctx.scale(scale, scale);
// 绘制连线
data.line.forEach(([x1, y1, x2, y2]) => {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
});
for (const [id, [x, y]] of Object.entries(data.locs) as [
FloorIds,
LocArr
][]) {
drawThumbnail(ctx, id, scale, [x, y], offset, size);
}
}
function drawThumbnail(
ctx: CanvasRenderingContext2D,
floorId: FloorIds,
scale: number,
pos: [number, number],
offset: [number, number],
size: [number, number]
) {
const [x, y] = pos;
const [ox, oy] = offset;
const [width, height] = size;
const map = core.status.maps[floorId];
if (
ox + x * scale + (map.width * scale) / 2 < 0 ||
ox + x * scale - (map.width * scale) / 2 > width ||
oy + y * scale + (map.height * scale) / 2 < 0 ||
oy + y * scale - (map.height * scale) / 2 > height
) {
return;
}
}

View File

@ -7,6 +7,7 @@ import Settings from '../ui/settings.vue';
import Desc from '../ui/desc.vue';
import Skill from '../ui/skill.vue';
import SkillTree from '../ui/skillTree.vue';
import Fly from '../ui/fly.vue';
export const bookOpened = ref(false);
export const toolOpened = ref(false);
@ -16,6 +17,7 @@ export const settingsOpened = ref(false);
export const descOpened = ref(false);
export const skillOpened = ref(false);
export const skillTreeOpened = ref(false);
export const flyOpened = ref(false);
export const transition = ref(true);
export const noClosePanel = ref(false);
@ -30,7 +32,8 @@ const UI_LIST: [Ref<boolean>, Component][] = [
[settingsOpened, Settings],
[descOpened, Desc],
[skillOpened, Skill],
[skillTreeOpened, SkillTree]
[skillTreeOpened, SkillTree],
[flyOpened, Fly]
];
/** ui栈 */
@ -63,7 +66,8 @@ export default function init() {
settingsOpened,
descOpened,
skillOpened,
skillTreeOpened
skillTreeOpened,
flyOpened
};
}

View File

@ -35,6 +35,8 @@
.selectable {
border: #0000 0.5px solid;
padding: 1% 3% 1% 3%;
width: 100%;
}
.selectable[selected='true'] {

View File

@ -132,6 +132,8 @@ interface Actions extends VoidedActionFuncs {
* @param x
*/
_out(x: number): boolean;
_getNextFlyFloor(delta: number, index: number): number;
}
declare const actions: new () => Actions;

View File

@ -196,7 +196,7 @@ interface EventData {
* @param toId
* @param callback
*/
flyTo(toId: FloorIds, callback: () => void): void;
flyTo(toId: FloorIds, callback?: () => void): boolean;
/**
*

30
src/types/map.d.ts vendored
View File

@ -187,6 +187,11 @@ interface Floor<T extends FloorIds = FloorIds> extends FloorBase<T> {
*
*/
blocks: Block[];
/**
*
*/
deleted?: boolean;
}
interface ResolvedFloor<T extends FloorIds = FloorIds> extends FloorBase<T> {
@ -400,6 +405,31 @@ interface DrawThumbnailConfig {
* 使v2优化
*/
v2: boolean;
/**
*
*/
inFlyMap: boolean;
/**
*
*/
x: number;
/**
*
*/
y: number;
/**
*
*/
w: number;
/**
*
*/
h: number;
}
interface BlockFilter {

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

@ -18,7 +18,8 @@ interface PluginDeclaration
extends PluginUtils,
PluginUis,
PluginUse,
SkillTree {
SkillTree,
MiniMap {
/**
* 使core.addPop或core.plugin.addPop调用
* @param px
@ -149,6 +150,9 @@ interface PluginUis {
/** 技能树界面是否打开 */
readonly skillTreeOpened: Ref<boolean>;
/** 楼传界面是否打开 */
readonly flyOpened: Ref<boolean>;
/** ui栈 */
readonly uiStack: Ref<Component[]>;
@ -246,6 +250,13 @@ interface SkillTree {
loadSkillTree(data: number[]): void;
}
interface MiniMap {
/**
*
*/
splitArea(): void;
}
type Chapter = 'chapter1';
interface Skill {

View File

@ -201,7 +201,6 @@ function keydown(e: KeyboardEvent) {
}
onMounted(async () => {
const div = document.getElementById('book') as HTMLDivElement;
if (core.plugin.transition.value) await sleep(600);
else await sleep(50);
document.addEventListener('keyup', keyup);

View File

@ -4,7 +4,7 @@
><div id="desc-list">
<div
v-for="(data, k) in desc"
class="selectable desc-item"
class="selectable"
:selected="selected === k"
@click="click(k)"
>
@ -53,9 +53,4 @@ function show(condition: string) {
display: flex;
flex-direction: column;
}
.desc-item {
padding: 1% 3% 1% 3%;
width: 100%;
}
</style>

748
src/ui/fly.vue Normal file
View File

@ -0,0 +1,748 @@
<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>
<a-switch
class="fly-settings"
v-model:checked="noBorder"
checked-children="ON"
un-checked-children="OFF"
></a-switch>
</div>
<div v-if="!isMobile" id="fly-tradition">
<span>传统按键模式</span>
<a-switch
class="fly-settings"
v-model:checked="tradition"
checked-children="ON"
un-checked-children="OFF"
></a-switch>
</div>
</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
>
<a-divider type="vertical" dashed id="divider-left"></a-divider>
<div id="fly-map-div">
<canvas id="fly-map" @click="click"></canvas>
</div>
</div>
<a-divider
id="divider-right"
dashed
:type="isMobile ? 'horizontal' : 'vertical'"
></a-divider>
<div id="fly-right">
<canvas id="fly-thumbnail" @click="fly"></canvas>
<div id="fly-tools">
<double-left-outlined
@click="changeFloorByDelta(-10)"
class="button-text"
/>
<left-outlined
@click="changeFloorByDelta(-1)"
class="button-text"
/>
<span id="fly-now">{{ title }}</span>
<right-outlined
@click="changeFloorByDelta(1)"
class="button-text"
/>
<double-right-outlined
@click="changeFloorByDelta(10)"
class="button-text"
/>
</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, getMapDrawData, getMapData } from '../plugin/ui/fly';
import { cancelGlobalDrag, isMobile, useDrag, useWheel } from '../plugin/use';
import {
LeftOutlined,
DoubleLeftOutlined,
RightOutlined,
DoubleRightOutlined
} from '@ant-design/icons-vue';
import { debounce } from 'lodash';
import { keycode, tip } from '../plugin/utils';
import { sleep } from 'mutate-animate';
import { KeyCode } from '../plugin/keyCodes';
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(false);
const tradition = ref(false);
let scale = isMobile ? 1.5 : 3;
let ox = 0;
let oy = 0;
let drawedThumbnail: Partial<Record<FloorIds, boolean>> = {};
let thumbnailLoc: Partial<Record<FloorIds, Loc2>> = {};
const floor = computed(() => {
return core.status.maps[nowFloor.value];
});
watch(nowFloor, draw);
watch(nowArea, n => {
ox = 0;
oy = 0;
scale = 3;
lastScale = 3;
if (!area[n].includes(nowFloor.value))
nowFloor.value =
area[n].find(v => v === core.status.floorId) ?? area[n][0];
});
watch(noBorder, n => {
drawedThumbnail = {};
drawMap();
});
const temp = document.createElement('canvas');
const tempCtx = temp.getContext('2d')!;
let map: HTMLCanvasElement;
let mapCtx: CanvasRenderingContext2D;
let thumb: HTMLCanvasElement;
let thumbCtx: CanvasRenderingContext2D;
function exit() {
core.plugin.flyOpened.value = false;
}
const title = computed(() => {
return core.status.maps[nowFloor.value].title;
});
/**
* 绘制小地图
* @param noCache 是否不使用缓存
*/
function drawMap(noCache: boolean = false) {
const border = noBorder.value ? 0.5 : 1;
const data = getMapDrawData(
nowFloor.value,
noBorder.value ? 0 : 5,
border,
noCache
);
const ctx = tempCtx;
const s = scale * devicePixelRatio;
temp.width = data.width * s;
temp.height = data.height * s;
ctx.lineWidth = (border * devicePixelRatio) / 2;
ctx.strokeStyle = '#fff';
ctx.scale(s, s);
ctx.translate(5, 5);
if (!noBorder.value) {
// 线
data.line.forEach(([x1, y1, x2, y2]) => {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
});
}
//
for (const [id, [x, y]] of Object.entries(data.locs) as [
FloorIds,
LocArr
][]) {
if (!noBorder.value) drawBorder(id, x, y);
drawThumbnail(id, x, y);
}
drawToTarget();
}
function drawBorder(id: FloorIds, x: number, y: number) {
const border = noBorder.value ? 0.5 : 1;
const ctx = tempCtx;
ctx.lineWidth = border * devicePixelRatio;
const map = core.status.maps[id];
if (!core.hasVisitedFloor(id)) {
ctx.fillStyle = '#d0d';
} else {
ctx.fillStyle = '#000';
}
if (id === nowFloor.value) {
ctx.strokeStyle = 'gold';
} else {
ctx.strokeStyle = '#fff';
}
ctx.strokeRect(
x - map.width / 2,
y - map.height / 2,
map.width,
map.height
);
ctx.fillRect(x - map.width / 2, y - map.height / 2, map.width, map.height);
if (id === nowFloor.value) {
ctx.fillStyle = '#ff04';
ctx.fillRect(
x - map.width / 2,
y - map.height / 2,
map.width,
map.height
);
}
}
/**
* 绘制小地图至目标画布
*/
function drawToTarget(s: number = 1) {
mapCtx.clearRect(0, 0, map.width, map.height);
mapCtx.drawImage(
temp,
0,
0,
temp.width,
temp.height,
ox * devicePixelRatio + (map.width - temp.width) / 2,
oy * devicePixelRatio + (map.height - temp.height) / 2,
temp.width,
temp.height
);
}
/**
* 检查是否应该绘制缩略图
*/
function checkThumbnail(floorId: FloorIds, x: number, y: number) {
const floor = core.status.maps[floorId];
const s = scale * devicePixelRatio;
const px = ox * devicePixelRatio + (map.width - temp.width) / 2 + 5 * s;
const py = oy * devicePixelRatio + (map.height - temp.height) / 2 + 5 * s;
const left = px + (x - floor.width / 2) * s;
const top = py + (y - floor.height / 2) * s;
const right = left + floor.width * s;
const bottom = top + floor.height * s;
thumbnailLoc[floorId] = [left, top, right, bottom];
if (
drawedThumbnail[floorId] ||
(!noBorder.value && scale <= 4) ||
right < 0 ||
bottom < 0 ||
left > map.width ||
top > map.height
)
return false;
return true;
}
/**
* 绘制缩略图
*/
function drawThumbnail(
floorId: FloorIds,
x: number,
y: number,
noCheck: boolean = false
) {
if (!noCheck && !checkThumbnail(floorId, x, y)) return;
const floor = core.status.maps[floorId];
drawedThumbnail[floorId] = true;
//
const ctx = tempCtx;
core.drawThumbnail(floorId, void 0, {
all: true,
inFlyMap: true,
x: x - floor.width / 2,
y: y - floor.height / 2,
w: floor.width,
h: floor.height,
ctx,
damage: true
});
if (!core.hasVisitedFloor(floorId)) {
ctx.fillStyle = '#d0d6';
ctx.fillRect(
x - floor.width / 2,
y - floor.height / 2,
floor.width,
floor.height
);
ctx.fillStyle = '#000';
}
if (nowFloor.value === floorId) {
ctx.fillStyle = '#ff04';
ctx.fillRect(
x - floor.width / 2,
y - floor.height / 2,
floor.width,
floor.height
);
ctx.fillStyle = '#000';
}
}
/**
* 当移动时检查是否应该绘制缩略图
*/
function checkMoveThumbnail() {
const border = noBorder.value ? 0.5 : 1;
const data = getMapDrawData(nowFloor.value, noBorder.value ? 0 : 5, border);
for (const [id, [x, y]] of Object.entries(data.locs) as [
FloorIds,
LocArr
][]) {
if (checkThumbnail(id, x, y)) drawThumbnail(id, x, y, true);
}
}
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() {
drawedThumbnail = {};
drawMap();
drawRight();
}
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 = '';
drawedThumbnail = {};
drawMap();
lastScale = s;
}, 200);
function resize(delta: number) {
ox *= delta;
oy *= delta;
scale = delta * scale;
changeScale(scale);
map.style.transform = `scale(${scale / lastScale})`;
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;
ox += dx;
oy += dy;
lastX = x;
lastY = y;
checkMoveThumbnail();
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(
thumbnailLoc
) as [FloorIds, Loc2][]) {
if (x >= left && x <= right && y >= top && y <= bottom) {
if (id === nowFloor.value) {
fly();
} else {
nowFloor.value = id;
}
}
}
}
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) {
while (to !== now) {
to -= Math.sign(delta);
const floor = core.status.maps[core.floorIds[to]];
if (!floor.deleted) break;
}
}
nowFloor.value = core.floorIds[to];
changeAreaByFloor(nowFloor.value);
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;
locateMap(target);
nowFloor.value = target;
return;
}
}
}
/**
* 居中地图
* @param id 楼层id
*/
function locateMap(id: FloorIds) {
const data = getMapDrawData(
id,
noBorder.value ? 5 : 0,
noBorder.value ? 0.5 : 1
);
const [x, y] = data.locs[id]!;
ox = (-x + data.width / 2) * scale;
oy = (-y + data.height / 2) * scale;
}
// --------------------
function keyup(e: KeyboardEvent) {
const c = keycode(e.keyCode);
if (c === KeyCode.Enter || c === KeyCode.Space || c === KeyCode.KeyC) fly();
if (c === KeyCode.Escape || c === KeyCode.KeyX || c === KeyCode.KeyG) {
exit();
}
if (!tradition.value) {
if (c === KeyCode.LeftArrow) changeFloorByDir('left');
if (c === KeyCode.RightArrow) changeFloorByDir('right');
if (c === KeyCode.UpArrow) changeFloorByDir('up');
if (c === KeyCode.DownArrow) changeFloorByDir('down');
if (c === KeyCode.PageUp) changeFloorByDelta(1);
if (c === KeyCode.PageDown) changeFloorByDelta(-1);
} else {
if (c === KeyCode.UpArrow) changeFloorByDelta(1);
if (c === KeyCode.DownArrow) changeFloorByDelta(-1);
if (c === KeyCode.LeftArrow) changeFloorByDelta(-10);
if (c === KeyCode.RightArrow) changeFloorByDelta(10);
if (c === KeyCode.PageUp) changeFloorByDelta(10);
if (c === KeyCode.PageDown) changeFloorByDelta(-10);
}
}
// --------------------
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;
mapCtx = map.getContext('2d')!;
thumb = document.getElementById('fly-thumbnail') as HTMLCanvasElement;
thumbCtx = thumb.getContext('2d')!;
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());
});
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);
});
await sleep(50);
if (core.plugin.transition.value) await sleep(600);
document.addEventListener('keyup', keyup);
map.addEventListener('touchstart', touchdown);
map.addEventListener('touchend', touchup);
map.addEventListener('touchend', touchmove);
});
onUnmounted(() => {
cancelGlobalDrag(drag);
document.removeEventListener('keyup', keyup);
});
</script>
<style lang="less" scoped>
#fly {
width: 100%;
height: 100%;
font-size: 2.7vh;
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;
}
#fly-tools {
margin: 0;
width: 80%;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
#fly-thumbnail {
width: 35vw;
height: 35vw;
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;
}
@media screen and (max-width: 600px) {
#fly {
padding: 5%;
font-size: 3.8vw;
}
#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>

View File

@ -3,7 +3,7 @@
><template #left
><div id="setting-list">
<span
class="selectable setting-item"
class="selectable"
:selected="selected === 'transition'"
@click="click('transition')"
>界面动画:&nbsp;&nbsp;&nbsp;{{
@ -11,7 +11,7 @@
}}</span
>
<span
class="selectable setting-item"
class="selectable"
:selected="selected === 'itemDetail'"
@click="click('itemDetail')"
>宝石血瓶显伤:&nbsp;&nbsp;&nbsp;{{
@ -19,7 +19,7 @@
}}</span
>
<span
class="selectable setting-item"
class="selectable"
:selected="selected === 'autoSkill'"
@click="click('autoSkill')"
>自动切换技能:&nbsp;&nbsp;&nbsp;{{
@ -27,7 +27,7 @@
}}</span
>
<span
class="selectable setting-item"
class="selectable"
:selected="selected === 'autoScale'"
@click="click('autoScale')"
>自动放缩:&nbsp;&nbsp;&nbsp;{{

View File

@ -67,11 +67,6 @@ function exit() {
flex-direction: column;
}
.skill-item {
width: 100%;
padding: 1% 3% 1% 3%;
}
.skill-item[selectable='false'] {
color: gray;
}